diff --git a/.changelog/12119.txt b/.changelog/12119.txt new file mode 100644 index 00000000000..c4cd2f4c5a2 --- /dev/null +++ b/.changelog/12119.txt @@ -0,0 +1,3 @@ +```release-note: bug +accesscontextmanager: Fixed permadiff for perimeter ingress / egress rule resources +``` \ No newline at end of file diff --git a/google/services/accesscontextmanager/resource_access_context_manager_service_perimeter_dry_run_egress_policy.go b/google/services/accesscontextmanager/resource_access_context_manager_service_perimeter_dry_run_egress_policy.go index 4e472229c82..884455c143f 100644 --- a/google/services/accesscontextmanager/resource_access_context_manager_service_perimeter_dry_run_egress_policy.go +++ b/google/services/accesscontextmanager/resource_access_context_manager_service_perimeter_dry_run_egress_policy.go @@ -22,6 +22,8 @@ import ( "log" "net/http" "reflect" + "slices" + "sort" "time" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" @@ -31,6 +33,48 @@ import ( "github.com/hashicorp/terraform-provider-google/google/verify" ) +func AccessContextManagerServicePerimeterDryRunEgressPolicyEgressToResourcesDiffSupressFunc(_, _, _ string, d *schema.ResourceData) bool { + old, new := d.GetChange("egress_to.0.resources") + + oldResources, err := tpgresource.InterfaceSliceToStringSlice(old) + if err != nil { + log.Printf("[ERROR] Failed to convert config value: %s", err) + return false + } + + newResources, err := tpgresource.InterfaceSliceToStringSlice(new) + if err != nil { + log.Printf("[ERROR] Failed to convert config value: %s", err) + return false + } + + sort.Strings(oldResources) + sort.Strings(newResources) + + return slices.Equal(oldResources, newResources) +} + +func AccessContextManagerServicePerimeterDryRunEgressPolicyIngressToResourcesDiffSupressFunc(_, _, _ string, d *schema.ResourceData) bool { + old, new := d.GetChange("ingress_to.0.resources") + + oldResources, err := tpgresource.InterfaceSliceToStringSlice(old) + if err != nil { + log.Printf("[ERROR] Failed to convert config value: %s", err) + return false + } + + newResources, err := tpgresource.InterfaceSliceToStringSlice(new) + if err != nil { + log.Printf("[ERROR] Failed to convert config value: %s", err) + return false + } + + sort.Strings(oldResources) + sort.Strings(newResources) + + return slices.Equal(oldResources, newResources) +} + func ResourceAccessContextManagerServicePerimeterDryRunEgressPolicy() *schema.Resource { return &schema.Resource{ Create: resourceAccessContextManagerServicePerimeterDryRunEgressPolicyCreate, @@ -172,9 +216,10 @@ field set to '*' will allow all methods AND permissions for all services.`, }, }, "resources": { - Type: schema.TypeList, - Optional: true, - ForceNew: true, + Type: schema.TypeList, + Optional: true, + ForceNew: true, + DiffSuppressFunc: AccessContextManagerServicePerimeterDryRunEgressPolicyEgressToResourcesDiffSupressFunc, Description: `A list of resources, currently only projects in the form 'projects/', that match this to stanza. A request matches if it contains a resource in this list. If * is specified for resources, @@ -499,7 +544,29 @@ func flattenNestedAccessContextManagerServicePerimeterDryRunEgressPolicyEgressTo return []interface{}{transformed} } func flattenNestedAccessContextManagerServicePerimeterDryRunEgressPolicyEgressToResources(v interface{}, d *schema.ResourceData, config *transport_tpg.Config) interface{} { - return v + rawConfigValue := d.Get("egress_to.0.resources") + + // Convert config value to []string + configValue, err := tpgresource.InterfaceSliceToStringSlice(rawConfigValue) + if err != nil { + log.Printf("[ERROR] Failed to convert config value: %s", err) + return v + } + + // Convert v to []string + apiStringValue, err := tpgresource.InterfaceSliceToStringSlice(v) + if err != nil { + log.Printf("[ERROR] Failed to convert API value: %s", err) + return v + } + + sortedStrings, err := tpgresource.SortStringsByConfigOrder(configValue, apiStringValue) + if err != nil { + log.Printf("[ERROR] Could not sort API response value: %s", err) + return v + } + + return sortedStrings } func flattenNestedAccessContextManagerServicePerimeterDryRunEgressPolicyEgressToExternalResources(v interface{}, d *schema.ResourceData, config *transport_tpg.Config) interface{} { diff --git a/google/services/accesscontextmanager/resource_access_context_manager_service_perimeter_dry_run_ingress_policy.go b/google/services/accesscontextmanager/resource_access_context_manager_service_perimeter_dry_run_ingress_policy.go index 3c2941b5cb2..ee6529a6aaa 100644 --- a/google/services/accesscontextmanager/resource_access_context_manager_service_perimeter_dry_run_ingress_policy.go +++ b/google/services/accesscontextmanager/resource_access_context_manager_service_perimeter_dry_run_ingress_policy.go @@ -22,6 +22,8 @@ import ( "log" "net/http" "reflect" + "slices" + "sort" "time" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" @@ -31,6 +33,48 @@ import ( "github.com/hashicorp/terraform-provider-google/google/verify" ) +func AccessContextManagerServicePerimeterDryRunIngressPolicyEgressToResourcesDiffSupressFunc(_, _, _ string, d *schema.ResourceData) bool { + old, new := d.GetChange("egress_to.0.resources") + + oldResources, err := tpgresource.InterfaceSliceToStringSlice(old) + if err != nil { + log.Printf("[ERROR] Failed to convert config value: %s", err) + return false + } + + newResources, err := tpgresource.InterfaceSliceToStringSlice(new) + if err != nil { + log.Printf("[ERROR] Failed to convert config value: %s", err) + return false + } + + sort.Strings(oldResources) + sort.Strings(newResources) + + return slices.Equal(oldResources, newResources) +} + +func AccessContextManagerServicePerimeterDryRunIngressPolicyIngressToResourcesDiffSupressFunc(_, _, _ string, d *schema.ResourceData) bool { + old, new := d.GetChange("ingress_to.0.resources") + + oldResources, err := tpgresource.InterfaceSliceToStringSlice(old) + if err != nil { + log.Printf("[ERROR] Failed to convert config value: %s", err) + return false + } + + newResources, err := tpgresource.InterfaceSliceToStringSlice(new) + if err != nil { + log.Printf("[ERROR] Failed to convert config value: %s", err) + return false + } + + sort.Strings(oldResources) + sort.Strings(newResources) + + return slices.Equal(oldResources, newResources) +} + func ResourceAccessContextManagerServicePerimeterDryRunIngressPolicy() *schema.Resource { return &schema.Resource{ Create: resourceAccessContextManagerServicePerimeterDryRunIngressPolicyCreate, @@ -173,9 +217,10 @@ field set to '*' will allow all methods AND permissions for all services.`, }, }, "resources": { - Type: schema.TypeList, - Optional: true, - ForceNew: true, + Type: schema.TypeList, + Optional: true, + ForceNew: true, + DiffSuppressFunc: AccessContextManagerServicePerimeterDryRunIngressPolicyIngressToResourcesDiffSupressFunc, Description: `A list of resources, currently only projects in the form 'projects/', protected by this 'ServicePerimeter' that are allowed to be accessed by sources defined in the @@ -500,7 +545,29 @@ func flattenNestedAccessContextManagerServicePerimeterDryRunIngressPolicyIngress return []interface{}{transformed} } func flattenNestedAccessContextManagerServicePerimeterDryRunIngressPolicyIngressToResources(v interface{}, d *schema.ResourceData, config *transport_tpg.Config) interface{} { - return v + rawConfigValue := d.Get("ingress_to.0.resources") + + // Convert config value to []string + configValue, err := tpgresource.InterfaceSliceToStringSlice(rawConfigValue) + if err != nil { + log.Printf("[ERROR] Failed to convert config value: %s", err) + return v + } + + // Convert v to []string + apiStringValue, err := tpgresource.InterfaceSliceToStringSlice(v) + if err != nil { + log.Printf("[ERROR] Failed to convert API value: %s", err) + return v + } + + sortedStrings, err := tpgresource.SortStringsByConfigOrder(configValue, apiStringValue) + if err != nil { + log.Printf("[ERROR] Could not sort API response value: %s", err) + return v + } + + return sortedStrings } func flattenNestedAccessContextManagerServicePerimeterDryRunIngressPolicyIngressToOperations(v interface{}, d *schema.ResourceData, config *transport_tpg.Config) interface{} { diff --git a/google/services/accesscontextmanager/resource_access_context_manager_service_perimeter_egress_policy.go b/google/services/accesscontextmanager/resource_access_context_manager_service_perimeter_egress_policy.go index 9fb9aa3dfca..f533dbcc3bd 100644 --- a/google/services/accesscontextmanager/resource_access_context_manager_service_perimeter_egress_policy.go +++ b/google/services/accesscontextmanager/resource_access_context_manager_service_perimeter_egress_policy.go @@ -22,6 +22,8 @@ import ( "log" "net/http" "reflect" + "slices" + "sort" "time" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" @@ -31,6 +33,48 @@ import ( "github.com/hashicorp/terraform-provider-google/google/verify" ) +func AccessContextManagerServicePerimeterEgressPolicyEgressToResourcesDiffSupressFunc(_, _, _ string, d *schema.ResourceData) bool { + old, new := d.GetChange("egress_to.0.resources") + + oldResources, err := tpgresource.InterfaceSliceToStringSlice(old) + if err != nil { + log.Printf("[ERROR] Failed to convert config value: %s", err) + return false + } + + newResources, err := tpgresource.InterfaceSliceToStringSlice(new) + if err != nil { + log.Printf("[ERROR] Failed to convert config value: %s", err) + return false + } + + sort.Strings(oldResources) + sort.Strings(newResources) + + return slices.Equal(oldResources, newResources) +} + +func AccessContextManagerServicePerimeterEgressPolicyIngressToResourcesDiffSupressFunc(_, _, _ string, d *schema.ResourceData) bool { + old, new := d.GetChange("ingress_to.0.resources") + + oldResources, err := tpgresource.InterfaceSliceToStringSlice(old) + if err != nil { + log.Printf("[ERROR] Failed to convert config value: %s", err) + return false + } + + newResources, err := tpgresource.InterfaceSliceToStringSlice(new) + if err != nil { + log.Printf("[ERROR] Failed to convert config value: %s", err) + return false + } + + sort.Strings(oldResources) + sort.Strings(newResources) + + return slices.Equal(oldResources, newResources) +} + func ResourceAccessContextManagerServicePerimeterEgressPolicy() *schema.Resource { return &schema.Resource{ Create: resourceAccessContextManagerServicePerimeterEgressPolicyCreate, @@ -172,9 +216,10 @@ field set to '*' will allow all methods AND permissions for all services.`, }, }, "resources": { - Type: schema.TypeList, - Optional: true, - ForceNew: true, + Type: schema.TypeList, + Optional: true, + ForceNew: true, + DiffSuppressFunc: AccessContextManagerServicePerimeterEgressPolicyEgressToResourcesDiffSupressFunc, Description: `A list of resources, currently only projects in the form 'projects/', that match this to stanza. A request matches if it contains a resource in this list. If * is specified for resources, @@ -497,7 +542,29 @@ func flattenNestedAccessContextManagerServicePerimeterEgressPolicyEgressTo(v int return []interface{}{transformed} } func flattenNestedAccessContextManagerServicePerimeterEgressPolicyEgressToResources(v interface{}, d *schema.ResourceData, config *transport_tpg.Config) interface{} { - return v + rawConfigValue := d.Get("egress_to.0.resources") + + // Convert config value to []string + configValue, err := tpgresource.InterfaceSliceToStringSlice(rawConfigValue) + if err != nil { + log.Printf("[ERROR] Failed to convert config value: %s", err) + return v + } + + // Convert v to []string + apiStringValue, err := tpgresource.InterfaceSliceToStringSlice(v) + if err != nil { + log.Printf("[ERROR] Failed to convert API value: %s", err) + return v + } + + sortedStrings, err := tpgresource.SortStringsByConfigOrder(configValue, apiStringValue) + if err != nil { + log.Printf("[ERROR] Could not sort API response value: %s", err) + return v + } + + return sortedStrings } func flattenNestedAccessContextManagerServicePerimeterEgressPolicyEgressToExternalResources(v interface{}, d *schema.ResourceData, config *transport_tpg.Config) interface{} { diff --git a/google/services/accesscontextmanager/resource_access_context_manager_service_perimeter_ingress_policy.go b/google/services/accesscontextmanager/resource_access_context_manager_service_perimeter_ingress_policy.go index 2b963e34e4f..64e0c33d736 100644 --- a/google/services/accesscontextmanager/resource_access_context_manager_service_perimeter_ingress_policy.go +++ b/google/services/accesscontextmanager/resource_access_context_manager_service_perimeter_ingress_policy.go @@ -22,6 +22,8 @@ import ( "log" "net/http" "reflect" + "slices" + "sort" "time" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" @@ -31,6 +33,48 @@ import ( "github.com/hashicorp/terraform-provider-google/google/verify" ) +func AccessContextManagerServicePerimeterIngressPolicyEgressToResourcesDiffSupressFunc(_, _, _ string, d *schema.ResourceData) bool { + old, new := d.GetChange("egress_to.0.resources") + + oldResources, err := tpgresource.InterfaceSliceToStringSlice(old) + if err != nil { + log.Printf("[ERROR] Failed to convert config value: %s", err) + return false + } + + newResources, err := tpgresource.InterfaceSliceToStringSlice(new) + if err != nil { + log.Printf("[ERROR] Failed to convert config value: %s", err) + return false + } + + sort.Strings(oldResources) + sort.Strings(newResources) + + return slices.Equal(oldResources, newResources) +} + +func AccessContextManagerServicePerimeterIngressPolicyIngressToResourcesDiffSupressFunc(_, _, _ string, d *schema.ResourceData) bool { + old, new := d.GetChange("ingress_to.0.resources") + + oldResources, err := tpgresource.InterfaceSliceToStringSlice(old) + if err != nil { + log.Printf("[ERROR] Failed to convert config value: %s", err) + return false + } + + newResources, err := tpgresource.InterfaceSliceToStringSlice(new) + if err != nil { + log.Printf("[ERROR] Failed to convert config value: %s", err) + return false + } + + sort.Strings(oldResources) + sort.Strings(newResources) + + return slices.Equal(oldResources, newResources) +} + func ResourceAccessContextManagerServicePerimeterIngressPolicy() *schema.Resource { return &schema.Resource{ Create: resourceAccessContextManagerServicePerimeterIngressPolicyCreate, @@ -176,9 +220,10 @@ field set to '*' will allow all methods AND permissions for all services.`, }, }, "resources": { - Type: schema.TypeList, - Optional: true, - ForceNew: true, + Type: schema.TypeList, + Optional: true, + ForceNew: true, + DiffSuppressFunc: AccessContextManagerServicePerimeterIngressPolicyIngressToResourcesDiffSupressFunc, Description: `A list of resources, currently only projects in the form 'projects/', protected by this 'ServicePerimeter' that are allowed to be accessed by sources defined in the @@ -501,7 +546,29 @@ func flattenNestedAccessContextManagerServicePerimeterIngressPolicyIngressTo(v i return []interface{}{transformed} } func flattenNestedAccessContextManagerServicePerimeterIngressPolicyIngressToResources(v interface{}, d *schema.ResourceData, config *transport_tpg.Config) interface{} { - return v + rawConfigValue := d.Get("ingress_to.0.resources") + + // Convert config value to []string + configValue, err := tpgresource.InterfaceSliceToStringSlice(rawConfigValue) + if err != nil { + log.Printf("[ERROR] Failed to convert config value: %s", err) + return v + } + + // Convert v to []string + apiStringValue, err := tpgresource.InterfaceSliceToStringSlice(v) + if err != nil { + log.Printf("[ERROR] Failed to convert API value: %s", err) + return v + } + + sortedStrings, err := tpgresource.SortStringsByConfigOrder(configValue, apiStringValue) + if err != nil { + log.Printf("[ERROR] Could not sort API response value: %s", err) + return v + } + + return sortedStrings } func flattenNestedAccessContextManagerServicePerimeterIngressPolicyIngressToOperations(v interface{}, d *schema.ResourceData, config *transport_tpg.Config) interface{} {