diff --git a/btp/provider/datasource_subaccount_service_binding.go b/btp/provider/datasource_subaccount_service_binding.go index 5a543fb5..510740e8 100644 --- a/btp/provider/datasource_subaccount_service_binding.go +++ b/btp/provider/datasource_subaccount_service_binding.go @@ -4,6 +4,7 @@ import ( "context" "fmt" + "github.com/hashicorp/terraform-plugin-framework-jsontypes/jsontypes" "github.com/hashicorp/terraform-plugin-framework-validators/stringvalidator" "github.com/hashicorp/terraform-plugin-framework/datasource" "github.com/hashicorp/terraform-plugin-framework/datasource/schema" @@ -92,6 +93,7 @@ You must be assigned to the admin or viewer role of the subaccount.`, "parameters": schema.StringAttribute{ MarkdownDescription: "The parameters of the service binding as a valid JSON object.", Computed: true, + CustomType: jsontypes.NormalizedType{}, }, "state": schema.StringAttribute{ MarkdownDescription: "The current state of the service binding. Possible values are: \n" + @@ -148,7 +150,7 @@ func (ds *subaccountServiceBindingDataSource) Read(ctx context.Context, req data } data, diags = subaccountServiceBindingValueFrom(ctx, cliRes) - data.Parameters = types.StringNull() // the API doesn't return parameters for already created instances + data.Parameters = jsontypes.NewNormalizedNull() // the API doesn't return parameters for already created instances resp.Diagnostics.Append(diags...) diags = resp.State.Set(ctx, &data) diff --git a/btp/provider/resource_subaccount_environment_instance.go b/btp/provider/resource_subaccount_environment_instance.go index c2cdaa09..4e837b4e 100644 --- a/btp/provider/resource_subaccount_environment_instance.go +++ b/btp/provider/resource_subaccount_environment_instance.go @@ -7,6 +7,7 @@ import ( "regexp" "strings" + "github.com/hashicorp/terraform-plugin-framework-jsontypes/jsontypes" "github.com/hashicorp/terraform-plugin-framework-timeouts/resource/timeouts" "github.com/hashicorp/terraform-plugin-framework/path" "github.com/hashicorp/terraform-plugin-framework/resource" @@ -93,6 +94,7 @@ __Further documentation:__ "parameters": schema.StringAttribute{ MarkdownDescription: "The configuration parameters for the environment instance.", Required: true, + CustomType: jsontypes.NormalizedType{}, }, "landscape_label": schema.StringAttribute{ MarkdownDescription: "The name of the landscape within the logged in region on which the environment instance is created.", @@ -222,7 +224,7 @@ func (rs *subaccountEnvironmentInstanceResource) Read(ctx context.Context, req r //The "parameter" string contains a status field that needs to be omitted as it is not a parameter that can be defined by the caller // This way we stay consistent between CREATE and IMPORT of environment instances via Terraform reStatus := regexp.MustCompile(`,"status":"(.*?)"`) - updatedState.Parameters = types.StringValue(reStatus.ReplaceAllString(updatedState.Parameters.ValueString(), "")) + updatedState.Parameters = jsontypes.NewNormalizedValue(reStatus.ReplaceAllString(updatedState.Parameters.ValueString(), "")) } resp.Diagnostics.Append(diags...) @@ -257,7 +259,7 @@ func (rs *subaccountEnvironmentInstanceResource) Create(ctx context.Context, req timeoutsLocal := plan.Timeouts plan, diags = subaccountEnvironmentInstanceValueFrom(ctx, cliRes) - plan.Parameters = types.StringValue(parameters) + plan.Parameters = jsontypes.NewNormalizedValue(parameters) resp.Diagnostics.Append(diags...) createTimeout, diags := timeoutsLocal.Create(ctx, tfutils.DefaultTimeout) @@ -292,7 +294,7 @@ func (rs *subaccountEnvironmentInstanceResource) Create(ctx context.Context, req } plan, diags = subaccountEnvironmentInstanceValueFrom(ctx, updatedRes.(provisioning.EnvironmentInstanceResponseObject)) - plan.Parameters = types.StringValue(parameters) + plan.Parameters = jsontypes.NewNormalizedValue(parameters) plan.Timeouts = timeoutsLocal resp.Diagnostics.Append(diags...) diff --git a/btp/provider/resource_subaccount_environment_instance_test.go b/btp/provider/resource_subaccount_environment_instance_test.go index c89fd536..5241c9aa 100644 --- a/btp/provider/resource_subaccount_environment_instance_test.go +++ b/btp/provider/resource_subaccount_environment_instance_test.go @@ -50,10 +50,11 @@ func TestResourceSubaccountEnvironmentInstance(t *testing.T) { ), }, { - ResourceName: "btp_subaccount_environment_instance.uut", - ImportStateIdFunc: getEnvironmentInstanceIdForImport("btp_subaccount_environment_instance.uut"), - ImportState: true, - ImportStateVerify: true, + ResourceName: "btp_subaccount_environment_instance.uut", + ImportStateIdFunc: getEnvironmentInstanceIdForImport("btp_subaccount_environment_instance.uut"), + ImportState: true, + ImportStateVerify: true, + ImportStateVerifyIgnore: []string{"parameters"}, }, }, }) @@ -200,8 +201,8 @@ func hclResourceSubaccountEnvironmentInstanceCF(resourceName string, subaccountN InstanceName: orgName, Users: []cfUsers{ { - Id: user, Email: user, + Id: user, }, }, } @@ -244,8 +245,8 @@ func hclResourceSubaccountEnvironmentInstanceCFTimeout(resourceName string, suba InstanceName: orgName, Users: []cfUsers{ { - Id: user, Email: user, + Id: user, }, }, } diff --git a/btp/provider/resource_subaccount_service_binding.go b/btp/provider/resource_subaccount_service_binding.go index 77e44b3b..a50e1f85 100644 --- a/btp/provider/resource_subaccount_service_binding.go +++ b/btp/provider/resource_subaccount_service_binding.go @@ -8,6 +8,7 @@ import ( "strings" "time" + "github.com/hashicorp/terraform-plugin-framework-jsontypes/jsontypes" "github.com/hashicorp/terraform-plugin-framework/path" "github.com/hashicorp/terraform-plugin-framework/resource" "github.com/hashicorp/terraform-plugin-framework/resource/schema" @@ -72,6 +73,7 @@ You must be assigned to the admin or the service administrator role of the subac Optional: true, Computed: true, Default: stringdefault.StaticString(`{}`), + CustomType: jsontypes.NormalizedType{}, PlanModifiers: []planmodifier.String{ stringplanmodifier.RequiresReplace(), stringplanmodifier.UseStateForUnknown(), @@ -160,7 +162,7 @@ func (rs *subaccountServiceBindingResource) Read(ctx context.Context, req resour updatedState.Parameters = state.Parameters } else if updatedState.Parameters.IsNull() && state.Parameters.IsNull() { // During the import of the resource both values might be empty, so we need to apply the default value form the schema if not existing - updatedState.Parameters = types.StringValue("{}") + updatedState.Parameters = jsontypes.NewNormalizedValue("{}") } resp.Diagnostics.Append(diags...) diff --git a/btp/provider/resource_subaccount_service_instance.go b/btp/provider/resource_subaccount_service_instance.go index f2954777..16c2b74c 100644 --- a/btp/provider/resource_subaccount_service_instance.go +++ b/btp/provider/resource_subaccount_service_instance.go @@ -8,6 +8,7 @@ import ( "net/http" "strings" + "github.com/hashicorp/terraform-plugin-framework-jsontypes/jsontypes" "github.com/hashicorp/terraform-plugin-framework-timeouts/resource/timeouts" "github.com/hashicorp/terraform-plugin-framework/diag" "github.com/hashicorp/terraform-plugin-framework/path" @@ -94,6 +95,7 @@ You must be assigned to the admin or the service administrator role of the subac MarkdownDescription: "The configuration parameters for the service instance.", Optional: true, Sensitive: true, + CustomType: jsontypes.NormalizedType{}, Validators: []validator.String{ jsonvalidator.ValidJSON(), }, @@ -179,7 +181,7 @@ func (rs *subaccountServiceInstanceResource) Read(ctx context.Context, req resou // Handle resource import if cliRes.Parameters != "" && state.Parameters.ValueString() == "" { - newState.Parameters = types.StringValue(cliRes.Parameters) + newState.Parameters = jsontypes.NewNormalizedValue(cliRes.Parameters) } else { newState.Parameters = state.Parameters } diff --git a/btp/provider/resource_subaccount_subscription.go b/btp/provider/resource_subaccount_subscription.go index 35dfb73c..c95ef0f3 100644 --- a/btp/provider/resource_subaccount_subscription.go +++ b/btp/provider/resource_subaccount_subscription.go @@ -7,6 +7,7 @@ import ( "net/http" "strings" + "github.com/hashicorp/terraform-plugin-framework-jsontypes/jsontypes" "github.com/hashicorp/terraform-plugin-framework-timeouts/resource/timeouts" "github.com/hashicorp/terraform-plugin-framework/diag" "github.com/hashicorp/terraform-plugin-framework/path" @@ -84,6 +85,7 @@ You must be assigned to the admin role of the subaccount.`, Optional: true, Computed: true, Default: stringdefault.StaticString(`{}`), + CustomType: jsontypes.NormalizedType{}, PlanModifiers: []planmodifier.String{ stringplanmodifier.UseStateForUnknown(), }, @@ -258,7 +260,7 @@ func (rs *subaccountSubscriptionResource) Read(ctx context.Context, req resource newState.Parameters = state.Parameters } else if newState.Parameters.IsNull() && state.Parameters.IsNull() { // During the import of the resource both values might be empty, so we need to apply the default value form the schema if not existing - newState.Parameters = types.StringValue("{}") + newState.Parameters = jsontypes.NewNormalizedValue("{}") } resp.Diagnostics.Append(diags...) diff --git a/btp/provider/type_subaccount_environment_instance.go b/btp/provider/type_subaccount_environment_instance.go index 123ed9f2..01874432 100644 --- a/btp/provider/type_subaccount_environment_instance.go +++ b/btp/provider/type_subaccount_environment_instance.go @@ -3,37 +3,39 @@ package provider import ( "context" + "github.com/hashicorp/terraform-plugin-framework-jsontypes/jsontypes" "github.com/hashicorp/terraform-plugin-framework-timeouts/resource/timeouts" "github.com/hashicorp/terraform-plugin-framework/diag" "github.com/hashicorp/terraform-plugin-framework/types" "github.com/SAP/terraform-provider-btp/internal/btpcli/types/provisioning" + "github.com/SAP/terraform-provider-btp/internal/tfutils" ) type subaccountEnvironmentInstanceType struct { - SubaccountId types.String `tfsdk:"subaccount_id"` - Id types.String `tfsdk:"id"` - BrokerId types.String `tfsdk:"broker_id"` - CreatedDate types.String `tfsdk:"created_date"` - CustomLabels types.Map `tfsdk:"custom_labels"` - DashboardUrl types.String `tfsdk:"dashboard_url"` - Description types.String `tfsdk:"description"` - EnvironmentType types.String `tfsdk:"environment_type"` - Labels types.String `tfsdk:"labels"` - LandscapeLabel types.String `tfsdk:"landscape_label"` - LastModified types.String `tfsdk:"last_modified"` - Name types.String `tfsdk:"name"` - Operation types.String `tfsdk:"operation"` - Parameters types.String `tfsdk:"parameters"` - PlanId types.String `tfsdk:"plan_id"` - PlanName types.String `tfsdk:"plan_name"` - PlatformId types.String `tfsdk:"platform_id"` - ServiceId types.String `tfsdk:"service_id"` - ServiceName types.String `tfsdk:"service_name"` - State types.String `tfsdk:"state"` - TenantId types.String `tfsdk:"tenant_id"` - Type_ types.String `tfsdk:"type"` - Timeouts timeouts.Value `tfsdk:"timeouts"` + SubaccountId types.String `tfsdk:"subaccount_id"` + Id types.String `tfsdk:"id"` + BrokerId types.String `tfsdk:"broker_id"` + CreatedDate types.String `tfsdk:"created_date"` + CustomLabels types.Map `tfsdk:"custom_labels"` + DashboardUrl types.String `tfsdk:"dashboard_url"` + Description types.String `tfsdk:"description"` + EnvironmentType types.String `tfsdk:"environment_type"` + Labels types.String `tfsdk:"labels"` + LandscapeLabel types.String `tfsdk:"landscape_label"` + LastModified types.String `tfsdk:"last_modified"` + Name types.String `tfsdk:"name"` + Operation types.String `tfsdk:"operation"` + Parameters jsontypes.Normalized `tfsdk:"parameters"` + PlanId types.String `tfsdk:"plan_id"` + PlanName types.String `tfsdk:"plan_name"` + PlatformId types.String `tfsdk:"platform_id"` + ServiceId types.String `tfsdk:"service_id"` + ServiceName types.String `tfsdk:"service_name"` + State types.String `tfsdk:"state"` + TenantId types.String `tfsdk:"tenant_id"` + Type_ types.String `tfsdk:"type"` + Timeouts timeouts.Value `tfsdk:"timeouts"` } func subaccountEnvironmentInstanceValueFrom(ctx context.Context, value provisioning.EnvironmentInstanceResponseObject) (subaccountEnvironmentInstanceType, diag.Diagnostics) { @@ -49,7 +51,6 @@ func subaccountEnvironmentInstanceValueFrom(ctx context.Context, value provision LastModified: timeToValue(value.ModifiedDate.Time()), Name: types.StringValue(value.Name), Operation: types.StringValue(value.Operation), - Parameters: types.StringValue(value.Parameters), PlanId: types.StringValue(value.PlanId), PlanName: types.StringValue(value.PlanName), PlatformId: types.StringValue(value.PlatformId), @@ -63,6 +64,17 @@ func subaccountEnvironmentInstanceValueFrom(ctx context.Context, value provision var diags, diagnostics diag.Diagnostics + normalized, err := tfutils.NormalizeJSON(value.Parameters) + if err != nil { + diags.AddError( + "Invalid JSON in Environment Instance Parameters", + "Could not normalize the JSON string found in the Environment Instance Parameters attribute: "+err.Error(), + ) + diagnostics.Append(diags...) + return environmentInstance, diagnostics + } + environmentInstance.Parameters = jsontypes.NewNormalizedValue(normalized) + environmentInstance.CustomLabels, diags = types.MapValueFrom(ctx, types.SetType{ElemType: types.StringType}, value.CustomLabels) diagnostics.Append(diags...) diff --git a/btp/provider/type_subaccount_service_binding.go b/btp/provider/type_subaccount_service_binding.go index 0e3508e2..1702950c 100644 --- a/btp/provider/type_subaccount_service_binding.go +++ b/btp/provider/type_subaccount_service_binding.go @@ -3,6 +3,7 @@ package provider import ( "context" + "github.com/hashicorp/terraform-plugin-framework-jsontypes/jsontypes" "github.com/hashicorp/terraform-plugin-framework/diag" "github.com/hashicorp/terraform-plugin-framework/types" @@ -11,19 +12,19 @@ import ( ) type subaccountServiceBindingType struct { - SubaccountId types.String `tfsdk:"subaccount_id"` - ServiceInstanceId types.String `tfsdk:"service_instance_id"` - Name types.String `tfsdk:"name"` - Parameters types.String `tfsdk:"parameters"` - Id types.String `tfsdk:"id"` - Ready types.Bool `tfsdk:"ready"` - Context types.String `tfsdk:"context"` - BindResource types.Map `tfsdk:"bind_resource"` - Credentials types.String `tfsdk:"credentials"` - State types.String `tfsdk:"state"` - CreatedDate types.String `tfsdk:"created_date"` - LastModified types.String `tfsdk:"last_modified"` - Labels types.Map `tfsdk:"labels"` + SubaccountId types.String `tfsdk:"subaccount_id"` + ServiceInstanceId types.String `tfsdk:"service_instance_id"` + Name types.String `tfsdk:"name"` + Parameters jsontypes.Normalized `tfsdk:"parameters"` + Id types.String `tfsdk:"id"` + Ready types.Bool `tfsdk:"ready"` + Context types.String `tfsdk:"context"` + BindResource types.Map `tfsdk:"bind_resource"` + Credentials types.String `tfsdk:"credentials"` + State types.String `tfsdk:"state"` + CreatedDate types.String `tfsdk:"created_date"` + LastModified types.String `tfsdk:"last_modified"` + Labels types.Map `tfsdk:"labels"` } func subaccountServiceBindingValueFrom(ctx context.Context, value servicemanager.ServiceBindingResponseObject) (subaccountServiceBindingType, diag.Diagnostics) { diff --git a/btp/provider/type_subaccount_service_instance.go b/btp/provider/type_subaccount_service_instance.go index f9ad109f..04131e4d 100644 --- a/btp/provider/type_subaccount_service_instance.go +++ b/btp/provider/type_subaccount_service_instance.go @@ -3,6 +3,7 @@ package provider import ( "context" + "github.com/hashicorp/terraform-plugin-framework-jsontypes/jsontypes" "github.com/hashicorp/terraform-plugin-framework-timeouts/resource/timeouts" "github.com/hashicorp/terraform-plugin-framework/diag" "github.com/hashicorp/terraform-plugin-framework/types" @@ -12,23 +13,23 @@ import ( ) type subaccountServiceInstanceType struct { - SubaccountId types.String `tfsdk:"subaccount_id"` - Id types.String `tfsdk:"id"` - Name types.String `tfsdk:"name"` - Parameters types.String `tfsdk:"parameters"` - Ready types.Bool `tfsdk:"ready"` - ServicePlanId types.String `tfsdk:"serviceplan_id"` - PlatformId types.String `tfsdk:"platform_id"` - ReferencedInstanceId types.String `tfsdk:"referenced_instance_id"` - Shared types.Bool `tfsdk:"shared"` - Context types.String `tfsdk:"context"` - Usable types.Bool `tfsdk:"usable"` - State types.String `tfsdk:"state"` - CreatedDate types.String `tfsdk:"created_date"` - LastModified types.String `tfsdk:"last_modified"` - Labels types.Map `tfsdk:"labels"` - Timeouts timeouts.Value `tfsdk:"timeouts"` - DashboardUrl types.String `tfsdk:"dashboard_url"` + SubaccountId types.String `tfsdk:"subaccount_id"` + Id types.String `tfsdk:"id"` + Name types.String `tfsdk:"name"` + Parameters jsontypes.Normalized `tfsdk:"parameters"` + Ready types.Bool `tfsdk:"ready"` + ServicePlanId types.String `tfsdk:"serviceplan_id"` + PlatformId types.String `tfsdk:"platform_id"` + ReferencedInstanceId types.String `tfsdk:"referenced_instance_id"` + Shared types.Bool `tfsdk:"shared"` + Context types.String `tfsdk:"context"` + Usable types.Bool `tfsdk:"usable"` + State types.String `tfsdk:"state"` + CreatedDate types.String `tfsdk:"created_date"` + LastModified types.String `tfsdk:"last_modified"` + Labels types.Map `tfsdk:"labels"` + Timeouts timeouts.Value `tfsdk:"timeouts"` + DashboardUrl types.String `tfsdk:"dashboard_url"` } func subaccountServiceInstanceValueFrom(ctx context.Context, value servicemanager.ServiceInstanceResponseObject) (subaccountServiceInstanceType, diag.Diagnostics) { diff --git a/btp/provider/type_subaccount_subscription.go b/btp/provider/type_subaccount_subscription.go index 60e5bf2d..4debcaf9 100644 --- a/btp/provider/type_subaccount_subscription.go +++ b/btp/provider/type_subaccount_subscription.go @@ -3,6 +3,7 @@ package provider import ( "context" + "github.com/hashicorp/terraform-plugin-framework-jsontypes/jsontypes" "github.com/hashicorp/terraform-plugin-framework-timeouts/resource/timeouts" "github.com/hashicorp/terraform-plugin-framework/diag" "github.com/hashicorp/terraform-plugin-framework/types" @@ -11,34 +12,34 @@ import ( ) type subaccountSubscriptionType struct { - SubaccountId types.String `tfsdk:"subaccount_id"` - Id types.String `tfsdk:"id"` - AppName types.String `tfsdk:"app_name"` - PlanName types.String `tfsdk:"plan_name"` - Parameters types.String `tfsdk:"parameters"` - AdditionalPlanFeatures types.Set `tfsdk:"additional_plan_features"` - AppId types.String `tfsdk:"app_id"` - AuthenticationProvider types.String `tfsdk:"authentication_provider"` - Category types.String `tfsdk:"category"` - CommercialAppName types.String `tfsdk:"commercial_app_name"` - CreatedDate types.String `tfsdk:"created_date"` - CustomerDeveloped types.Bool `tfsdk:"customer_developed"` - Description types.String `tfsdk:"description"` - DisplayName types.String `tfsdk:"display_name"` - FormationSolutionName types.String `tfsdk:"formation_solution_name"` - GlobalAccountId types.String `tfsdk:"globalaccount_id"` - Labels types.Map `tfsdk:"labels"` - LastModified types.String `tfsdk:"last_modified"` - PlatformEntityId types.String `tfsdk:"platform_entity_id"` - Quota types.Int64 `tfsdk:"quota"` - State types.String `tfsdk:"state"` - SubscribedSubaccountId types.String `tfsdk:"subscribed_subaccount_id"` - SubscribedTenantId types.String `tfsdk:"subscribed_tenant_id"` - SubscriptionUrl types.String `tfsdk:"subscription_url"` - SupportsParametersUpdates types.Bool `tfsdk:"supports_parameters_updates"` - SupportsPlanUpdates types.Bool `tfsdk:"supports_plan_updates"` - TenantId types.String `tfsdk:"tenant_id"` - Timeouts timeouts.Value `tfsdk:"timeouts"` + SubaccountId types.String `tfsdk:"subaccount_id"` + Id types.String `tfsdk:"id"` + AppName types.String `tfsdk:"app_name"` + PlanName types.String `tfsdk:"plan_name"` + Parameters jsontypes.Normalized `tfsdk:"parameters"` + AdditionalPlanFeatures types.Set `tfsdk:"additional_plan_features"` + AppId types.String `tfsdk:"app_id"` + AuthenticationProvider types.String `tfsdk:"authentication_provider"` + Category types.String `tfsdk:"category"` + CommercialAppName types.String `tfsdk:"commercial_app_name"` + CreatedDate types.String `tfsdk:"created_date"` + CustomerDeveloped types.Bool `tfsdk:"customer_developed"` + Description types.String `tfsdk:"description"` + DisplayName types.String `tfsdk:"display_name"` + FormationSolutionName types.String `tfsdk:"formation_solution_name"` + GlobalAccountId types.String `tfsdk:"globalaccount_id"` + Labels types.Map `tfsdk:"labels"` + LastModified types.String `tfsdk:"last_modified"` + PlatformEntityId types.String `tfsdk:"platform_entity_id"` + Quota types.Int64 `tfsdk:"quota"` + State types.String `tfsdk:"state"` + SubscribedSubaccountId types.String `tfsdk:"subscribed_subaccount_id"` + SubscribedTenantId types.String `tfsdk:"subscribed_tenant_id"` + SubscriptionUrl types.String `tfsdk:"subscription_url"` + SupportsParametersUpdates types.Bool `tfsdk:"supports_parameters_updates"` + SupportsPlanUpdates types.Bool `tfsdk:"supports_plan_updates"` + TenantId types.String `tfsdk:"tenant_id"` + Timeouts timeouts.Value `tfsdk:"timeouts"` } func subaccountSubscriptionValueFrom(ctx context.Context, value saas_manager_service.EntitledApplicationsResponseObject) (subaccountSubscriptionType, diag.Diagnostics) { @@ -57,7 +58,7 @@ func subaccountSubscriptionValueFrom(ctx context.Context, value saas_manager_ser FormationSolutionName: types.StringValue(value.FormationSolutionName), GlobalAccountId: types.StringValue(value.GlobalAccountId), LastModified: timeToValue(value.ModifiedDate.Time()), - Parameters: types.StringNull(), + Parameters: jsontypes.NewNormalizedNull(), PlanName: types.StringValue(value.PlanName), PlatformEntityId: types.StringValue(value.PlatformEntityId), Quota: types.Int64Value(int64(value.Quota)), diff --git a/go.mod b/go.mod index 6d0ef45e..38ffb59c 100644 --- a/go.mod +++ b/go.mod @@ -36,6 +36,7 @@ require ( github.com/hashicorp/logutils v1.0.0 // indirect github.com/hashicorp/terraform-exec v0.23.0 // indirect github.com/hashicorp/terraform-json v0.25.0 // indirect + github.com/hashicorp/terraform-plugin-framework-jsontypes v0.2.0 // indirect github.com/hashicorp/terraform-plugin-log v0.9.0 // indirect github.com/hashicorp/terraform-plugin-sdk/v2 v2.37.0 // indirect github.com/hashicorp/terraform-registry-address v0.4.0 // indirect diff --git a/go.sum b/go.sum index c6cfff3a..0cc94e36 100644 --- a/go.sum +++ b/go.sum @@ -84,6 +84,8 @@ github.com/hashicorp/terraform-json v0.25.0 h1:rmNqc/CIfcWawGiwXmRuiXJKEiJu1ntGo github.com/hashicorp/terraform-json v0.25.0/go.mod h1:sMKS8fiRDX4rVlR6EJUMudg1WcanxCMoWwTLkgZP/vc= github.com/hashicorp/terraform-plugin-framework v1.16.1 h1:1+zwFm3MEqd/0K3YBB2v9u9DtyYHyEuhVOfeIXbteWA= github.com/hashicorp/terraform-plugin-framework v1.16.1/go.mod h1:0xFOxLy5lRzDTayc4dzK/FakIgBhNf/lC4499R9cV4Y= +github.com/hashicorp/terraform-plugin-framework-jsontypes v0.2.0 h1:SJXL5FfJJm17554Kpt9jFXngdM6fXbnUnZ6iT2IeiYA= +github.com/hashicorp/terraform-plugin-framework-jsontypes v0.2.0/go.mod h1:p0phD0IYhsu9bR4+6OetVvvH59I6LwjXGnTVEr8ox6E= github.com/hashicorp/terraform-plugin-framework-timeouts v0.7.0 h1:jblRy1PkLfPm5hb5XeMa3tezusnMRziUGqtT5epSYoI= github.com/hashicorp/terraform-plugin-framework-timeouts v0.7.0/go.mod h1:5jm2XK8uqrdiSRfD5O47OoxyGMCnwTcl8eoiDgSa+tc= github.com/hashicorp/terraform-plugin-framework-validators v0.19.0 h1:Zz3iGgzxe/1XBkooZCewS0nJAaCFPFPHdNJd8FgE4Ow= diff --git a/internal/tfutils/tfutils.go b/internal/tfutils/tfutils.go index dc345595..d955ac84 100644 --- a/internal/tfutils/tfutils.go +++ b/internal/tfutils/tfutils.go @@ -240,3 +240,18 @@ func (je *jsonEncoder) Encode(_ reflect.Type, field reflect.Value) (string, erro return string(arr), err } + +// normalizeJSON takes a raw JSON string, unmarshals it, and re-marshals it to produce a normalized JSON string. +func NormalizeJSON(raw string) (string, error) { + var tmp interface{} + if err := json.Unmarshal([]byte(raw), &tmp); err != nil { + return "", err + } + + normalized, err := json.Marshal(tmp) + if err != nil { + return "", err + } + + return string(normalized), nil +} diff --git a/internal/tfutils/tfutils_test.go b/internal/tfutils/tfutils_test.go index 5a86412a..9d1a86bf 100644 --- a/internal/tfutils/tfutils_test.go +++ b/internal/tfutils/tfutils_test.go @@ -164,3 +164,56 @@ func TestToBTPCLIParamsMap(t *testing.T) { }) } } + +func TestNormalizeJSON(t *testing.T) { + tests := []struct { + name string + input string + expected string + expectError bool + }{ + { + name: "Valid JSON with reordered keys", + input: `{"id":1,"email":"test@sap.com"}`, + expected: `{"email":"test@sap.com","id":1}`, + expectError: false, + }, + { + name: "Valid JSON with nested structure", + input: `{"instance_name":"test","cf_users":[{"email":"test@sap.com","id":3}]}`, + expected: `{"cf_users":[{"email":"test@sap.com","id":3}],"instance_name":"test"}`, + expectError: false, + }, + { + name: "Empty JSON object", + input: `{}`, + expected: `{}`, + expectError: false, + }, + { + name: "Invalid JSON", + input: `{"a":1,}`, + expected: "", + expectError: true, + }, + { + name: "Valid JSON array", + input: `[{"b":2,"a":1},{"d":4,"c":3}]`, + expected: `[{"a":1,"b":2},{"c":3,"d":4}]`, + expectError: false, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + result, err := NormalizeJSON(tt.input) + + if tt.expectError { + assert.Error(t, err) + } else { + assert.NoError(t, err) + assert.JSONEq(t, tt.expected, result) + } + }) + } +}