From 6ad5705bfa5838c406a029566e92826f17e7bf92 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20Bernacki?= Date: Mon, 14 Oct 2024 16:40:07 +0200 Subject: [PATCH 01/13] fmc smart license base version --- docs/data-sources/smart_license.md | 35 +++ docs/resources/smart_license.md | 45 +++ .../fmc_smart_license/data-source.tf | 3 + .../resources/fmc_smart_license/import.sh | 1 + .../resources/fmc_smart_license/resource.tf | 4 + gen/definitions/smart_license.yaml | 16 + .../provider/data_source_fmc_smart_license.go | 124 ++++++++ .../data_source_fmc_smart_license_test.go | 66 +++++ internal/provider/model_fmc_smart_license.go | 113 ++++++++ internal/provider/provider.go | 2 + .../provider/resource_fmc_smart_license.go | 274 ++++++++++++++++++ .../resource_fmc_smart_license_test.go | 83 ++++++ 12 files changed, 766 insertions(+) create mode 100644 docs/data-sources/smart_license.md create mode 100644 docs/resources/smart_license.md create mode 100644 examples/data-sources/fmc_smart_license/data-source.tf create mode 100644 examples/resources/fmc_smart_license/import.sh create mode 100644 examples/resources/fmc_smart_license/resource.tf create mode 100644 gen/definitions/smart_license.yaml create mode 100644 internal/provider/data_source_fmc_smart_license.go create mode 100644 internal/provider/data_source_fmc_smart_license_test.go create mode 100644 internal/provider/model_fmc_smart_license.go create mode 100644 internal/provider/resource_fmc_smart_license.go create mode 100644 internal/provider/resource_fmc_smart_license_test.go diff --git a/docs/data-sources/smart_license.md b/docs/data-sources/smart_license.md new file mode 100644 index 00000000..0fc6461e --- /dev/null +++ b/docs/data-sources/smart_license.md @@ -0,0 +1,35 @@ +--- +# generated by https://github.com/hashicorp/terraform-plugin-docs +page_title: "fmc_smart_license Data Source - terraform-provider-fmc" +subcategory: "License" +description: |- + This data source can read the Smart License. +--- + +# fmc_smart_license (Data Source) + +This data source can read the Smart License. + +## Example Usage + +```terraform +data "fmc_smart_license" "example" { + id = "76d24097-41c4-4558-a4d0-a8c07ac08470" +} +``` + + +## Schema + +### Required + +- `id` (String) The id of the object + +### Optional + +- `domain` (String) The name of the FMC domain + +### Read-Only + +- `registration_type` (String) Action to be executed on the smart license. +- `token` (String) Registration token. diff --git a/docs/resources/smart_license.md b/docs/resources/smart_license.md new file mode 100644 index 00000000..5de90964 --- /dev/null +++ b/docs/resources/smart_license.md @@ -0,0 +1,45 @@ +--- +# generated by https://github.com/hashicorp/terraform-plugin-docs +page_title: "fmc_smart_license Resource - terraform-provider-fmc" +subcategory: "License" +description: |- + This resource can manage a Smart License. +--- + +# fmc_smart_license (Resource) + +This resource can manage a Smart License. + +## Example Usage + +```terraform +resource "fmc_smart_license" "example" { + registration_type = "REGISTER" + token = "X2M3YmJlY..." +} +``` + + +## Schema + +### Required + +- `registration_type` (String) Action to be executed on the smart license. + - Choices: `REGISTER`, `DEREGISTER`, `EVALUATION` + +### Optional + +- `domain` (String) The name of the FMC domain +- `token` (String) Registration token. + +### Read-Only + +- `id` (String) The id of the object + +## Import + +Import is supported using the following syntax: + +```shell +terraform import fmc_smart_license.example "" +``` diff --git a/examples/data-sources/fmc_smart_license/data-source.tf b/examples/data-sources/fmc_smart_license/data-source.tf new file mode 100644 index 00000000..d8575b6d --- /dev/null +++ b/examples/data-sources/fmc_smart_license/data-source.tf @@ -0,0 +1,3 @@ +data "fmc_smart_license" "example" { + id = "76d24097-41c4-4558-a4d0-a8c07ac08470" +} diff --git a/examples/resources/fmc_smart_license/import.sh b/examples/resources/fmc_smart_license/import.sh new file mode 100644 index 00000000..5755b391 --- /dev/null +++ b/examples/resources/fmc_smart_license/import.sh @@ -0,0 +1 @@ +terraform import fmc_smart_license.example "" diff --git a/examples/resources/fmc_smart_license/resource.tf b/examples/resources/fmc_smart_license/resource.tf new file mode 100644 index 00000000..c0e65436 --- /dev/null +++ b/examples/resources/fmc_smart_license/resource.tf @@ -0,0 +1,4 @@ +resource "fmc_smart_license" "example" { + registration_type = "REGISTER" + token = "X2M3YmJlY..." +} diff --git a/gen/definitions/smart_license.yaml b/gen/definitions/smart_license.yaml new file mode 100644 index 00000000..5a5c1df7 --- /dev/null +++ b/gen/definitions/smart_license.yaml @@ -0,0 +1,16 @@ +--- +name: Smart License +rest_endpoint: /api/fmc_platform/v1/license/smartlicenses +doc_category: License +attributes: + - model_name: registrationType + type: String + description: Action to be executed on the smart license. + enum_values: [REGISTER, DEREGISTER, EVALUATION] + mandatory: true + example: REGISTER + - model_name: token + type: String + description: Registration token. + example: "X2M3YmJlY..." + exclude_test: true diff --git a/internal/provider/data_source_fmc_smart_license.go b/internal/provider/data_source_fmc_smart_license.go new file mode 100644 index 00000000..ab702801 --- /dev/null +++ b/internal/provider/data_source_fmc_smart_license.go @@ -0,0 +1,124 @@ +// Copyright © 2023 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/datasource" + "github.com/hashicorp/terraform-plugin-framework/datasource/schema" + "github.com/hashicorp/terraform-plugin-log/tflog" + "github.com/netascode/go-fmc" +) + +// 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 = &SmartLicenseDataSource{} + _ datasource.DataSourceWithConfigure = &SmartLicenseDataSource{} +) + +func NewSmartLicenseDataSource() datasource.DataSource { + return &SmartLicenseDataSource{} +} + +type SmartLicenseDataSource struct { + client *fmc.Client +} + +func (d *SmartLicenseDataSource) Metadata(_ context.Context, req datasource.MetadataRequest, resp *datasource.MetadataResponse) { + resp.TypeName = req.ProviderTypeName + "_smart_license" +} + +func (d *SmartLicenseDataSource) 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 Smart License.", + + Attributes: map[string]schema.Attribute{ + "id": schema.StringAttribute{ + MarkdownDescription: "The id of the object", + Required: true, + }, + "domain": schema.StringAttribute{ + MarkdownDescription: "The name of the FMC domain", + Optional: true, + }, + "registration_type": schema.StringAttribute{ + MarkdownDescription: "Action to be executed on the smart license.", + Computed: true, + }, + "token": schema.StringAttribute{ + MarkdownDescription: "Registration token.", + Computed: true, + }, + }, + } +} + +func (d *SmartLicenseDataSource) Configure(_ context.Context, req datasource.ConfigureRequest, _ *datasource.ConfigureResponse) { + if req.ProviderData == nil { + return + } + + d.client = req.ProviderData.(*FmcProviderData).Client +} + +// End of section. //template:end model + +// Section below is generated&owned by "gen/generator.go". //template:begin read + +func (d *SmartLicenseDataSource) Read(ctx context.Context, req datasource.ReadRequest, resp *datasource.ReadResponse) { + var config SmartLicense + + // Read config + diags := req.Config.Get(ctx, &config) + resp.Diagnostics.Append(diags...) + if resp.Diagnostics.HasError() { + return + } + + // Set request domain if provided + reqMods := [](func(*fmc.Req)){} + if !config.Domain.IsNull() && config.Domain.ValueString() != "" { + reqMods = append(reqMods, fmc.DomainName(config.Domain.ValueString())) + } + + tflog.Debug(ctx, fmt.Sprintf("%s: Beginning Read", config.Id.String())) + + res, err := d.client.Get(config.getPath()+"/"+url.QueryEscape(config.Id.ValueString()), reqMods...) + 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.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_fmc_smart_license_test.go b/internal/provider/data_source_fmc_smart_license_test.go new file mode 100644 index 00000000..510faea5 --- /dev/null +++ b/internal/provider/data_source_fmc_smart_license_test.go @@ -0,0 +1,66 @@ +// Copyright © 2023 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 TestAccDataSourceFmcSmartLicense(t *testing.T) { + var checks []resource.TestCheckFunc + checks = append(checks, resource.TestCheckResourceAttr("data.fmc_smart_license.test", "registration_type", "REGISTER")) + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + ProtoV6ProviderFactories: testAccProtoV6ProviderFactories, + Steps: []resource.TestStep{ + { + Config: testAccDataSourceFmcSmartLicenseConfig(), + Check: resource.ComposeTestCheckFunc(checks...), + }, + }, + }) +} + +// End of section. //template:end testAccDataSource + +// Section below is generated&owned by "gen/generator.go". //template:begin testPrerequisites +// End of section. //template:end testPrerequisites + +// Section below is generated&owned by "gen/generator.go". //template:begin testAccDataSourceConfig + +func testAccDataSourceFmcSmartLicenseConfig() string { + config := `resource "fmc_smart_license" "test" {` + "\n" + config += ` registration_type = "REGISTER"` + "\n" + config += `}` + "\n" + + config += ` + data "fmc_smart_license" "test" { + id = fmc_smart_license.test.id + } + ` + return config +} + +// End of section. //template:end testAccDataSourceConfig diff --git a/internal/provider/model_fmc_smart_license.go b/internal/provider/model_fmc_smart_license.go new file mode 100644 index 00000000..cf40a7ec --- /dev/null +++ b/internal/provider/model_fmc_smart_license.go @@ -0,0 +1,113 @@ +// Copyright © 2023 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" + + "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 SmartLicense struct { + Id types.String `tfsdk:"id"` + Domain types.String `tfsdk:"domain"` + RegistrationType types.String `tfsdk:"registration_type"` + Token types.String `tfsdk:"token"` +} + +// End of section. //template:end types + +// Section below is generated&owned by "gen/generator.go". //template:begin getPath + +func (data SmartLicense) getPath() string { + return "/api/fmc_platform/v1/license/smartlicenses" +} + +// End of section. //template:end getPath + +// Section below is generated&owned by "gen/generator.go". //template:begin toBody + +func (data SmartLicense) toBody(ctx context.Context, state SmartLicense) string { + body := "" + if data.Id.ValueString() != "" { + body, _ = sjson.Set(body, "id", data.Id.ValueString()) + } + if !data.RegistrationType.IsNull() { + body, _ = sjson.Set(body, "registrationType", data.RegistrationType.ValueString()) + } + if !data.Token.IsNull() { + body, _ = sjson.Set(body, "token", data.Token.ValueString()) + } + return body +} + +// End of section. //template:end toBody + +// Section below is generated&owned by "gen/generator.go". //template:begin fromBody + +func (data *SmartLicense) fromBody(ctx context.Context, res gjson.Result) { + if value := res.Get("registrationType"); value.Exists() { + data.RegistrationType = types.StringValue(value.String()) + } else { + data.RegistrationType = types.StringNull() + } + if value := res.Get("token"); value.Exists() { + data.Token = types.StringValue(value.String()) + } else { + data.Token = types.StringNull() + } +} + +// 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 *SmartLicense) fromBodyPartial(ctx context.Context, res gjson.Result) { + if value := res.Get("registrationType"); value.Exists() && !data.RegistrationType.IsNull() { + data.RegistrationType = types.StringValue(value.String()) + } else { + data.RegistrationType = types.StringNull() + } + if value := res.Get("token"); value.Exists() && !data.Token.IsNull() { + data.Token = types.StringValue(value.String()) + } else { + data.Token = types.StringNull() + } +} + +// 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 *SmartLicense) 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 df96f1de..716d018c 100644 --- a/internal/provider/provider.go +++ b/internal/provider/provider.go @@ -311,6 +311,7 @@ func (p *FmcProvider) Resources(ctx context.Context) []func() resource.Resource NewPortGroupResource, NewRangeResource, NewSecurityZoneResource, + NewSmartLicenseResource, NewStandardACLResource, NewURLResource, NewURLGroupResource, @@ -343,6 +344,7 @@ func (p *FmcProvider) DataSources(ctx context.Context) []func() datasource.DataS NewPortGroupDataSource, NewRangeDataSource, NewSecurityZoneDataSource, + NewSmartLicenseDataSource, NewStandardACLDataSource, NewURLDataSource, NewURLGroupDataSource, diff --git a/internal/provider/resource_fmc_smart_license.go b/internal/provider/resource_fmc_smart_license.go new file mode 100644 index 00000000..fa0dbca7 --- /dev/null +++ b/internal/provider/resource_fmc_smart_license.go @@ -0,0 +1,274 @@ +// Copyright © 2023 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-validators/stringvalidator" + "github.com/hashicorp/terraform-plugin-framework/path" + "github.com/hashicorp/terraform-plugin-framework/resource" + "github.com/hashicorp/terraform-plugin-framework/resource/schema" + "github.com/hashicorp/terraform-plugin-framework/resource/schema/planmodifier" + "github.com/hashicorp/terraform-plugin-framework/resource/schema/stringplanmodifier" + "github.com/hashicorp/terraform-plugin-framework/schema/validator" + "github.com/hashicorp/terraform-plugin-framework/types" + "github.com/hashicorp/terraform-plugin-log/tflog" + "github.com/netascode/go-fmc" + "github.com/netascode/terraform-provider-fmc/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 = &SmartLicenseResource{} + _ resource.ResourceWithImportState = &SmartLicenseResource{} +) + +func NewSmartLicenseResource() resource.Resource { + return &SmartLicenseResource{} +} + +type SmartLicenseResource struct { + client *fmc.Client +} + +func (r *SmartLicenseResource) Metadata(ctx context.Context, req resource.MetadataRequest, resp *resource.MetadataResponse) { + resp.TypeName = req.ProviderTypeName + "_smart_license" +} + +func (r *SmartLicenseResource) 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 a Smart License.").String, + + Attributes: map[string]schema.Attribute{ + "id": schema.StringAttribute{ + MarkdownDescription: "The id of the object", + Computed: true, + PlanModifiers: []planmodifier.String{ + stringplanmodifier.UseStateForUnknown(), + }, + }, + "domain": schema.StringAttribute{ + MarkdownDescription: "The name of the FMC domain", + Optional: true, + PlanModifiers: []planmodifier.String{ + stringplanmodifier.RequiresReplace(), + }, + }, + "registration_type": schema.StringAttribute{ + MarkdownDescription: helpers.NewAttributeDescription("Action to be executed on the smart license.").AddStringEnumDescription("REGISTER", "DEREGISTER", "EVALUATION").String, + Required: true, + Validators: []validator.String{ + stringvalidator.OneOf("REGISTER", "DEREGISTER", "EVALUATION"), + }, + }, + "token": schema.StringAttribute{ + MarkdownDescription: helpers.NewAttributeDescription("Registration token.").String, + Optional: true, + }, + }, + } +} + +func (r *SmartLicenseResource) Configure(_ context.Context, req resource.ConfigureRequest, _ *resource.ConfigureResponse) { + if req.ProviderData == nil { + return + } + + r.client = req.ProviderData.(*FmcProviderData).Client +} + +// End of section. //template:end model + +// Section below is generated&owned by "gen/generator.go". //template:begin create + +func (r *SmartLicenseResource) Create(ctx context.Context, req resource.CreateRequest, resp *resource.CreateResponse) { + var plan SmartLicense + + // Read plan + diags := req.Plan.Get(ctx, &plan) + if resp.Diagnostics.Append(diags...); resp.Diagnostics.HasError() { + return + } + + // Set request domain if provided + reqMods := [](func(*fmc.Req)){} + if !plan.Domain.IsNull() && plan.Domain.ValueString() != "" { + reqMods = append(reqMods, fmc.DomainName(plan.Domain.ValueString())) + } + + tflog.Debug(ctx, fmt.Sprintf("%s: Beginning Create", plan.Id.ValueString())) + + // Create object + body := plan.toBody(ctx, SmartLicense{}) + res, err := r.client.Post(plan.getPath(), body, reqMods...) + 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 *SmartLicenseResource) Read(ctx context.Context, req resource.ReadRequest, resp *resource.ReadResponse) { + var state SmartLicense + + // Read state + diags := req.State.Get(ctx, &state) + if resp.Diagnostics.Append(diags...); resp.Diagnostics.HasError() { + return + } + + // Set request domain if provided + reqMods := [](func(*fmc.Req)){} + if !state.Domain.IsNull() && state.Domain.ValueString() != "" { + reqMods = append(reqMods, fmc.DomainName(state.Domain.ValueString())) + } + + tflog.Debug(ctx, fmt.Sprintf("%s: Beginning Read", state.Id.String())) + + res, err := r.client.Get(state.getPath()+"/"+url.QueryEscape(state.Id.ValueString()), reqMods...) + if err != nil && strings.Contains(err.Error(), "StatusCode 404") { + resp.State.RemoveResource(ctx) + return + } else if err != nil { + resp.Diagnostics.AddError("Client Error", fmt.Sprintf("Failed to retrieve object (GET), got error: %s, %s", err, res.String())) + return + } + + imp, diags := helpers.IsFlagImporting(ctx, req) + if resp.Diagnostics.Append(diags...); resp.Diagnostics.HasError() { + return + } + + // After `terraform import` we switch to a full read. + if imp { + state.fromBody(ctx, res) + } else { + state.fromBodyPartial(ctx, res) + } + + tflog.Debug(ctx, fmt.Sprintf("%s: Read finished successfully", state.Id.ValueString())) + + diags = resp.State.Set(ctx, &state) + resp.Diagnostics.Append(diags...) + + helpers.SetFlagImporting(ctx, false, resp.Private, &resp.Diagnostics) +} + +// End of section. //template:end read + +// Section below is generated&owned by "gen/generator.go". //template:begin update + +func (r *SmartLicenseResource) Update(ctx context.Context, req resource.UpdateRequest, resp *resource.UpdateResponse) { + var plan, state SmartLicense + + // 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 + } + + // Set request domain if provided + reqMods := [](func(*fmc.Req)){} + if !plan.Domain.IsNull() && plan.Domain.ValueString() != "" { + reqMods = append(reqMods, fmc.DomainName(plan.Domain.ValueString())) + } + + 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, reqMods...) + 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 *SmartLicenseResource) Delete(ctx context.Context, req resource.DeleteRequest, resp *resource.DeleteResponse) { + var state SmartLicense + + // Read state + diags := req.State.Get(ctx, &state) + if resp.Diagnostics.Append(diags...); resp.Diagnostics.HasError() { + return + } + + // Set request domain if provided + reqMods := [](func(*fmc.Req)){} + if !state.Domain.IsNull() && state.Domain.ValueString() != "" { + reqMods = append(reqMods, fmc.DomainName(state.Domain.ValueString())) + } + + tflog.Debug(ctx, fmt.Sprintf("%s: Beginning Delete", state.Id.ValueString())) + res, err := r.client.Delete(state.getPath()+"/"+url.QueryEscape(state.Id.ValueString()), reqMods...) + 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.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 *SmartLicenseResource) ImportState(ctx context.Context, req resource.ImportStateRequest, resp *resource.ImportStateResponse) { + resource.ImportStatePassthroughID(ctx, path.Root("id"), req, resp) + + helpers.SetFlagImporting(ctx, true, resp.Private, &resp.Diagnostics) +} + +// End of section. //template:end import diff --git a/internal/provider/resource_fmc_smart_license_test.go b/internal/provider/resource_fmc_smart_license_test.go new file mode 100644 index 00000000..5982d6e0 --- /dev/null +++ b/internal/provider/resource_fmc_smart_license_test.go @@ -0,0 +1,83 @@ +// Copyright © 2023 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 TestAccFmcSmartLicense(t *testing.T) { + var checks []resource.TestCheckFunc + checks = append(checks, resource.TestCheckResourceAttr("fmc_smart_license.test", "registration_type", "REGISTER")) + + var steps []resource.TestStep + if os.Getenv("SKIP_MINIMUM_TEST") == "" { + steps = append(steps, resource.TestStep{ + Config: testAccFmcSmartLicenseConfig_minimum(), + }) + } + steps = append(steps, resource.TestStep{ + Config: testAccFmcSmartLicenseConfig_all(), + Check: resource.ComposeTestCheckFunc(checks...), + }) + steps = append(steps, resource.TestStep{ + ResourceName: "fmc_smart_license.test", + ImportState: true, + }) + + 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 +// End of section. //template:end testPrerequisites + +// Section below is generated&owned by "gen/generator.go". //template:begin testAccConfigMinimal + +func testAccFmcSmartLicenseConfig_minimum() string { + config := `resource "fmc_smart_license" "test" {` + "\n" + config += ` registration_type = "REGISTER"` + "\n" + config += `}` + "\n" + return config +} + +// End of section. //template:end testAccConfigMinimal + +// Section below is generated&owned by "gen/generator.go". //template:begin testAccConfigAll + +func testAccFmcSmartLicenseConfig_all() string { + config := `resource "fmc_smart_license" "test" {` + "\n" + config += ` registration_type = "REGISTER"` + "\n" + config += `}` + "\n" + return config +} + +// End of section. //template:end testAccConfigAll From 5abc4d7d314b77c567b4ac39252fa385aa00c63d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20Bernacki?= Date: Fri, 18 Oct 2024 14:41:03 +0200 Subject: [PATCH 02/13] add regStatus attribute --- docs/data-sources/smart_license.md | 1 + docs/resources/smart_license.md | 1 + gen/definitions/smart_license.yaml | 9 +++++++ .../provider/data_source_fmc_smart_license.go | 4 +++ internal/provider/model_fmc_smart_license.go | 22 ++++++++++++--- .../provider/resource_fmc_smart_license.go | 27 ++++--------------- 6 files changed, 38 insertions(+), 26 deletions(-) diff --git a/docs/data-sources/smart_license.md b/docs/data-sources/smart_license.md index 0fc6461e..62e940fe 100644 --- a/docs/data-sources/smart_license.md +++ b/docs/data-sources/smart_license.md @@ -31,5 +31,6 @@ data "fmc_smart_license" "example" { ### Read-Only +- `registration_status` (String) Status of a smart license. - `registration_type` (String) Action to be executed on the smart license. - `token` (String) Registration token. diff --git a/docs/resources/smart_license.md b/docs/resources/smart_license.md index 5de90964..2bb21569 100644 --- a/docs/resources/smart_license.md +++ b/docs/resources/smart_license.md @@ -30,6 +30,7 @@ resource "fmc_smart_license" "example" { ### Optional - `domain` (String) The name of the FMC domain +- `registration_status` (String) Status of a smart license. - `token` (String) Registration token. ### Read-Only diff --git a/gen/definitions/smart_license.yaml b/gen/definitions/smart_license.yaml index 5a5c1df7..3189352c 100644 --- a/gen/definitions/smart_license.yaml +++ b/gen/definitions/smart_license.yaml @@ -2,6 +2,8 @@ name: Smart License rest_endpoint: /api/fmc_platform/v1/license/smartlicenses doc_category: License +no_update: true +no_delete: true attributes: - model_name: registrationType type: String @@ -14,3 +16,10 @@ attributes: description: Registration token. example: "X2M3YmJlY..." exclude_test: true + - model_name: regStatus + tf_name: registration_status + type: String + description: Status of a smart license. + example: EVALUATION + exclude_test: true + exclude_example: true diff --git a/internal/provider/data_source_fmc_smart_license.go b/internal/provider/data_source_fmc_smart_license.go index ab702801..a2668c50 100644 --- a/internal/provider/data_source_fmc_smart_license.go +++ b/internal/provider/data_source_fmc_smart_license.go @@ -73,6 +73,10 @@ func (d *SmartLicenseDataSource) Schema(ctx context.Context, req datasource.Sche MarkdownDescription: "Registration token.", Computed: true, }, + "registration_status": schema.StringAttribute{ + MarkdownDescription: "Status of a smart license.", + Computed: true, + }, }, } } diff --git a/internal/provider/model_fmc_smart_license.go b/internal/provider/model_fmc_smart_license.go index cf40a7ec..0e65474d 100644 --- a/internal/provider/model_fmc_smart_license.go +++ b/internal/provider/model_fmc_smart_license.go @@ -31,10 +31,11 @@ import ( // Section below is generated&owned by "gen/generator.go". //template:begin types type SmartLicense struct { - Id types.String `tfsdk:"id"` - Domain types.String `tfsdk:"domain"` - RegistrationType types.String `tfsdk:"registration_type"` - Token types.String `tfsdk:"token"` + Id types.String `tfsdk:"id"` + Domain types.String `tfsdk:"domain"` + RegistrationType types.String `tfsdk:"registration_type"` + Token types.String `tfsdk:"token"` + RegistrationStatus types.String `tfsdk:"registration_status"` } // End of section. //template:end types @@ -60,6 +61,9 @@ func (data SmartLicense) toBody(ctx context.Context, state SmartLicense) string if !data.Token.IsNull() { body, _ = sjson.Set(body, "token", data.Token.ValueString()) } + if !data.RegistrationStatus.IsNull() { + body, _ = sjson.Set(body, "regStatus", data.RegistrationStatus.ValueString()) + } return body } @@ -78,6 +82,11 @@ func (data *SmartLicense) fromBody(ctx context.Context, res gjson.Result) { } else { data.Token = types.StringNull() } + if value := res.Get("regStatus"); value.Exists() { + data.RegistrationStatus = types.StringValue(value.String()) + } else { + data.RegistrationStatus = types.StringNull() + } } // End of section. //template:end fromBody @@ -99,6 +108,11 @@ func (data *SmartLicense) fromBodyPartial(ctx context.Context, res gjson.Result) } else { data.Token = types.StringNull() } + if value := res.Get("regStatus"); value.Exists() && !data.RegistrationStatus.IsNull() { + data.RegistrationStatus = types.StringValue(value.String()) + } else { + data.RegistrationStatus = types.StringNull() + } } // End of section. //template:end fromBodyPartial diff --git a/internal/provider/resource_fmc_smart_license.go b/internal/provider/resource_fmc_smart_license.go index fa0dbca7..27c06a0e 100644 --- a/internal/provider/resource_fmc_smart_license.go +++ b/internal/provider/resource_fmc_smart_license.go @@ -21,7 +21,6 @@ package provider import ( "context" "fmt" - "net/url" "strings" "github.com/hashicorp/terraform-plugin-framework-validators/stringvalidator" @@ -90,6 +89,10 @@ func (r *SmartLicenseResource) Schema(ctx context.Context, req resource.SchemaRe MarkdownDescription: helpers.NewAttributeDescription("Registration token.").String, Optional: true, }, + "registration_status": schema.StringAttribute{ + MarkdownDescription: helpers.NewAttributeDescription("Status of a smart license.").String, + Optional: true, + }, }, } } @@ -104,8 +107,6 @@ func (r *SmartLicenseResource) Configure(_ context.Context, req resource.Configu // End of section. //template:end model -// Section below is generated&owned by "gen/generator.go". //template:begin create - func (r *SmartLicenseResource) Create(ctx context.Context, req resource.CreateRequest, resp *resource.CreateResponse) { var plan SmartLicense @@ -140,10 +141,6 @@ func (r *SmartLicenseResource) Create(ctx context.Context, req resource.CreateRe 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 *SmartLicenseResource) Read(ctx context.Context, req resource.ReadRequest, resp *resource.ReadResponse) { var state SmartLicense @@ -161,7 +158,7 @@ func (r *SmartLicenseResource) Read(ctx context.Context, req resource.ReadReques tflog.Debug(ctx, fmt.Sprintf("%s: Beginning Read", state.Id.String())) - res, err := r.client.Get(state.getPath()+"/"+url.QueryEscape(state.Id.ValueString()), reqMods...) + res, err := r.client.Get(state.getPath(), reqMods...) if err != nil && strings.Contains(err.Error(), "StatusCode 404") { resp.State.RemoveResource(ctx) return @@ -190,8 +187,6 @@ func (r *SmartLicenseResource) Read(ctx context.Context, req resource.ReadReques 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 *SmartLicenseResource) Update(ctx context.Context, req resource.UpdateRequest, resp *resource.UpdateResponse) { @@ -217,13 +212,6 @@ func (r *SmartLicenseResource) Update(ctx context.Context, req resource.UpdateRe 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, reqMods...) - 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) @@ -250,11 +238,6 @@ func (r *SmartLicenseResource) Delete(ctx context.Context, req resource.DeleteRe } tflog.Debug(ctx, fmt.Sprintf("%s: Beginning Delete", state.Id.ValueString())) - res, err := r.client.Delete(state.getPath()+"/"+url.QueryEscape(state.Id.ValueString()), reqMods...) - 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.Id.ValueString())) From 06e0772351fedeaba25da63ab82b7c244a2bae25 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20Bernacki?= Date: Mon, 28 Oct 2024 16:11:30 +0100 Subject: [PATCH 03/13] add debug config --- .gitignore | 1 + .vscode/launch.json | 13 ++++++++++++ internal/provider/model_fmc_smart_license.go | 7 ++----- .../provider/resource_fmc_smart_license.go | 21 +++++++++++++------ main.go | 7 +++++++ 5 files changed, 38 insertions(+), 11 deletions(-) diff --git a/.gitignore b/.gitignore index cd61dcb8..fad74c21 100644 --- a/.gitignore +++ b/.gitignore @@ -17,6 +17,7 @@ website/node_modules ./*.tfstate .terraform/ .vscode/settings.json +.vscode/private.env *.log *.bak *~ diff --git a/.vscode/launch.json b/.vscode/launch.json index d2e326d8..79760c21 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -11,6 +11,19 @@ "mode": "auto", "program": "gen/generator.go", "cwd": "./" + }, + { + "name": "Debug Terraform Provider", + "type": "go", + "request": "launch", + "mode": "debug", + // this assumes your workspace is the root of the repo + "program": "${workspaceFolder}", + "env": {}, + "args": [ + "-debug", + ], + "envFile": "${workspaceFolder}/.vscode/private.env" } ] } \ No newline at end of file diff --git a/internal/provider/model_fmc_smart_license.go b/internal/provider/model_fmc_smart_license.go index 0e65474d..d723610c 100644 --- a/internal/provider/model_fmc_smart_license.go +++ b/internal/provider/model_fmc_smart_license.go @@ -91,13 +91,12 @@ func (data *SmartLicense) fromBody(ctx context.Context, res gjson.Result) { // 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 *SmartLicense) fromBodyPartial(ctx context.Context, res gjson.Result) { + res = res.Get("items.0") if value := res.Get("registrationType"); value.Exists() && !data.RegistrationType.IsNull() { data.RegistrationType = types.StringValue(value.String()) } else { @@ -108,15 +107,13 @@ func (data *SmartLicense) fromBodyPartial(ctx context.Context, res gjson.Result) } else { data.Token = types.StringNull() } - if value := res.Get("regStatus"); value.Exists() && !data.RegistrationStatus.IsNull() { + if value := res.Get("regStatus"); value.Exists() { data.RegistrationStatus = types.StringValue(value.String()) } else { data.RegistrationStatus = types.StringNull() } } -// 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. diff --git a/internal/provider/resource_fmc_smart_license.go b/internal/provider/resource_fmc_smart_license.go index 27c06a0e..cfd419aa 100644 --- a/internal/provider/resource_fmc_smart_license.go +++ b/internal/provider/resource_fmc_smart_license.go @@ -109,6 +109,7 @@ func (r *SmartLicenseResource) Configure(_ context.Context, req resource.Configu func (r *SmartLicenseResource) Create(ctx context.Context, req resource.CreateRequest, resp *resource.CreateResponse) { var plan SmartLicense + var state SmartLicense // Read plan diags := req.Plan.Get(ctx, &plan) @@ -122,15 +123,23 @@ func (r *SmartLicenseResource) Create(ctx context.Context, req resource.CreateRe reqMods = append(reqMods, fmc.DomainName(plan.Domain.ValueString())) } - tflog.Debug(ctx, fmt.Sprintf("%s: Beginning Create", plan.Id.ValueString())) - - // Create object - body := plan.toBody(ctx, SmartLicense{}) - res, err := r.client.Post(plan.getPath(), body, reqMods...) + // Read state + res, err := r.client.Get(state.getPath(), reqMods...) if err != nil { - resp.Diagnostics.AddError("Client Error", fmt.Sprintf("Failed to configure object (POST/PUT), got error: %s, %s", err, res.String())) + resp.Diagnostics.AddError("Client Error", fmt.Sprintf("Failed to retrieve object (GET), got error: %s, %s", err, res.String())) return } + state.fromBodyPartial(ctx, res) + + tflog.Debug(ctx, fmt.Sprintf("%s: Beginning Create", plan.Id.ValueString())) + + // Create object + // body := plan.toBody(ctx, SmartLicense{}) + // res, err = r.client.Post(plan.getPath(), body, reqMods...) + // 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())) diff --git a/main.go b/main.go index 91a53da9..7daef432 100644 --- a/main.go +++ b/main.go @@ -19,6 +19,7 @@ package main import ( "context" + "flag" "log" "github.com/hashicorp/terraform-plugin-framework/providerserver" @@ -54,8 +55,14 @@ var ( ) func main() { + var debug bool + + flag.BoolVar(&debug, "debug", false, "set to true to run the provider with support for debuggers like delve") + flag.Parse() + opts := providerserver.ServeOpts{ Address: "registry.terraform.io/netascode/fmc", + Debug: debug, } err := providerserver.Serve(context.Background(), provider.New(version), opts) From 17797571c90b30a2b2a93e9551abc586b66539e8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20Bernacki?= Date: Wed, 30 Oct 2024 16:11:49 +0100 Subject: [PATCH 04/13] logic for create method --- internal/provider/model_fmc_smart_license.go | 14 ++---- .../provider/resource_fmc_smart_license.go | 48 +++++++++++++------ 2 files changed, 38 insertions(+), 24 deletions(-) diff --git a/internal/provider/model_fmc_smart_license.go b/internal/provider/model_fmc_smart_license.go index d723610c..80daeeb4 100644 --- a/internal/provider/model_fmc_smart_license.go +++ b/internal/provider/model_fmc_smart_license.go @@ -48,8 +48,6 @@ func (data SmartLicense) getPath() string { // End of section. //template:end getPath -// Section below is generated&owned by "gen/generator.go". //template:begin toBody - func (data SmartLicense) toBody(ctx context.Context, state SmartLicense) string { body := "" if data.Id.ValueString() != "" { @@ -61,14 +59,9 @@ func (data SmartLicense) toBody(ctx context.Context, state SmartLicense) string if !data.Token.IsNull() { body, _ = sjson.Set(body, "token", data.Token.ValueString()) } - if !data.RegistrationStatus.IsNull() { - body, _ = sjson.Set(body, "regStatus", data.RegistrationStatus.ValueString()) - } return body } -// End of section. //template:end toBody - // Section below is generated&owned by "gen/generator.go". //template:begin fromBody func (data *SmartLicense) fromBody(ctx context.Context, res gjson.Result) { @@ -91,12 +84,13 @@ func (data *SmartLicense) fromBody(ctx context.Context, res gjson.Result) { // 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 *SmartLicense) fromBodyPartial(ctx context.Context, res gjson.Result) { - res = res.Get("items.0") if value := res.Get("registrationType"); value.Exists() && !data.RegistrationType.IsNull() { data.RegistrationType = types.StringValue(value.String()) } else { @@ -107,13 +101,15 @@ func (data *SmartLicense) fromBodyPartial(ctx context.Context, res gjson.Result) } else { data.Token = types.StringNull() } - if value := res.Get("regStatus"); value.Exists() { + if value := res.Get("regStatus"); value.Exists() && !data.RegistrationStatus.IsNull() { data.RegistrationStatus = types.StringValue(value.String()) } else { data.RegistrationStatus = types.StringNull() } } +// 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. diff --git a/internal/provider/resource_fmc_smart_license.go b/internal/provider/resource_fmc_smart_license.go index cfd419aa..26826999 100644 --- a/internal/provider/resource_fmc_smart_license.go +++ b/internal/provider/resource_fmc_smart_license.go @@ -38,8 +38,6 @@ import ( // 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 = &SmartLicenseResource{} @@ -91,7 +89,7 @@ func (r *SmartLicenseResource) Schema(ctx context.Context, req resource.SchemaRe }, "registration_status": schema.StringAttribute{ MarkdownDescription: helpers.NewAttributeDescription("Status of a smart license.").String, - Optional: true, + Computed: true, }, }, } @@ -105,8 +103,6 @@ func (r *SmartLicenseResource) Configure(_ context.Context, req resource.Configu r.client = req.ProviderData.(*FmcProviderData).Client } -// End of section. //template:end model - func (r *SmartLicenseResource) Create(ctx context.Context, req resource.CreateRequest, resp *resource.CreateResponse) { var plan SmartLicense var state SmartLicense @@ -123,27 +119,49 @@ func (r *SmartLicenseResource) Create(ctx context.Context, req resource.CreateRe reqMods = append(reqMods, fmc.DomainName(plan.Domain.ValueString())) } - // Read state + // Read state before create res, err := r.client.Get(state.getPath(), reqMods...) if err != nil { resp.Diagnostics.AddError("Client Error", fmt.Sprintf("Failed to retrieve object (GET), got error: %s, %s", err, res.String())) return } - state.fromBodyPartial(ctx, res) + state.fromBody(ctx, res.Get("items.0")) + + if state.RegistrationStatus.ValueString() == "EVALUATION" && plan.RegistrationType.ValueString() == "EVALUATION" { + plan.RegistrationStatus = state.RegistrationStatus + plan.Id = types.StringValue("") + diags = resp.State.Set(ctx, &plan) + resp.Diagnostics.Append(diags...) + return + } tflog.Debug(ctx, fmt.Sprintf("%s: Beginning Create", plan.Id.ValueString())) + if plan.RegistrationType.ValueString() == "REGISTER" && plan.Token.ValueString() == "" { + resp.Diagnostics.AddError("Provider Error", "Token required for registration") + return + } + // Create object - // body := plan.toBody(ctx, SmartLicense{}) - // res, err = r.client.Post(plan.getPath(), body, reqMods...) - // if err != nil { - // resp.Diagnostics.AddError("Client Error", fmt.Sprintf("Failed to configure object (POST/PUT), got error: %s, %s", err, res.String())) - // return - // } + body := plan.toBody(ctx, SmartLicense{}) + res, err = r.client.Post(plan.getPath(), body, reqMods...) + 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())) + // Read state after create + res, err = r.client.Get(state.getPath(), reqMods...) + if err != nil { + resp.Diagnostics.AddError("Client Error", fmt.Sprintf("Failed to retrieve object (GET), got error: %s, %s", err, res.String())) + return + } + state.fromBody(ctx, res.Get("items.0")) + plan.RegistrationStatus = state.RegistrationStatus + diags = resp.State.Set(ctx, &plan) resp.Diagnostics.Append(diags...) @@ -183,9 +201,9 @@ func (r *SmartLicenseResource) Read(ctx context.Context, req resource.ReadReques // After `terraform import` we switch to a full read. if imp { - state.fromBody(ctx, res) + state.fromBody(ctx, res.Get("items.0")) } else { - state.fromBodyPartial(ctx, res) + state.fromBodyPartial(ctx, res.Get("items.0")) } tflog.Debug(ctx, fmt.Sprintf("%s: Read finished successfully", state.Id.ValueString())) From b3d703a6d9eeb8c67e3f50b6625d5d005e42d591 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20Bernacki?= Date: Thu, 31 Oct 2024 15:50:18 +0100 Subject: [PATCH 05/13] no data_source and no import --- docs/data-sources/smart_license.md | 36 ----- docs/resources/smart_license.md | 8 -- .../fmc_smart_license/data-source.tf | 3 - .../resources/fmc_smart_license/import.sh | 1 - gen/definitions/smart_license.yaml | 2 + .../provider/data_source_fmc_smart_license.go | 128 ------------------ .../data_source_fmc_smart_license_test.go | 66 --------- internal/provider/model_fmc_smart_license.go | 8 -- internal/provider/provider.go | 1 - .../provider/resource_fmc_smart_license.go | 36 ++--- .../resource_fmc_smart_license_test.go | 4 - 11 files changed, 12 insertions(+), 281 deletions(-) delete mode 100644 docs/data-sources/smart_license.md delete mode 100644 examples/data-sources/fmc_smart_license/data-source.tf delete mode 100644 examples/resources/fmc_smart_license/import.sh delete mode 100644 internal/provider/data_source_fmc_smart_license.go delete mode 100644 internal/provider/data_source_fmc_smart_license_test.go diff --git a/docs/data-sources/smart_license.md b/docs/data-sources/smart_license.md deleted file mode 100644 index 62e940fe..00000000 --- a/docs/data-sources/smart_license.md +++ /dev/null @@ -1,36 +0,0 @@ ---- -# generated by https://github.com/hashicorp/terraform-plugin-docs -page_title: "fmc_smart_license Data Source - terraform-provider-fmc" -subcategory: "License" -description: |- - This data source can read the Smart License. ---- - -# fmc_smart_license (Data Source) - -This data source can read the Smart License. - -## Example Usage - -```terraform -data "fmc_smart_license" "example" { - id = "76d24097-41c4-4558-a4d0-a8c07ac08470" -} -``` - - -## Schema - -### Required - -- `id` (String) The id of the object - -### Optional - -- `domain` (String) The name of the FMC domain - -### Read-Only - -- `registration_status` (String) Status of a smart license. -- `registration_type` (String) Action to be executed on the smart license. -- `token` (String) Registration token. diff --git a/docs/resources/smart_license.md b/docs/resources/smart_license.md index 2bb21569..fe7e886d 100644 --- a/docs/resources/smart_license.md +++ b/docs/resources/smart_license.md @@ -36,11 +36,3 @@ resource "fmc_smart_license" "example" { ### Read-Only - `id` (String) The id of the object - -## Import - -Import is supported using the following syntax: - -```shell -terraform import fmc_smart_license.example "" -``` diff --git a/examples/data-sources/fmc_smart_license/data-source.tf b/examples/data-sources/fmc_smart_license/data-source.tf deleted file mode 100644 index d8575b6d..00000000 --- a/examples/data-sources/fmc_smart_license/data-source.tf +++ /dev/null @@ -1,3 +0,0 @@ -data "fmc_smart_license" "example" { - id = "76d24097-41c4-4558-a4d0-a8c07ac08470" -} diff --git a/examples/resources/fmc_smart_license/import.sh b/examples/resources/fmc_smart_license/import.sh deleted file mode 100644 index 5755b391..00000000 --- a/examples/resources/fmc_smart_license/import.sh +++ /dev/null @@ -1 +0,0 @@ -terraform import fmc_smart_license.example "" diff --git a/gen/definitions/smart_license.yaml b/gen/definitions/smart_license.yaml index 3189352c..c3e7e306 100644 --- a/gen/definitions/smart_license.yaml +++ b/gen/definitions/smart_license.yaml @@ -4,6 +4,8 @@ rest_endpoint: /api/fmc_platform/v1/license/smartlicenses doc_category: License no_update: true no_delete: true +no_import: true +no_data_source: true attributes: - model_name: registrationType type: String diff --git a/internal/provider/data_source_fmc_smart_license.go b/internal/provider/data_source_fmc_smart_license.go deleted file mode 100644 index a2668c50..00000000 --- a/internal/provider/data_source_fmc_smart_license.go +++ /dev/null @@ -1,128 +0,0 @@ -// Copyright © 2023 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/datasource" - "github.com/hashicorp/terraform-plugin-framework/datasource/schema" - "github.com/hashicorp/terraform-plugin-log/tflog" - "github.com/netascode/go-fmc" -) - -// 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 = &SmartLicenseDataSource{} - _ datasource.DataSourceWithConfigure = &SmartLicenseDataSource{} -) - -func NewSmartLicenseDataSource() datasource.DataSource { - return &SmartLicenseDataSource{} -} - -type SmartLicenseDataSource struct { - client *fmc.Client -} - -func (d *SmartLicenseDataSource) Metadata(_ context.Context, req datasource.MetadataRequest, resp *datasource.MetadataResponse) { - resp.TypeName = req.ProviderTypeName + "_smart_license" -} - -func (d *SmartLicenseDataSource) 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 Smart License.", - - Attributes: map[string]schema.Attribute{ - "id": schema.StringAttribute{ - MarkdownDescription: "The id of the object", - Required: true, - }, - "domain": schema.StringAttribute{ - MarkdownDescription: "The name of the FMC domain", - Optional: true, - }, - "registration_type": schema.StringAttribute{ - MarkdownDescription: "Action to be executed on the smart license.", - Computed: true, - }, - "token": schema.StringAttribute{ - MarkdownDescription: "Registration token.", - Computed: true, - }, - "registration_status": schema.StringAttribute{ - MarkdownDescription: "Status of a smart license.", - Computed: true, - }, - }, - } -} - -func (d *SmartLicenseDataSource) Configure(_ context.Context, req datasource.ConfigureRequest, _ *datasource.ConfigureResponse) { - if req.ProviderData == nil { - return - } - - d.client = req.ProviderData.(*FmcProviderData).Client -} - -// End of section. //template:end model - -// Section below is generated&owned by "gen/generator.go". //template:begin read - -func (d *SmartLicenseDataSource) Read(ctx context.Context, req datasource.ReadRequest, resp *datasource.ReadResponse) { - var config SmartLicense - - // Read config - diags := req.Config.Get(ctx, &config) - resp.Diagnostics.Append(diags...) - if resp.Diagnostics.HasError() { - return - } - - // Set request domain if provided - reqMods := [](func(*fmc.Req)){} - if !config.Domain.IsNull() && config.Domain.ValueString() != "" { - reqMods = append(reqMods, fmc.DomainName(config.Domain.ValueString())) - } - - tflog.Debug(ctx, fmt.Sprintf("%s: Beginning Read", config.Id.String())) - - res, err := d.client.Get(config.getPath()+"/"+url.QueryEscape(config.Id.ValueString()), reqMods...) - 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.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_fmc_smart_license_test.go b/internal/provider/data_source_fmc_smart_license_test.go deleted file mode 100644 index 510faea5..00000000 --- a/internal/provider/data_source_fmc_smart_license_test.go +++ /dev/null @@ -1,66 +0,0 @@ -// Copyright © 2023 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 TestAccDataSourceFmcSmartLicense(t *testing.T) { - var checks []resource.TestCheckFunc - checks = append(checks, resource.TestCheckResourceAttr("data.fmc_smart_license.test", "registration_type", "REGISTER")) - resource.Test(t, resource.TestCase{ - PreCheck: func() { testAccPreCheck(t) }, - ProtoV6ProviderFactories: testAccProtoV6ProviderFactories, - Steps: []resource.TestStep{ - { - Config: testAccDataSourceFmcSmartLicenseConfig(), - Check: resource.ComposeTestCheckFunc(checks...), - }, - }, - }) -} - -// End of section. //template:end testAccDataSource - -// Section below is generated&owned by "gen/generator.go". //template:begin testPrerequisites -// End of section. //template:end testPrerequisites - -// Section below is generated&owned by "gen/generator.go". //template:begin testAccDataSourceConfig - -func testAccDataSourceFmcSmartLicenseConfig() string { - config := `resource "fmc_smart_license" "test" {` + "\n" - config += ` registration_type = "REGISTER"` + "\n" - config += `}` + "\n" - - config += ` - data "fmc_smart_license" "test" { - id = fmc_smart_license.test.id - } - ` - return config -} - -// End of section. //template:end testAccDataSourceConfig diff --git a/internal/provider/model_fmc_smart_license.go b/internal/provider/model_fmc_smart_license.go index 80daeeb4..28a1fe45 100644 --- a/internal/provider/model_fmc_smart_license.go +++ b/internal/provider/model_fmc_smart_license.go @@ -62,18 +62,12 @@ func (data SmartLicense) toBody(ctx context.Context, state SmartLicense) string return body } -// Section below is generated&owned by "gen/generator.go". //template:begin fromBody - func (data *SmartLicense) fromBody(ctx context.Context, res gjson.Result) { if value := res.Get("registrationType"); value.Exists() { data.RegistrationType = types.StringValue(value.String()) - } else { - data.RegistrationType = types.StringNull() } if value := res.Get("token"); value.Exists() { data.Token = types.StringValue(value.String()) - } else { - data.Token = types.StringNull() } if value := res.Get("regStatus"); value.Exists() { data.RegistrationStatus = types.StringValue(value.String()) @@ -82,8 +76,6 @@ func (data *SmartLicense) fromBody(ctx context.Context, res gjson.Result) { } } -// 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 diff --git a/internal/provider/provider.go b/internal/provider/provider.go index 716d018c..8ff4b6b4 100644 --- a/internal/provider/provider.go +++ b/internal/provider/provider.go @@ -344,7 +344,6 @@ func (p *FmcProvider) DataSources(ctx context.Context) []func() datasource.DataS NewPortGroupDataSource, NewRangeDataSource, NewSecurityZoneDataSource, - NewSmartLicenseDataSource, NewStandardACLDataSource, NewURLDataSource, NewURLGroupDataSource, diff --git a/internal/provider/resource_fmc_smart_license.go b/internal/provider/resource_fmc_smart_license.go index 26826999..e86e46a1 100644 --- a/internal/provider/resource_fmc_smart_license.go +++ b/internal/provider/resource_fmc_smart_license.go @@ -24,7 +24,6 @@ import ( "strings" "github.com/hashicorp/terraform-plugin-framework-validators/stringvalidator" - "github.com/hashicorp/terraform-plugin-framework/path" "github.com/hashicorp/terraform-plugin-framework/resource" "github.com/hashicorp/terraform-plugin-framework/resource/schema" "github.com/hashicorp/terraform-plugin-framework/resource/schema/planmodifier" @@ -40,8 +39,7 @@ import ( // Ensure provider defined types fully satisfy framework interfaces var ( - _ resource.Resource = &SmartLicenseResource{} - _ resource.ResourceWithImportState = &SmartLicenseResource{} + _ resource.Resource = &SmartLicenseResource{} ) func NewSmartLicenseResource() resource.Resource { @@ -193,18 +191,7 @@ func (r *SmartLicenseResource) Read(ctx context.Context, req resource.ReadReques 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.Get("items.0")) - } else { - state.fromBodyPartial(ctx, res.Get("items.0")) - } + state.fromBody(ctx, res.Get("items.0")) tflog.Debug(ctx, fmt.Sprintf("%s: Read finished successfully", state.Id.ValueString())) @@ -214,8 +201,6 @@ func (r *SmartLicenseResource) Read(ctx context.Context, req resource.ReadReques helpers.SetFlagImporting(ctx, false, resp.Private, &resp.Diagnostics) } -// Section below is generated&owned by "gen/generator.go". //template:begin update - func (r *SmartLicenseResource) Update(ctx context.Context, req resource.UpdateRequest, resp *resource.UpdateResponse) { var plan, state SmartLicense @@ -239,14 +224,20 @@ func (r *SmartLicenseResource) Update(ctx context.Context, req resource.UpdateRe tflog.Debug(ctx, fmt.Sprintf("%s: Beginning Update", plan.Id.ValueString())) + body := plan.toBody(ctx, SmartLicense{}) + res, err := r.client.Post(plan.getPath(), body, reqMods...) + 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: 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 *SmartLicenseResource) Delete(ctx context.Context, req resource.DeleteRequest, resp *resource.DeleteResponse) { @@ -274,11 +265,4 @@ func (r *SmartLicenseResource) Delete(ctx context.Context, req resource.DeleteRe // End of section. //template:end delete // Section below is generated&owned by "gen/generator.go". //template:begin import - -func (r *SmartLicenseResource) ImportState(ctx context.Context, req resource.ImportStateRequest, resp *resource.ImportStateResponse) { - resource.ImportStatePassthroughID(ctx, path.Root("id"), req, resp) - - helpers.SetFlagImporting(ctx, true, resp.Private, &resp.Diagnostics) -} - // End of section. //template:end import diff --git a/internal/provider/resource_fmc_smart_license_test.go b/internal/provider/resource_fmc_smart_license_test.go index 5982d6e0..5d405164 100644 --- a/internal/provider/resource_fmc_smart_license_test.go +++ b/internal/provider/resource_fmc_smart_license_test.go @@ -43,10 +43,6 @@ func TestAccFmcSmartLicense(t *testing.T) { Config: testAccFmcSmartLicenseConfig_all(), Check: resource.ComposeTestCheckFunc(checks...), }) - steps = append(steps, resource.TestStep{ - ResourceName: "fmc_smart_license.test", - ImportState: true, - }) resource.Test(t, resource.TestCase{ PreCheck: func() { testAccPreCheck(t) }, From 1704f425a0efdf9b775b8334a65ec41ec65612fa Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20Bernacki?= Date: Tue, 5 Nov 2024 11:06:08 +0100 Subject: [PATCH 06/13] logic for upadate and delete --- docs/resources/smart_license.md | 4 +- gen/definitions/smart_license.yaml | 8 +- internal/provider/model_fmc_smart_license.go | 12 +-- .../provider/resource_fmc_smart_license.go | 74 +++++++++++++++---- 4 files changed, 76 insertions(+), 22 deletions(-) diff --git a/docs/resources/smart_license.md b/docs/resources/smart_license.md index fe7e886d..21c02a3c 100644 --- a/docs/resources/smart_license.md +++ b/docs/resources/smart_license.md @@ -25,14 +25,14 @@ resource "fmc_smart_license" "example" { ### Required - `registration_type` (String) Action to be executed on the smart license. - - Choices: `REGISTER`, `DEREGISTER`, `EVALUATION` + - Choices: `REGISTER`, `EVALUATION` ### Optional - `domain` (String) The name of the FMC domain -- `registration_status` (String) Status of a smart license. - `token` (String) Registration token. ### Read-Only - `id` (String) The id of the object +- `registration_status` (String) Status of a smart license. diff --git a/gen/definitions/smart_license.yaml b/gen/definitions/smart_license.yaml index c3e7e306..e4a4852e 100644 --- a/gen/definitions/smart_license.yaml +++ b/gen/definitions/smart_license.yaml @@ -10,7 +10,7 @@ attributes: - model_name: registrationType type: String description: Action to be executed on the smart license. - enum_values: [REGISTER, DEREGISTER, EVALUATION] + enum_values: [REGISTER, EVALUATION] mandatory: true example: REGISTER - model_name: token @@ -25,3 +25,9 @@ attributes: example: EVALUATION exclude_test: true exclude_example: true + - model_name: force + type: Bool + description: Set to true to re-register smart license. + example: false + exclude_test: true + exclude_example: true diff --git a/internal/provider/model_fmc_smart_license.go b/internal/provider/model_fmc_smart_license.go index 28a1fe45..c75d6f3b 100644 --- a/internal/provider/model_fmc_smart_license.go +++ b/internal/provider/model_fmc_smart_license.go @@ -36,6 +36,7 @@ type SmartLicense struct { RegistrationType types.String `tfsdk:"registration_type"` Token types.String `tfsdk:"token"` RegistrationStatus types.String `tfsdk:"registration_status"` + Force types.Bool `tfsdk:"force"` } // End of section. //template:end types @@ -63,12 +64,6 @@ func (data SmartLicense) toBody(ctx context.Context, state SmartLicense) string } func (data *SmartLicense) fromBody(ctx context.Context, res gjson.Result) { - if value := res.Get("registrationType"); value.Exists() { - data.RegistrationType = types.StringValue(value.String()) - } - if value := res.Get("token"); value.Exists() { - data.Token = types.StringValue(value.String()) - } if value := res.Get("regStatus"); value.Exists() { data.RegistrationStatus = types.StringValue(value.String()) } else { @@ -98,6 +93,11 @@ func (data *SmartLicense) fromBodyPartial(ctx context.Context, res gjson.Result) } else { data.RegistrationStatus = types.StringNull() } + if value := res.Get("force"); value.Exists() && !data.Force.IsNull() { + data.Force = types.BoolValue(value.Bool()) + } else { + data.Force = types.BoolNull() + } } // End of section. //template:end fromBodyPartial diff --git a/internal/provider/resource_fmc_smart_license.go b/internal/provider/resource_fmc_smart_license.go index e86e46a1..cc71d7bf 100644 --- a/internal/provider/resource_fmc_smart_license.go +++ b/internal/provider/resource_fmc_smart_license.go @@ -33,6 +33,7 @@ import ( "github.com/hashicorp/terraform-plugin-log/tflog" "github.com/netascode/go-fmc" "github.com/netascode/terraform-provider-fmc/internal/provider/helpers" + "github.com/tidwall/gjson" ) // End of section. //template:end imports @@ -75,10 +76,10 @@ func (r *SmartLicenseResource) Schema(ctx context.Context, req resource.SchemaRe }, }, "registration_type": schema.StringAttribute{ - MarkdownDescription: helpers.NewAttributeDescription("Action to be executed on the smart license.").AddStringEnumDescription("REGISTER", "DEREGISTER", "EVALUATION").String, + MarkdownDescription: helpers.NewAttributeDescription("Action to be executed on the smart license.").AddStringEnumDescription("REGISTER", "EVALUATION").String, Required: true, Validators: []validator.String{ - stringvalidator.OneOf("REGISTER", "DEREGISTER", "EVALUATION"), + stringvalidator.OneOf("REGISTER", "EVALUATION"), }, }, "token": schema.StringAttribute{ @@ -222,24 +223,60 @@ func (r *SmartLicenseResource) Update(ctx context.Context, req resource.UpdateRe reqMods = append(reqMods, fmc.DomainName(plan.Domain.ValueString())) } - tflog.Debug(ctx, fmt.Sprintf("%s: Beginning Update", plan.Id.ValueString())) + if state.RegistrationStatus.ValueString() == "EVALUATION" && plan.RegistrationType.ValueString() == "EVALUATION" { + plan.RegistrationStatus = state.RegistrationStatus + plan.Id = types.StringValue("") + diags = resp.State.Set(ctx, &plan) + resp.Diagnostics.Append(diags...) + return + } - body := plan.toBody(ctx, SmartLicense{}) - res, err := r.client.Post(plan.getPath(), body, reqMods...) - if err != nil { - resp.Diagnostics.AddError("Client Error", fmt.Sprintf("Failed to configure object (POST/PUT), got error: %s, %s", err, res.String())) + if plan.RegistrationType.ValueString() == "REGISTER" && plan.Token.ValueString() == "" { + resp.Diagnostics.AddError("Provider Error", "Token required for registration") return } - plan.Id = types.StringValue(res.Get("id").String()) - tflog.Debug(ctx, fmt.Sprintf("%s: Update finished successfully", plan.Id.ValueString())) + if plan.Force.ValueBool() && state.RegistrationStatus.ValueString() != "UNREGISTERED" { + res, err := r.deregisterSmartLicense(ctx, reqMods...) + if err != nil { + resp.Diagnostics.AddError("Client Error", fmt.Sprintf("Failed to configure object (POST/PUT), got error: %s, %s", err, res.String())) + return + } + res, err = r.client.Get(state.getPath(), reqMods...) + if err != nil { + resp.Diagnostics.AddError("Client Error", fmt.Sprintf("Failed to retrieve object (GET), got error: %s, %s", err, res.String())) + return + } + state.fromBody(ctx, res.Get("items.0")) + } + + if state.RegistrationStatus.ValueString() != "REGISTERED" { + tflog.Debug(ctx, fmt.Sprintf("%s: Beginning Update", plan.Id.ValueString())) + + body := plan.toBody(ctx, SmartLicense{}) + res, err := r.client.Post(plan.getPath(), body, reqMods...) + 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: Update finished successfully", plan.Id.ValueString())) + + // Read state after update + res, err = r.client.Get(state.getPath(), reqMods...) + if err != nil { + resp.Diagnostics.AddError("Client Error", fmt.Sprintf("Failed to retrieve object (GET), got error: %s, %s", err, res.String())) + return + } + state.fromBody(ctx, res.Get("items.0")) + } + plan.RegistrationStatus = state.RegistrationStatus diags = resp.State.Set(ctx, &plan) resp.Diagnostics.Append(diags...) } -// Section below is generated&owned by "gen/generator.go". //template:begin delete - func (r *SmartLicenseResource) Delete(ctx context.Context, req resource.DeleteRequest, resp *resource.DeleteResponse) { var state SmartLicense @@ -256,13 +293,24 @@ func (r *SmartLicenseResource) Delete(ctx context.Context, req resource.DeleteRe } tflog.Debug(ctx, fmt.Sprintf("%s: Beginning Delete", state.Id.ValueString())) + res, err := r.deregisterSmartLicense(ctx, reqMods...) + 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.Id.ValueString())) resp.State.RemoveResource(ctx) } -// End of section. //template:end delete - // Section below is generated&owned by "gen/generator.go". //template:begin import // End of section. //template:end import + +func (r *SmartLicenseResource) deregisterSmartLicense(ctx context.Context, reqMods ...func(*fmc.Req)) (gjson.Result, error) { + plan := SmartLicense{ + RegistrationType: types.StringValue("DEREGISTER"), + } + body := plan.toBody(ctx, SmartLicense{}) + return r.client.Post(plan.getPath(), body, reqMods...) +} From 3b882f017ab95a75ab65aaef76a67ee6313a2b45 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20Bernacki?= Date: Wed, 6 Nov 2024 14:19:13 +0100 Subject: [PATCH 07/13] add force flag to schema --- docs/resources/smart_license.md | 1 + internal/provider/resource_fmc_smart_license.go | 4 ++++ 2 files changed, 5 insertions(+) diff --git a/docs/resources/smart_license.md b/docs/resources/smart_license.md index 21c02a3c..54e60a12 100644 --- a/docs/resources/smart_license.md +++ b/docs/resources/smart_license.md @@ -30,6 +30,7 @@ resource "fmc_smart_license" "example" { ### Optional - `domain` (String) The name of the FMC domain +- `force` (Boolean) Set to true to re-register smart license. - `token` (String) Registration token. ### Read-Only diff --git a/internal/provider/resource_fmc_smart_license.go b/internal/provider/resource_fmc_smart_license.go index cc71d7bf..3a4c89b9 100644 --- a/internal/provider/resource_fmc_smart_license.go +++ b/internal/provider/resource_fmc_smart_license.go @@ -90,6 +90,10 @@ func (r *SmartLicenseResource) Schema(ctx context.Context, req resource.SchemaRe MarkdownDescription: helpers.NewAttributeDescription("Status of a smart license.").String, Computed: true, }, + "force": schema.BoolAttribute{ + MarkdownDescription: helpers.NewAttributeDescription("Set to true to re-register smart license.").String, + Optional: true, + }, }, } } From b69de79359b60ee27d1b9dcc468f3c969bae33f4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20Bernacki?= Date: Tue, 12 Nov 2024 14:22:57 +0100 Subject: [PATCH 08/13] remove debug config --- .gitignore | 1 - .vscode/launch.json | 13 ------------- main.go | 7 ------- 3 files changed, 21 deletions(-) diff --git a/.gitignore b/.gitignore index fad74c21..cd61dcb8 100644 --- a/.gitignore +++ b/.gitignore @@ -17,7 +17,6 @@ website/node_modules ./*.tfstate .terraform/ .vscode/settings.json -.vscode/private.env *.log *.bak *~ diff --git a/.vscode/launch.json b/.vscode/launch.json index 79760c21..d2e326d8 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -11,19 +11,6 @@ "mode": "auto", "program": "gen/generator.go", "cwd": "./" - }, - { - "name": "Debug Terraform Provider", - "type": "go", - "request": "launch", - "mode": "debug", - // this assumes your workspace is the root of the repo - "program": "${workspaceFolder}", - "env": {}, - "args": [ - "-debug", - ], - "envFile": "${workspaceFolder}/.vscode/private.env" } ] } \ No newline at end of file diff --git a/main.go b/main.go index 7daef432..91a53da9 100644 --- a/main.go +++ b/main.go @@ -19,7 +19,6 @@ package main import ( "context" - "flag" "log" "github.com/hashicorp/terraform-plugin-framework/providerserver" @@ -55,14 +54,8 @@ var ( ) func main() { - var debug bool - - flag.BoolVar(&debug, "debug", false, "set to true to run the provider with support for debuggers like delve") - flag.Parse() - opts := providerserver.ServeOpts{ Address: "registry.terraform.io/netascode/fmc", - Debug: debug, } err := providerserver.Serve(context.Background(), provider.New(version), opts) From f6222bf77bdb7d83167ad34e3c5000ad3061f2b7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20Bernacki?= Date: Mon, 18 Nov 2024 11:12:47 +0100 Subject: [PATCH 09/13] small fix in provider --- internal/provider/provider.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/internal/provider/provider.go b/internal/provider/provider.go index ce126081..205dde07 100644 --- a/internal/provider/provider.go +++ b/internal/provider/provider.go @@ -318,8 +318,8 @@ func (p *FmcProvider) Resources(ctx context.Context) []func() resource.Resource NewRangeResource, NewRangesResource, NewSecurityZoneResource, - NewSmartLicenseResource, NewSecurityZonesResource, + NewSmartLicenseResource, NewStandardACLResource, NewURLResource, NewURLGroupResource, From d3303dac233566deffeba7487c378b7244a824c2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20Bernacki?= Date: Mon, 18 Nov 2024 11:25:30 +0100 Subject: [PATCH 10/13] comments for create and update logic --- docs/resources/smart_license.md | 4 ++-- gen/definitions/smart_license.yaml | 2 +- internal/provider/resource_fmc_smart_license.go | 9 ++++++++- 3 files changed, 11 insertions(+), 4 deletions(-) diff --git a/docs/resources/smart_license.md b/docs/resources/smart_license.md index 54e60a12..314fe3b7 100644 --- a/docs/resources/smart_license.md +++ b/docs/resources/smart_license.md @@ -31,9 +31,9 @@ resource "fmc_smart_license" "example" { - `domain` (String) The name of the FMC domain - `force` (Boolean) Set to true to re-register smart license. -- `token` (String) Registration token. +- `registration_status` (String) Status of a smart license. +- `token` (String) Registration token. Mandatory when registrationType set to REGISTER. ### Read-Only - `id` (String) The id of the object -- `registration_status` (String) Status of a smart license. diff --git a/gen/definitions/smart_license.yaml b/gen/definitions/smart_license.yaml index e4a4852e..8a7e7636 100644 --- a/gen/definitions/smart_license.yaml +++ b/gen/definitions/smart_license.yaml @@ -15,7 +15,7 @@ attributes: example: REGISTER - model_name: token type: String - description: Registration token. + description: Registration token. Mandatory when registrationType set to REGISTER. example: "X2M3YmJlY..." exclude_test: true - model_name: regStatus diff --git a/internal/provider/resource_fmc_smart_license.go b/internal/provider/resource_fmc_smart_license.go index 3a4c89b9..13d832ed 100644 --- a/internal/provider/resource_fmc_smart_license.go +++ b/internal/provider/resource_fmc_smart_license.go @@ -83,7 +83,7 @@ func (r *SmartLicenseResource) Schema(ctx context.Context, req resource.SchemaRe }, }, "token": schema.StringAttribute{ - MarkdownDescription: helpers.NewAttributeDescription("Registration token.").String, + MarkdownDescription: helpers.NewAttributeDescription("Registration token. Mandatory when registrationType set to REGISTER.").String, Optional: true, }, "registration_status": schema.StringAttribute{ @@ -130,6 +130,8 @@ func (r *SmartLicenseResource) Create(ctx context.Context, req resource.CreateRe } state.fromBody(ctx, res.Get("items.0")) + // When smart license is already in evaluation mode and user requests evaluation mode - do nothing + // It's not automatically detected by terraform, because two different fields keep status (RegistrationStatus) and requested status (RegistrationType) if state.RegistrationStatus.ValueString() == "EVALUATION" && plan.RegistrationType.ValueString() == "EVALUATION" { plan.RegistrationStatus = state.RegistrationStatus plan.Id = types.StringValue("") @@ -140,6 +142,7 @@ func (r *SmartLicenseResource) Create(ctx context.Context, req resource.CreateRe tflog.Debug(ctx, fmt.Sprintf("%s: Beginning Create", plan.Id.ValueString())) + // Check if token provided when registration is requested if plan.RegistrationType.ValueString() == "REGISTER" && plan.Token.ValueString() == "" { resp.Diagnostics.AddError("Provider Error", "Token required for registration") return @@ -227,6 +230,7 @@ func (r *SmartLicenseResource) Update(ctx context.Context, req resource.UpdateRe reqMods = append(reqMods, fmc.DomainName(plan.Domain.ValueString())) } + // Same logic as in create if state.RegistrationStatus.ValueString() == "EVALUATION" && plan.RegistrationType.ValueString() == "EVALUATION" { plan.RegistrationStatus = state.RegistrationStatus plan.Id = types.StringValue("") @@ -235,11 +239,13 @@ func (r *SmartLicenseResource) Update(ctx context.Context, req resource.UpdateRe return } + // Check if token provided when registration is requested if plan.RegistrationType.ValueString() == "REGISTER" && plan.Token.ValueString() == "" { resp.Diagnostics.AddError("Provider Error", "Token required for registration") return } + // When re-registration is forced, only deregister license if it is not already unregistered if plan.Force.ValueBool() && state.RegistrationStatus.ValueString() != "UNREGISTERED" { res, err := r.deregisterSmartLicense(ctx, reqMods...) if err != nil { @@ -254,6 +260,7 @@ func (r *SmartLicenseResource) Update(ctx context.Context, req resource.UpdateRe state.fromBody(ctx, res.Get("items.0")) } + // When force flag is not set to true, only register license if it is not already registered if state.RegistrationStatus.ValueString() != "REGISTERED" { tflog.Debug(ctx, fmt.Sprintf("%s: Beginning Update", plan.Id.ValueString())) From dc720391b2189c40eb39638465e0f99933ec9184 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20Bernacki?= Date: Mon, 18 Nov 2024 14:47:22 +0100 Subject: [PATCH 11/13] custom example and doc update --- docs/resources/smart_license.md | 12 ++- .../resources/fmc_smart_license/resource.tf | 9 +- gen/definitions/smart_license.yaml | 13 +++ gen/generator.go | 2 + gen/schema/schema.yaml | 3 +- gen/templates/model.go | 2 + gen/templates/resource.go | 2 + gen/templates/resource.tf | 98 ++++++++++--------- internal/provider/model_fmc_smart_license.go | 1 - .../provider/resource_fmc_smart_license.go | 24 ----- 10 files changed, 88 insertions(+), 78 deletions(-) diff --git a/docs/resources/smart_license.md b/docs/resources/smart_license.md index 314fe3b7..a15b9fdd 100644 --- a/docs/resources/smart_license.md +++ b/docs/resources/smart_license.md @@ -13,9 +13,16 @@ This resource can manage a Smart License. ## Example Usage ```terraform -resource "fmc_smart_license" "example" { +// Enable Evaluation Mode +resource "fmc_smart_license" "license" { + registration_type = "EVALUATION" +} + +// Force to re-register with the provided token +resource "fmc_smart_license" "license" { registration_type = "REGISTER" token = "X2M3YmJlY..." + force = true } ``` @@ -29,11 +36,10 @@ resource "fmc_smart_license" "example" { ### Optional -- `domain` (String) The name of the FMC domain - `force` (Boolean) Set to true to re-register smart license. -- `registration_status` (String) Status of a smart license. - `token` (String) Registration token. Mandatory when registrationType set to REGISTER. ### Read-Only - `id` (String) The id of the object +- `registration_status` (String) Status of a smart license. diff --git a/examples/resources/fmc_smart_license/resource.tf b/examples/resources/fmc_smart_license/resource.tf index c0e65436..71d3a760 100644 --- a/examples/resources/fmc_smart_license/resource.tf +++ b/examples/resources/fmc_smart_license/resource.tf @@ -1,4 +1,11 @@ -resource "fmc_smart_license" "example" { +// Enable Evaluation Mode +resource "fmc_smart_license" "license" { + registration_type = "EVALUATION" +} + +// Force to re-register with the provided token +resource "fmc_smart_license" "license" { registration_type = "REGISTER" token = "X2M3YmJlY..." + force = true } diff --git a/gen/definitions/smart_license.yaml b/gen/definitions/smart_license.yaml index 8a7e7636..7e858cce 100644 --- a/gen/definitions/smart_license.yaml +++ b/gen/definitions/smart_license.yaml @@ -6,6 +6,19 @@ no_update: true no_delete: true no_import: true no_data_source: true +domain_independent: true +resource_custom_example: |- + // Enable Evaluation Mode + resource "fmc_smart_license" "license" { + registration_type = "EVALUATION" + } + + // Force to re-register with the provided token + resource "fmc_smart_license" "license" { + registration_type = "REGISTER" + token = "X2M3YmJlY..." + force = true + } attributes: - model_name: registrationType type: String diff --git a/gen/generator.go b/gen/generator.go index de292a52..704a2a53 100644 --- a/gen/generator.go +++ b/gen/generator.go @@ -115,6 +115,8 @@ type YamlConfig struct { TestPrerequisites string `yaml:"test_prerequisites"` IsBulk bool `yaml:"is_bulk"` ImportNameQuery bool `yaml:"import_name_query"` + ResourceCustomExample string `yaml:"resource_custom_example"` + DomainIndependent bool `yaml:"domain_independent"` } type YamlConfigAttribute struct { diff --git a/gen/schema/schema.yaml b/gen/schema/schema.yaml index 44484f77..ad6fd59c 100644 --- a/gen/schema/schema.yaml +++ b/gen/schema/schema.yaml @@ -18,6 +18,8 @@ test_tags: list(str(), required=False) # List of test tags, tests are only execu test_prerequisites: str(required=False) # HCL code that is included in the acceptance tests to define prerequisites is_bulk: bool(required=False) # Treat this as a bulk resource import_name_query: bool(required=False) # Set to true if import should be done using object name +resource_custom_example: str(required=False) # If provided, example for resource will not be generated. This custom example will be used instead for documentation +domain_independent: bool(required=False) # If set to true if the resource/data source is not domain dependent --- attribute: model_name: str(required=False) # Name of the attribute in the model (payload) @@ -54,4 +56,3 @@ attribute: minimum_test_value: str(required=False) # Value used for "minimum" resource acceptance test test_tags: list(str(), required=False) # List of test tags, attribute is only included in acceptance tests if an environment variable with one of these tags is configured attributes: list(include('attribute'), required=False) # List of attributes, only relevant if type is "List" or "Set" - \ No newline at end of file diff --git a/gen/templates/model.go b/gen/templates/model.go index 5c80c2ec..cf788d28 100644 --- a/gen/templates/model.go +++ b/gen/templates/model.go @@ -38,7 +38,9 @@ import ( type {{camelCase .Name}} struct { Id types.String `tfsdk:"id"` + {{- if not .DomainIndependent}} Domain types.String `tfsdk:"domain"` + {{- end}} {{- range .Attributes}} {{- if not .Value}} {{- if isNestedListSet .}} diff --git a/gen/templates/resource.go b/gen/templates/resource.go index 152af131..cee62238 100644 --- a/gen/templates/resource.go +++ b/gen/templates/resource.go @@ -88,6 +88,7 @@ func (r *{{camelCase .Name}}Resource) Schema(ctx context.Context, req resource.S stringplanmodifier.UseStateForUnknown(), }, }, + {{- if not .DomainIndependent}} "domain": schema.StringAttribute{ MarkdownDescription: "The name of the FMC domain", Optional: true, @@ -95,6 +96,7 @@ func (r *{{camelCase .Name}}Resource) Schema(ctx context.Context, req resource.S stringplanmodifier.RequiresReplace(), }, }, + {{- end}} {{- range .Attributes}} {{- if not .Value}} "{{.TfName}}": schema.{{if isNestedListMapSet .}}{{.Type}}Nested{{else if isList .}}List{{else if isSet .}}Set{{else if eq .Type "Versions"}}List{{else if eq .Type "Version"}}Int64{{else}}{{.Type}}{{end}}Attribute{ diff --git a/gen/templates/resource.tf b/gen/templates/resource.tf index bb2ccf66..84245757 100644 --- a/gen/templates/resource.tf +++ b/gen/templates/resource.tf @@ -1,53 +1,55 @@ -resource "fmc_{{snakeCase .Name}}" "example" { -{{- range .Attributes}} -{{- if and (not .ExcludeExample) (not .Value) (not .ResourceId)}} -{{- if isNestedListMapSet .}} - {{.TfName}} = - {{- if isNestedListSet . -}} - [ +{{- if not .ResourceCustomExample}} resource "fmc_{{snakeCase .Name}}" "example" { + {{- range .Attributes}} + {{- if and (not .ExcludeExample) (not .Value) (not .ResourceId)}} + {{- if isNestedListMapSet .}} + {{.TfName}} = + {{- if isNestedListSet . -}} + [ + { + {{- else -}} { - {{- else -}} - { - {{.MapKeyExample}} = { - {{- end}} - {{- range .Attributes}} - {{- if and (not .ExcludeExample) (not .Value)}} - {{- if isNestedListSet .}} - {{.TfName}} = [ - { - {{- range .Attributes}} - {{- if and (not .ExcludeExample) (not .Value)}} - {{- if isNestedListSet .}} - {{.TfName}} = [ - { - {{- range .Attributes}} - {{- if and (not .ExcludeExample) (not .Value)}} - {{.TfName}} = {{if eq .Type "String"}}"{{.Example}}"{{else if isStringListSet .}}["{{.Example}}"]{{else if isInt64ListSet .}}[{{.Example}}]{{else}}{{.Example}}{{end}} - {{- end}} - {{- end}} - } - ] - {{- else}} - {{.TfName}} = {{if eq .Type "String"}}"{{.Example}}"{{else if isStringListSet .}}["{{.Example}}"]{{else if isInt64ListSet .}}[{{.Example}}]{{else}}{{.Example}}{{end}} - {{- end}} - {{- end}} - {{- end}} - } - ] - {{- else}} - {{.TfName}} = {{if eq .Type "String"}}"{{.Example}}"{{else if isStringListSet .}}["{{.Example}}"]{{else if isInt64ListSet .}}[{{.Example}}]{{else}}{{.Example}}{{end}} - {{- end}} - {{- end}} - {{- end}} + {{.MapKeyExample}} = { + {{- end}} + {{- range .Attributes}} + {{- if and (not .ExcludeExample) (not .Value)}} + {{- if isNestedListSet .}} + {{.TfName}} = [ + { + {{- range .Attributes}} + {{- if and (not .ExcludeExample) (not .Value)}} + {{- if isNestedListSet .}} + {{.TfName}} = [ + { + {{- range .Attributes}} + {{- if and (not .ExcludeExample) (not .Value)}} + {{.TfName}} = {{if eq .Type "String"}}"{{.Example}}"{{else if isStringListSet .}}["{{.Example}}"]{{else if isInt64ListSet .}}[{{.Example}}]{{else}}{{.Example}}{{end}} + {{- end}} + {{- end}} + } + ] + {{- else}} + {{.TfName}} = {{if eq .Type "String"}}"{{.Example}}"{{else if isStringListSet .}}["{{.Example}}"]{{else if isInt64ListSet .}}[{{.Example}}]{{else}}{{.Example}}{{end}} + {{- end}} + {{- end}} + {{- end}} + } + ] + {{- else}} + {{.TfName}} = {{if eq .Type "String"}}"{{.Example}}"{{else if isStringListSet .}}["{{.Example}}"]{{else if isInt64ListSet .}}[{{.Example}}]{{else}}{{.Example}}{{end}} + {{- end}} + {{- end}} + {{- end}} + } + {{- if isNestedListSet .}} + ] + {{- else}} } - {{- if isNestedListSet .}} - ] + {{- end}} {{- else}} - } + {{.TfName}} = {{if eq .Type "String"}}"{{.Example}}"{{else if isStringListSet .}}["{{.Example}}"]{{else if isInt64ListSet .}}[{{.Example}}]{{else}}{{.Example}}{{end}} {{- end}} -{{- else}} - {{.TfName}} = {{if eq .Type "String"}}"{{.Example}}"{{else if isStringListSet .}}["{{.Example}}"]{{else if isInt64ListSet .}}[{{.Example}}]{{else}}{{.Example}}{{end}} -{{- end}} -{{- end}} + {{- end}} + {{- end}} + } +{{- else}} {{.ResourceCustomExample}} {{- end}} -} diff --git a/internal/provider/model_fmc_smart_license.go b/internal/provider/model_fmc_smart_license.go index c75d6f3b..206f6e29 100644 --- a/internal/provider/model_fmc_smart_license.go +++ b/internal/provider/model_fmc_smart_license.go @@ -32,7 +32,6 @@ import ( type SmartLicense struct { Id types.String `tfsdk:"id"` - Domain types.String `tfsdk:"domain"` RegistrationType types.String `tfsdk:"registration_type"` Token types.String `tfsdk:"token"` RegistrationStatus types.String `tfsdk:"registration_status"` diff --git a/internal/provider/resource_fmc_smart_license.go b/internal/provider/resource_fmc_smart_license.go index 13d832ed..4d3cf50f 100644 --- a/internal/provider/resource_fmc_smart_license.go +++ b/internal/provider/resource_fmc_smart_license.go @@ -68,13 +68,6 @@ func (r *SmartLicenseResource) Schema(ctx context.Context, req resource.SchemaRe stringplanmodifier.UseStateForUnknown(), }, }, - "domain": schema.StringAttribute{ - MarkdownDescription: "The name of the FMC domain", - Optional: true, - PlanModifiers: []planmodifier.String{ - stringplanmodifier.RequiresReplace(), - }, - }, "registration_type": schema.StringAttribute{ MarkdownDescription: helpers.NewAttributeDescription("Action to be executed on the smart license.").AddStringEnumDescription("REGISTER", "EVALUATION").String, Required: true, @@ -116,11 +109,7 @@ func (r *SmartLicenseResource) Create(ctx context.Context, req resource.CreateRe return } - // Set request domain if provided reqMods := [](func(*fmc.Req)){} - if !plan.Domain.IsNull() && plan.Domain.ValueString() != "" { - reqMods = append(reqMods, fmc.DomainName(plan.Domain.ValueString())) - } // Read state before create res, err := r.client.Get(state.getPath(), reqMods...) @@ -183,12 +172,7 @@ func (r *SmartLicenseResource) Read(ctx context.Context, req resource.ReadReques return } - // Set request domain if provided reqMods := [](func(*fmc.Req)){} - if !state.Domain.IsNull() && state.Domain.ValueString() != "" { - reqMods = append(reqMods, fmc.DomainName(state.Domain.ValueString())) - } - tflog.Debug(ctx, fmt.Sprintf("%s: Beginning Read", state.Id.String())) res, err := r.client.Get(state.getPath(), reqMods...) @@ -224,11 +208,7 @@ func (r *SmartLicenseResource) Update(ctx context.Context, req resource.UpdateRe return } - // Set request domain if provided reqMods := [](func(*fmc.Req)){} - if !plan.Domain.IsNull() && plan.Domain.ValueString() != "" { - reqMods = append(reqMods, fmc.DomainName(plan.Domain.ValueString())) - } // Same logic as in create if state.RegistrationStatus.ValueString() == "EVALUATION" && plan.RegistrationType.ValueString() == "EVALUATION" { @@ -297,11 +277,7 @@ func (r *SmartLicenseResource) Delete(ctx context.Context, req resource.DeleteRe return } - // Set request domain if provided reqMods := [](func(*fmc.Req)){} - if !state.Domain.IsNull() && state.Domain.ValueString() != "" { - reqMods = append(reqMods, fmc.DomainName(state.Domain.ValueString())) - } tflog.Debug(ctx, fmt.Sprintf("%s: Beginning Delete", state.Id.ValueString())) res, err := r.deregisterSmartLicense(ctx, reqMods...) From c4d5cee00a4cdab7b92d884aab14774a00970a37 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20Bernacki?= Date: Mon, 18 Nov 2024 15:45:55 +0100 Subject: [PATCH 12/13] use evaluation mode for tests --- gen/definitions/smart_license.yaml | 2 +- internal/provider/resource_fmc_smart_license.go | 7 +++++-- internal/provider/resource_fmc_smart_license_test.go | 11 ++++------- 3 files changed, 10 insertions(+), 10 deletions(-) diff --git a/gen/definitions/smart_license.yaml b/gen/definitions/smart_license.yaml index 7e858cce..91bce750 100644 --- a/gen/definitions/smart_license.yaml +++ b/gen/definitions/smart_license.yaml @@ -25,7 +25,7 @@ attributes: description: Action to be executed on the smart license. enum_values: [REGISTER, EVALUATION] mandatory: true - example: REGISTER + example: EVALUATION - model_name: token type: String description: Registration token. Mandatory when registrationType set to REGISTER. diff --git a/internal/provider/resource_fmc_smart_license.go b/internal/provider/resource_fmc_smart_license.go index 4d3cf50f..f9037fc2 100644 --- a/internal/provider/resource_fmc_smart_license.go +++ b/internal/provider/resource_fmc_smart_license.go @@ -277,6 +277,11 @@ func (r *SmartLicenseResource) Delete(ctx context.Context, req resource.DeleteRe return } + // Smart license cannot be deregistered if in evaluation mode + if state.RegistrationStatus.ValueString() == "EVALUATION" { + return + } + reqMods := [](func(*fmc.Req)){} tflog.Debug(ctx, fmt.Sprintf("%s: Beginning Delete", state.Id.ValueString())) @@ -287,8 +292,6 @@ func (r *SmartLicenseResource) Delete(ctx context.Context, req resource.DeleteRe } tflog.Debug(ctx, fmt.Sprintf("%s: Delete finished successfully", state.Id.ValueString())) - - resp.State.RemoveResource(ctx) } // Section below is generated&owned by "gen/generator.go". //template:begin import diff --git a/internal/provider/resource_fmc_smart_license_test.go b/internal/provider/resource_fmc_smart_license_test.go index 5d405164..38e88f97 100644 --- a/internal/provider/resource_fmc_smart_license_test.go +++ b/internal/provider/resource_fmc_smart_license_test.go @@ -27,11 +27,10 @@ import ( // End of section. //template:end imports -// Section below is generated&owned by "gen/generator.go". //template:begin testAcc - func TestAccFmcSmartLicense(t *testing.T) { var checks []resource.TestCheckFunc - checks = append(checks, resource.TestCheckResourceAttr("fmc_smart_license.test", "registration_type", "REGISTER")) + checks = append(checks, resource.TestCheckResourceAttr("fmc_smart_license.test", "registration_type", "EVALUATION")) + checks = append(checks, resource.TestCheckResourceAttr("fmc_smart_license.test", "registration_status", "EVALUATION")) var steps []resource.TestStep if os.Getenv("SKIP_MINIMUM_TEST") == "" { @@ -51,8 +50,6 @@ func TestAccFmcSmartLicense(t *testing.T) { }) } -// End of section. //template:end testAcc - // Section below is generated&owned by "gen/generator.go". //template:begin testPrerequisites // End of section. //template:end testPrerequisites @@ -60,7 +57,7 @@ func TestAccFmcSmartLicense(t *testing.T) { func testAccFmcSmartLicenseConfig_minimum() string { config := `resource "fmc_smart_license" "test" {` + "\n" - config += ` registration_type = "REGISTER"` + "\n" + config += ` registration_type = "EVALUATION"` + "\n" config += `}` + "\n" return config } @@ -71,7 +68,7 @@ func testAccFmcSmartLicenseConfig_minimum() string { func testAccFmcSmartLicenseConfig_all() string { config := `resource "fmc_smart_license" "test" {` + "\n" - config += ` registration_type = "REGISTER"` + "\n" + config += ` registration_type = "EVALUATION"` + "\n" config += `}` + "\n" return config } From efc7060edb9e80b790160f7d94f35c41c9875178 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20Bernacki?= Date: Wed, 4 Dec 2024 12:57:48 +0100 Subject: [PATCH 13/13] computed attribute for regStatus --- gen/definitions/smart_license.yaml | 1 + internal/provider/model_fmc_smart_license.go | 7 +++++++ internal/provider/resource_fmc_smart_license.go | 7 +++++++ 3 files changed, 15 insertions(+) diff --git a/gen/definitions/smart_license.yaml b/gen/definitions/smart_license.yaml index 91bce750..a70cebc6 100644 --- a/gen/definitions/smart_license.yaml +++ b/gen/definitions/smart_license.yaml @@ -35,6 +35,7 @@ attributes: tf_name: registration_status type: String description: Status of a smart license. + computed: true example: EVALUATION exclude_test: true exclude_example: true diff --git a/internal/provider/model_fmc_smart_license.go b/internal/provider/model_fmc_smart_license.go index 206f6e29..7277a4a9 100644 --- a/internal/provider/model_fmc_smart_license.go +++ b/internal/provider/model_fmc_smart_license.go @@ -106,6 +106,13 @@ func (data *SmartLicense) fromBodyPartial(ctx context.Context, res gjson.Result) // 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 *SmartLicense) fromBodyUnknowns(ctx context.Context, res gjson.Result) { + if data.RegistrationStatus.IsUnknown() { + if value := res.Get("regStatus"); value.Exists() { + data.RegistrationStatus = types.StringValue(value.String()) + } else { + data.RegistrationStatus = types.StringNull() + } + } } // End of section. //template:end fromBodyUnknowns diff --git a/internal/provider/resource_fmc_smart_license.go b/internal/provider/resource_fmc_smart_license.go index f9037fc2..0b078903 100644 --- a/internal/provider/resource_fmc_smart_license.go +++ b/internal/provider/resource_fmc_smart_license.go @@ -38,6 +38,8 @@ import ( // 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 = &SmartLicenseResource{} @@ -82,6 +84,9 @@ func (r *SmartLicenseResource) Schema(ctx context.Context, req resource.SchemaRe "registration_status": schema.StringAttribute{ MarkdownDescription: helpers.NewAttributeDescription("Status of a smart license.").String, Computed: true, + PlanModifiers: []planmodifier.String{ + stringplanmodifier.UseStateForUnknown(), + }, }, "force": schema.BoolAttribute{ MarkdownDescription: helpers.NewAttributeDescription("Set to true to re-register smart license.").String, @@ -99,6 +104,8 @@ func (r *SmartLicenseResource) Configure(_ context.Context, req resource.Configu r.client = req.ProviderData.(*FmcProviderData).Client } +// End of section. //template:end model + func (r *SmartLicenseResource) Create(ctx context.Context, req resource.CreateRequest, resp *resource.CreateResponse) { var plan SmartLicense var state SmartLicense