From c6eef280f3a8a16bae7b291b8c98ef69d21016f3 Mon Sep 17 00:00:00 2001 From: Kuba Mazurkiewicz <132581633+kuba-mazurkiewicz@users.noreply.github.com> Date: Tue, 22 Oct 2024 11:40:44 +0200 Subject: [PATCH] Fix managing default network access and device admin resources (#85) --- CHANGELOG.md | 4 ++ docs/guides/changelog.md | 4 ++ gen/definitions/device_admin_policy_set.yaml | 1 - .../network_access_policy_set.yaml | 1 - gen/templates/resource.go | 61 +++++++++++-------- ...ce_ise_device_admin_authentication_rule.go | 28 ++++++--- ...rce_ise_device_admin_authorization_rule.go | 28 ++++++--- .../resource_ise_device_admin_policy_set.go | 47 +++++++------- ..._ise_network_access_authentication_rule.go | 28 ++++++--- ...e_ise_network_access_authorization_rule.go | 28 ++++++--- .../resource_ise_network_access_policy_set.go | 47 +++++++------- templates/guides/changelog.md.tmpl | 4 ++ 12 files changed, 172 insertions(+), 109 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 4932a9c..1599c57 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,7 @@ +## 0.2.4 + +- Fix managing `Default` network access and device admin policy_set, authentication_rule and authorization_rule + ## 0.2.3 - Add `network_access_*_update_rank` and `device_admin_*_update_rank` resources to update rank under network access and device admin policy sets to bypass API limitation which restricts rank assignments to a strictly incremental sequence. More detailed information is available [here](https://registry.terraform.io/providers/CiscoDevNet/ise/latest/docs/guides/authentication_rules). diff --git a/docs/guides/changelog.md b/docs/guides/changelog.md index aec0377..5d2d62c 100644 --- a/docs/guides/changelog.md +++ b/docs/guides/changelog.md @@ -7,6 +7,10 @@ description: |- # Changelog +## 0.2.4 + +- Fix managing `Default` network access and device admin policy_set, authentication_rule and authorization_rule + ## 0.2.3 - Add `network_access_*_update_rank` and `device_admin_*_update_rank` resources to update rank under network access and device admin policy sets to bypass API limitation which restricts rank assignments to a strictly incremental sequence. More detailed information is available [here](https://registry.terraform.io/providers/CiscoDevNet/ise/latest/docs/guides/authentication_rules). diff --git a/gen/definitions/device_admin_policy_set.yaml b/gen/definitions/device_admin_policy_set.yaml index 06df169..4daea14 100644 --- a/gen/definitions/device_admin_policy_set.yaml +++ b/gen/definitions/device_admin_policy_set.yaml @@ -14,7 +14,6 @@ attributes: example: PolicySet1 - model_name: description type: String - computed: true description: The description of the policy set example: My description - model_name: isProxy diff --git a/gen/definitions/network_access_policy_set.yaml b/gen/definitions/network_access_policy_set.yaml index 78dd11b..cea8764 100644 --- a/gen/definitions/network_access_policy_set.yaml +++ b/gen/definitions/network_access_policy_set.yaml @@ -14,7 +14,6 @@ attributes: example: PolicySet1 - model_name: description type: String - computed: true description: The description of the policy set example: My description - model_name: isProxy diff --git a/gen/templates/resource.go b/gen/templates/resource.go index edfd8e6..b6a9173 100644 --- a/gen/templates/resource.go +++ b/gen/templates/resource.go @@ -523,14 +523,6 @@ func (r *{{camelCase .Name}}Resource) Create(ctx context.Context, req resource.C } {{- if .IdPath}} plan.Id = types.StringValue(res.Get("{{.IdPath}}").String()) - {{- if and (not (strContains (camelCase .Name) "Rule")) .UpdateDefault }} - if plan.Description.IsUnknown() { - plan.Description = types.StringNull() - } - if plan.Rank.IsUnknown() { - plan.Rank = types.Int64Null() - } - {{- end}} {{- else if hasId .Attributes}} {{- $id := getId .Attributes}} plan.Id = types.StringValue(fmt.Sprint(plan.{{toGoName $id.TfName}}.Value{{$id.Type}}())) @@ -545,27 +537,38 @@ func (r *{{camelCase .Name}}Resource) Create(ctx context.Context, req resource.C resp.Diagnostics.AddError("Client Error", fmt.Sprintf("Failed to retrieve objects, got error: %s", err)) return } - if value := res.Get("response"); len(value.Array()) > 0 { - value.ForEach(func(k, v gjson.Result) bool { - {{- if strContains (camelCase .Name) "Rule" }} - if v.Get("rule.name").String() == plan.Name.ValueString() { - plan.Id = types.StringValue(v.Get("rule.id").String()) - return false - } - {{- else}} - if v.Get("name").String() == plan.Name.ValueString() { - plan.Id = types.StringValue(v.Get("id").String()) - plan.Description = types.StringValue(v.Get("description").String()) - plan.Rank = types.Int64Value(v.Get("rank").Int()) - return false - } - {{- end}} - return true - }) - } + // Find id {{- if not (strContains (camelCase .Name) "Rule") }} - body = plan.toBody(ctx, {{camelCase .Name}}{}) + res = res.Get("response.#(name==\"" + plan.Name.ValueString() + "\")") + plan.Id = types.StringValue(res.Get("id").String()) + {{- else}} + res = res.Get("response.#(rule.name==\"" + plan.Name.ValueString() + "\")") + plan.Id = types.StringValue(res.Get("rule.id").String()) {{- end}} + + // Read existing attributes from the API + res, err = r.client.Get(plan.getPath() + "/" + url.QueryEscape(plan.Id.ValueString())) + if err != nil { + resp.Diagnostics.AddError("Client Error", fmt.Sprintf("Failed to retrieve object (GET), got error: %s", err)) + return + } + var existingData {{camelCase .Name}} + + // Populate existingData with current state from the API + existingData.fromBody(ctx, res) + + if plan.Name.ValueString() == "Default" { + {{- if not (strContains (camelCase .Name) "Rule") }} + // Set Rank and Description in the request body from the existing data for Default Policy Set + // Description on Default policy set cannot be modify, hence reading that form existing resource and setting to body + body, _ = sjson.Set(body, "description", existingData.Description.ValueString()) + body, _ = sjson.Set(body, "rank", existingData.Rank.ValueInt64()) + {{- else}} + // Set Rank in the request body from the existing data for Default Auth and Authz Rules + body, _ = sjson.Set(body, "rule.rank", existingData.Rank.ValueInt64()) + {{- end}} + } + res, err = r.client.Put(plan.getPath()+"/"+plan.Id.ValueString(), body) if err != nil { resp.Diagnostics.AddError("Client Error", fmt.Sprintf("Failed to configure object (PUT), got error: %s, %s", err, res.String())) @@ -693,6 +696,10 @@ func (r *{{camelCase .Name}}Resource) Update(ctx context.Context, req resource.U body, _ = sjson.Set(body, "rule.rank", existingData.Rank.ValueInt64()) {{- else}} body, _ = sjson.Set(body, "rank", existingData.Rank.ValueInt64()) + // Description on Default policy set cannot be modify, hence reading that form existing resource and setting to body + if plan.Name.ValueString() == "Default" { + body, _ = sjson.Set(body, "description", existingData.Description.ValueString()) + } {{- end}} } {{- end}} diff --git a/internal/provider/resource_ise_device_admin_authentication_rule.go b/internal/provider/resource_ise_device_admin_authentication_rule.go index 7592f88..aa489db 100644 --- a/internal/provider/resource_ise_device_admin_authentication_rule.go +++ b/internal/provider/resource_ise_device_admin_authentication_rule.go @@ -37,7 +37,6 @@ import ( "github.com/hashicorp/terraform-plugin-framework/types" "github.com/hashicorp/terraform-plugin-log/tflog" "github.com/netascode/go-ise" - "github.com/tidwall/gjson" "github.com/tidwall/sjson" ) @@ -303,15 +302,26 @@ func (r *DeviceAdminAuthenticationRuleResource) Create(ctx context.Context, req resp.Diagnostics.AddError("Client Error", fmt.Sprintf("Failed to retrieve objects, got error: %s", err)) return } - if value := res.Get("response"); len(value.Array()) > 0 { - value.ForEach(func(k, v gjson.Result) bool { - if v.Get("rule.name").String() == plan.Name.ValueString() { - plan.Id = types.StringValue(v.Get("rule.id").String()) - return false - } - return true - }) + // Find id + res = res.Get("response.#(rule.name==\"" + plan.Name.ValueString() + "\")") + plan.Id = types.StringValue(res.Get("rule.id").String()) + + // Read existing attributes from the API + res, err = r.client.Get(plan.getPath() + "/" + url.QueryEscape(plan.Id.ValueString())) + if err != nil { + resp.Diagnostics.AddError("Client Error", fmt.Sprintf("Failed to retrieve object (GET), got error: %s", err)) + return } + var existingData DeviceAdminAuthenticationRule + + // Populate existingData with current state from the API + existingData.fromBody(ctx, res) + + if plan.Name.ValueString() == "Default" { + // Set Rank in the request body from the existing data for Default Auth and Authz Rules + body, _ = sjson.Set(body, "rule.rank", existingData.Rank.ValueInt64()) + } + res, err = r.client.Put(plan.getPath()+"/"+plan.Id.ValueString(), body) if err != nil { resp.Diagnostics.AddError("Client Error", fmt.Sprintf("Failed to configure object (PUT), got error: %s, %s", err, res.String())) diff --git a/internal/provider/resource_ise_device_admin_authorization_rule.go b/internal/provider/resource_ise_device_admin_authorization_rule.go index 0d3e74f..a27635e 100644 --- a/internal/provider/resource_ise_device_admin_authorization_rule.go +++ b/internal/provider/resource_ise_device_admin_authorization_rule.go @@ -37,7 +37,6 @@ import ( "github.com/hashicorp/terraform-plugin-framework/types" "github.com/hashicorp/terraform-plugin-log/tflog" "github.com/netascode/go-ise" - "github.com/tidwall/gjson" "github.com/tidwall/sjson" ) @@ -287,15 +286,26 @@ func (r *DeviceAdminAuthorizationRuleResource) Create(ctx context.Context, req r resp.Diagnostics.AddError("Client Error", fmt.Sprintf("Failed to retrieve objects, got error: %s", err)) return } - if value := res.Get("response"); len(value.Array()) > 0 { - value.ForEach(func(k, v gjson.Result) bool { - if v.Get("rule.name").String() == plan.Name.ValueString() { - plan.Id = types.StringValue(v.Get("rule.id").String()) - return false - } - return true - }) + // Find id + res = res.Get("response.#(rule.name==\"" + plan.Name.ValueString() + "\")") + plan.Id = types.StringValue(res.Get("rule.id").String()) + + // Read existing attributes from the API + res, err = r.client.Get(plan.getPath() + "/" + url.QueryEscape(plan.Id.ValueString())) + if err != nil { + resp.Diagnostics.AddError("Client Error", fmt.Sprintf("Failed to retrieve object (GET), got error: %s", err)) + return } + var existingData DeviceAdminAuthorizationRule + + // Populate existingData with current state from the API + existingData.fromBody(ctx, res) + + if plan.Name.ValueString() == "Default" { + // Set Rank in the request body from the existing data for Default Auth and Authz Rules + body, _ = sjson.Set(body, "rule.rank", existingData.Rank.ValueInt64()) + } + res, err = r.client.Put(plan.getPath()+"/"+plan.Id.ValueString(), body) if err != nil { resp.Diagnostics.AddError("Client Error", fmt.Sprintf("Failed to configure object (PUT), got error: %s, %s", err, res.String())) diff --git a/internal/provider/resource_ise_device_admin_policy_set.go b/internal/provider/resource_ise_device_admin_policy_set.go index f109da8..735884d 100644 --- a/internal/provider/resource_ise_device_admin_policy_set.go +++ b/internal/provider/resource_ise_device_admin_policy_set.go @@ -37,7 +37,6 @@ import ( "github.com/hashicorp/terraform-plugin-framework/types" "github.com/hashicorp/terraform-plugin-log/tflog" "github.com/netascode/go-ise" - "github.com/tidwall/gjson" "github.com/tidwall/sjson" ) @@ -84,10 +83,6 @@ func (r *DeviceAdminPolicySetResource) Schema(ctx context.Context, req resource. "description": schema.StringAttribute{ MarkdownDescription: helpers.NewAttributeDescription("The description of the policy set").String, Optional: true, - Computed: true, - PlanModifiers: []planmodifier.String{ - stringplanmodifier.UseStateForUnknown(), - }, }, "is_proxy": schema.BoolAttribute{ MarkdownDescription: helpers.NewAttributeDescription("Flag which indicates if the policy set service is of type 'Proxy Sequence' or 'Allowed Protocols'").String, @@ -281,30 +276,34 @@ func (r *DeviceAdminPolicySetResource) Create(ctx context.Context, req resource. return } plan.Id = types.StringValue(res.Get("response.id").String()) - if plan.Description.IsUnknown() { - plan.Description = types.StringNull() - } - if plan.Rank.IsUnknown() { - plan.Rank = types.Int64Null() - } } else { res, err := r.client.Get(plan.getPath()) if err != nil { resp.Diagnostics.AddError("Client Error", fmt.Sprintf("Failed to retrieve objects, got error: %s", err)) return } - if value := res.Get("response"); len(value.Array()) > 0 { - value.ForEach(func(k, v gjson.Result) bool { - if v.Get("name").String() == plan.Name.ValueString() { - plan.Id = types.StringValue(v.Get("id").String()) - plan.Description = types.StringValue(v.Get("description").String()) - plan.Rank = types.Int64Value(v.Get("rank").Int()) - return false - } - return true - }) + // Find id + res = res.Get("response.#(name==\"" + plan.Name.ValueString() + "\")") + plan.Id = types.StringValue(res.Get("id").String()) + + // Read existing attributes from the API + res, err = r.client.Get(plan.getPath() + "/" + url.QueryEscape(plan.Id.ValueString())) + if err != nil { + resp.Diagnostics.AddError("Client Error", fmt.Sprintf("Failed to retrieve object (GET), got error: %s", err)) + return + } + var existingData DeviceAdminPolicySet + + // Populate existingData with current state from the API + existingData.fromBody(ctx, res) + + if plan.Name.ValueString() == "Default" { + // Set Rank and Description in the request body from the existing data for Default Policy Set + // Description on Default policy set cannot be modify, hence reading that form existing resource and setting to body + body, _ = sjson.Set(body, "description", existingData.Description.ValueString()) + body, _ = sjson.Set(body, "rank", existingData.Rank.ValueInt64()) } - body = plan.toBody(ctx, DeviceAdminPolicySet{}) + res, err = r.client.Put(plan.getPath()+"/"+plan.Id.ValueString(), body) if err != nil { resp.Diagnostics.AddError("Client Error", fmt.Sprintf("Failed to configure object (PUT), got error: %s, %s", err, res.String())) @@ -389,6 +388,10 @@ func (r *DeviceAdminPolicySetResource) Update(ctx context.Context, req resource. existingData.fromBody(ctx, res) // Set Rank in the request body from the existing data if it's missing from the plan body, _ = sjson.Set(body, "rank", existingData.Rank.ValueInt64()) + // Description on Default policy set cannot be modify, hence reading that form existing resource and setting to body + if plan.Name.ValueString() == "Default" { + body, _ = sjson.Set(body, "description", existingData.Description.ValueString()) + } } res, err := r.client.Put(plan.getPath()+"/"+url.QueryEscape(plan.Id.ValueString()), body) diff --git a/internal/provider/resource_ise_network_access_authentication_rule.go b/internal/provider/resource_ise_network_access_authentication_rule.go index 54ecae6..0cf472c 100644 --- a/internal/provider/resource_ise_network_access_authentication_rule.go +++ b/internal/provider/resource_ise_network_access_authentication_rule.go @@ -37,7 +37,6 @@ import ( "github.com/hashicorp/terraform-plugin-framework/types" "github.com/hashicorp/terraform-plugin-log/tflog" "github.com/netascode/go-ise" - "github.com/tidwall/gjson" "github.com/tidwall/sjson" ) @@ -303,15 +302,26 @@ func (r *NetworkAccessAuthenticationRuleResource) Create(ctx context.Context, re resp.Diagnostics.AddError("Client Error", fmt.Sprintf("Failed to retrieve objects, got error: %s", err)) return } - if value := res.Get("response"); len(value.Array()) > 0 { - value.ForEach(func(k, v gjson.Result) bool { - if v.Get("rule.name").String() == plan.Name.ValueString() { - plan.Id = types.StringValue(v.Get("rule.id").String()) - return false - } - return true - }) + // Find id + res = res.Get("response.#(rule.name==\"" + plan.Name.ValueString() + "\")") + plan.Id = types.StringValue(res.Get("rule.id").String()) + + // Read existing attributes from the API + res, err = r.client.Get(plan.getPath() + "/" + url.QueryEscape(plan.Id.ValueString())) + if err != nil { + resp.Diagnostics.AddError("Client Error", fmt.Sprintf("Failed to retrieve object (GET), got error: %s", err)) + return } + var existingData NetworkAccessAuthenticationRule + + // Populate existingData with current state from the API + existingData.fromBody(ctx, res) + + if plan.Name.ValueString() == "Default" { + // Set Rank in the request body from the existing data for Default Auth and Authz Rules + body, _ = sjson.Set(body, "rule.rank", existingData.Rank.ValueInt64()) + } + res, err = r.client.Put(plan.getPath()+"/"+plan.Id.ValueString(), body) if err != nil { resp.Diagnostics.AddError("Client Error", fmt.Sprintf("Failed to configure object (PUT), got error: %s, %s", err, res.String())) diff --git a/internal/provider/resource_ise_network_access_authorization_rule.go b/internal/provider/resource_ise_network_access_authorization_rule.go index 1ab501b..d872e7a 100644 --- a/internal/provider/resource_ise_network_access_authorization_rule.go +++ b/internal/provider/resource_ise_network_access_authorization_rule.go @@ -37,7 +37,6 @@ import ( "github.com/hashicorp/terraform-plugin-framework/types" "github.com/hashicorp/terraform-plugin-log/tflog" "github.com/netascode/go-ise" - "github.com/tidwall/gjson" "github.com/tidwall/sjson" ) @@ -287,15 +286,26 @@ func (r *NetworkAccessAuthorizationRuleResource) Create(ctx context.Context, req resp.Diagnostics.AddError("Client Error", fmt.Sprintf("Failed to retrieve objects, got error: %s", err)) return } - if value := res.Get("response"); len(value.Array()) > 0 { - value.ForEach(func(k, v gjson.Result) bool { - if v.Get("rule.name").String() == plan.Name.ValueString() { - plan.Id = types.StringValue(v.Get("rule.id").String()) - return false - } - return true - }) + // Find id + res = res.Get("response.#(rule.name==\"" + plan.Name.ValueString() + "\")") + plan.Id = types.StringValue(res.Get("rule.id").String()) + + // Read existing attributes from the API + res, err = r.client.Get(plan.getPath() + "/" + url.QueryEscape(plan.Id.ValueString())) + if err != nil { + resp.Diagnostics.AddError("Client Error", fmt.Sprintf("Failed to retrieve object (GET), got error: %s", err)) + return } + var existingData NetworkAccessAuthorizationRule + + // Populate existingData with current state from the API + existingData.fromBody(ctx, res) + + if plan.Name.ValueString() == "Default" { + // Set Rank in the request body from the existing data for Default Auth and Authz Rules + body, _ = sjson.Set(body, "rule.rank", existingData.Rank.ValueInt64()) + } + res, err = r.client.Put(plan.getPath()+"/"+plan.Id.ValueString(), body) if err != nil { resp.Diagnostics.AddError("Client Error", fmt.Sprintf("Failed to configure object (PUT), got error: %s, %s", err, res.String())) diff --git a/internal/provider/resource_ise_network_access_policy_set.go b/internal/provider/resource_ise_network_access_policy_set.go index 0d31846..857f3b7 100644 --- a/internal/provider/resource_ise_network_access_policy_set.go +++ b/internal/provider/resource_ise_network_access_policy_set.go @@ -37,7 +37,6 @@ import ( "github.com/hashicorp/terraform-plugin-framework/types" "github.com/hashicorp/terraform-plugin-log/tflog" "github.com/netascode/go-ise" - "github.com/tidwall/gjson" "github.com/tidwall/sjson" ) @@ -84,10 +83,6 @@ func (r *NetworkAccessPolicySetResource) Schema(ctx context.Context, req resourc "description": schema.StringAttribute{ MarkdownDescription: helpers.NewAttributeDescription("The description of the policy set").String, Optional: true, - Computed: true, - PlanModifiers: []planmodifier.String{ - stringplanmodifier.UseStateForUnknown(), - }, }, "is_proxy": schema.BoolAttribute{ MarkdownDescription: helpers.NewAttributeDescription("Flag which indicates if the policy set service is of type 'Proxy Sequence' or 'Allowed Protocols'").String, @@ -281,30 +276,34 @@ func (r *NetworkAccessPolicySetResource) Create(ctx context.Context, req resourc return } plan.Id = types.StringValue(res.Get("response.id").String()) - if plan.Description.IsUnknown() { - plan.Description = types.StringNull() - } - if plan.Rank.IsUnknown() { - plan.Rank = types.Int64Null() - } } else { res, err := r.client.Get(plan.getPath()) if err != nil { resp.Diagnostics.AddError("Client Error", fmt.Sprintf("Failed to retrieve objects, got error: %s", err)) return } - if value := res.Get("response"); len(value.Array()) > 0 { - value.ForEach(func(k, v gjson.Result) bool { - if v.Get("name").String() == plan.Name.ValueString() { - plan.Id = types.StringValue(v.Get("id").String()) - plan.Description = types.StringValue(v.Get("description").String()) - plan.Rank = types.Int64Value(v.Get("rank").Int()) - return false - } - return true - }) + // Find id + res = res.Get("response.#(name==\"" + plan.Name.ValueString() + "\")") + plan.Id = types.StringValue(res.Get("id").String()) + + // Read existing attributes from the API + res, err = r.client.Get(plan.getPath() + "/" + url.QueryEscape(plan.Id.ValueString())) + if err != nil { + resp.Diagnostics.AddError("Client Error", fmt.Sprintf("Failed to retrieve object (GET), got error: %s", err)) + return + } + var existingData NetworkAccessPolicySet + + // Populate existingData with current state from the API + existingData.fromBody(ctx, res) + + if plan.Name.ValueString() == "Default" { + // Set Rank and Description in the request body from the existing data for Default Policy Set + // Description on Default policy set cannot be modify, hence reading that form existing resource and setting to body + body, _ = sjson.Set(body, "description", existingData.Description.ValueString()) + body, _ = sjson.Set(body, "rank", existingData.Rank.ValueInt64()) } - body = plan.toBody(ctx, NetworkAccessPolicySet{}) + res, err = r.client.Put(plan.getPath()+"/"+plan.Id.ValueString(), body) if err != nil { resp.Diagnostics.AddError("Client Error", fmt.Sprintf("Failed to configure object (PUT), got error: %s, %s", err, res.String())) @@ -389,6 +388,10 @@ func (r *NetworkAccessPolicySetResource) Update(ctx context.Context, req resourc existingData.fromBody(ctx, res) // Set Rank in the request body from the existing data if it's missing from the plan body, _ = sjson.Set(body, "rank", existingData.Rank.ValueInt64()) + // Description on Default policy set cannot be modify, hence reading that form existing resource and setting to body + if plan.Name.ValueString() == "Default" { + body, _ = sjson.Set(body, "description", existingData.Description.ValueString()) + } } res, err := r.client.Put(plan.getPath()+"/"+url.QueryEscape(plan.Id.ValueString()), body) diff --git a/templates/guides/changelog.md.tmpl b/templates/guides/changelog.md.tmpl index aec0377..5d2d62c 100644 --- a/templates/guides/changelog.md.tmpl +++ b/templates/guides/changelog.md.tmpl @@ -7,6 +7,10 @@ description: |- # Changelog +## 0.2.4 + +- Fix managing `Default` network access and device admin policy_set, authentication_rule and authorization_rule + ## 0.2.3 - Add `network_access_*_update_rank` and `device_admin_*_update_rank` resources to update rank under network access and device admin policy sets to bypass API limitation which restricts rank assignments to a strictly incremental sequence. More detailed information is available [here](https://registry.terraform.io/providers/CiscoDevNet/ise/latest/docs/guides/authentication_rules).