From 967a9c962a09b0f977725b30469e52388f019bca Mon Sep 17 00:00:00 2001 From: danischm Date: Tue, 1 Oct 2024 11:55:55 +0200 Subject: [PATCH] Add appliance firewalled service resource and data source --- CHANGELOG.md | 4 + .../appliance_firewalled_service.md | 34 +++ docs/guides/changelog.md | 4 + .../resources/appliance_firewalled_service.md | 49 ++++ .../data-source.tf | 4 + .../import.sh | 1 + .../resource.tf | 6 + .../appliance_firewalled_service.yaml | 42 +++ ...rce_meraki_appliance_firewalled_service.go | 129 +++++++++ ...eraki_appliance_firewalled_service_test.go | 92 ++++++ ...del_meraki_appliance_firewalled_service.go | 107 +++++++ internal/provider/provider.go | 2 + ...rce_meraki_appliance_firewalled_service.go | 264 ++++++++++++++++++ ...eraki_appliance_firewalled_service_test.go | 128 +++++++++ templates/guides/changelog.md.tmpl | 4 + 15 files changed, 870 insertions(+) create mode 100644 docs/data-sources/appliance_firewalled_service.md create mode 100644 docs/resources/appliance_firewalled_service.md create mode 100644 examples/data-sources/meraki_appliance_firewalled_service/data-source.tf create mode 100644 examples/resources/meraki_appliance_firewalled_service/import.sh create mode 100644 examples/resources/meraki_appliance_firewalled_service/resource.tf create mode 100644 gen/definitions/appliance_firewalled_service.yaml create mode 100644 internal/provider/data_source_meraki_appliance_firewalled_service.go create mode 100644 internal/provider/data_source_meraki_appliance_firewalled_service_test.go create mode 100644 internal/provider/model_meraki_appliance_firewalled_service.go create mode 100644 internal/provider/resource_meraki_appliance_firewalled_service.go create mode 100644 internal/provider/resource_meraki_appliance_firewalled_service_test.go diff --git a/CHANGELOG.md b/CHANGELOG.md index 36023ce..646bfcf 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,7 @@ +## 0.1.2 (unreleased) + +- Add `meraki_appliance_firewalled_service` resource and data source + ## 0.1.1 - Add `meraki_network_floor_plans` data source diff --git a/docs/data-sources/appliance_firewalled_service.md b/docs/data-sources/appliance_firewalled_service.md new file mode 100644 index 0000000..7d26d8c --- /dev/null +++ b/docs/data-sources/appliance_firewalled_service.md @@ -0,0 +1,34 @@ +--- +# generated by https://github.com/hashicorp/terraform-plugin-docs +page_title: "meraki_appliance_firewalled_service Data Source - terraform-provider-meraki" +subcategory: "Appliances" +description: |- + This data source can read the Appliance Firewalled Service configuration. +--- + +# meraki_appliance_firewalled_service (Data Source) + +This data source can read the `Appliance Firewalled Service` configuration. + +## Example Usage + +```terraform +data "meraki_appliance_firewalled_service" "example" { + network_id = "L_123456" + service = "ICMP" +} +``` + + +## Schema + +### Required + +- `network_id` (String) Network ID +- `service` (String) Service + +### Read-Only + +- `access` (String) A string indicating the rule for which IPs are allowed to use the specified service. Acceptable values are 'blocked' (no remote IPs can access the service), 'restricted' (only allowed IPs can access the service), and 'unrestriced' (any remote IP can access the service). This field is required +- `allowed_ips` (List of String) An array of allowed IPs that can access the service. This field is required if 'access' is set to 'restricted'. Otherwise this field is ignored +- `id` (String) The id of the object diff --git a/docs/guides/changelog.md b/docs/guides/changelog.md index 5788d4e..c885ceb 100644 --- a/docs/guides/changelog.md +++ b/docs/guides/changelog.md @@ -7,6 +7,10 @@ description: |- # Changelog +## 0.1.2 (unreleased) + +- Add `meraki_appliance_firewalled_service` resource and data source + ## 0.1.1 - Add `meraki_network_floor_plans` data source diff --git a/docs/resources/appliance_firewalled_service.md b/docs/resources/appliance_firewalled_service.md new file mode 100644 index 0000000..bce224f --- /dev/null +++ b/docs/resources/appliance_firewalled_service.md @@ -0,0 +1,49 @@ +--- +# generated by https://github.com/hashicorp/terraform-plugin-docs +page_title: "meraki_appliance_firewalled_service Resource - terraform-provider-meraki" +subcategory: "Appliances" +description: |- + This resource can manage the Appliance Firewalled Service configuration. +--- + +# meraki_appliance_firewalled_service (Resource) + +This resource can manage the `Appliance Firewalled Service` configuration. + +## Example Usage + +```terraform +resource "meraki_appliance_firewalled_service" "example" { + network_id = "L_123456" + service = "ICMP" + access = "restricted" + allowed_ips = ["123.123.123.1"] +} +``` + + +## Schema + +### Required + +- `access` (String) A string indicating the rule for which IPs are allowed to use the specified service. Acceptable values are 'blocked' (no remote IPs can access the service), 'restricted' (only allowed IPs can access the service), and 'unrestriced' (any remote IP can access the service). This field is required + - Choices: `blocked`, `restricted`, `unrestricted` +- `network_id` (String) Network ID +- `service` (String) Service + - Choices: `ICMP`, `SNMP`, `web` + +### Optional + +- `allowed_ips` (List of String) An array of allowed IPs that can access the service. This field is required if 'access' is set to 'restricted'. Otherwise this field is ignored + +### Read-Only + +- `id` (String) The id of the object + +## Import + +Import is supported using the following syntax: + +```shell +terraform import meraki_appliance_firewalled_service.example "," +``` diff --git a/examples/data-sources/meraki_appliance_firewalled_service/data-source.tf b/examples/data-sources/meraki_appliance_firewalled_service/data-source.tf new file mode 100644 index 0000000..d9454b5 --- /dev/null +++ b/examples/data-sources/meraki_appliance_firewalled_service/data-source.tf @@ -0,0 +1,4 @@ +data "meraki_appliance_firewalled_service" "example" { + network_id = "L_123456" + service = "ICMP" +} diff --git a/examples/resources/meraki_appliance_firewalled_service/import.sh b/examples/resources/meraki_appliance_firewalled_service/import.sh new file mode 100644 index 0000000..be61687 --- /dev/null +++ b/examples/resources/meraki_appliance_firewalled_service/import.sh @@ -0,0 +1 @@ +terraform import meraki_appliance_firewalled_service.example "," diff --git a/examples/resources/meraki_appliance_firewalled_service/resource.tf b/examples/resources/meraki_appliance_firewalled_service/resource.tf new file mode 100644 index 0000000..a335d5e --- /dev/null +++ b/examples/resources/meraki_appliance_firewalled_service/resource.tf @@ -0,0 +1,6 @@ +resource "meraki_appliance_firewalled_service" "example" { + network_id = "L_123456" + service = "ICMP" + access = "restricted" + allowed_ips = ["123.123.123.1"] +} diff --git a/gen/definitions/appliance_firewalled_service.yaml b/gen/definitions/appliance_firewalled_service.yaml new file mode 100644 index 0000000..394e527 --- /dev/null +++ b/gen/definitions/appliance_firewalled_service.yaml @@ -0,0 +1,42 @@ +name: Appliance Firewalled Service +spec_endpoint: /networks/{networkId}/appliance/firewall/firewalledServices/{service} +rest_endpoint: /networks/%v/appliance/firewall/firewalledServices/%v +put_create: true +no_delete: true +doc_category: Appliances +test_variables: [test_org, test_network] +attributes: + - tf_name: network_id + type: String + reference: true + description: Network ID + example: L_123456 + test_value: meraki_network.test.id + - tf_name: service + type: String + id: true + reference: true + description: Service + example: ICMP + enum_values: [ICMP, SNMP, web] + - model_name: access + type: String + mandatory: true + description: A string indicating the rule for which IPs are allowed to use the specified service. Acceptable values are 'blocked' (no remote IPs can access the service), 'restricted' (only allowed IPs can access the service), and 'unrestriced' (any remote IP can access the service). This field is required + example: restricted + enum_values: [blocked, restricted, unrestricted] + minimum_test_value: '"unrestricted"' + - model_name: allowedIps + type: List + element_type: String + description: An array of allowed IPs that can access the service. This field is required if 'access' is set to 'restricted'. Otherwise this field is ignored + example: 123.123.123.1 +test_prerequisites: | + data "meraki_organization" "test" { + name = var.test_org + } + resource "meraki_network" "test" { + organization_id = data.meraki_organization.test.id + name = var.test_network + product_types = ["switch", "wireless", "appliance"] + } diff --git a/internal/provider/data_source_meraki_appliance_firewalled_service.go b/internal/provider/data_source_meraki_appliance_firewalled_service.go new file mode 100644 index 0000000..3bbc6b8 --- /dev/null +++ b/internal/provider/data_source_meraki_appliance_firewalled_service.go @@ -0,0 +1,129 @@ +// Copyright © 2024 Cisco Systems, Inc. and its affiliates. +// All rights reserved. +// +// Licensed under the Mozilla Public License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://mozilla.org/MPL/2.0/ +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +// SPDX-License-Identifier: MPL-2.0 + +package provider + +// Section below is generated&owned by "gen/generator.go". //template:begin imports +import ( + "context" + "fmt" + + "github.com/hashicorp/terraform-plugin-framework/datasource" + "github.com/hashicorp/terraform-plugin-framework/datasource/schema" + "github.com/hashicorp/terraform-plugin-framework/types" + "github.com/hashicorp/terraform-plugin-log/tflog" + "github.com/netascode/go-meraki" +) + +// End of section. //template:end imports + +// Section below is generated&owned by "gen/generator.go". //template:begin model + +// Ensure the implementation satisfies the expected interfaces. +var ( + _ datasource.DataSource = &ApplianceFirewalledServiceDataSource{} + _ datasource.DataSourceWithConfigure = &ApplianceFirewalledServiceDataSource{} +) + +func NewApplianceFirewalledServiceDataSource() datasource.DataSource { + return &ApplianceFirewalledServiceDataSource{} +} + +type ApplianceFirewalledServiceDataSource struct { + client *meraki.Client +} + +func (d *ApplianceFirewalledServiceDataSource) Metadata(_ context.Context, req datasource.MetadataRequest, resp *datasource.MetadataResponse) { + resp.TypeName = req.ProviderTypeName + "_appliance_firewalled_service" +} + +func (d *ApplianceFirewalledServiceDataSource) Schema(ctx context.Context, req datasource.SchemaRequest, resp *datasource.SchemaResponse) { + resp.Schema = schema.Schema{ + // This description is used by the documentation generator and the language server. + MarkdownDescription: "This data source can read the `Appliance Firewalled Service` configuration.", + + Attributes: map[string]schema.Attribute{ + "id": schema.StringAttribute{ + MarkdownDescription: "The id of the object", + Computed: true, + }, + "network_id": schema.StringAttribute{ + MarkdownDescription: "Network ID", + Required: true, + }, + "service": schema.StringAttribute{ + MarkdownDescription: "Service", + Required: true, + }, + "access": schema.StringAttribute{ + MarkdownDescription: "A string indicating the rule for which IPs are allowed to use the specified service. Acceptable values are 'blocked' (no remote IPs can access the service), 'restricted' (only allowed IPs can access the service), and 'unrestriced' (any remote IP can access the service). This field is required", + Computed: true, + }, + "allowed_ips": schema.ListAttribute{ + MarkdownDescription: "An array of allowed IPs that can access the service. This field is required if 'access' is set to 'restricted'. Otherwise this field is ignored", + ElementType: types.StringType, + Computed: true, + }, + }, + } +} + +func (d *ApplianceFirewalledServiceDataSource) Configure(_ context.Context, req datasource.ConfigureRequest, _ *datasource.ConfigureResponse) { + if req.ProviderData == nil { + return + } + + d.client = req.ProviderData.(*MerakiProviderData).Client +} + +// End of section. //template:end model + +// Section below is generated&owned by "gen/generator.go". //template:begin read + +func (d *ApplianceFirewalledServiceDataSource) Read(ctx context.Context, req datasource.ReadRequest, resp *datasource.ReadResponse) { + var config ApplianceFirewalledService + + // Read config + diags := req.Config.Get(ctx, &config) + resp.Diagnostics.Append(diags...) + if resp.Diagnostics.HasError() { + return + } + + tflog.Debug(ctx, fmt.Sprintf("%s: Beginning Read", config.Id.String())) + + var res meraki.Res + var err error + + if !res.Exists() { + res, err = d.client.Get(config.getPath()) + if err != nil { + resp.Diagnostics.AddError("Client Error", fmt.Sprintf("Failed to retrieve object, got error: %s", err)) + return + } + } + + config.fromBody(ctx, res) + config.Id = config.Service + + tflog.Debug(ctx, fmt.Sprintf("%s: Read finished successfully", config.Id.ValueString())) + + diags = resp.State.Set(ctx, &config) + resp.Diagnostics.Append(diags...) +} + +// End of section. //template:end read diff --git a/internal/provider/data_source_meraki_appliance_firewalled_service_test.go b/internal/provider/data_source_meraki_appliance_firewalled_service_test.go new file mode 100644 index 0000000..5df3a5e --- /dev/null +++ b/internal/provider/data_source_meraki_appliance_firewalled_service_test.go @@ -0,0 +1,92 @@ +// Copyright © 2024 Cisco Systems, Inc. and its affiliates. +// All rights reserved. +// +// Licensed under the Mozilla Public License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://mozilla.org/MPL/2.0/ +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +// SPDX-License-Identifier: MPL-2.0 + +package provider + +// Section below is generated&owned by "gen/generator.go". //template:begin imports +import ( + "os" + "testing" + + "github.com/hashicorp/terraform-plugin-testing/helper/resource" +) + +// End of section. //template:end imports + +// Section below is generated&owned by "gen/generator.go". //template:begin testAccDataSource + +func TestAccDataSourceMerakiApplianceFirewalledService(t *testing.T) { + if os.Getenv("TF_VAR_test_org") == "" || os.Getenv("TF_VAR_test_network") == "" { + t.Skip("skipping test, set environment variable TF_VAR_test_org and TF_VAR_test_network") + } + var checks []resource.TestCheckFunc + checks = append(checks, resource.TestCheckResourceAttr("data.meraki_appliance_firewalled_service.test", "service", "ICMP")) + checks = append(checks, resource.TestCheckResourceAttr("data.meraki_appliance_firewalled_service.test", "access", "restricted")) + checks = append(checks, resource.TestCheckResourceAttr("data.meraki_appliance_firewalled_service.test", "allowed_ips.0", "123.123.123.1")) + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + ProtoV6ProviderFactories: testAccProtoV6ProviderFactories, + Steps: []resource.TestStep{ + { + Config: testAccDataSourceMerakiApplianceFirewalledServicePrerequisitesConfig + testAccDataSourceMerakiApplianceFirewalledServiceConfig(), + Check: resource.ComposeTestCheckFunc(checks...), + }, + }, + }) +} + +// End of section. //template:end testAccDataSource + +// Section below is generated&owned by "gen/generator.go". //template:begin testPrerequisites + +const testAccDataSourceMerakiApplianceFirewalledServicePrerequisitesConfig = ` +variable "test_org" {} +variable "test_network" {} +data "meraki_organization" "test" { + name = var.test_org +} +resource "meraki_network" "test" { + organization_id = data.meraki_organization.test.id + name = var.test_network + product_types = ["switch", "wireless", "appliance"] +} + +` + +// End of section. //template:end testPrerequisites + +// Section below is generated&owned by "gen/generator.go". //template:begin testAccDataSourceConfig + +func testAccDataSourceMerakiApplianceFirewalledServiceConfig() string { + config := `resource "meraki_appliance_firewalled_service" "test" {` + "\n" + config += ` network_id = meraki_network.test.id` + "\n" + config += ` service = "ICMP"` + "\n" + config += ` access = "restricted"` + "\n" + config += ` allowed_ips = ["123.123.123.1"]` + "\n" + config += `}` + "\n" + + config += ` + data "meraki_appliance_firewalled_service" "test" { + network_id = meraki_network.test.id + service = "ICMP" + depends_on = [meraki_appliance_firewalled_service.test] + } + ` + return config +} + +// End of section. //template:end testAccDataSourceConfig diff --git a/internal/provider/model_meraki_appliance_firewalled_service.go b/internal/provider/model_meraki_appliance_firewalled_service.go new file mode 100644 index 0000000..b52ddb8 --- /dev/null +++ b/internal/provider/model_meraki_appliance_firewalled_service.go @@ -0,0 +1,107 @@ +// Copyright © 2024 Cisco Systems, Inc. and its affiliates. +// All rights reserved. +// +// Licensed under the Mozilla Public License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://mozilla.org/MPL/2.0/ +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +// SPDX-License-Identifier: MPL-2.0 + +package provider + +// Section below is generated&owned by "gen/generator.go". //template:begin imports +import ( + "context" + "fmt" + "net/url" + + "github.com/CiscoDevNet/terraform-provider-meraki/internal/provider/helpers" + "github.com/hashicorp/terraform-plugin-framework/types" + "github.com/netascode/go-meraki" + "github.com/tidwall/sjson" +) + +// End of section. //template:end imports + +// Section below is generated&owned by "gen/generator.go". //template:begin types + +type ApplianceFirewalledService struct { + Id types.String `tfsdk:"id"` + NetworkId types.String `tfsdk:"network_id"` + Service types.String `tfsdk:"service"` + Access types.String `tfsdk:"access"` + AllowedIps types.List `tfsdk:"allowed_ips"` +} + +// End of section. //template:end types + +// Section below is generated&owned by "gen/generator.go". //template:begin getPath + +func (data ApplianceFirewalledService) getPath() string { + return fmt.Sprintf("/networks/%v/appliance/firewall/firewalledServices/%v", url.QueryEscape(data.NetworkId.ValueString()), url.QueryEscape(data.Service.ValueString())) +} + +// End of section. //template:end getPath + +// Section below is generated&owned by "gen/generator.go". //template:begin toBody + +func (data ApplianceFirewalledService) toBody(ctx context.Context, state ApplianceFirewalledService) string { + body := "" + if !data.Access.IsNull() { + body, _ = sjson.Set(body, "access", data.Access.ValueString()) + } + if !data.AllowedIps.IsNull() { + var values []string + data.AllowedIps.ElementsAs(ctx, &values, false) + body, _ = sjson.Set(body, "allowedIps", values) + } + return body +} + +// End of section. //template:end toBody + +// Section below is generated&owned by "gen/generator.go". //template:begin fromBody + +func (data *ApplianceFirewalledService) fromBody(ctx context.Context, res meraki.Res) { + if value := res.Get("access"); value.Exists() && value.Value() != nil { + data.Access = types.StringValue(value.String()) + } else { + data.Access = types.StringNull() + } + if value := res.Get("allowedIps"); value.Exists() && value.Value() != nil { + data.AllowedIps = helpers.GetStringList(value.Array()) + } else { + data.AllowedIps = types.ListNull(types.StringType) + } +} + +// End of section. //template:end fromBody + +// Section below is generated&owned by "gen/generator.go". //template:begin fromBodyPartial + +// fromBodyPartial reads values from a gjson.Result into a tfstate model. It ignores null attributes in order to +// uncouple the provider from the exact values that the backend API might summon to replace nulls. (Such behavior might +// easily change across versions of the backend API.) For List/Set/Map attributes, the func only updates the +// "managed" elements, instead of all elements. +func (data *ApplianceFirewalledService) fromBodyPartial(ctx context.Context, res meraki.Res) { + if value := res.Get("access"); value.Exists() && !data.Access.IsNull() { + data.Access = types.StringValue(value.String()) + } else { + data.Access = types.StringNull() + } + if value := res.Get("allowedIps"); value.Exists() && !data.AllowedIps.IsNull() { + data.AllowedIps = helpers.GetStringList(value.Array()) + } else { + data.AllowedIps = types.ListNull(types.StringType) + } +} + +// End of section. //template:end fromBodyPartial diff --git a/internal/provider/provider.go b/internal/provider/provider.go index 1f5fcb7..9f25c3a 100644 --- a/internal/provider/provider.go +++ b/internal/provider/provider.go @@ -220,6 +220,7 @@ func (p *MerakiProvider) Resources(ctx context.Context) []func() resource.Resour NewApplianceCellularFirewallRulesResource, NewApplianceConnectivityMonitoringDestinationsResource, NewApplianceContentFilteringResource, + NewApplianceFirewalledServiceResource, NewDeviceResource, NewDeviceCellularSIMsResource, NewDeviceManagementInterfaceResource, @@ -314,6 +315,7 @@ func (p *MerakiProvider) DataSources(ctx context.Context) []func() datasource.Da NewApplianceCellularFirewallRulesDataSource, NewApplianceConnectivityMonitoringDestinationsDataSource, NewApplianceContentFilteringDataSource, + NewApplianceFirewalledServiceDataSource, NewDeviceDataSource, NewDeviceCellularSIMsDataSource, NewDeviceManagementInterfaceDataSource, diff --git a/internal/provider/resource_meraki_appliance_firewalled_service.go b/internal/provider/resource_meraki_appliance_firewalled_service.go new file mode 100644 index 0000000..c9476a2 --- /dev/null +++ b/internal/provider/resource_meraki_appliance_firewalled_service.go @@ -0,0 +1,264 @@ +// Copyright © 2024 Cisco Systems, Inc. and its affiliates. +// All rights reserved. +// +// Licensed under the Mozilla Public License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://mozilla.org/MPL/2.0/ +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +// SPDX-License-Identifier: MPL-2.0 + +package provider + +// Section below is generated&owned by "gen/generator.go". //template:begin imports +import ( + "context" + "fmt" + "strings" + + "github.com/CiscoDevNet/terraform-provider-meraki/internal/provider/helpers" + "github.com/hashicorp/terraform-plugin-framework-validators/stringvalidator" + "github.com/hashicorp/terraform-plugin-framework/path" + "github.com/hashicorp/terraform-plugin-framework/resource" + "github.com/hashicorp/terraform-plugin-framework/resource/schema" + "github.com/hashicorp/terraform-plugin-framework/resource/schema/planmodifier" + "github.com/hashicorp/terraform-plugin-framework/resource/schema/stringplanmodifier" + "github.com/hashicorp/terraform-plugin-framework/schema/validator" + "github.com/hashicorp/terraform-plugin-framework/types" + "github.com/hashicorp/terraform-plugin-log/tflog" + "github.com/netascode/go-meraki" +) + +// End of section. //template:end imports + +// Section below is generated&owned by "gen/generator.go". //template:begin model + +// Ensure provider defined types fully satisfy framework interfaces +var ( + _ resource.Resource = &ApplianceFirewalledServiceResource{} + _ resource.ResourceWithImportState = &ApplianceFirewalledServiceResource{} +) + +func NewApplianceFirewalledServiceResource() resource.Resource { + return &ApplianceFirewalledServiceResource{} +} + +type ApplianceFirewalledServiceResource struct { + client *meraki.Client +} + +func (r *ApplianceFirewalledServiceResource) Metadata(ctx context.Context, req resource.MetadataRequest, resp *resource.MetadataResponse) { + resp.TypeName = req.ProviderTypeName + "_appliance_firewalled_service" +} + +func (r *ApplianceFirewalledServiceResource) Schema(ctx context.Context, req resource.SchemaRequest, resp *resource.SchemaResponse) { + resp.Schema = schema.Schema{ + // This description is used by the documentation generator and the language server. + MarkdownDescription: helpers.NewAttributeDescription("This resource can manage the `Appliance Firewalled Service` configuration.").String, + + Attributes: map[string]schema.Attribute{ + "id": schema.StringAttribute{ + MarkdownDescription: "The id of the object", + Computed: true, + PlanModifiers: []planmodifier.String{ + stringplanmodifier.UseStateForUnknown(), + }, + }, + "network_id": schema.StringAttribute{ + MarkdownDescription: helpers.NewAttributeDescription("Network ID").String, + Required: true, + PlanModifiers: []planmodifier.String{ + stringplanmodifier.RequiresReplace(), + }, + }, + "service": schema.StringAttribute{ + MarkdownDescription: helpers.NewAttributeDescription("Service").AddStringEnumDescription("ICMP", "SNMP", "web").String, + Required: true, + Validators: []validator.String{ + stringvalidator.OneOf("ICMP", "SNMP", "web"), + }, + PlanModifiers: []planmodifier.String{ + stringplanmodifier.RequiresReplace(), + }, + }, + "access": schema.StringAttribute{ + MarkdownDescription: helpers.NewAttributeDescription("A string indicating the rule for which IPs are allowed to use the specified service. Acceptable values are 'blocked' (no remote IPs can access the service), 'restricted' (only allowed IPs can access the service), and 'unrestriced' (any remote IP can access the service). This field is required").AddStringEnumDescription("blocked", "restricted", "unrestricted").String, + Required: true, + Validators: []validator.String{ + stringvalidator.OneOf("blocked", "restricted", "unrestricted"), + }, + }, + "allowed_ips": schema.ListAttribute{ + MarkdownDescription: helpers.NewAttributeDescription("An array of allowed IPs that can access the service. This field is required if 'access' is set to 'restricted'. Otherwise this field is ignored").String, + ElementType: types.StringType, + Optional: true, + }, + }, + } +} + +func (r *ApplianceFirewalledServiceResource) Configure(_ context.Context, req resource.ConfigureRequest, _ *resource.ConfigureResponse) { + if req.ProviderData == nil { + return + } + + r.client = req.ProviderData.(*MerakiProviderData).Client +} + +// End of section. //template:end model + +// Section below is generated&owned by "gen/generator.go". //template:begin create + +func (r *ApplianceFirewalledServiceResource) Create(ctx context.Context, req resource.CreateRequest, resp *resource.CreateResponse) { + var plan ApplianceFirewalledService + + // Read plan + diags := req.Plan.Get(ctx, &plan) + if resp.Diagnostics.Append(diags...); resp.Diagnostics.HasError() { + return + } + + tflog.Debug(ctx, fmt.Sprintf("%s: Beginning Create", plan.Id.ValueString())) + + // Create object + body := plan.toBody(ctx, ApplianceFirewalledService{}) + res, err := r.client.Put(plan.getPath(), body) + if err != nil { + resp.Diagnostics.AddError("Client Error", fmt.Sprintf("Failed to configure object (POST/PUT), got error: %s, %s", err, res.String())) + return + } + plan.Id = plan.Service + + tflog.Debug(ctx, fmt.Sprintf("%s: Create finished successfully", plan.Id.ValueString())) + + diags = resp.State.Set(ctx, &plan) + resp.Diagnostics.Append(diags...) + + helpers.SetFlagImporting(ctx, false, resp.Private, &resp.Diagnostics) +} + +// End of section. //template:end create + +// Section below is generated&owned by "gen/generator.go". //template:begin read + +func (r *ApplianceFirewalledServiceResource) Read(ctx context.Context, req resource.ReadRequest, resp *resource.ReadResponse) { + var state ApplianceFirewalledService + + // Read state + diags := req.State.Get(ctx, &state) + if resp.Diagnostics.Append(diags...); resp.Diagnostics.HasError() { + return + } + + tflog.Debug(ctx, fmt.Sprintf("%s: Beginning Read", state.Id.String())) + res, err := r.client.Get(state.getPath()) + if err != nil && strings.Contains(err.Error(), "StatusCode 404") { + resp.State.RemoveResource(ctx) + return + } else if err != nil { + resp.Diagnostics.AddError("Client Error", fmt.Sprintf("Failed to retrieve object (GET), got error: %s, %s", err, res.String())) + return + } + + imp, diags := helpers.IsFlagImporting(ctx, req) + if resp.Diagnostics.Append(diags...); resp.Diagnostics.HasError() { + return + } + + // After `terraform import` we switch to a full read. + if imp { + state.fromBody(ctx, res) + } else { + state.fromBodyPartial(ctx, res) + } + + tflog.Debug(ctx, fmt.Sprintf("%s: Read finished successfully", state.Id.ValueString())) + + diags = resp.State.Set(ctx, &state) + resp.Diagnostics.Append(diags...) + + helpers.SetFlagImporting(ctx, false, resp.Private, &resp.Diagnostics) +} + +// End of section. //template:end read + +// Section below is generated&owned by "gen/generator.go". //template:begin update + +func (r *ApplianceFirewalledServiceResource) Update(ctx context.Context, req resource.UpdateRequest, resp *resource.UpdateResponse) { + var plan, state ApplianceFirewalledService + + // Read plan + diags := req.Plan.Get(ctx, &plan) + if resp.Diagnostics.Append(diags...); resp.Diagnostics.HasError() { + return + } + + // Read state + diags = req.State.Get(ctx, &state) + if resp.Diagnostics.Append(diags...); resp.Diagnostics.HasError() { + return + } + + tflog.Debug(ctx, fmt.Sprintf("%s: Beginning Update", plan.Id.ValueString())) + + body := plan.toBody(ctx, state) + res, err := r.client.Put(plan.getPath(), body) + if err != nil { + resp.Diagnostics.AddError("Client Error", fmt.Sprintf("Failed to configure object (PUT), got error: %s, %s", err, res.String())) + return + } + + tflog.Debug(ctx, fmt.Sprintf("%s: Update finished successfully", plan.Id.ValueString())) + + diags = resp.State.Set(ctx, &plan) + resp.Diagnostics.Append(diags...) +} + +// End of section. //template:end update + +// Section below is generated&owned by "gen/generator.go". //template:begin delete + +func (r *ApplianceFirewalledServiceResource) Delete(ctx context.Context, req resource.DeleteRequest, resp *resource.DeleteResponse) { + var state ApplianceFirewalledService + + // Read state + diags := req.State.Get(ctx, &state) + if resp.Diagnostics.Append(diags...); resp.Diagnostics.HasError() { + return + } + + tflog.Debug(ctx, fmt.Sprintf("%s: Beginning Delete", state.Id.ValueString())) + + tflog.Debug(ctx, fmt.Sprintf("%s: Delete finished successfully", state.Id.ValueString())) + + resp.State.RemoveResource(ctx) +} + +// End of section. //template:end delete + +// Section below is generated&owned by "gen/generator.go". //template:begin import +func (r *ApplianceFirewalledServiceResource) ImportState(ctx context.Context, req resource.ImportStateRequest, resp *resource.ImportStateResponse) { + idParts := strings.Split(req.ID, ",") + + if len(idParts) != 2 || idParts[0] == "" || idParts[1] == "" { + resp.Diagnostics.AddError( + "Unexpected Import Identifier", + fmt.Sprintf("Expected import identifier with format: ,. Got: %q", req.ID), + ) + return + } + resp.Diagnostics.Append(resp.State.SetAttribute(ctx, path.Root("network_id"), idParts[0])...) + resp.Diagnostics.Append(resp.State.SetAttribute(ctx, path.Root("service"), idParts[1])...) + resp.Diagnostics.Append(resp.State.SetAttribute(ctx, path.Root("id"), idParts[1])...) + + helpers.SetFlagImporting(ctx, true, resp.Private, &resp.Diagnostics) +} + +// End of section. //template:end import diff --git a/internal/provider/resource_meraki_appliance_firewalled_service_test.go b/internal/provider/resource_meraki_appliance_firewalled_service_test.go new file mode 100644 index 0000000..b359742 --- /dev/null +++ b/internal/provider/resource_meraki_appliance_firewalled_service_test.go @@ -0,0 +1,128 @@ +// Copyright © 2024 Cisco Systems, Inc. and its affiliates. +// All rights reserved. +// +// Licensed under the Mozilla Public License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://mozilla.org/MPL/2.0/ +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +// SPDX-License-Identifier: MPL-2.0 + +package provider + +// Section below is generated&owned by "gen/generator.go". //template:begin imports +import ( + "fmt" + "os" + "testing" + + "github.com/hashicorp/terraform-plugin-testing/helper/resource" + "github.com/hashicorp/terraform-plugin-testing/terraform" +) + +// End of section. //template:end imports + +// Section below is generated&owned by "gen/generator.go". //template:begin testAcc + +func TestAccMerakiApplianceFirewalledService(t *testing.T) { + if os.Getenv("TF_VAR_test_org") == "" || os.Getenv("TF_VAR_test_network") == "" { + t.Skip("skipping test, set environment variable TF_VAR_test_org and TF_VAR_test_network") + } + var checks []resource.TestCheckFunc + checks = append(checks, resource.TestCheckResourceAttr("meraki_appliance_firewalled_service.test", "service", "ICMP")) + checks = append(checks, resource.TestCheckResourceAttr("meraki_appliance_firewalled_service.test", "access", "restricted")) + checks = append(checks, resource.TestCheckResourceAttr("meraki_appliance_firewalled_service.test", "allowed_ips.0", "123.123.123.1")) + + var steps []resource.TestStep + if os.Getenv("SKIP_MINIMUM_TEST") == "" { + steps = append(steps, resource.TestStep{ + Config: testAccMerakiApplianceFirewalledServicePrerequisitesConfig + testAccMerakiApplianceFirewalledServiceConfig_minimum(), + }) + } + steps = append(steps, resource.TestStep{ + Config: testAccMerakiApplianceFirewalledServicePrerequisitesConfig + testAccMerakiApplianceFirewalledServiceConfig_all(), + Check: resource.ComposeTestCheckFunc(checks...), + }) + steps = append(steps, resource.TestStep{ + ResourceName: "meraki_appliance_firewalled_service.test", + ImportState: true, + ImportStateVerify: true, + ImportStateIdFunc: merakiApplianceFirewalledServiceImportStateIdFunc("meraki_appliance_firewalled_service.test"), + ImportStateVerifyIgnore: []string{}, + Check: resource.ComposeTestCheckFunc(checks...), + }) + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + ProtoV6ProviderFactories: testAccProtoV6ProviderFactories, + Steps: steps, + }) +} + +// End of section. //template:end testAcc + +// Section below is generated&owned by "gen/generator.go". //template:begin importStateIdFunc + +func merakiApplianceFirewalledServiceImportStateIdFunc(resourceName string) resource.ImportStateIdFunc { + return func(s *terraform.State) (string, error) { + primary := s.RootModule().Resources[resourceName].Primary + NetworkId := primary.Attributes["network_id"] + Service := primary.Attributes["service"] + + return fmt.Sprintf("%s,%s", NetworkId, Service), nil + } +} + +// End of section. //template:end importStateIdFunc + +// Section below is generated&owned by "gen/generator.go". //template:begin testPrerequisites + +const testAccMerakiApplianceFirewalledServicePrerequisitesConfig = ` +variable "test_org" {} +variable "test_network" {} +data "meraki_organization" "test" { + name = var.test_org +} +resource "meraki_network" "test" { + organization_id = data.meraki_organization.test.id + name = var.test_network + product_types = ["switch", "wireless", "appliance"] +} + +` + +// End of section. //template:end testPrerequisites + +// Section below is generated&owned by "gen/generator.go". //template:begin testAccConfigMinimal + +func testAccMerakiApplianceFirewalledServiceConfig_minimum() string { + config := `resource "meraki_appliance_firewalled_service" "test" {` + "\n" + config += ` network_id = meraki_network.test.id` + "\n" + config += ` service = "ICMP"` + "\n" + config += ` access = "unrestricted"` + "\n" + config += `}` + "\n" + return config +} + +// End of section. //template:end testAccConfigMinimal + +// Section below is generated&owned by "gen/generator.go". //template:begin testAccConfigAll + +func testAccMerakiApplianceFirewalledServiceConfig_all() string { + config := `resource "meraki_appliance_firewalled_service" "test" {` + "\n" + config += ` network_id = meraki_network.test.id` + "\n" + config += ` service = "ICMP"` + "\n" + config += ` access = "restricted"` + "\n" + config += ` allowed_ips = ["123.123.123.1"]` + "\n" + config += `}` + "\n" + return config +} + +// End of section. //template:end testAccConfigAll diff --git a/templates/guides/changelog.md.tmpl b/templates/guides/changelog.md.tmpl index 5788d4e..c885ceb 100644 --- a/templates/guides/changelog.md.tmpl +++ b/templates/guides/changelog.md.tmpl @@ -7,6 +7,10 @@ description: |- # Changelog +## 0.1.2 (unreleased) + +- Add `meraki_appliance_firewalled_service` resource and data source + ## 0.1.1 - Add `meraki_network_floor_plans` data source