From 6e9e204ce7f8b418294b9ebdd60dcb627c27cbe1 Mon Sep 17 00:00:00 2001 From: Marcin Parafiniuk Date: Tue, 3 Sep 2024 16:07:28 +0200 Subject: [PATCH 1/4] network_settings example --- docs/data-sources/network_settings.md | 32 ++ docs/resources/network_settings.md | 43 +++ docs/resources/organization.md | 2 +- .../meraki_network_settings/data-source.tf | 4 + .../meraki_network_settings/import.sh | 1 + .../meraki_network_settings/resource.tf | 4 + .../resources/meraki_organization/resource.tf | 2 +- gen/definitions/admin.yaml | 2 +- gen/definitions/network.yaml | 2 +- gen/definitions/networks_settings.yaml | 27 ++ gen/definitions/organization.yaml | 2 +- gen/templates/data_source.go | 2 +- internal/provider/data_source_meraki_admin.go | 1 + .../provider/data_source_meraki_admin_test.go | 2 +- .../provider/data_source_meraki_network.go | 1 + .../data_source_meraki_network_settings.go | 128 ++++++++ ...ata_source_meraki_network_settings_test.go | 81 +++++ .../data_source_meraki_network_test.go | 2 +- .../data_source_meraki_organization.go | 1 + .../data_source_meraki_organization_test.go | 6 +- .../provider/model_meraki_network_settings.go | 101 +++++++ internal/provider/provider.go | 2 + .../provider/resource_meraki_admin_test.go | 2 +- .../resource_meraki_network_settings.go | 279 ++++++++++++++++++ .../resource_meraki_network_settings_test.go | 93 ++++++ .../provider/resource_meraki_network_test.go | 2 +- .../resource_meraki_organization_test.go | 6 +- 27 files changed, 814 insertions(+), 16 deletions(-) create mode 100644 docs/data-sources/network_settings.md create mode 100644 docs/resources/network_settings.md create mode 100644 examples/data-sources/meraki_network_settings/data-source.tf create mode 100644 examples/resources/meraki_network_settings/import.sh create mode 100644 examples/resources/meraki_network_settings/resource.tf create mode 100644 gen/definitions/networks_settings.yaml create mode 100644 internal/provider/data_source_meraki_network_settings.go create mode 100644 internal/provider/data_source_meraki_network_settings_test.go create mode 100644 internal/provider/model_meraki_network_settings.go create mode 100644 internal/provider/resource_meraki_network_settings.go create mode 100644 internal/provider/resource_meraki_network_settings_test.go diff --git a/docs/data-sources/network_settings.md b/docs/data-sources/network_settings.md new file mode 100644 index 0000000..4ef8140 --- /dev/null +++ b/docs/data-sources/network_settings.md @@ -0,0 +1,32 @@ +--- +# generated by https://github.com/hashicorp/terraform-plugin-docs +page_title: "meraki_network_settings Data Source - terraform-provider-meraki" +subcategory: "Networks" +description: |- + This data source can read the Network Settings configuration. +--- + +# meraki_network_settings (Data Source) + +This data source can read the `Network Settings` configuration. + +## Example Usage + +```terraform +data "meraki_network_settings" "example" { + id = "12345678" + network_id = "L_123456" +} +``` + + +## Schema + +### Required + +- `id` (String) The id of the object +- `network_id` (String) Network ID + +### Read-Only + +- `local_status_page_enabled` (Boolean) asdasdas diff --git a/docs/resources/network_settings.md b/docs/resources/network_settings.md new file mode 100644 index 0000000..5223867 --- /dev/null +++ b/docs/resources/network_settings.md @@ -0,0 +1,43 @@ +--- +# generated by https://github.com/hashicorp/terraform-plugin-docs +page_title: "meraki_network_settings Resource - terraform-provider-meraki" +subcategory: "Networks" +description: |- + This resource can manage the Network Settings configuration. +--- + +# meraki_network_settings (Resource) + +This resource can manage the `Network Settings` configuration. + +## Example Usage + +```terraform +resource "meraki_network_settings" "example" { + network_id = "L_123456" + local_status_page_enabled = false +} +``` + + +## Schema + +### Required + +- `network_id` (String) Network ID + +### Optional + +- `local_status_page_enabled` (Boolean) asdasdas + +### Read-Only + +- `id` (String) The id of the object + +## Import + +Import is supported using the following syntax: + +```shell +terraform import meraki_network_settings.example "," +``` diff --git a/docs/resources/organization.md b/docs/resources/organization.md index 226abdc..2d5ce88 100644 --- a/docs/resources/organization.md +++ b/docs/resources/organization.md @@ -14,7 +14,7 @@ This resource can manage the `Organization` configuration. ```terraform resource "meraki_organization" "example" { - name = "My organization" + name = "Dev" management_details = [ { name = "MSP ID" diff --git a/examples/data-sources/meraki_network_settings/data-source.tf b/examples/data-sources/meraki_network_settings/data-source.tf new file mode 100644 index 0000000..05b9577 --- /dev/null +++ b/examples/data-sources/meraki_network_settings/data-source.tf @@ -0,0 +1,4 @@ +data "meraki_network_settings" "example" { + id = "12345678" + network_id = "L_123456" +} diff --git a/examples/resources/meraki_network_settings/import.sh b/examples/resources/meraki_network_settings/import.sh new file mode 100644 index 0000000..bc2ad84 --- /dev/null +++ b/examples/resources/meraki_network_settings/import.sh @@ -0,0 +1 @@ +terraform import meraki_network_settings.example "," diff --git a/examples/resources/meraki_network_settings/resource.tf b/examples/resources/meraki_network_settings/resource.tf new file mode 100644 index 0000000..9cd8ae9 --- /dev/null +++ b/examples/resources/meraki_network_settings/resource.tf @@ -0,0 +1,4 @@ +resource "meraki_network_settings" "example" { + network_id = "L_123456" + local_status_page_enabled = false +} diff --git a/examples/resources/meraki_organization/resource.tf b/examples/resources/meraki_organization/resource.tf index 586604b..0fd9cd0 100644 --- a/examples/resources/meraki_organization/resource.tf +++ b/examples/resources/meraki_organization/resource.tf @@ -1,5 +1,5 @@ resource "meraki_organization" "example" { - name = "My organization" + name = "Dev" management_details = [ { name = "MSP ID" diff --git a/gen/definitions/admin.yaml b/gen/definitions/admin.yaml index 9ac5448..e449b14 100644 --- a/gen/definitions/admin.yaml +++ b/gen/definitions/admin.yaml @@ -61,7 +61,7 @@ attributes: example: west test_prerequisites: | data "meraki_organization" "test" { - name = "TF Test" + name = "Dev" } resource "meraki_network" "test" { organization_id = data.meraki_organization.test.id diff --git a/gen/definitions/network.yaml b/gen/definitions/network.yaml index 74130f8..66f0bbf 100644 --- a/gen/definitions/network.yaml +++ b/gen/definitions/network.yaml @@ -36,5 +36,5 @@ attributes: example: tag1 test_prerequisites: | data "meraki_organization" "test" { - name = "TF Test" + name = "Dev" } diff --git a/gen/definitions/networks_settings.yaml b/gen/definitions/networks_settings.yaml new file mode 100644 index 0000000..a166ef6 --- /dev/null +++ b/gen/definitions/networks_settings.yaml @@ -0,0 +1,27 @@ +--- +name: Network Settings +rest_endpoint: /networks/%v/settings +no_delete: true +put_create: true +doc_category: Networks +get_from_all: true +attributes: + - tf_name: network_id + type: String + reference: true + description: Network ID + example: L_123456 + test_value: meraki_network.test.id + - model_name: localStatusPageEnabled + type: Bool + description: asdasdas + example: false +test_prerequisites: | + data "meraki_organization" "test" { + name = "Dev" + } + resource "meraki_network" "test" { + organization_id = data.meraki_organization.test.id + name = "Network1" + product_types = ["switch"] + } diff --git a/gen/definitions/organization.yaml b/gen/definitions/organization.yaml index 8818660..54c0de3 100644 --- a/gen/definitions/organization.yaml +++ b/gen/definitions/organization.yaml @@ -8,7 +8,7 @@ attributes: type: String mandatory: true description: The name of the organization - example: My organization + example: Dev - model_name: details data_path: [management] description: Details related to organization management, possibly empty diff --git a/gen/templates/data_source.go b/gen/templates/data_source.go index 8171644..39f0299 100644 --- a/gen/templates/data_source.go +++ b/gen/templates/data_source.go @@ -181,9 +181,9 @@ func (d *{{camelCase .Name}}DataSource) Read(ctx context.Context, req datasource tflog.Debug(ctx, fmt.Sprintf("%s: Beginning Read", config.Id.String())) - {{- if .DataSourceNameQuery}} var res gjson.Result var err error + {{- if .DataSourceNameQuery}} if config.Id.IsNull() && !config.Name.IsNull() { res, err = d.client.Get(config.getPath()) if err != nil { diff --git a/internal/provider/data_source_meraki_admin.go b/internal/provider/data_source_meraki_admin.go index 9241129..9e62a94 100644 --- a/internal/provider/data_source_meraki_admin.go +++ b/internal/provider/data_source_meraki_admin.go @@ -149,6 +149,7 @@ func (d *AdminDataSource) Read(ctx context.Context, req datasource.ReadRequest, } tflog.Debug(ctx, fmt.Sprintf("%s: Beginning Read", config.Id.String())) + var res gjson.Result var err error if config.Id.IsNull() && !config.Name.IsNull() { diff --git a/internal/provider/data_source_meraki_admin_test.go b/internal/provider/data_source_meraki_admin_test.go index b7f69ee..252df62 100644 --- a/internal/provider/data_source_meraki_admin_test.go +++ b/internal/provider/data_source_meraki_admin_test.go @@ -58,7 +58,7 @@ func TestAccDataSourceMerakiAdmin(t *testing.T) { const testAccDataSourceMerakiAdminPrerequisitesConfig = ` data "meraki_organization" "test" { - name = "TF Test" + name = "Dev" } resource "meraki_network" "test" { organization_id = data.meraki_organization.test.id diff --git a/internal/provider/data_source_meraki_network.go b/internal/provider/data_source_meraki_network.go index 4ef9c9d..305a26e 100644 --- a/internal/provider/data_source_meraki_network.go +++ b/internal/provider/data_source_meraki_network.go @@ -128,6 +128,7 @@ func (d *NetworkDataSource) Read(ctx context.Context, req datasource.ReadRequest } tflog.Debug(ctx, fmt.Sprintf("%s: Beginning Read", config.Id.String())) + var res gjson.Result var err error if config.Id.IsNull() && !config.Name.IsNull() { diff --git a/internal/provider/data_source_meraki_network_settings.go b/internal/provider/data_source_meraki_network_settings.go new file mode 100644 index 0000000..7d8b426 --- /dev/null +++ b/internal/provider/data_source_meraki_network_settings.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 ( + "context" + "fmt" + + "github.com/hashicorp/terraform-plugin-framework/datasource" + "github.com/hashicorp/terraform-plugin-framework/datasource/schema" + "github.com/hashicorp/terraform-plugin-log/tflog" + "github.com/netascode/go-meraki" + "github.com/tidwall/gjson" +) + +// 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 = &NetworkSettingsDataSource{} + _ datasource.DataSourceWithConfigure = &NetworkSettingsDataSource{} +) + +func NewNetworkSettingsDataSource() datasource.DataSource { + return &NetworkSettingsDataSource{} +} + +type NetworkSettingsDataSource struct { + client *meraki.Client +} + +func (d *NetworkSettingsDataSource) Metadata(_ context.Context, req datasource.MetadataRequest, resp *datasource.MetadataResponse) { + resp.TypeName = req.ProviderTypeName + "_network_settings" +} + +func (d *NetworkSettingsDataSource) 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 `Network Settings` configuration.", + + Attributes: map[string]schema.Attribute{ + "id": schema.StringAttribute{ + MarkdownDescription: "The id of the object", + Required: true, + }, + "network_id": schema.StringAttribute{ + MarkdownDescription: "Network ID", + Required: true, + }, + "local_status_page_enabled": schema.BoolAttribute{ + MarkdownDescription: "asdasdas", + Computed: true, + }, + }, + } +} + +func (d *NetworkSettingsDataSource) 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 *NetworkSettingsDataSource) Read(ctx context.Context, req datasource.ReadRequest, resp *datasource.ReadResponse) { + var config NetworkSettings + + // 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 gjson.Result + 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 + } + } + if len(res.Array()) > 0 { + res.ForEach(func(k, v gjson.Result) bool { + if config.Id.ValueString() == v.Get("id").String() { + res = v + return false + } + return true + }) + } + + config.fromBody(ctx, res) + + 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_network_settings_test.go b/internal/provider/data_source_meraki_network_settings_test.go new file mode 100644 index 0000000..075f14f --- /dev/null +++ b/internal/provider/data_source_meraki_network_settings_test.go @@ -0,0 +1,81 @@ +// 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 ( + "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 TestAccDataSourceMerakiNetworkSettings(t *testing.T) { + var checks []resource.TestCheckFunc + checks = append(checks, resource.TestCheckResourceAttr("data.meraki_network_settings.test", "local_status_page_enabled", "false")) + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + ProtoV6ProviderFactories: testAccProtoV6ProviderFactories, + Steps: []resource.TestStep{ + { + Config: testAccDataSourceMerakiNetworkSettingsPrerequisitesConfig + testAccDataSourceMerakiNetworkSettingsConfig(), + Check: resource.ComposeTestCheckFunc(checks...), + }, + }, + }) +} + +// End of section. //template:end testAccDataSource + +// Section below is generated&owned by "gen/generator.go". //template:begin testPrerequisites + +const testAccDataSourceMerakiNetworkSettingsPrerequisitesConfig = ` +data "meraki_organization" "test" { + name = "Dev" +} +resource "meraki_network" "test" { + organization_id = data.meraki_organization.test.id + name = "Network1" + product_types = ["switch"] +} + +` + +// End of section. //template:end testPrerequisites + +// Section below is generated&owned by "gen/generator.go". //template:begin testAccDataSourceConfig + +func testAccDataSourceMerakiNetworkSettingsConfig() string { + config := `resource "meraki_network_settings" "test" {` + "\n" + config += ` network_id = meraki_network.test.id` + "\n" + config += ` local_status_page_enabled = false` + "\n" + config += `}` + "\n" + + config += ` + data "meraki_network_settings" "test" { + id = meraki_network_settings.test.id + network_id = meraki_network.test.id + } + ` + return config +} + +// End of section. //template:end testAccDataSourceConfig diff --git a/internal/provider/data_source_meraki_network_test.go b/internal/provider/data_source_meraki_network_test.go index db4f4ff..a216abd 100644 --- a/internal/provider/data_source_meraki_network_test.go +++ b/internal/provider/data_source_meraki_network_test.go @@ -57,7 +57,7 @@ func TestAccDataSourceMerakiNetwork(t *testing.T) { const testAccDataSourceMerakiNetworkPrerequisitesConfig = ` data "meraki_organization" "test" { - name = "TF Test" + name = "Dev" } ` diff --git a/internal/provider/data_source_meraki_organization.go b/internal/provider/data_source_meraki_organization.go index c1463e9..5197fc7 100644 --- a/internal/provider/data_source_meraki_organization.go +++ b/internal/provider/data_source_meraki_organization.go @@ -122,6 +122,7 @@ func (d *OrganizationDataSource) Read(ctx context.Context, req datasource.ReadRe } tflog.Debug(ctx, fmt.Sprintf("%s: Beginning Read", config.Id.String())) + var res gjson.Result var err error if config.Id.IsNull() && !config.Name.IsNull() { diff --git a/internal/provider/data_source_meraki_organization_test.go b/internal/provider/data_source_meraki_organization_test.go index 15016c3..c405328 100644 --- a/internal/provider/data_source_meraki_organization_test.go +++ b/internal/provider/data_source_meraki_organization_test.go @@ -30,7 +30,7 @@ import ( func TestAccDataSourceMerakiOrganization(t *testing.T) { var checks []resource.TestCheckFunc - checks = append(checks, resource.TestCheckResourceAttr("data.meraki_organization.test", "name", "My organization")) + checks = append(checks, resource.TestCheckResourceAttr("data.meraki_organization.test", "name", "Dev")) checks = append(checks, resource.TestCheckResourceAttr("data.meraki_organization.test", "management_details.0.name", "MSP ID")) checks = append(checks, resource.TestCheckResourceAttr("data.meraki_organization.test", "management_details.0.value", "123456")) resource.Test(t, resource.TestCase{ @@ -58,7 +58,7 @@ func TestAccDataSourceMerakiOrganization(t *testing.T) { func testAccDataSourceMerakiOrganizationConfig() string { config := `resource "meraki_organization" "test" {` + "\n" - config += ` name = "My organization"` + "\n" + config += ` name = "Dev"` + "\n" config += ` management_details = [{` + "\n" config += ` name = "MSP ID"` + "\n" config += ` value = "123456"` + "\n" @@ -75,7 +75,7 @@ func testAccDataSourceMerakiOrganizationConfig() string { func testAccNamedDataSourceMerakiOrganizationConfig() string { config := `resource "meraki_organization" "test" {` + "\n" - config += ` name = "My organization"` + "\n" + config += ` name = "Dev"` + "\n" config += ` management_details = [{` + "\n" config += ` name = "MSP ID"` + "\n" config += ` value = "123456"` + "\n" diff --git a/internal/provider/model_meraki_network_settings.go b/internal/provider/model_meraki_network_settings.go new file mode 100644 index 0000000..1b562a3 --- /dev/null +++ b/internal/provider/model_meraki_network_settings.go @@ -0,0 +1,101 @@ +// 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/hashicorp/terraform-plugin-framework/types" + "github.com/tidwall/gjson" + "github.com/tidwall/sjson" +) + +// End of section. //template:end imports + +// Section below is generated&owned by "gen/generator.go". //template:begin types + +type NetworkSettings struct { + Id types.String `tfsdk:"id"` + NetworkId types.String `tfsdk:"network_id"` + LocalStatusPageEnabled types.Bool `tfsdk:"local_status_page_enabled"` +} + +// End of section. //template:end types + +// Section below is generated&owned by "gen/generator.go". //template:begin getPath + +func (data NetworkSettings) getPath() string { + return fmt.Sprintf("/networks/%v/settings", url.QueryEscape(data.NetworkId.ValueString())) +} + +// End of section. //template:end getPath + +// Section below is generated&owned by "gen/generator.go". //template:begin toBody + +func (data NetworkSettings) toBody(ctx context.Context, state NetworkSettings) string { + body := "" + if data.Id.ValueString() != "" { + body, _ = sjson.Set(body, "id", data.Id.ValueString()) + } + if !data.LocalStatusPageEnabled.IsNull() { + body, _ = sjson.Set(body, "localStatusPageEnabled", data.LocalStatusPageEnabled.ValueBool()) + } + return body +} + +// End of section. //template:end toBody + +// Section below is generated&owned by "gen/generator.go". //template:begin fromBody + +func (data *NetworkSettings) fromBody(ctx context.Context, res gjson.Result) { + if value := res.Get("localStatusPageEnabled"); value.Exists() { + data.LocalStatusPageEnabled = types.BoolValue(value.Bool()) + } else { + data.LocalStatusPageEnabled = types.BoolNull() + } +} + +// 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 *NetworkSettings) fromBodyPartial(ctx context.Context, res gjson.Result) { + if value := res.Get("localStatusPageEnabled"); value.Exists() && !data.LocalStatusPageEnabled.IsNull() { + data.LocalStatusPageEnabled = types.BoolValue(value.Bool()) + } else { + data.LocalStatusPageEnabled = types.BoolNull() + } +} + +// End of section. //template:end fromBodyPartial + +// Section below is generated&owned by "gen/generator.go". //template:begin fromBodyUnknowns + +// fromBodyUnknowns updates the Unknown Computed tfstate values from a JSON. +// Known values are not changed (usual for Computed attributes with UseStateForUnknown or with Default). +func (data *NetworkSettings) fromBodyUnknowns(ctx context.Context, res gjson.Result) { +} + +// End of section. //template:end fromBodyUnknowns diff --git a/internal/provider/provider.go b/internal/provider/provider.go index c00c003..0b06e83 100644 --- a/internal/provider/provider.go +++ b/internal/provider/provider.go @@ -219,6 +219,7 @@ func (p *MerakiProvider) Resources(ctx context.Context) []func() resource.Resour return []func() resource.Resource{ NewAdminResource, NewNetworkResource, + NewNetworkSettingsResource, NewOrganizationResource, } } @@ -227,6 +228,7 @@ func (p *MerakiProvider) DataSources(ctx context.Context) []func() datasource.Da return []func() datasource.DataSource{ NewAdminDataSource, NewNetworkDataSource, + NewNetworkSettingsDataSource, NewOrganizationDataSource, } } diff --git a/internal/provider/resource_meraki_admin_test.go b/internal/provider/resource_meraki_admin_test.go index e48102a..800333b 100644 --- a/internal/provider/resource_meraki_admin_test.go +++ b/internal/provider/resource_meraki_admin_test.go @@ -62,7 +62,7 @@ func TestAccMerakiAdmin(t *testing.T) { const testAccMerakiAdminPrerequisitesConfig = ` data "meraki_organization" "test" { - name = "TF Test" + name = "Dev" } resource "meraki_network" "test" { organization_id = data.meraki_organization.test.id diff --git a/internal/provider/resource_meraki_network_settings.go b/internal/provider/resource_meraki_network_settings.go new file mode 100644 index 0000000..dc4d4bd --- /dev/null +++ b/internal/provider/resource_meraki_network_settings.go @@ -0,0 +1,279 @@ +// 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" + "strings" + + "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/types" + "github.com/hashicorp/terraform-plugin-log/tflog" + "github.com/netascode/go-meraki" + "github.com/netascode/terraform-provider-meraki/internal/provider/helpers" + "github.com/tidwall/gjson" +) + +// 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 = &NetworkSettingsResource{} + _ resource.ResourceWithImportState = &NetworkSettingsResource{} +) + +func NewNetworkSettingsResource() resource.Resource { + return &NetworkSettingsResource{} +} + +type NetworkSettingsResource struct { + client *meraki.Client +} + +func (r *NetworkSettingsResource) Metadata(ctx context.Context, req resource.MetadataRequest, resp *resource.MetadataResponse) { + resp.TypeName = req.ProviderTypeName + "_network_settings" +} + +func (r *NetworkSettingsResource) 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 `Network Settings` 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(), + }, + }, + "local_status_page_enabled": schema.BoolAttribute{ + MarkdownDescription: helpers.NewAttributeDescription("asdasdas").String, + Optional: true, + }, + }, + } +} + +func (r *NetworkSettingsResource) 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 *NetworkSettingsResource) Create(ctx context.Context, req resource.CreateRequest, resp *resource.CreateResponse) { + var plan NetworkSettings + + // Read plan + diags := req.Plan.Get(ctx, &plan) + if resp.Diagnostics.Append(diags...); resp.Diagnostics.HasError() { + return + } + tflog.Debug(ctx, fmt.Sprintf("%s: considering object name %s", plan.Id, plan.Name)) + + if plan.Id.ValueString() == "" && plan.Name.ValueString() != "" { + res, err := r.client.Get(plan.getPath()) + if err != nil { + resp.Diagnostics.AddError("Client Error", fmt.Sprintf("Failed to retrieve objects, got error: %s", err)) + return + } + if len(res.Array()) > 0 { + res.ForEach(func(k, v gjson.Result) bool { + if plan.Name.ValueString() == v.Get("name").String() { + plan.Id = types.StringValue(v.Get("id").String()) + tflog.Debug(ctx, fmt.Sprintf("%s: Found object with name '%s', id: %s", plan.Id, plan.Name.ValueString(), plan.Id)) + return false + } + return true + }) + } + + if plan.Id.ValueString() == "" { + resp.Diagnostics.AddError("Client Error", fmt.Sprintf("Failed to find object with name: %s", plan.Name.ValueString())) + return + } + } + + tflog.Debug(ctx, fmt.Sprintf("%s: Beginning Create", plan.Id.ValueString())) + + // Create object + body := plan.toBody(ctx, NetworkSettings{}) + res, err := r.client.Put(plan.getPath()+"/"+url.PathEscape(plan.Id.ValueString()), 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 = types.StringValue(res.Get("id").String()) + + 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 *NetworkSettingsResource) Read(ctx context.Context, req resource.ReadRequest, resp *resource.ReadResponse) { + var state NetworkSettings + + // 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 + } + if len(res.Array()) > 0 { + res.ForEach(func(k, v gjson.Result) bool { + if state.Id.ValueString() == v.Get("id").String() { + res = v + return false + } + return true + }) + } + + 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 *NetworkSettingsResource) Update(ctx context.Context, req resource.UpdateRequest, resp *resource.UpdateResponse) { + var plan, state NetworkSettings + + // 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()+"/"+url.QueryEscape(plan.Id.ValueString()), 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 *NetworkSettingsResource) Delete(ctx context.Context, req resource.DeleteRequest, resp *resource.DeleteResponse) { + var state NetworkSettings + + // 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 *NetworkSettingsResource) 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("id"), idParts[1])...) + + helpers.SetFlagImporting(ctx, true, resp.Private, &resp.Diagnostics) +} + +// End of section. //template:end import diff --git a/internal/provider/resource_meraki_network_settings_test.go b/internal/provider/resource_meraki_network_settings_test.go new file mode 100644 index 0000000..3de4b55 --- /dev/null +++ b/internal/provider/resource_meraki_network_settings_test.go @@ -0,0 +1,93 @@ +// 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 testAcc + +func TestAccMerakiNetworkSettings(t *testing.T) { + var checks []resource.TestCheckFunc + checks = append(checks, resource.TestCheckResourceAttr("meraki_network_settings.test", "local_status_page_enabled", "false")) + + var steps []resource.TestStep + if os.Getenv("SKIP_MINIMUM_TEST") == "" { + steps = append(steps, resource.TestStep{ + Config: testAccMerakiNetworkSettingsPrerequisitesConfig + testAccMerakiNetworkSettingsConfig_minimum(), + }) + } + steps = append(steps, resource.TestStep{ + Config: testAccMerakiNetworkSettingsPrerequisitesConfig + testAccMerakiNetworkSettingsConfig_all(), + 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 testPrerequisites + +const testAccMerakiNetworkSettingsPrerequisitesConfig = ` +data "meraki_organization" "test" { + name = "Dev" +} +resource "meraki_network" "test" { + organization_id = data.meraki_organization.test.id + name = "Network1" + product_types = ["switch"] +} + +` + +// End of section. //template:end testPrerequisites + +// Section below is generated&owned by "gen/generator.go". //template:begin testAccConfigMinimal + +func testAccMerakiNetworkSettingsConfig_minimum() string { + config := `resource "meraki_network_settings" "test" {` + "\n" + config += ` network_id = meraki_network.test.id` + "\n" + config += `}` + "\n" + return config +} + +// End of section. //template:end testAccConfigMinimal + +// Section below is generated&owned by "gen/generator.go". //template:begin testAccConfigAll + +func testAccMerakiNetworkSettingsConfig_all() string { + config := `resource "meraki_network_settings" "test" {` + "\n" + config += ` network_id = meraki_network.test.id` + "\n" + config += ` local_status_page_enabled = false` + "\n" + config += `}` + "\n" + return config +} + +// End of section. //template:end testAccConfigAll diff --git a/internal/provider/resource_meraki_network_test.go b/internal/provider/resource_meraki_network_test.go index a7398d3..9eb36fb 100644 --- a/internal/provider/resource_meraki_network_test.go +++ b/internal/provider/resource_meraki_network_test.go @@ -61,7 +61,7 @@ func TestAccMerakiNetwork(t *testing.T) { const testAccMerakiNetworkPrerequisitesConfig = ` data "meraki_organization" "test" { - name = "TF Test" + name = "Dev" } ` diff --git a/internal/provider/resource_meraki_organization_test.go b/internal/provider/resource_meraki_organization_test.go index da5695a..40374ca 100644 --- a/internal/provider/resource_meraki_organization_test.go +++ b/internal/provider/resource_meraki_organization_test.go @@ -31,7 +31,7 @@ import ( func TestAccMerakiOrganization(t *testing.T) { var checks []resource.TestCheckFunc - checks = append(checks, resource.TestCheckResourceAttr("meraki_organization.test", "name", "My organization")) + checks = append(checks, resource.TestCheckResourceAttr("meraki_organization.test", "name", "Dev")) checks = append(checks, resource.TestCheckResourceAttr("meraki_organization.test", "management_details.0.name", "MSP ID")) checks = append(checks, resource.TestCheckResourceAttr("meraki_organization.test", "management_details.0.value", "123456")) @@ -66,7 +66,7 @@ func TestAccMerakiOrganization(t *testing.T) { func testAccMerakiOrganizationConfig_minimum() string { config := `resource "meraki_organization" "test" {` + "\n" - config += ` name = "My organization"` + "\n" + config += ` name = "Dev"` + "\n" config += `}` + "\n" return config } @@ -77,7 +77,7 @@ func testAccMerakiOrganizationConfig_minimum() string { func testAccMerakiOrganizationConfig_all() string { config := `resource "meraki_organization" "test" {` + "\n" - config += ` name = "My organization"` + "\n" + config += ` name = "Dev"` + "\n" config += ` management_details = [{` + "\n" config += ` name = "MSP ID"` + "\n" config += ` value = "123456"` + "\n" From 62b9a27f7e5e33c42d146940268b1d5aceabdb7f Mon Sep 17 00:00:00 2001 From: Marcin Parafiniuk Date: Wed, 4 Sep 2024 21:38:40 +0200 Subject: [PATCH 2/4] network settings works? --- .gitignore | 1 + docs/resources/network_settings.md | 5 +-- gen/definitions/networks_settings.yaml | 2 +- gen/templates/resource.go | 27 ------------- go.mod | 3 ++ go.sum | 2 - .../data_source_meraki_network_settings.go | 12 +----- .../resource_meraki_network_settings.go | 38 +------------------ .../resource_meraki_network_settings_test.go | 1 + 9 files changed, 11 insertions(+), 80 deletions(-) diff --git a/.gitignore b/.gitignore index fd3ad8e..a68eea4 100644 --- a/.gitignore +++ b/.gitignore @@ -26,6 +26,7 @@ website/node_modules *.iml website/vendor +terraform-provider-meraki # Test exclusions !command/test-fixtures/**/*.tfstate diff --git a/docs/resources/network_settings.md b/docs/resources/network_settings.md index 5223867..e000069 100644 --- a/docs/resources/network_settings.md +++ b/docs/resources/network_settings.md @@ -24,11 +24,8 @@ resource "meraki_network_settings" "example" { ### Required -- `network_id` (String) Network ID - -### Optional - - `local_status_page_enabled` (Boolean) asdasdas +- `network_id` (String) Network ID ### Read-Only diff --git a/gen/definitions/networks_settings.yaml b/gen/definitions/networks_settings.yaml index a166ef6..1eab87f 100644 --- a/gen/definitions/networks_settings.yaml +++ b/gen/definitions/networks_settings.yaml @@ -4,7 +4,6 @@ rest_endpoint: /networks/%v/settings no_delete: true put_create: true doc_category: Networks -get_from_all: true attributes: - tf_name: network_id type: String @@ -16,6 +15,7 @@ attributes: type: Bool description: asdasdas example: false + mandatory: true test_prerequisites: | data "meraki_organization" "test" { name = "Dev" diff --git a/gen/templates/resource.go b/gen/templates/resource.go index d89d5e7..f70f3fd 100644 --- a/gen/templates/resource.go +++ b/gen/templates/resource.go @@ -416,33 +416,6 @@ func (r *{{camelCase .Name}}Resource) Create(ctx context.Context, req resource.C return } - {{- if .PutCreate}} - tflog.Debug(ctx, fmt.Sprintf("%s: considering object name %s", plan.Id, plan.Name)) - - if plan.Id.ValueString() == "" && plan.Name.ValueString() != "" { - res, err := r.client.Get(plan.getPath()) - if err != nil { - resp.Diagnostics.AddError("Client Error", fmt.Sprintf("Failed to retrieve objects, got error: %s", err)) - return - } - if len(res.Array()) > 0 { - res.ForEach(func(k, v gjson.Result) bool { - if plan.Name.ValueString() == v.Get("name").String() { - plan.Id = types.StringValue(v.Get("id").String()) - tflog.Debug(ctx, fmt.Sprintf("%s: Found object with name '%s', id: %s", plan.Id, plan.Name.ValueString(), plan.Id)) - return false - } - return true - }) - } - - if plan.Id.ValueString() == "" { - resp.Diagnostics.AddError("Client Error", fmt.Sprintf("Failed to find object with name: %s", plan.Name.ValueString())) - return - } - } - {{- end}} - tflog.Debug(ctx, fmt.Sprintf("%s: Beginning Create", plan.Id.ValueString())) // Create object diff --git a/go.mod b/go.mod index 97da5a6..e1b2500 100644 --- a/go.mod +++ b/go.mod @@ -3,6 +3,7 @@ module github.com/netascode/terraform-provider-meraki go 1.22 require ( + github.com/davecgh/go-spew v1.1.1 github.com/hashicorp/terraform-plugin-docs v0.19.4 github.com/hashicorp/terraform-plugin-framework v1.11.0 github.com/hashicorp/terraform-plugin-framework-validators v0.13.0 @@ -16,6 +17,8 @@ require ( gopkg.in/yaml.v3 v3.0.1 ) +replace github.com/netascode/go-meraki v0.0.0-20240901102824-a67592c39438 => /Users/maparafi/go-meraki + require ( github.com/BurntSushi/toml v1.2.1 // indirect github.com/Kunde21/markdownfmt/v3 v3.1.0 // indirect diff --git a/go.sum b/go.sum index 35f3d27..cc77968 100644 --- a/go.sum +++ b/go.sum @@ -164,8 +164,6 @@ github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RR github.com/mitchellh/reflectwalk v1.0.0/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx0jmZXqmk4esnw= github.com/mitchellh/reflectwalk v1.0.2 h1:G2LzWKi524PWgd3mLHV8Y5k7s6XUvT0Gef6zxSIeXaQ= github.com/mitchellh/reflectwalk v1.0.2/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx0jmZXqmk4esnw= -github.com/netascode/go-meraki v0.0.0-20240901102824-a67592c39438 h1:dsFYRJ2aXEddmIz76weQAZ6QzliSmMpVaZxGlW4wTUA= -github.com/netascode/go-meraki v0.0.0-20240901102824-a67592c39438/go.mod h1:xZ/kiJA+SZY32p5t1J9Jdhvtig9BU4SMbxKnfAJrmLE= github.com/oklog/run v1.1.0 h1:GEenZ1cK0+q0+wsJew9qUg/DyD8k3JzYsZAi5gYi2mA= github.com/oklog/run v1.1.0/go.mod h1:sVPdnTZT1zYwAJeCMu2Th4T21pA3FPOQRfWjQlk7DVU= github.com/pjbgf/sha1cd v0.3.0 h1:4D5XXmUUBUl/xQ6IjCkEAbqXskkq/4O7LmGn0AqMDs4= diff --git a/internal/provider/data_source_meraki_network_settings.go b/internal/provider/data_source_meraki_network_settings.go index 7d8b426..9c56f1c 100644 --- a/internal/provider/data_source_meraki_network_settings.go +++ b/internal/provider/data_source_meraki_network_settings.go @@ -21,6 +21,7 @@ package provider import ( "context" "fmt" + "net/url" "github.com/hashicorp/terraform-plugin-framework/datasource" "github.com/hashicorp/terraform-plugin-framework/datasource/schema" @@ -101,21 +102,12 @@ func (d *NetworkSettingsDataSource) Read(ctx context.Context, req datasource.Rea var err error if !res.Exists() { - res, err = d.client.Get(config.getPath()) + res, err = d.client.Get(config.getPath() + "/" + url.QueryEscape(config.Id.ValueString())) if err != nil { resp.Diagnostics.AddError("Client Error", fmt.Sprintf("Failed to retrieve object, got error: %s", err)) return } } - if len(res.Array()) > 0 { - res.ForEach(func(k, v gjson.Result) bool { - if config.Id.ValueString() == v.Get("id").String() { - res = v - return false - } - return true - }) - } config.fromBody(ctx, res) diff --git a/internal/provider/resource_meraki_network_settings.go b/internal/provider/resource_meraki_network_settings.go index dc4d4bd..adc0a4b 100644 --- a/internal/provider/resource_meraki_network_settings.go +++ b/internal/provider/resource_meraki_network_settings.go @@ -33,7 +33,6 @@ import ( "github.com/hashicorp/terraform-plugin-log/tflog" "github.com/netascode/go-meraki" "github.com/netascode/terraform-provider-meraki/internal/provider/helpers" - "github.com/tidwall/gjson" ) // End of section. //template:end imports @@ -80,7 +79,7 @@ func (r *NetworkSettingsResource) Schema(ctx context.Context, req resource.Schem }, "local_status_page_enabled": schema.BoolAttribute{ MarkdownDescription: helpers.NewAttributeDescription("asdasdas").String, - Optional: true, + Required: true, }, }, } @@ -106,30 +105,6 @@ func (r *NetworkSettingsResource) Create(ctx context.Context, req resource.Creat if resp.Diagnostics.Append(diags...); resp.Diagnostics.HasError() { return } - tflog.Debug(ctx, fmt.Sprintf("%s: considering object name %s", plan.Id, plan.Name)) - - if plan.Id.ValueString() == "" && plan.Name.ValueString() != "" { - res, err := r.client.Get(plan.getPath()) - if err != nil { - resp.Diagnostics.AddError("Client Error", fmt.Sprintf("Failed to retrieve objects, got error: %s", err)) - return - } - if len(res.Array()) > 0 { - res.ForEach(func(k, v gjson.Result) bool { - if plan.Name.ValueString() == v.Get("name").String() { - plan.Id = types.StringValue(v.Get("id").String()) - tflog.Debug(ctx, fmt.Sprintf("%s: Found object with name '%s', id: %s", plan.Id, plan.Name.ValueString(), plan.Id)) - return false - } - return true - }) - } - - if plan.Id.ValueString() == "" { - resp.Diagnostics.AddError("Client Error", fmt.Sprintf("Failed to find object with name: %s", plan.Name.ValueString())) - return - } - } tflog.Debug(ctx, fmt.Sprintf("%s: Beginning Create", plan.Id.ValueString())) @@ -164,7 +139,7 @@ func (r *NetworkSettingsResource) Read(ctx context.Context, req resource.ReadReq } tflog.Debug(ctx, fmt.Sprintf("%s: Beginning Read", state.Id.String())) - res, err := r.client.Get(state.getPath()) + res, err := r.client.Get(state.getPath() + "/" + url.QueryEscape(state.Id.ValueString())) if err != nil && strings.Contains(err.Error(), "StatusCode 404") { resp.State.RemoveResource(ctx) return @@ -172,15 +147,6 @@ func (r *NetworkSettingsResource) Read(ctx context.Context, req resource.ReadReq resp.Diagnostics.AddError("Client Error", fmt.Sprintf("Failed to retrieve object (GET), got error: %s, %s", err, res.String())) return } - if len(res.Array()) > 0 { - res.ForEach(func(k, v gjson.Result) bool { - if state.Id.ValueString() == v.Get("id").String() { - res = v - return false - } - return true - }) - } imp, diags := helpers.IsFlagImporting(ctx, req) if resp.Diagnostics.Append(diags...); resp.Diagnostics.HasError() { diff --git a/internal/provider/resource_meraki_network_settings_test.go b/internal/provider/resource_meraki_network_settings_test.go index 3de4b55..a654c77 100644 --- a/internal/provider/resource_meraki_network_settings_test.go +++ b/internal/provider/resource_meraki_network_settings_test.go @@ -74,6 +74,7 @@ resource "meraki_network" "test" { func testAccMerakiNetworkSettingsConfig_minimum() string { config := `resource "meraki_network_settings" "test" {` + "\n" config += ` network_id = meraki_network.test.id` + "\n" + config += ` local_status_page_enabled = false` + "\n" config += `}` + "\n" return config } From 011e87f270738c05d58f74bd3f44150f9ac2f53b Mon Sep 17 00:00:00 2001 From: Marcin Parafiniuk Date: Fri, 6 Sep 2024 13:25:28 +0200 Subject: [PATCH 3/4] group policies bonjour works --- docs/data-sources/network_group_policies.md | 46 +++ docs/data-sources/network_settings.md | 7 +- docs/resources/network_group_policies.md | 62 ++++ docs/resources/network_settings.md | 19 +- .../data-source.tf | 4 + .../meraki_network_settings/data-source.tf | 2 +- .../meraki_network_group_policies/import.sh | 1 + .../meraki_network_group_policies/resource.tf | 12 + .../meraki_network_settings/import.sh | 2 +- .../meraki_network_settings/resource.tf | 9 +- gen/definitions/admin.yaml | 2 +- gen/definitions/networks_group_policies.yaml | 50 ++++ gen/definitions/networks_settings.yaml | 26 +- gen/generator.go | 10 + gen/templates/data_source.go | 20 +- gen/templates/data_source_test.go | 2 +- gen/templates/model.go | 16 +- gen/templates/resource.go | 38 +-- .../provider/data_source_meraki_admin_test.go | 2 +- ...ta_source_meraki_network_group_policies.go | 181 ++++++++++++ ...urce_meraki_network_group_policies_test.go | 116 ++++++++ .../data_source_meraki_network_settings.go | 20 ++ ...ata_source_meraki_network_settings_test.go | 11 +- .../model_meraki_network_group_policies.go | 220 ++++++++++++++ .../provider/model_meraki_network_settings.go | 66 ++++- internal/provider/provider.go | 2 + .../provider/resource_meraki_admin_test.go | 2 +- .../resource_meraki_network_group_policies.go | 275 ++++++++++++++++++ ...urce_meraki_network_group_policies_test.go | 104 +++++++ .../resource_meraki_network_settings.go | 20 ++ .../resource_meraki_network_settings_test.go | 11 +- 31 files changed, 1303 insertions(+), 55 deletions(-) create mode 100644 docs/data-sources/network_group_policies.md create mode 100644 docs/resources/network_group_policies.md create mode 100644 examples/data-sources/meraki_network_group_policies/data-source.tf create mode 100644 examples/resources/meraki_network_group_policies/import.sh create mode 100644 examples/resources/meraki_network_group_policies/resource.tf create mode 100644 gen/definitions/networks_group_policies.yaml create mode 100644 internal/provider/data_source_meraki_network_group_policies.go create mode 100644 internal/provider/data_source_meraki_network_group_policies_test.go create mode 100644 internal/provider/model_meraki_network_group_policies.go create mode 100644 internal/provider/resource_meraki_network_group_policies.go create mode 100644 internal/provider/resource_meraki_network_group_policies_test.go diff --git a/docs/data-sources/network_group_policies.md b/docs/data-sources/network_group_policies.md new file mode 100644 index 0000000..085a212 --- /dev/null +++ b/docs/data-sources/network_group_policies.md @@ -0,0 +1,46 @@ +--- +# generated by https://github.com/hashicorp/terraform-plugin-docs +page_title: "meraki_network_group_policies Data Source - terraform-provider-meraki" +subcategory: "Networks" +description: |- + This data source can read the Network Group Policies configuration. +--- + +# meraki_network_group_policies (Data Source) + +This data source can read the `Network Group Policies` configuration. + +## Example Usage + +```terraform +data "meraki_network_group_policies" "example" { + id = "L_123456" + network_id = "L_123456" +} +``` + + +## Schema + +### Required + +- `network_id` (String) Network ID + +### Optional + +- `group_policy_id` (String) The id of the object +- `name` (String) + +### Read-Only + +- `bonjour_forwarding_rules` (Attributes List) (see [below for nested schema](#nestedatt--bonjour_forwarding_rules)) +- `bonjour_forwarding_settings` (String) + + +### Nested Schema for `bonjour_forwarding_rules` + +Read-Only: + +- `description` (String) +- `services` (List of String) +- `vlan_id` (String) diff --git a/docs/data-sources/network_settings.md b/docs/data-sources/network_settings.md index 4ef8140..4b41384 100644 --- a/docs/data-sources/network_settings.md +++ b/docs/data-sources/network_settings.md @@ -14,7 +14,7 @@ This data source can read the `Network Settings` configuration. ```terraform data "meraki_network_settings" "example" { - id = "12345678" + id = "L_123456" network_id = "L_123456" } ``` @@ -29,4 +29,9 @@ data "meraki_network_settings" "example" { ### Read-Only +- `local_status_page_authentication_enabled` (Boolean) +- `local_status_page_authentication_password` (String) - `local_status_page_enabled` (Boolean) asdasdas +- `named_vlans_enabled` (Boolean) +- `remote_status_page_enabled` (Boolean) +- `secure_port_enabled` (Boolean) diff --git a/docs/resources/network_group_policies.md b/docs/resources/network_group_policies.md new file mode 100644 index 0000000..c91faad --- /dev/null +++ b/docs/resources/network_group_policies.md @@ -0,0 +1,62 @@ +--- +# generated by https://github.com/hashicorp/terraform-plugin-docs +page_title: "meraki_network_group_policies Resource - terraform-provider-meraki" +subcategory: "Networks" +description: |- + This resource can manage the Network Group Policies configuration. +--- + +# meraki_network_group_policies (Resource) + +This resource can manage the `Network Group Policies` configuration. + +## Example Usage + +```terraform +resource "meraki_network_group_policies" "example" { + network_id = "L_123456" + name = "test_group_policy" + bonjour_forwarding_settings = "custom" + bonjour_forwarding_rules = [ + { + description = "a simple bonjour rule" + services = ["All Services"] + vlan_id = "2" + } + ] +} +``` + + +## Schema + +### Required + +- `name` (String) +- `network_id` (String) Network ID + +### Optional + +- `bonjour_forwarding_rules` (Attributes List) (see [below for nested schema](#nestedatt--bonjour_forwarding_rules)) +- `bonjour_forwarding_settings` (String) + +### Read-Only + +- `group_policy_id` (String) The id of the object + + +### Nested Schema for `bonjour_forwarding_rules` + +Optional: + +- `description` (String) +- `services` (List of String) +- `vlan_id` (String) + +## Import + +Import is supported using the following syntax: + +```shell +terraform import meraki_network_group_policies.example "" +``` diff --git a/docs/resources/network_settings.md b/docs/resources/network_settings.md index e000069..dc5e68b 100644 --- a/docs/resources/network_settings.md +++ b/docs/resources/network_settings.md @@ -14,8 +14,13 @@ This resource can manage the `Network Settings` configuration. ```terraform resource "meraki_network_settings" "example" { - network_id = "L_123456" - local_status_page_enabled = false + network_id = "L_123456" + local_status_page_enabled = false + remote_status_page_enabled = false + local_status_page_authentication_enabled = false + local_status_page_authentication_password = "miles123" + named_vlans_enabled = false + secure_port_enabled = false } ``` @@ -27,6 +32,14 @@ resource "meraki_network_settings" "example" { - `local_status_page_enabled` (Boolean) asdasdas - `network_id` (String) Network ID +### Optional + +- `local_status_page_authentication_enabled` (Boolean) +- `local_status_page_authentication_password` (String) +- `named_vlans_enabled` (Boolean) +- `remote_status_page_enabled` (Boolean) +- `secure_port_enabled` (Boolean) + ### Read-Only - `id` (String) The id of the object @@ -36,5 +49,5 @@ resource "meraki_network_settings" "example" { Import is supported using the following syntax: ```shell -terraform import meraki_network_settings.example "," +terraform import meraki_network_settings.example "" ``` diff --git a/examples/data-sources/meraki_network_group_policies/data-source.tf b/examples/data-sources/meraki_network_group_policies/data-source.tf new file mode 100644 index 0000000..0fe7e49 --- /dev/null +++ b/examples/data-sources/meraki_network_group_policies/data-source.tf @@ -0,0 +1,4 @@ +data "meraki_network_group_policies" "example" { + id = "L_123456" + network_id = "L_123456" +} diff --git a/examples/data-sources/meraki_network_settings/data-source.tf b/examples/data-sources/meraki_network_settings/data-source.tf index 05b9577..3a22e58 100644 --- a/examples/data-sources/meraki_network_settings/data-source.tf +++ b/examples/data-sources/meraki_network_settings/data-source.tf @@ -1,4 +1,4 @@ data "meraki_network_settings" "example" { - id = "12345678" + id = "L_123456" network_id = "L_123456" } diff --git a/examples/resources/meraki_network_group_policies/import.sh b/examples/resources/meraki_network_group_policies/import.sh new file mode 100644 index 0000000..5a78678 --- /dev/null +++ b/examples/resources/meraki_network_group_policies/import.sh @@ -0,0 +1 @@ +terraform import meraki_network_group_policies.example "" diff --git a/examples/resources/meraki_network_group_policies/resource.tf b/examples/resources/meraki_network_group_policies/resource.tf new file mode 100644 index 0000000..290b95e --- /dev/null +++ b/examples/resources/meraki_network_group_policies/resource.tf @@ -0,0 +1,12 @@ +resource "meraki_network_group_policies" "example" { + network_id = "L_123456" + name = "test_group_policy" + bonjour_forwarding_settings = "custom" + bonjour_forwarding_rules = [ + { + description = "a simple bonjour rule" + services = ["All Services"] + vlan_id = "2" + } + ] +} diff --git a/examples/resources/meraki_network_settings/import.sh b/examples/resources/meraki_network_settings/import.sh index bc2ad84..8f0ed53 100644 --- a/examples/resources/meraki_network_settings/import.sh +++ b/examples/resources/meraki_network_settings/import.sh @@ -1 +1 @@ -terraform import meraki_network_settings.example "," +terraform import meraki_network_settings.example "" diff --git a/examples/resources/meraki_network_settings/resource.tf b/examples/resources/meraki_network_settings/resource.tf index 9cd8ae9..d56c679 100644 --- a/examples/resources/meraki_network_settings/resource.tf +++ b/examples/resources/meraki_network_settings/resource.tf @@ -1,4 +1,9 @@ resource "meraki_network_settings" "example" { - network_id = "L_123456" - local_status_page_enabled = false + network_id = "L_123456" + local_status_page_enabled = false + remote_status_page_enabled = false + local_status_page_authentication_enabled = false + local_status_page_authentication_password = "miles123" + named_vlans_enabled = false + secure_port_enabled = false } diff --git a/gen/definitions/admin.yaml b/gen/definitions/admin.yaml index e449b14..0e5845f 100644 --- a/gen/definitions/admin.yaml +++ b/gen/definitions/admin.yaml @@ -66,5 +66,5 @@ test_prerequisites: | resource "meraki_network" "test" { organization_id = data.meraki_organization.test.id name = "Network1" - product_types = ["switch"] + product_types = ["switch", "wireless"] } diff --git a/gen/definitions/networks_group_policies.yaml b/gen/definitions/networks_group_policies.yaml new file mode 100644 index 0000000..e20426e --- /dev/null +++ b/gen/definitions/networks_group_policies.yaml @@ -0,0 +1,50 @@ +--- +name: Network Group Policies +rest_endpoint: /networks/%v/groupPolicies +doc_category: Networks +id_name: group_policy_id +data_source_name_query: true +attributes: + - tf_name: network_id + type: String + id: true + reference: true + description: Network ID + example: L_123456 + test_value: meraki_network.test.id + - model_name: name + type: String + mandatory: true + example: test_group_policy + - model_name: settings + data_path: [bonjourForwarding] + type: String + example: custom + - model_name: rules + data_path: [bonjourForwarding] + type: List + attributes: + - model_name: description + type: String + example: a simple bonjour rule + - model_name: services + type: List + element_type: String + example: All Services + - model_name: vlanId + type: String + example: "2" + + + + + +test_prerequisites: | + data "meraki_organization" "test" { + name = "Dev" + } + resource "meraki_network" "test" { + organization_id = data.meraki_organization.test.id + name = "Network1" + product_types = ["switch", "wireless"] + } diff --git a/gen/definitions/networks_settings.yaml b/gen/definitions/networks_settings.yaml index 1eab87f..38557c4 100644 --- a/gen/definitions/networks_settings.yaml +++ b/gen/definitions/networks_settings.yaml @@ -4,9 +4,11 @@ rest_endpoint: /networks/%v/settings no_delete: true put_create: true doc_category: Networks +id_from_attribute: true attributes: - tf_name: network_id type: String + id: true reference: true description: Network ID example: L_123456 @@ -16,6 +18,28 @@ attributes: description: asdasdas example: false mandatory: true + - model_name: remoteStatusPageEnabled + type: Bool + example: false + - model_name: enabled + data_path: [localStatusPage, authentication] + type: Bool + example: false + - model_name: password + data_path: [localStatusPage, authentication] + type: String + example: miles123 + write_only: true + - model_name: enabled + data_path: [namedVlans] + type: Bool + example: false + - model_name: enabled + data_path: [securePort] + type: Bool + example: false + + test_prerequisites: | data "meraki_organization" "test" { name = "Dev" @@ -23,5 +47,5 @@ test_prerequisites: | resource "meraki_network" "test" { organization_id = data.meraki_organization.test.id name = "Network1" - product_types = ["switch"] + product_types = ["switch", "wireless"] } diff --git a/gen/generator.go b/gen/generator.go index 72c071b..9404c6b 100644 --- a/gen/generator.go +++ b/gen/generator.go @@ -111,6 +111,7 @@ type YamlConfig struct { Attributes []YamlConfigAttribute `yaml:"attributes"` TestTags []string `yaml:"test_tags"` TestPrerequisites string `yaml:"test_prerequisites"` + IdName string `yaml:"id_name"` } type YamlConfigAttribute struct { @@ -177,6 +178,11 @@ func CamelCase(s string) string { return strings.Join(g, "") } +func DromedaryCase(s string) string { + s = ToGoName(s) + return strings.ToLower(string(s[0])) + s[1:] +} + // Templating helper function to convert string to snake case func SnakeCase(s string) string { var g []string @@ -345,6 +351,7 @@ func Subtract(a, b int) int { // Map of templating functions var functions = template.FuncMap{ "toGoName": ToGoName, + "dromedaryCase": DromedaryCase, "camelCase": CamelCase, "snakeCase": SnakeCase, "sprintf": fmt.Sprintf, @@ -451,6 +458,9 @@ func NewYamlConfig(bytes []byte) (YamlConfig, error) { if config.TfName == "" { config.TfName = strings.Replace(config.Name, " ", "_", -1) } + if config.IdName == "" { + config.IdName = "id" + } return config, nil } diff --git a/gen/templates/data_source.go b/gen/templates/data_source.go index 39f0299..86643f7 100644 --- a/gen/templates/data_source.go +++ b/gen/templates/data_source.go @@ -64,7 +64,7 @@ func (d *{{camelCase .Name}}DataSource) Schema(ctx context.Context, req datasour MarkdownDescription: "{{.DsDescription}}", Attributes: map[string]schema.Attribute{ - "id": schema.StringAttribute{ + "{{ .IdName }}": schema.StringAttribute{ MarkdownDescription: "The id of the object", {{- if not .DataSourceNameQuery}} Required: true, @@ -150,7 +150,7 @@ func (d *{{camelCase .Name}}DataSource) Schema(ctx context.Context, req datasour func (d *{{camelCase .Name}}DataSource) ConfigValidators(ctx context.Context) []datasource.ConfigValidator { return []datasource.ConfigValidator{ datasourcevalidator.ExactlyOneOf( - path.MatchRoot("id"), + path.MatchRoot("{{ .IdName }}"), path.MatchRoot("name"), ), } @@ -179,12 +179,12 @@ func (d *{{camelCase .Name}}DataSource) Read(ctx context.Context, req datasource return } - tflog.Debug(ctx, fmt.Sprintf("%s: Beginning Read", config.Id.String())) + tflog.Debug(ctx, fmt.Sprintf("%s: Beginning Read", config.{{ toGoName .IdName }}.String())) var res gjson.Result var err error {{- if .DataSourceNameQuery}} - if config.Id.IsNull() && !config.Name.IsNull() { + if config.{{toGoName .IdName}}.IsNull() && !config.Name.IsNull() { res, err = d.client.Get(config.getPath()) if err != nil { resp.Diagnostics.AddError("Client Error", fmt.Sprintf("Failed to retrieve objects, got error: %s", err)) @@ -193,8 +193,8 @@ func (d *{{camelCase .Name}}DataSource) Read(ctx context.Context, req datasource if len(res.Array()) > 0 { res.ForEach(func(k, v gjson.Result) bool { if config.Name.ValueString() == v.Get("name").String() { - config.Id = types.StringValue(v.Get("id").String()) - tflog.Debug(ctx, fmt.Sprintf("%s: Found object with name '%v', id: %v", config.Id.String(), config.Name.ValueString(), config.Id.String())) + config.{{toGoName .IdName}} = types.StringValue(v.Get("{{dromedaryCase .IdName}}").String()) + tflog.Debug(ctx, fmt.Sprintf("%s: Found object with name '%v', id: %v", config.{{toGoName .IdName}}.String(), config.Name.ValueString(), config.{{toGoName .IdName}}.String())) res = v return false } @@ -202,7 +202,7 @@ func (d *{{camelCase .Name}}DataSource) Read(ctx context.Context, req datasource }) } - if config.Id.IsNull() { + if config.{{toGoName .IdName}}.IsNull() { resp.Diagnostics.AddError("Client Error", fmt.Sprintf("Failed to find object with name: %s", config.Name.ValueString())) return } @@ -213,7 +213,7 @@ func (d *{{camelCase .Name}}DataSource) Read(ctx context.Context, req datasource {{- if .GetFromAll}} res, err = d.client.Get(config.getPath()) {{- else}} - res, err = d.client.Get(config.getPath() + "/" + url.QueryEscape(config.Id.ValueString())) + res, err = d.client.Get(config.getPath() + "/" + url.QueryEscape(config.{{toGoName .IdName}}.ValueString())) {{- end}} if err != nil { resp.Diagnostics.AddError("Client Error", fmt.Sprintf("Failed to retrieve object, got error: %s", err)) @@ -223,7 +223,7 @@ func (d *{{camelCase .Name}}DataSource) Read(ctx context.Context, req datasource {{- if .GetFromAll}} if len(res.Array()) > 0 { res.ForEach(func(k, v gjson.Result) bool { - if config.Id.ValueString() == v.Get("id").String() { + if config.{{toGoName .IdName}}.ValueString() == v.Get("{{dromedaryCase .IdName}}").String() { res = v return false } @@ -234,7 +234,7 @@ func (d *{{camelCase .Name}}DataSource) Read(ctx context.Context, req datasource config.fromBody(ctx, res) - tflog.Debug(ctx, fmt.Sprintf("%s: Read finished successfully", config.Id.ValueString())) + tflog.Debug(ctx, fmt.Sprintf("%s: Read finished successfully", config.{{toGoName .IdName}}.ValueString())) diags = resp.State.Set(ctx, &config) resp.Diagnostics.Append(diags...) diff --git a/gen/templates/data_source_test.go b/gen/templates/data_source_test.go index 3007e17..89a5d5e 100644 --- a/gen/templates/data_source_test.go +++ b/gen/templates/data_source_test.go @@ -234,7 +234,7 @@ func testAccDataSourceMeraki{{camelCase .Name}}Config() string { config += ` data "meraki_{{snakeCase .Name}}" "test" { - id = meraki_{{snakeCase $name}}.test.id + {{ .IdName }} = meraki_{{snakeCase $name}}.test.{{ .IdName }} {{- range .Attributes}} {{- if .Reference}} {{.TfName}} = {{if .TestValue}}{{.TestValue}}{{else}}{{if eq .Type "String"}}"{{.Example}}"{{else if isStringListSet .}}["{{.Example}}"]{{else if isInt64ListSet .}}[{{.Example}}]{{else}}{{.Example}}{{end}}{{end}} diff --git a/gen/templates/model.go b/gen/templates/model.go index 32a5eea..dfd2dbb 100644 --- a/gen/templates/model.go +++ b/gen/templates/model.go @@ -37,7 +37,7 @@ import ( // Section below is generated&owned by "gen/generator.go". //template:begin types type {{camelCase .Name}} struct { - Id types.String `tfsdk:"id"` + {{ toGoName .IdName }} types.String `tfsdk:"{{.IdName}}"` {{- range .Attributes}} {{- if not .Value}} {{- if isNestedListSet .}} @@ -141,8 +141,8 @@ func (data {{camelCase .Name}}) getPath() string { func (data {{camelCase .Name}}) toBody(ctx context.Context, state {{camelCase .Name}}) string { body := "" - if data.Id.ValueString() != "" { - body, _ = sjson.Set(body, "id", data.Id.ValueString()) + if data.{{toGoName .IdName}}.ValueString() != "" { + body, _ = sjson.Set(body, "{{dromedaryCase .IdName}}", data.{{toGoName .IdName}}.ValueString()) } {{- range .Attributes}} {{- if .Value}} @@ -292,7 +292,7 @@ func (data *{{camelCase .Name}}) fromBody(ctx context.Context, res gjson.Result) parentRes.{{if .ModelName}}Get("{{range .DataPath}}{{.}}.{{end}}{{.ModelName}}").{{end}}ForEach( func(_, v gjson.Result) bool { - if v.Get("id").String() == data.Id.ValueString() && data.Id.ValueString() != "" { + if v.Get("{{dromedaryCase .IdName}}").String() == data.{{toGoName .IdName}}.ValueString() && data.{{toGoName .IdName}}.ValueString() != "" { res = v return false // break ForEach } @@ -300,7 +300,7 @@ func (data *{{camelCase .Name}}) fromBody(ctx context.Context, res gjson.Result) }, ) if !res.Exists() { - tflog.Debug(ctx, fmt.Sprintf("subresource not found, removing: uuid=%s, key=%v", data.Id, k)) + tflog.Debug(ctx, fmt.Sprintf("subresource not found, removing: uuid=%s, key=%v", data.{{toGoName .IdName}}, k)) delete((*parent).{{toGoName .TfName}}, k) } {{- template "fromBodyTemplate" .}} @@ -348,7 +348,7 @@ func (data *{{camelCase .Name}}) fromBodyPartial(ctx context.Context, res gjson. parentRes.{{if .ModelName}}Get("{{range .DataPath}}{{.}}.{{end}}{{.ModelName}}").{{end}}ForEach( func(_, v gjson.Result) bool { - if v.Get("id").String() == data.Id.ValueString() && data.Id.ValueString() != "" { + if v.Get("{{dromedaryCase .IdName}}").String() == data.{{toGoName .IdName}}.ValueString() && data.{{toGoName .IdName}}.ValueString() != "" { res = v return false // break ForEach } @@ -474,13 +474,13 @@ func (data *{{camelCase .Name}}) fromBodyUnknowns(ctx context.Context, res gjson var r gjson.Result res.{{if .ModelName}}Get("{{range .DataPath}}{{.}}.{{end}}{{.ModelName}}").{{end}}ForEach( func(_, v gjson.Result) bool { - if val.Id.IsUnknown() { + if val.{{toGoName .IdName}}.IsUnknown() { if v.Get("name").String() == i { r = v return false // break ForEach } } else { - if v.Get("id").String() == val.Id.ValueString() && val.Id.ValueString() != "" { + if v.Get("{{dromedaryCase .IdName}}").String() == val.{{toGoName .IdName}}.ValueString() && val.{{toGoName .IdName}}.ValueString() != "" { r = v return false // break ForEach } diff --git a/gen/templates/resource.go b/gen/templates/resource.go index f70f3fd..1af9bd4 100644 --- a/gen/templates/resource.go +++ b/gen/templates/resource.go @@ -71,7 +71,7 @@ func (r *{{camelCase .Name}}Resource) Schema(ctx context.Context, req resource.S MarkdownDescription: helpers.NewAttributeDescription("{{.ResDescription}}").String, Attributes: map[string]schema.Attribute{ - "id": schema.StringAttribute{ + "{{.IdName}}": schema.StringAttribute{ MarkdownDescription: "The id of the object", Computed: true, PlanModifiers: []planmodifier.String{ @@ -416,13 +416,13 @@ func (r *{{camelCase .Name}}Resource) Create(ctx context.Context, req resource.C return } - tflog.Debug(ctx, fmt.Sprintf("%s: Beginning Create", plan.Id.ValueString())) + tflog.Debug(ctx, fmt.Sprintf("%s: Beginning Create", plan.{{toGoName .IdName}}.ValueString())) // Create object body := plan.toBody(ctx, {{camelCase .Name}}{}) {{- if .PutCreate}} - res, err := r.client.Put(plan.getPath()+"/"+url.PathEscape(plan.Id.ValueString()), body) + res, err := r.client.Put(plan.getPath()+"/"+url.PathEscape(plan.{{toGoName .IdName}}.ValueString()), body) {{- else}} res, err := r.client.Post(plan.getPath(), body) {{- end}} @@ -430,10 +430,10 @@ func (r *{{camelCase .Name}}Resource) Create(ctx context.Context, req resource.C resp.Diagnostics.AddError("Client Error", fmt.Sprintf("Failed to configure object (POST/PUT), got error: %s, %s", err, res.String())) return } - plan.Id = types.StringValue(res.Get("id").String()) + plan.{{toGoName .IdName}} = types.StringValue(res.Get("{{dromedaryCase .IdName}}").String()) {{- if hasResourceId .Attributes}} - res, err = r.client.Get(plan.getPath() + "/" + url.QueryEscape(plan.Id.ValueString())) + res, err = r.client.Get(plan.getPath() + "/" + url.QueryEscape(plan.{{toGoName .IdName}}.ValueString())) if err != nil { resp.Diagnostics.AddError("Client Error", fmt.Sprintf("Failed to retrieve object (GET), got error: %s, %s", err, res.String())) return @@ -441,7 +441,7 @@ func (r *{{camelCase .Name}}Resource) Create(ctx context.Context, req resource.C plan.fromBodyUnknowns(ctx, res) {{- end}} - tflog.Debug(ctx, fmt.Sprintf("%s: Create finished successfully", plan.Id.ValueString())) + tflog.Debug(ctx, fmt.Sprintf("%s: Create finished successfully", plan.{{toGoName .IdName}}.ValueString())) diags = resp.State.Set(ctx, &plan) resp.Diagnostics.Append(diags...) @@ -462,12 +462,12 @@ func (r *{{camelCase .Name}}Resource) Read(ctx context.Context, req resource.Rea return } - tflog.Debug(ctx, fmt.Sprintf("%s: Beginning Read", state.Id.String())) + tflog.Debug(ctx, fmt.Sprintf("%s: Beginning Read", state.{{toGoName .IdName}}.String())) {{- if .GetFromAll}} res, err := r.client.Get(state.getPath()) {{- else}} - res, err := r.client.Get(state.getPath() + "/" + url.QueryEscape(state.Id.ValueString())) + res, err := r.client.Get(state.getPath() + "/" + url.QueryEscape(state.{{toGoName .IdName}}.ValueString())) {{- end}} if err != nil && strings.Contains(err.Error(), "StatusCode 404") { resp.State.RemoveResource(ctx) @@ -479,7 +479,7 @@ func (r *{{camelCase .Name}}Resource) Read(ctx context.Context, req resource.Rea {{- if .GetFromAll}} if len(res.Array()) > 0 { res.ForEach(func(k, v gjson.Result) bool { - if state.Id.ValueString() == v.Get("id").String() { + if state.{{toGoName .IdName}}.ValueString() == v.Get("{{dromedaryCase .IdName}}").String() { res = v return false } @@ -500,7 +500,7 @@ func (r *{{camelCase .Name}}Resource) Read(ctx context.Context, req resource.Rea state.fromBodyPartial(ctx, res) } - tflog.Debug(ctx, fmt.Sprintf("%s: Read finished successfully", state.Id.ValueString())) + tflog.Debug(ctx, fmt.Sprintf("%s: Read finished successfully", state.{{toGoName .IdName}}.ValueString())) diags = resp.State.Set(ctx, &state) resp.Diagnostics.Append(diags...) @@ -527,18 +527,18 @@ func (r *{{camelCase .Name}}Resource) Update(ctx context.Context, req resource.U return } - tflog.Debug(ctx, fmt.Sprintf("%s: Beginning Update", plan.Id.ValueString())) + tflog.Debug(ctx, fmt.Sprintf("%s: Beginning Update", plan.{{toGoName .IdName}}.ValueString())) {{- if not .NoUpdate}} body := plan.toBody(ctx, state) - res, err := r.client.Put(plan.getPath() + "/" + url.QueryEscape(plan.Id.ValueString()), body) + res, err := r.client.Put(plan.getPath() + "/" + url.QueryEscape(plan.{{toGoName .IdName}}.ValueString()), body) if err != nil { resp.Diagnostics.AddError("Client Error", fmt.Sprintf("Failed to configure object (PUT), got error: %s, %s", err, res.String())) return } {{- if hasResourceId .Attributes}} - res, err = r.client.Get(plan.getPath() + "/" + url.QueryEscape(plan.Id.ValueString())) + res, err = r.client.Get(plan.getPath() + "/" + url.QueryEscape(plan.{{toGoName .IdName}}.ValueString())) if err != nil { resp.Diagnostics.AddError("Client Error", fmt.Sprintf("Failed to retrieve object (GET), got error: %s, %s", err, res.String())) return @@ -547,7 +547,7 @@ func (r *{{camelCase .Name}}Resource) Update(ctx context.Context, req resource.U {{- end}} {{- end}} - tflog.Debug(ctx, fmt.Sprintf("%s: Update finished successfully", plan.Id.ValueString())) + tflog.Debug(ctx, fmt.Sprintf("%s: Update finished successfully", plan.{{toGoName .IdName}}.ValueString())) diags = resp.State.Set(ctx, &plan) resp.Diagnostics.Append(diags...) @@ -566,17 +566,17 @@ func (r *{{camelCase .Name}}Resource) Delete(ctx context.Context, req resource.D return } - tflog.Debug(ctx, fmt.Sprintf("%s: Beginning Delete", state.Id.ValueString())) + tflog.Debug(ctx, fmt.Sprintf("%s: Beginning Delete", state.{{toGoName .IdName}}.ValueString())) {{- if not .NoDelete}} - res, err := r.client.Delete(state.getPath() + "/" + url.QueryEscape(state.Id.ValueString())) + res, err := r.client.Delete(state.getPath() + "/" + url.QueryEscape(state.{{toGoName .IdName}}.ValueString())) if err != nil { resp.Diagnostics.AddError("Client Error", fmt.Sprintf("Failed to delete object (DELETE), got error: %s, %s", err, res.String())) return } {{- end}} - tflog.Debug(ctx, fmt.Sprintf("%s: Delete finished successfully", state.Id.ValueString())) + tflog.Debug(ctx, fmt.Sprintf("%s: Delete finished successfully", state.{{toGoName .IdName}}.ValueString())) resp.State.RemoveResource(ctx) } @@ -602,9 +602,9 @@ func (r *{{camelCase .Name}}Resource) ImportState(ctx context.Context, req resou resp.Diagnostics.Append(resp.State.SetAttribute(ctx, path.Root("{{$attr.TfName}}"), idParts[{{$index}}])...) {{- end}} {{- end}} - resp.Diagnostics.Append(resp.State.SetAttribute(ctx, path.Root("id"), idParts[{{subtract (importParts .Attributes) 1}}])...) + resp.Diagnostics.Append(resp.State.SetAttribute(ctx, path.Root("{{.IdName}}"), idParts[{{subtract (importParts .Attributes) 1}}])...) {{- else}} - resource.ImportStatePassthroughID(ctx, path.Root("id"), req, resp) + resource.ImportStatePassthroughID(ctx, path.Root("{{.IdName}}"), req, resp) {{- end}} helpers.SetFlagImporting(ctx, true, resp.Private, &resp.Diagnostics) diff --git a/internal/provider/data_source_meraki_admin_test.go b/internal/provider/data_source_meraki_admin_test.go index 252df62..35d6190 100644 --- a/internal/provider/data_source_meraki_admin_test.go +++ b/internal/provider/data_source_meraki_admin_test.go @@ -63,7 +63,7 @@ data "meraki_organization" "test" { resource "meraki_network" "test" { organization_id = data.meraki_organization.test.id name = "Network1" - product_types = ["switch"] + product_types = ["switch", "wireless"] } ` diff --git a/internal/provider/data_source_meraki_network_group_policies.go b/internal/provider/data_source_meraki_network_group_policies.go new file mode 100644 index 0000000..124caff --- /dev/null +++ b/internal/provider/data_source_meraki_network_group_policies.go @@ -0,0 +1,181 @@ +// 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/hashicorp/terraform-plugin-framework-validators/datasourcevalidator" + "github.com/hashicorp/terraform-plugin-framework/datasource" + "github.com/hashicorp/terraform-plugin-framework/datasource/schema" + "github.com/hashicorp/terraform-plugin-framework/path" + "github.com/hashicorp/terraform-plugin-framework/types" + "github.com/hashicorp/terraform-plugin-log/tflog" + "github.com/netascode/go-meraki" + "github.com/tidwall/gjson" +) + +// 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 = &NetworkGroupPoliciesDataSource{} + _ datasource.DataSourceWithConfigure = &NetworkGroupPoliciesDataSource{} +) + +func NewNetworkGroupPoliciesDataSource() datasource.DataSource { + return &NetworkGroupPoliciesDataSource{} +} + +type NetworkGroupPoliciesDataSource struct { + client *meraki.Client +} + +func (d *NetworkGroupPoliciesDataSource) Metadata(_ context.Context, req datasource.MetadataRequest, resp *datasource.MetadataResponse) { + resp.TypeName = req.ProviderTypeName + "_network_group_policies" +} + +func (d *NetworkGroupPoliciesDataSource) 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 `Network Group Policies` configuration.", + + Attributes: map[string]schema.Attribute{ + "group_policy_id": schema.StringAttribute{ + MarkdownDescription: "The id of the object", + Optional: true, + Computed: true, + }, + "network_id": schema.StringAttribute{ + MarkdownDescription: "Network ID", + Required: true, + }, + "name": schema.StringAttribute{ + MarkdownDescription: "", + Optional: true, + Computed: true, + }, + "bonjour_forwarding_settings": schema.StringAttribute{ + MarkdownDescription: "", + Computed: true, + }, + "bonjour_forwarding_rules": schema.ListNestedAttribute{ + MarkdownDescription: "", + Computed: true, + NestedObject: schema.NestedAttributeObject{ + Attributes: map[string]schema.Attribute{ + "description": schema.StringAttribute{ + MarkdownDescription: "", + Computed: true, + }, + "services": schema.ListAttribute{ + MarkdownDescription: "", + ElementType: types.StringType, + Computed: true, + }, + "vlan_id": schema.StringAttribute{ + MarkdownDescription: "", + Computed: true, + }, + }, + }, + }, + }, + } +} +func (d *NetworkGroupPoliciesDataSource) ConfigValidators(ctx context.Context) []datasource.ConfigValidator { + return []datasource.ConfigValidator{ + datasourcevalidator.ExactlyOneOf( + path.MatchRoot("group_policy_id"), + path.MatchRoot("name"), + ), + } +} + +func (d *NetworkGroupPoliciesDataSource) 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 *NetworkGroupPoliciesDataSource) Read(ctx context.Context, req datasource.ReadRequest, resp *datasource.ReadResponse) { + var config NetworkGroupPolicies + + // 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.GroupPolicyId.String())) + + var res gjson.Result + var err error + if config.GroupPolicyId.IsNull() && !config.Name.IsNull() { + res, err = d.client.Get(config.getPath()) + if err != nil { + resp.Diagnostics.AddError("Client Error", fmt.Sprintf("Failed to retrieve objects, got error: %s", err)) + return + } + if len(res.Array()) > 0 { + res.ForEach(func(k, v gjson.Result) bool { + if config.Name.ValueString() == v.Get("name").String() { + config.GroupPolicyId = types.StringValue(v.Get("groupPolicyId").String()) + tflog.Debug(ctx, fmt.Sprintf("%s: Found object with name '%v', id: %v", config.GroupPolicyId.String(), config.Name.ValueString(), config.GroupPolicyId.String())) + res = v + return false + } + return true + }) + } + + if config.GroupPolicyId.IsNull() { + resp.Diagnostics.AddError("Client Error", fmt.Sprintf("Failed to find object with name: %s", config.Name.ValueString())) + return + } + } + + if !res.Exists() { + res, err = d.client.Get(config.getPath() + "/" + url.QueryEscape(config.GroupPolicyId.ValueString())) + if err != nil { + resp.Diagnostics.AddError("Client Error", fmt.Sprintf("Failed to retrieve object, got error: %s", err)) + return + } + } + + config.fromBody(ctx, res) + + tflog.Debug(ctx, fmt.Sprintf("%s: Read finished successfully", config.GroupPolicyId.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_network_group_policies_test.go b/internal/provider/data_source_meraki_network_group_policies_test.go new file mode 100644 index 0000000..be20ce6 --- /dev/null +++ b/internal/provider/data_source_meraki_network_group_policies_test.go @@ -0,0 +1,116 @@ +// 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 ( + "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 TestAccDataSourceMerakiNetworkGroupPolicies(t *testing.T) { + var checks []resource.TestCheckFunc + checks = append(checks, resource.TestCheckResourceAttr("data.meraki_network_group_policies.test", "name", "test_group_policy")) + checks = append(checks, resource.TestCheckResourceAttr("data.meraki_network_group_policies.test", "bonjour_forwarding_settings", "custom")) + checks = append(checks, resource.TestCheckResourceAttr("data.meraki_network_group_policies.test", "bonjour_forwarding_rules.0.description", "a simple bonjour rule")) + checks = append(checks, resource.TestCheckResourceAttr("data.meraki_network_group_policies.test", "bonjour_forwarding_rules.0.services.0", "All Services")) + checks = append(checks, resource.TestCheckResourceAttr("data.meraki_network_group_policies.test", "bonjour_forwarding_rules.0.vlan_id", "2")) + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + ProtoV6ProviderFactories: testAccProtoV6ProviderFactories, + Steps: []resource.TestStep{ + { + Config: testAccDataSourceMerakiNetworkGroupPoliciesPrerequisitesConfig + testAccDataSourceMerakiNetworkGroupPoliciesConfig(), + Check: resource.ComposeTestCheckFunc(checks...), + }, + { + Config: testAccDataSourceMerakiNetworkGroupPoliciesPrerequisitesConfig + testAccNamedDataSourceMerakiNetworkGroupPoliciesConfig(), + Check: resource.ComposeTestCheckFunc(checks...), + }, + }, + }) +} + +// End of section. //template:end testAccDataSource + +// Section below is generated&owned by "gen/generator.go". //template:begin testPrerequisites + +const testAccDataSourceMerakiNetworkGroupPoliciesPrerequisitesConfig = ` +data "meraki_organization" "test" { + name = "Dev" +} +resource "meraki_network" "test" { + organization_id = data.meraki_organization.test.id + name = "Network1" + product_types = ["switch", "wireless"] +} + +` + +// End of section. //template:end testPrerequisites + +// Section below is generated&owned by "gen/generator.go". //template:begin testAccDataSourceConfig + +func testAccDataSourceMerakiNetworkGroupPoliciesConfig() string { + config := `resource "meraki_network_group_policies" "test" {` + "\n" + config += ` network_id = meraki_network.test.id` + "\n" + config += ` name = "test_group_policy"` + "\n" + config += ` bonjour_forwarding_settings = "custom"` + "\n" + config += ` bonjour_forwarding_rules = [{` + "\n" + config += ` description = "a simple bonjour rule"` + "\n" + config += ` services = ["All Services"]` + "\n" + config += ` vlan_id = "2"` + "\n" + config += ` }]` + "\n" + config += `}` + "\n" + + config += ` + data "meraki_network_group_policies" "test" { + group_policy_id = meraki_network_group_policies.test.group_policy_id + network_id = meraki_network.test.id + } + ` + return config +} + +func testAccNamedDataSourceMerakiNetworkGroupPoliciesConfig() string { + config := `resource "meraki_network_group_policies" "test" {` + "\n" + config += ` network_id = meraki_network.test.id` + "\n" + config += ` name = "test_group_policy"` + "\n" + config += ` bonjour_forwarding_settings = "custom"` + "\n" + config += ` bonjour_forwarding_rules = [{` + "\n" + config += ` description = "a simple bonjour rule"` + "\n" + config += ` services = ["All Services"]` + "\n" + config += ` vlan_id = "2"` + "\n" + config += ` }]` + "\n" + config += `}` + "\n" + + config += ` + data "meraki_network_group_policies" "test" { + name = meraki_network_group_policies.test.name + network_id = meraki_network.test.id + } + ` + return config +} + +// End of section. //template:end testAccDataSourceConfig diff --git a/internal/provider/data_source_meraki_network_settings.go b/internal/provider/data_source_meraki_network_settings.go index 9c56f1c..31a2d32 100644 --- a/internal/provider/data_source_meraki_network_settings.go +++ b/internal/provider/data_source_meraki_network_settings.go @@ -70,6 +70,26 @@ func (d *NetworkSettingsDataSource) Schema(ctx context.Context, req datasource.S MarkdownDescription: "asdasdas", Computed: true, }, + "remote_status_page_enabled": schema.BoolAttribute{ + MarkdownDescription: "", + Computed: true, + }, + "local_status_page_authentication_enabled": schema.BoolAttribute{ + MarkdownDescription: "", + Computed: true, + }, + "local_status_page_authentication_password": schema.StringAttribute{ + MarkdownDescription: "", + Computed: true, + }, + "named_vlans_enabled": schema.BoolAttribute{ + MarkdownDescription: "", + Computed: true, + }, + "secure_port_enabled": schema.BoolAttribute{ + MarkdownDescription: "", + Computed: true, + }, }, } } diff --git a/internal/provider/data_source_meraki_network_settings_test.go b/internal/provider/data_source_meraki_network_settings_test.go index 075f14f..e37d6fb 100644 --- a/internal/provider/data_source_meraki_network_settings_test.go +++ b/internal/provider/data_source_meraki_network_settings_test.go @@ -31,6 +31,10 @@ import ( func TestAccDataSourceMerakiNetworkSettings(t *testing.T) { var checks []resource.TestCheckFunc checks = append(checks, resource.TestCheckResourceAttr("data.meraki_network_settings.test", "local_status_page_enabled", "false")) + checks = append(checks, resource.TestCheckResourceAttr("data.meraki_network_settings.test", "remote_status_page_enabled", "false")) + checks = append(checks, resource.TestCheckResourceAttr("data.meraki_network_settings.test", "local_status_page_authentication_enabled", "false")) + checks = append(checks, resource.TestCheckResourceAttr("data.meraki_network_settings.test", "named_vlans_enabled", "false")) + checks = append(checks, resource.TestCheckResourceAttr("data.meraki_network_settings.test", "secure_port_enabled", "false")) resource.Test(t, resource.TestCase{ PreCheck: func() { testAccPreCheck(t) }, ProtoV6ProviderFactories: testAccProtoV6ProviderFactories, @@ -54,7 +58,7 @@ data "meraki_organization" "test" { resource "meraki_network" "test" { organization_id = data.meraki_organization.test.id name = "Network1" - product_types = ["switch"] + product_types = ["switch", "wireless"] } ` @@ -67,6 +71,11 @@ func testAccDataSourceMerakiNetworkSettingsConfig() string { config := `resource "meraki_network_settings" "test" {` + "\n" config += ` network_id = meraki_network.test.id` + "\n" config += ` local_status_page_enabled = false` + "\n" + config += ` remote_status_page_enabled = false` + "\n" + config += ` local_status_page_authentication_enabled = false` + "\n" + config += ` local_status_page_authentication_password = "miles123"` + "\n" + config += ` named_vlans_enabled = false` + "\n" + config += ` secure_port_enabled = false` + "\n" config += `}` + "\n" config += ` diff --git a/internal/provider/model_meraki_network_group_policies.go b/internal/provider/model_meraki_network_group_policies.go new file mode 100644 index 0000000..730119b --- /dev/null +++ b/internal/provider/model_meraki_network_group_policies.go @@ -0,0 +1,220 @@ +// 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" + "slices" + + "github.com/hashicorp/terraform-plugin-framework/types" + "github.com/hashicorp/terraform-plugin-log/tflog" + "github.com/netascode/terraform-provider-meraki/internal/provider/helpers" + "github.com/tidwall/gjson" + "github.com/tidwall/sjson" +) + +// End of section. //template:end imports + +// Section below is generated&owned by "gen/generator.go". //template:begin types + +type NetworkGroupPolicies struct { + GroupPolicyId types.String `tfsdk:"group_policy_id"` + NetworkId types.String `tfsdk:"network_id"` + Name types.String `tfsdk:"name"` + BonjourForwardingSettings types.String `tfsdk:"bonjour_forwarding_settings"` + BonjourForwardingRules []NetworkGroupPoliciesBonjourForwardingRules `tfsdk:"bonjour_forwarding_rules"` +} + +type NetworkGroupPoliciesBonjourForwardingRules struct { + Description types.String `tfsdk:"description"` + Services types.List `tfsdk:"services"` + VlanId types.String `tfsdk:"vlan_id"` +} + +// End of section. //template:end types + +// Section below is generated&owned by "gen/generator.go". //template:begin getPath + +func (data NetworkGroupPolicies) getPath() string { + return fmt.Sprintf("/networks/%v/groupPolicies", url.QueryEscape(data.NetworkId.ValueString())) +} + +// End of section. //template:end getPath + +// Section below is generated&owned by "gen/generator.go". //template:begin toBody + +func (data NetworkGroupPolicies) toBody(ctx context.Context, state NetworkGroupPolicies) string { + body := "" + if data.GroupPolicyId.ValueString() != "" { + body, _ = sjson.Set(body, "groupPolicyId", data.GroupPolicyId.ValueString()) + } + if !data.Name.IsNull() { + body, _ = sjson.Set(body, "name", data.Name.ValueString()) + } + if !data.BonjourForwardingSettings.IsNull() { + body, _ = sjson.Set(body, "bonjourForwarding.settings", data.BonjourForwardingSettings.ValueString()) + } + if len(data.BonjourForwardingRules) > 0 { + body, _ = sjson.Set(body, "bonjourForwarding.rules", []interface{}{}) + for _, item := range data.BonjourForwardingRules { + itemBody := "" + if !item.Description.IsNull() { + itemBody, _ = sjson.Set(itemBody, "description", item.Description.ValueString()) + } + if !item.Services.IsNull() { + var values []string + item.Services.ElementsAs(ctx, &values, false) + itemBody, _ = sjson.Set(itemBody, "services", values) + } + if !item.VlanId.IsNull() { + itemBody, _ = sjson.Set(itemBody, "vlanId", item.VlanId.ValueString()) + } + body, _ = sjson.SetRaw(body, "bonjourForwarding.rules.-1", itemBody) + } + } + return body +} + +// End of section. //template:end toBody + +// Section below is generated&owned by "gen/generator.go". //template:begin fromBody + +func (data *NetworkGroupPolicies) fromBody(ctx context.Context, res gjson.Result) { + if value := res.Get("name"); value.Exists() { + data.Name = types.StringValue(value.String()) + } else { + data.Name = types.StringNull() + } + if value := res.Get("bonjourForwarding.settings"); value.Exists() { + data.BonjourForwardingSettings = types.StringValue(value.String()) + } else { + data.BonjourForwardingSettings = types.StringNull() + } + if value := res.Get("bonjourForwarding.rules"); value.Exists() { + data.BonjourForwardingRules = make([]NetworkGroupPoliciesBonjourForwardingRules, 0) + value.ForEach(func(k, res gjson.Result) bool { + parent := &data + data := NetworkGroupPoliciesBonjourForwardingRules{} + if value := res.Get("description"); value.Exists() { + data.Description = types.StringValue(value.String()) + } else { + data.Description = types.StringNull() + } + if value := res.Get("services"); value.Exists() { + data.Services = helpers.GetStringList(value.Array()) + } else { + data.Services = types.ListNull(types.StringType) + } + if value := res.Get("vlanId"); value.Exists() { + data.VlanId = types.StringValue(value.String()) + } else { + data.VlanId = types.StringNull() + } + (*parent).BonjourForwardingRules = append((*parent).BonjourForwardingRules, data) + return true + }) + } +} + +// 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 *NetworkGroupPolicies) fromBodyPartial(ctx context.Context, res gjson.Result) { + if value := res.Get("name"); value.Exists() && !data.Name.IsNull() { + data.Name = types.StringValue(value.String()) + } else { + data.Name = types.StringNull() + } + if value := res.Get("bonjourForwarding.settings"); value.Exists() && !data.BonjourForwardingSettings.IsNull() { + data.BonjourForwardingSettings = types.StringValue(value.String()) + } else { + data.BonjourForwardingSettings = types.StringNull() + } + for i := 0; i < len(data.BonjourForwardingRules); i++ { + keys := [...]string{"description", "vlanId"} + keyValues := [...]string{data.BonjourForwardingRules[i].Description.ValueString(), data.BonjourForwardingRules[i].VlanId.ValueString()} + + parent := &data + data := (*parent).BonjourForwardingRules[i] + parentRes := &res + var res gjson.Result + + parentRes.Get("bonjourForwarding.rules").ForEach( + func(_, v gjson.Result) bool { + found := false + for ik := range keys { + if v.Get(keys[ik]).String() != keyValues[ik] { + found = false + break + } + found = true + } + if found { + res = v + return false + } + return true + }, + ) + if !res.Exists() { + tflog.Debug(ctx, fmt.Sprintf("removing BonjourForwardingRules[%d] = %+v", + i, + (*parent).BonjourForwardingRules[i], + )) + (*parent).BonjourForwardingRules = slices.Delete((*parent).BonjourForwardingRules, i, i+1) + i-- + + continue + } + if value := res.Get("description"); value.Exists() && !data.Description.IsNull() { + data.Description = types.StringValue(value.String()) + } else { + data.Description = types.StringNull() + } + if value := res.Get("services"); value.Exists() && !data.Services.IsNull() { + data.Services = helpers.GetStringList(value.Array()) + } else { + data.Services = types.ListNull(types.StringType) + } + if value := res.Get("vlanId"); value.Exists() && !data.VlanId.IsNull() { + data.VlanId = types.StringValue(value.String()) + } else { + data.VlanId = types.StringNull() + } + (*parent).BonjourForwardingRules[i] = data + } +} + +// End of section. //template:end fromBodyPartial + +// Section below is generated&owned by "gen/generator.go". //template:begin fromBodyUnknowns + +// fromBodyUnknowns updates the Unknown Computed tfstate values from a JSON. +// Known values are not changed (usual for Computed attributes with UseStateForUnknown or with Default). +func (data *NetworkGroupPolicies) fromBodyUnknowns(ctx context.Context, res gjson.Result) { +} + +// End of section. //template:end fromBodyUnknowns diff --git a/internal/provider/model_meraki_network_settings.go b/internal/provider/model_meraki_network_settings.go index 1b562a3..dc33918 100644 --- a/internal/provider/model_meraki_network_settings.go +++ b/internal/provider/model_meraki_network_settings.go @@ -33,9 +33,14 @@ import ( // Section below is generated&owned by "gen/generator.go". //template:begin types type NetworkSettings struct { - Id types.String `tfsdk:"id"` - NetworkId types.String `tfsdk:"network_id"` - LocalStatusPageEnabled types.Bool `tfsdk:"local_status_page_enabled"` + Id types.String `tfsdk:"id"` + NetworkId types.String `tfsdk:"network_id"` + LocalStatusPageEnabled types.Bool `tfsdk:"local_status_page_enabled"` + RemoteStatusPageEnabled types.Bool `tfsdk:"remote_status_page_enabled"` + LocalStatusPageAuthenticationEnabled types.Bool `tfsdk:"local_status_page_authentication_enabled"` + LocalStatusPageAuthenticationPassword types.String `tfsdk:"local_status_page_authentication_password"` + NamedVlansEnabled types.Bool `tfsdk:"named_vlans_enabled"` + SecurePortEnabled types.Bool `tfsdk:"secure_port_enabled"` } // End of section. //template:end types @@ -58,6 +63,21 @@ func (data NetworkSettings) toBody(ctx context.Context, state NetworkSettings) s if !data.LocalStatusPageEnabled.IsNull() { body, _ = sjson.Set(body, "localStatusPageEnabled", data.LocalStatusPageEnabled.ValueBool()) } + if !data.RemoteStatusPageEnabled.IsNull() { + body, _ = sjson.Set(body, "remoteStatusPageEnabled", data.RemoteStatusPageEnabled.ValueBool()) + } + if !data.LocalStatusPageAuthenticationEnabled.IsNull() { + body, _ = sjson.Set(body, "localStatusPage.authentication.enabled", data.LocalStatusPageAuthenticationEnabled.ValueBool()) + } + if !data.LocalStatusPageAuthenticationPassword.IsNull() { + body, _ = sjson.Set(body, "localStatusPage.authentication.password", data.LocalStatusPageAuthenticationPassword.ValueString()) + } + if !data.NamedVlansEnabled.IsNull() { + body, _ = sjson.Set(body, "namedVlans.enabled", data.NamedVlansEnabled.ValueBool()) + } + if !data.SecurePortEnabled.IsNull() { + body, _ = sjson.Set(body, "securePort.enabled", data.SecurePortEnabled.ValueBool()) + } return body } @@ -71,6 +91,26 @@ func (data *NetworkSettings) fromBody(ctx context.Context, res gjson.Result) { } else { data.LocalStatusPageEnabled = types.BoolNull() } + if value := res.Get("remoteStatusPageEnabled"); value.Exists() { + data.RemoteStatusPageEnabled = types.BoolValue(value.Bool()) + } else { + data.RemoteStatusPageEnabled = types.BoolNull() + } + if value := res.Get("localStatusPage.authentication.enabled"); value.Exists() { + data.LocalStatusPageAuthenticationEnabled = types.BoolValue(value.Bool()) + } else { + data.LocalStatusPageAuthenticationEnabled = types.BoolNull() + } + if value := res.Get("namedVlans.enabled"); value.Exists() { + data.NamedVlansEnabled = types.BoolValue(value.Bool()) + } else { + data.NamedVlansEnabled = types.BoolNull() + } + if value := res.Get("securePort.enabled"); value.Exists() { + data.SecurePortEnabled = types.BoolValue(value.Bool()) + } else { + data.SecurePortEnabled = types.BoolNull() + } } // End of section. //template:end fromBody @@ -87,6 +127,26 @@ func (data *NetworkSettings) fromBodyPartial(ctx context.Context, res gjson.Resu } else { data.LocalStatusPageEnabled = types.BoolNull() } + if value := res.Get("remoteStatusPageEnabled"); value.Exists() && !data.RemoteStatusPageEnabled.IsNull() { + data.RemoteStatusPageEnabled = types.BoolValue(value.Bool()) + } else { + data.RemoteStatusPageEnabled = types.BoolNull() + } + if value := res.Get("localStatusPage.authentication.enabled"); value.Exists() && !data.LocalStatusPageAuthenticationEnabled.IsNull() { + data.LocalStatusPageAuthenticationEnabled = types.BoolValue(value.Bool()) + } else { + data.LocalStatusPageAuthenticationEnabled = types.BoolNull() + } + if value := res.Get("namedVlans.enabled"); value.Exists() && !data.NamedVlansEnabled.IsNull() { + data.NamedVlansEnabled = types.BoolValue(value.Bool()) + } else { + data.NamedVlansEnabled = types.BoolNull() + } + if value := res.Get("securePort.enabled"); value.Exists() && !data.SecurePortEnabled.IsNull() { + data.SecurePortEnabled = types.BoolValue(value.Bool()) + } else { + data.SecurePortEnabled = types.BoolNull() + } } // End of section. //template:end fromBodyPartial diff --git a/internal/provider/provider.go b/internal/provider/provider.go index 0b06e83..1001d94 100644 --- a/internal/provider/provider.go +++ b/internal/provider/provider.go @@ -219,6 +219,7 @@ func (p *MerakiProvider) Resources(ctx context.Context) []func() resource.Resour return []func() resource.Resource{ NewAdminResource, NewNetworkResource, + NewNetworkGroupPoliciesResource, NewNetworkSettingsResource, NewOrganizationResource, } @@ -228,6 +229,7 @@ func (p *MerakiProvider) DataSources(ctx context.Context) []func() datasource.Da return []func() datasource.DataSource{ NewAdminDataSource, NewNetworkDataSource, + NewNetworkGroupPoliciesDataSource, NewNetworkSettingsDataSource, NewOrganizationDataSource, } diff --git a/internal/provider/resource_meraki_admin_test.go b/internal/provider/resource_meraki_admin_test.go index 800333b..449bd6f 100644 --- a/internal/provider/resource_meraki_admin_test.go +++ b/internal/provider/resource_meraki_admin_test.go @@ -67,7 +67,7 @@ data "meraki_organization" "test" { resource "meraki_network" "test" { organization_id = data.meraki_organization.test.id name = "Network1" - product_types = ["switch"] + product_types = ["switch", "wireless"] } ` diff --git a/internal/provider/resource_meraki_network_group_policies.go b/internal/provider/resource_meraki_network_group_policies.go new file mode 100644 index 0000000..11db57f --- /dev/null +++ b/internal/provider/resource_meraki_network_group_policies.go @@ -0,0 +1,275 @@ +// 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" + "strings" + + "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/types" + "github.com/hashicorp/terraform-plugin-log/tflog" + "github.com/netascode/go-meraki" + "github.com/netascode/terraform-provider-meraki/internal/provider/helpers" +) + +// 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 = &NetworkGroupPoliciesResource{} + _ resource.ResourceWithImportState = &NetworkGroupPoliciesResource{} +) + +func NewNetworkGroupPoliciesResource() resource.Resource { + return &NetworkGroupPoliciesResource{} +} + +type NetworkGroupPoliciesResource struct { + client *meraki.Client +} + +func (r *NetworkGroupPoliciesResource) Metadata(ctx context.Context, req resource.MetadataRequest, resp *resource.MetadataResponse) { + resp.TypeName = req.ProviderTypeName + "_network_group_policies" +} + +func (r *NetworkGroupPoliciesResource) 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 `Network Group Policies` configuration.").String, + + Attributes: map[string]schema.Attribute{ + "group_policy_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(), + }, + }, + "name": schema.StringAttribute{ + MarkdownDescription: helpers.NewAttributeDescription("").String, + Required: true, + }, + "bonjour_forwarding_settings": schema.StringAttribute{ + MarkdownDescription: helpers.NewAttributeDescription("").String, + Optional: true, + }, + "bonjour_forwarding_rules": schema.ListNestedAttribute{ + MarkdownDescription: helpers.NewAttributeDescription("").String, + Optional: true, + NestedObject: schema.NestedAttributeObject{ + Attributes: map[string]schema.Attribute{ + "description": schema.StringAttribute{ + MarkdownDescription: helpers.NewAttributeDescription("").String, + Optional: true, + }, + "services": schema.ListAttribute{ + MarkdownDescription: helpers.NewAttributeDescription("").String, + ElementType: types.StringType, + Optional: true, + }, + "vlan_id": schema.StringAttribute{ + MarkdownDescription: helpers.NewAttributeDescription("").String, + Optional: true, + }, + }, + }, + }, + }, + } +} + +func (r *NetworkGroupPoliciesResource) 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 *NetworkGroupPoliciesResource) Create(ctx context.Context, req resource.CreateRequest, resp *resource.CreateResponse) { + var plan NetworkGroupPolicies + + // 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.GroupPolicyId.ValueString())) + + // Create object + body := plan.toBody(ctx, NetworkGroupPolicies{}) + res, err := r.client.Post(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.GroupPolicyId = types.StringValue(res.Get("groupPolicyId").String()) + + tflog.Debug(ctx, fmt.Sprintf("%s: Create finished successfully", plan.GroupPolicyId.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 *NetworkGroupPoliciesResource) Read(ctx context.Context, req resource.ReadRequest, resp *resource.ReadResponse) { + var state NetworkGroupPolicies + + // 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.GroupPolicyId.String())) + res, err := r.client.Get(state.getPath() + "/" + url.QueryEscape(state.GroupPolicyId.ValueString())) + 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.GroupPolicyId.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 *NetworkGroupPoliciesResource) Update(ctx context.Context, req resource.UpdateRequest, resp *resource.UpdateResponse) { + var plan, state NetworkGroupPolicies + + // 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.GroupPolicyId.ValueString())) + + body := plan.toBody(ctx, state) + res, err := r.client.Put(plan.getPath()+"/"+url.QueryEscape(plan.GroupPolicyId.ValueString()), 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.GroupPolicyId.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 *NetworkGroupPoliciesResource) Delete(ctx context.Context, req resource.DeleteRequest, resp *resource.DeleteResponse) { + var state NetworkGroupPolicies + + // 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.GroupPolicyId.ValueString())) + res, err := r.client.Delete(state.getPath() + "/" + url.QueryEscape(state.GroupPolicyId.ValueString())) + if err != nil { + resp.Diagnostics.AddError("Client Error", fmt.Sprintf("Failed to delete object (DELETE), got error: %s, %s", err, res.String())) + return + } + + tflog.Debug(ctx, fmt.Sprintf("%s: Delete finished successfully", state.GroupPolicyId.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 *NetworkGroupPoliciesResource) 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("group_policy_id"), idParts[1])...) + + helpers.SetFlagImporting(ctx, true, resp.Private, &resp.Diagnostics) +} + +// End of section. //template:end import diff --git a/internal/provider/resource_meraki_network_group_policies_test.go b/internal/provider/resource_meraki_network_group_policies_test.go new file mode 100644 index 0000000..11c83b1 --- /dev/null +++ b/internal/provider/resource_meraki_network_group_policies_test.go @@ -0,0 +1,104 @@ +// 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 testAcc + +func TestAccMerakiNetworkGroupPolicies(t *testing.T) { + var checks []resource.TestCheckFunc + checks = append(checks, resource.TestCheckResourceAttr("meraki_network_group_policies.test", "name", "test_group_policy")) + checks = append(checks, resource.TestCheckResourceAttr("meraki_network_group_policies.test", "bonjour_forwarding_settings", "custom")) + checks = append(checks, resource.TestCheckResourceAttr("meraki_network_group_policies.test", "bonjour_forwarding_rules.0.description", "a simple bonjour rule")) + checks = append(checks, resource.TestCheckResourceAttr("meraki_network_group_policies.test", "bonjour_forwarding_rules.0.services.0", "All Services")) + checks = append(checks, resource.TestCheckResourceAttr("meraki_network_group_policies.test", "bonjour_forwarding_rules.0.vlan_id", "2")) + + var steps []resource.TestStep + if os.Getenv("SKIP_MINIMUM_TEST") == "" { + steps = append(steps, resource.TestStep{ + Config: testAccMerakiNetworkGroupPoliciesPrerequisitesConfig + testAccMerakiNetworkGroupPoliciesConfig_minimum(), + }) + } + steps = append(steps, resource.TestStep{ + Config: testAccMerakiNetworkGroupPoliciesPrerequisitesConfig + testAccMerakiNetworkGroupPoliciesConfig_all(), + 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 testPrerequisites + +const testAccMerakiNetworkGroupPoliciesPrerequisitesConfig = ` +data "meraki_organization" "test" { + name = "Dev" +} +resource "meraki_network" "test" { + organization_id = data.meraki_organization.test.id + name = "Network1" + product_types = ["switch", "wireless"] +} + +` + +// End of section. //template:end testPrerequisites + +// Section below is generated&owned by "gen/generator.go". //template:begin testAccConfigMinimal + +func testAccMerakiNetworkGroupPoliciesConfig_minimum() string { + config := `resource "meraki_network_group_policies" "test" {` + "\n" + config += ` network_id = meraki_network.test.id` + "\n" + config += ` name = "test_group_policy"` + "\n" + config += `}` + "\n" + return config +} + +// End of section. //template:end testAccConfigMinimal + +// Section below is generated&owned by "gen/generator.go". //template:begin testAccConfigAll + +func testAccMerakiNetworkGroupPoliciesConfig_all() string { + config := `resource "meraki_network_group_policies" "test" {` + "\n" + config += ` network_id = meraki_network.test.id` + "\n" + config += ` name = "test_group_policy"` + "\n" + config += ` bonjour_forwarding_settings = "custom"` + "\n" + config += ` bonjour_forwarding_rules = [{` + "\n" + config += ` description = "a simple bonjour rule"` + "\n" + config += ` services = ["All Services"]` + "\n" + config += ` vlan_id = "2"` + "\n" + config += ` }]` + "\n" + config += `}` + "\n" + return config +} + +// End of section. //template:end testAccConfigAll diff --git a/internal/provider/resource_meraki_network_settings.go b/internal/provider/resource_meraki_network_settings.go index adc0a4b..b2aaaf0 100644 --- a/internal/provider/resource_meraki_network_settings.go +++ b/internal/provider/resource_meraki_network_settings.go @@ -81,6 +81,26 @@ func (r *NetworkSettingsResource) Schema(ctx context.Context, req resource.Schem MarkdownDescription: helpers.NewAttributeDescription("asdasdas").String, Required: true, }, + "remote_status_page_enabled": schema.BoolAttribute{ + MarkdownDescription: helpers.NewAttributeDescription("").String, + Optional: true, + }, + "local_status_page_authentication_enabled": schema.BoolAttribute{ + MarkdownDescription: helpers.NewAttributeDescription("").String, + Optional: true, + }, + "local_status_page_authentication_password": schema.StringAttribute{ + MarkdownDescription: helpers.NewAttributeDescription("").String, + Optional: true, + }, + "named_vlans_enabled": schema.BoolAttribute{ + MarkdownDescription: helpers.NewAttributeDescription("").String, + Optional: true, + }, + "secure_port_enabled": schema.BoolAttribute{ + MarkdownDescription: helpers.NewAttributeDescription("").String, + Optional: true, + }, }, } } diff --git a/internal/provider/resource_meraki_network_settings_test.go b/internal/provider/resource_meraki_network_settings_test.go index a654c77..e5fc763 100644 --- a/internal/provider/resource_meraki_network_settings_test.go +++ b/internal/provider/resource_meraki_network_settings_test.go @@ -32,6 +32,10 @@ import ( func TestAccMerakiNetworkSettings(t *testing.T) { var checks []resource.TestCheckFunc checks = append(checks, resource.TestCheckResourceAttr("meraki_network_settings.test", "local_status_page_enabled", "false")) + checks = append(checks, resource.TestCheckResourceAttr("meraki_network_settings.test", "remote_status_page_enabled", "false")) + checks = append(checks, resource.TestCheckResourceAttr("meraki_network_settings.test", "local_status_page_authentication_enabled", "false")) + checks = append(checks, resource.TestCheckResourceAttr("meraki_network_settings.test", "named_vlans_enabled", "false")) + checks = append(checks, resource.TestCheckResourceAttr("meraki_network_settings.test", "secure_port_enabled", "false")) var steps []resource.TestStep if os.Getenv("SKIP_MINIMUM_TEST") == "" { @@ -62,7 +66,7 @@ data "meraki_organization" "test" { resource "meraki_network" "test" { organization_id = data.meraki_organization.test.id name = "Network1" - product_types = ["switch"] + product_types = ["switch", "wireless"] } ` @@ -87,6 +91,11 @@ func testAccMerakiNetworkSettingsConfig_all() string { config := `resource "meraki_network_settings" "test" {` + "\n" config += ` network_id = meraki_network.test.id` + "\n" config += ` local_status_page_enabled = false` + "\n" + config += ` remote_status_page_enabled = false` + "\n" + config += ` local_status_page_authentication_enabled = false` + "\n" + config += ` local_status_page_authentication_password = "miles123"` + "\n" + config += ` named_vlans_enabled = false` + "\n" + config += ` secure_port_enabled = false` + "\n" config += `}` + "\n" return config } From f4fe66554b34e967fc1b4d5f7e6e8a9f5d213a07 Mon Sep 17 00:00:00 2001 From: Marcin Parafiniuk Date: Tue, 10 Sep 2024 11:08:44 +0200 Subject: [PATCH 4/4] fixes for bugs found by pre commit checks --- gen/definitions/networks_settings.yaml | 1 - gen/schema/schema.yaml | 1 + go.mod | 2 -- go.sum | 2 ++ 4 files changed, 3 insertions(+), 3 deletions(-) diff --git a/gen/definitions/networks_settings.yaml b/gen/definitions/networks_settings.yaml index 38557c4..3a407ed 100644 --- a/gen/definitions/networks_settings.yaml +++ b/gen/definitions/networks_settings.yaml @@ -4,7 +4,6 @@ rest_endpoint: /networks/%v/settings no_delete: true put_create: true doc_category: Networks -id_from_attribute: true attributes: - tf_name: network_id type: String diff --git a/gen/schema/schema.yaml b/gen/schema/schema.yaml index 95a654c..28244f8 100644 --- a/gen/schema/schema.yaml +++ b/gen/schema/schema.yaml @@ -14,6 +14,7 @@ skip_minimum_test: bool(required=False) # Do not perform a "minimum" (only manda attributes: list(include('attribute'), required=False) # List of attributes test_tags: list(str(), required=False) # List of test tags, tests are only executed if an environment variable with one of these tags is configured test_prerequisites: str(required=False) # HCL code that is included in the acceptance tests to define prerequisites +id_name: str(required=False) # the name of the attribute that will uniquely identify the resource, default "id" --- attribute: model_name: str(required=False) # Name of the attribute in the model (payload) diff --git a/go.mod b/go.mod index e1b2500..0c79fdf 100644 --- a/go.mod +++ b/go.mod @@ -17,8 +17,6 @@ require ( gopkg.in/yaml.v3 v3.0.1 ) -replace github.com/netascode/go-meraki v0.0.0-20240901102824-a67592c39438 => /Users/maparafi/go-meraki - require ( github.com/BurntSushi/toml v1.2.1 // indirect github.com/Kunde21/markdownfmt/v3 v3.1.0 // indirect diff --git a/go.sum b/go.sum index cc77968..35f3d27 100644 --- a/go.sum +++ b/go.sum @@ -164,6 +164,8 @@ github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RR github.com/mitchellh/reflectwalk v1.0.0/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx0jmZXqmk4esnw= github.com/mitchellh/reflectwalk v1.0.2 h1:G2LzWKi524PWgd3mLHV8Y5k7s6XUvT0Gef6zxSIeXaQ= github.com/mitchellh/reflectwalk v1.0.2/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx0jmZXqmk4esnw= +github.com/netascode/go-meraki v0.0.0-20240901102824-a67592c39438 h1:dsFYRJ2aXEddmIz76weQAZ6QzliSmMpVaZxGlW4wTUA= +github.com/netascode/go-meraki v0.0.0-20240901102824-a67592c39438/go.mod h1:xZ/kiJA+SZY32p5t1J9Jdhvtig9BU4SMbxKnfAJrmLE= github.com/oklog/run v1.1.0 h1:GEenZ1cK0+q0+wsJew9qUg/DyD8k3JzYsZAi5gYi2mA= github.com/oklog/run v1.1.0/go.mod h1:sVPdnTZT1zYwAJeCMu2Th4T21pA3FPOQRfWjQlk7DVU= github.com/pjbgf/sha1cd v0.3.0 h1:4D5XXmUUBUl/xQ6IjCkEAbqXskkq/4O7LmGn0AqMDs4=