diff --git a/internal/pkg/modifiers/comma_space_map_modifier.go b/internal/pkg/modifiers/comma_space_map_modifier.go new file mode 100644 index 0000000..70f65ad --- /dev/null +++ b/internal/pkg/modifiers/comma_space_map_modifier.go @@ -0,0 +1,97 @@ +package pkg + +import ( + "context" + "strings" + + "github.com/hashicorp/terraform-plugin-framework/resource/schema/planmodifier" + "github.com/hashicorp/terraform-plugin-framework/types" +) + +// CommaSpaceMapModifier ensures consistent formatting of comma-separated strings in map values. +type CommaSpaceMapModifier struct{} + +func (m CommaSpaceMapModifier) Description(ctx context.Context) string { + return "Ensures consistent formatting of comma-separated strings in map values with spaces after commas" +} + +func (m CommaSpaceMapModifier) MarkdownDescription(ctx context.Context) string { + return "Ensures consistent formatting of comma-separated strings in map values with spaces after commas" +} + +func (m CommaSpaceMapModifier) PlanModifyMap(ctx context.Context, req planmodifier.MapRequest, resp *planmodifier.MapResponse) { + if req.PlanValue.IsUnknown() || req.PlanValue.IsNull() { + return + } + + planElements := req.PlanValue.Elements() + newElements := make(map[string]types.String) + + // Track config format for each key + configFormats := make(map[string]bool) + + // Detect config format for each key + if !req.ConfigValue.IsNull() { + configElements := req.ConfigValue.Elements() + for key, v := range configElements { + if str, ok := v.(types.String); ok && !str.IsNull() { + configFormats[key] = strings.Contains(str.ValueString(), ", ") + } + } + } + + // Fallback to state value if config format not found + if !req.StateValue.IsNull() { + stateElements := req.StateValue.Elements() + for key, v := range stateElements { + if _, exists := configFormats[key]; !exists { + if str, ok := v.(types.String); ok && !str.IsNull() { + configFormats[key] = strings.Contains(str.ValueString(), ", ") + } + } + } + } + + for key, value := range planElements { + strValue, ok := value.(types.String) + if !ok { + continue + } + + if !strValue.IsNull() && !strValue.IsUnknown() { + parts := strings.Split(strValue.ValueString(), ",") + for i, part := range parts { + parts[i] = strings.TrimSpace(part) + } + + useSpaces, found := configFormats[key] + if !found { + useSpaces = false + } + + var formattedValue string + if useSpaces { + formattedValue = strings.Join(parts, ", ") + } else { + formattedValue = strings.Join(parts, ",") + } + + newElements[key] = types.StringValue(formattedValue) + } else { + newElements[key] = strValue + } + } + + newMapValue, diags := types.MapValueFrom(ctx, types.StringType, newElements) + resp.Diagnostics.Append(diags...) + if resp.Diagnostics.HasError() { + return + } + + resp.PlanValue = newMapValue +} + +// CommaSpaceMap returns a new instance of CommaSpaceMapModifier. +func CommaSpaceMap() CommaSpaceMapModifier { + return CommaSpaceMapModifier{} +} diff --git a/internal/provider/resource/identity_oidc_auth.go b/internal/provider/resource/identity_oidc_auth.go index df4e220..5cfc412 100644 --- a/internal/provider/resource/identity_oidc_auth.go +++ b/internal/provider/resource/identity_oidc_auth.go @@ -7,6 +7,7 @@ import ( "strings" infisical "terraform-provider-infisical/internal/client" infisicalclient "terraform-provider-infisical/internal/client" + pkg "terraform-provider-infisical/internal/pkg/modifiers" infisicalstrings "terraform-provider-infisical/internal/pkg/strings" "terraform-provider-infisical/internal/pkg/terraform" @@ -88,11 +89,14 @@ func (r *IdentityOidcAuthResource) Schema(_ context.Context, _ resource.SchemaRe PlanModifiers: []planmodifier.List{listplanmodifier.UseStateForUnknown()}, }, "bound_claims": schema.MapAttribute{ - Description: "The attributes that should be present in the JWT for it to be valid. The provided values can be a glob pattern.", - Optional: true, - Computed: true, - ElementType: types.StringType, - PlanModifiers: []planmodifier.Map{mapplanmodifier.UseStateForUnknown()}, + Description: "The attributes that should be present in the JWT for it to be valid. The provided values can be a glob pattern.", + Optional: true, + Computed: true, + ElementType: types.StringType, + PlanModifiers: []planmodifier.Map{ + mapplanmodifier.UseStateForUnknown(), + pkg.CommaSpaceMapModifier{}, + }, }, "bound_subject": schema.StringAttribute{ Description: "The expected principal that is the subject of the JWT.", @@ -175,7 +179,28 @@ func updateOidcAuthStateByApi(ctx context.Context, diagnose diag.Diagnostics, pl boundClaimsElements := make(map[string]attr.Value) for key, value := range newIdentityOidcAuth.BoundClaims { - boundClaimsElements[key] = types.StringValue(value) + // Check plan format + useSpaces := false + if !plan.BoundClaims.IsNull() { + if planValue, ok := plan.BoundClaims.Elements()[key]; ok { + if planStr, ok := planValue.(types.String); ok { + useSpaces = strings.Contains(planStr.ValueString(), ", ") + } + } + } + + // Split and normalize + parts := strings.Split(value, ",") + for i, part := range parts { + parts[i] = strings.TrimSpace(part) + } + + // Use the same format as the plan + if useSpaces { + boundClaimsElements[key] = types.StringValue(strings.Join(parts, ", ")) + } else { + boundClaimsElements[key] = types.StringValue(strings.Join(parts, ",")) + } } boundClaimsMapValue, diags := types.MapValue(types.StringType, boundClaimsElements)