diff --git a/ec/ecresource/deploymentresource/apm/v2/schema.go b/ec/ecresource/deploymentresource/apm/v2/schema.go index 6eabbfe11..f5f5a0703 100644 --- a/ec/ecresource/deploymentresource/apm/v2/schema.go +++ b/ec/ecresource/deploymentresource/apm/v2/schema.go @@ -45,18 +45,30 @@ func ApmConfigSchema() schema.Attribute { "user_settings_json": schema.StringAttribute{ Description: `An arbitrary JSON object allowing (non-admin) cluster owners to set their parameters (only one of this and 'user_settings_yaml' is allowed), provided they are on the whitelist ('user_settings_whitelist') and not on the blacklist ('user_settings_blacklist'). (This field together with 'user_settings_override*' and 'system_settings' defines the total set of resource settings)`, Optional: true, + PlanModifiers: []planmodifier.String{ + planmodifiers.SetNullWhenEmptyString(), + }, }, "user_settings_override_json": schema.StringAttribute{ Description: `An arbitrary JSON object allowing ECE admins owners to set clusters' parameters (only one of this and 'user_settings_override_yaml' is allowed), ie in addition to the documented 'system_settings'. (This field together with 'system_settings' and 'user_settings*' defines the total set of resource settings)`, Optional: true, + PlanModifiers: []planmodifier.String{ + planmodifiers.SetNullWhenEmptyString(), + }, }, "user_settings_yaml": schema.StringAttribute{ Description: `An arbitrary YAML object allowing (non-admin) cluster owners to set their parameters (only one of this and 'user_settings_json' is allowed), provided they are on the whitelist ('user_settings_whitelist') and not on the blacklist ('user_settings_blacklist'). (These field together with 'user_settings_override*' and 'system_settings' defines the total set of resource settings)`, Optional: true, + PlanModifiers: []planmodifier.String{ + planmodifiers.SetNullWhenEmptyString(), + }, }, "user_settings_override_yaml": schema.StringAttribute{ Description: `An arbitrary YAML object allowing ECE admins owners to set clusters' parameters (only one of this and 'user_settings_override_json' is allowed), ie in addition to the documented 'system_settings'. (This field together with 'system_settings' and 'user_settings*' defines the total set of resource settings)`, Optional: true, + PlanModifiers: []planmodifier.String{ + planmodifiers.SetNullWhenEmptyString(), + }, }, }, } diff --git a/ec/ecresource/deploymentresource/elasticsearch/v2/schema.go b/ec/ecresource/deploymentresource/elasticsearch/v2/schema.go index 9250f9219..bb11cb29f 100644 --- a/ec/ecresource/deploymentresource/elasticsearch/v2/schema.go +++ b/ec/ecresource/deploymentresource/elasticsearch/v2/schema.go @@ -175,18 +175,30 @@ func elasticsearchConfigSchema() schema.Attribute { "user_settings_json": schema.StringAttribute{ Description: `JSON-formatted user level "elasticsearch.yml" setting overrides`, Optional: true, + PlanModifiers: []planmodifier.String{ + planmodifiers.SetNullWhenEmptyString(), + }, }, "user_settings_override_json": schema.StringAttribute{ Description: `JSON-formatted admin (ECE) level "elasticsearch.yml" setting overrides`, Optional: true, + PlanModifiers: []planmodifier.String{ + planmodifiers.SetNullWhenEmptyString(), + }, }, "user_settings_yaml": schema.StringAttribute{ Description: `YAML-formatted user level "elasticsearch.yml" setting overrides`, Optional: true, + PlanModifiers: []planmodifier.String{ + planmodifiers.SetNullWhenEmptyString(), + }, }, "user_settings_override_yaml": schema.StringAttribute{ Description: `YAML-formatted admin (ECE) level "elasticsearch.yml" setting overrides`, Optional: true, + PlanModifiers: []planmodifier.String{ + planmodifiers.SetNullWhenEmptyString(), + }, }, }, } diff --git a/ec/ecresource/deploymentresource/enterprisesearch/v2/schema.go b/ec/ecresource/deploymentresource/enterprisesearch/v2/schema.go index c29f5d25d..3938d8fe4 100644 --- a/ec/ecresource/deploymentresource/enterprisesearch/v2/schema.go +++ b/ec/ecresource/deploymentresource/enterprisesearch/v2/schema.go @@ -127,18 +127,30 @@ func EnterpriseSearchSchema() schema.Attribute { "user_settings_json": schema.StringAttribute{ Description: `An arbitrary JSON object allowing (non-admin) cluster owners to set their parameters (only one of this and 'user_settings_yaml' is allowed), provided they are on the whitelist ('user_settings_whitelist') and not on the blacklist ('user_settings_blacklist'). (This field together with 'user_settings_override*' and 'system_settings' defines the total set of resource settings)`, Optional: true, + PlanModifiers: []planmodifier.String{ + planmodifiers.SetNullWhenEmptyString(), + }, }, "user_settings_override_json": schema.StringAttribute{ Description: `An arbitrary JSON object allowing ECE admins owners to set clusters' parameters (only one of this and 'user_settings_override_yaml' is allowed), ie in addition to the documented 'system_settings'. (This field together with 'system_settings' and 'user_settings*' defines the total set of resource settings)`, Optional: true, + PlanModifiers: []planmodifier.String{ + planmodifiers.SetNullWhenEmptyString(), + }, }, "user_settings_yaml": schema.StringAttribute{ Description: `An arbitrary YAML object allowing (non-admin) cluster owners to set their parameters (only one of this and 'user_settings_json' is allowed), provided they are on the whitelist ('user_settings_whitelist') and not on the blacklist ('user_settings_blacklist'). (These field together with 'user_settings_override*' and 'system_settings' defines the total set of resource settings)`, Optional: true, + PlanModifiers: []planmodifier.String{ + planmodifiers.SetNullWhenEmptyString(), + }, }, "user_settings_override_yaml": schema.StringAttribute{ Description: `An arbitrary YAML object allowing ECE admins owners to set clusters' parameters (only one of this and 'user_settings_override_json' is allowed), ie in addition to the documented 'system_settings'. (This field together with 'system_settings' and 'user_settings*' defines the total set of resource settings)`, Optional: true, + PlanModifiers: []planmodifier.String{ + planmodifiers.SetNullWhenEmptyString(), + }, }, }, }, diff --git a/ec/ecresource/deploymentresource/integrationsserver/v2/schema.go b/ec/ecresource/deploymentresource/integrationsserver/v2/schema.go index d77203475..8f9be4a56 100644 --- a/ec/ecresource/deploymentresource/integrationsserver/v2/schema.go +++ b/ec/ecresource/deploymentresource/integrationsserver/v2/schema.go @@ -131,18 +131,30 @@ func IntegrationsServerSchema() schema.Attribute { "user_settings_json": schema.StringAttribute{ Description: `An arbitrary JSON object allowing (non-admin) cluster owners to set their parameters (only one of this and 'user_settings_yaml' is allowed), provided they are on the whitelist ('user_settings_whitelist') and not on the blacklist ('user_settings_blacklist'). (This field together with 'user_settings_override*' and 'system_settings' defines the total set of resource settings)`, Optional: true, + PlanModifiers: []planmodifier.String{ + planmodifiers.SetNullWhenEmptyString(), + }, }, "user_settings_override_json": schema.StringAttribute{ Description: `An arbitrary JSON object allowing ECE admins owners to set clusters' parameters (only one of this and 'user_settings_override_yaml' is allowed), ie in addition to the documented 'system_settings'. (This field together with 'system_settings' and 'user_settings*' defines the total set of resource settings)`, Optional: true, + PlanModifiers: []planmodifier.String{ + planmodifiers.SetNullWhenEmptyString(), + }, }, "user_settings_yaml": schema.StringAttribute{ Description: `An arbitrary YAML object allowing (non-admin) cluster owners to set their parameters (only one of this and 'user_settings_json' is allowed), provided they are on the whitelist ('user_settings_whitelist') and not on the blacklist ('user_settings_blacklist'). (These field together with 'user_settings_override*' and 'system_settings' defines the total set of resource settings)`, Optional: true, + PlanModifiers: []planmodifier.String{ + planmodifiers.SetNullWhenEmptyString(), + }, }, "user_settings_override_yaml": schema.StringAttribute{ Description: `An arbitrary YAML object allowing ECE admins owners to set clusters' parameters (only one of this and 'user_settings_override_json' is allowed), ie in addition to the documented 'system_settings'. (This field together with 'system_settings' and 'user_settings*' defines the total set of resource settings)`, Optional: true, + PlanModifiers: []planmodifier.String{ + planmodifiers.SetNullWhenEmptyString(), + }, }, }, }, diff --git a/ec/ecresource/deploymentresource/kibana/v2/schema.go b/ec/ecresource/deploymentresource/kibana/v2/schema.go index f4b912643..0b8c6a2c5 100644 --- a/ec/ecresource/deploymentresource/kibana/v2/schema.go +++ b/ec/ecresource/deploymentresource/kibana/v2/schema.go @@ -110,18 +110,30 @@ func KibanaSchema() schema.Attribute { "user_settings_json": schema.StringAttribute{ Description: `An arbitrary JSON object allowing (non-admin) cluster owners to set their parameters (only one of this and 'user_settings_yaml' is allowed), provided they are on the whitelist ('user_settings_whitelist') and not on the blacklist ('user_settings_blacklist'). (This field together with 'user_settings_override*' and 'system_settings' defines the total set of resource settings)`, Optional: true, + PlanModifiers: []planmodifier.String{ + planmodifiers.SetNullWhenEmptyString(), + }, }, "user_settings_override_json": schema.StringAttribute{ Description: `An arbitrary JSON object allowing ECE admins owners to set clusters' parameters (only one of this and 'user_settings_override_yaml' is allowed), ie in addition to the documented 'system_settings'. (This field together with 'system_settings' and 'user_settings*' defines the total set of resource settings)`, Optional: true, + PlanModifiers: []planmodifier.String{ + planmodifiers.SetNullWhenEmptyString(), + }, }, "user_settings_yaml": schema.StringAttribute{ Description: `An arbitrary YAML object allowing (non-admin) cluster owners to set their parameters (only one of this and 'user_settings_json' is allowed), provided they are on the whitelist ('user_settings_whitelist') and not on the blacklist ('user_settings_blacklist'). (These field together with 'user_settings_override*' and 'system_settings' defines the total set of resource settings)`, Optional: true, + PlanModifiers: []planmodifier.String{ + planmodifiers.SetNullWhenEmptyString(), + }, }, "user_settings_override_yaml": schema.StringAttribute{ Description: `An arbitrary YAML object allowing ECE admins owners to set clusters' parameters (only one of this and 'user_settings_override_json' is allowed), ie in addition to the documented 'system_settings'. (This field together with 'system_settings' and 'user_settings*' defines the total set of resource settings)`, Optional: true, + PlanModifiers: []planmodifier.String{ + planmodifiers.SetNullWhenEmptyString(), + }, }, }, }, diff --git a/ec/internal/planmodifiers/set_null_when_empty_string.go b/ec/internal/planmodifiers/set_null_when_empty_string.go new file mode 100644 index 000000000..e193f84e3 --- /dev/null +++ b/ec/internal/planmodifiers/set_null_when_empty_string.go @@ -0,0 +1,53 @@ +// Licensed to Elasticsearch B.V. under one or more contributor +// license agreements. See the NOTICE file distributed with +// this work for additional information regarding copyright +// ownership. Elasticsearch B.V. licenses this file to you under +// the Apache 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 +// +// http://www.apache.org/licenses/LICENSE-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. + +package planmodifiers + +import ( + "context" + + "github.com/hashicorp/terraform-plugin-framework/resource/schema/planmodifier" + "github.com/hashicorp/terraform-plugin-framework/types" +) + +func SetNullWhenEmptyString() planmodifier.String { + return setNullWhenEmptyString{} +} + +type setNullWhenEmptyString struct{} + +func (m setNullWhenEmptyString) PlanModifyString(ctx context.Context, req planmodifier.StringRequest, resp *planmodifier.StringResponse) { + if req.PlanValue.IsNull() || req.PlanValue.IsUnknown() { + return + } + + if req.PlanValue.ValueString() != "" { + return + } + + resp.PlanValue = types.StringNull() +} + +// Description returns a human-readable description of the plan modifier. +func (r setNullWhenEmptyString) Description(ctx context.Context) string { + return "Set the plan value to null if currently configured to null." +} + +// MarkdownDescription returns a markdown description of the plan modifier. +func (r setNullWhenEmptyString) MarkdownDescription(ctx context.Context) string { + return "Set the plan value to null if currently configured to null." +} diff --git a/ec/internal/planmodifiers/set_null_when_empty_string_test.go b/ec/internal/planmodifiers/set_null_when_empty_string_test.go new file mode 100644 index 000000000..e0dce61ab --- /dev/null +++ b/ec/internal/planmodifiers/set_null_when_empty_string_test.go @@ -0,0 +1,72 @@ +// Licensed to Elasticsearch B.V. under one or more contributor +// license agreements. See the NOTICE file distributed with +// this work for additional information regarding copyright +// ownership. Elasticsearch B.V. licenses this file to you under +// the Apache 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 +// +// http://www.apache.org/licenses/LICENSE-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. + +package planmodifiers_test + +import ( + "context" + "testing" + + "github.com/elastic/terraform-provider-ec/ec/internal/planmodifiers" + "github.com/hashicorp/terraform-plugin-framework/resource/schema/planmodifier" + "github.com/hashicorp/terraform-plugin-framework/types" + "github.com/stretchr/testify/require" +) + +func TestSetNullWhenEmptyString(t *testing.T) { + tests := []struct { + name string + planValue types.String + expectedPlanValue types.String + }{ + { + name: "should do nothing when the plan value is null", + planValue: types.StringNull(), + expectedPlanValue: types.StringNull(), + }, + { + name: "should do nothing when the plan value is unknown", + planValue: types.StringUnknown(), + expectedPlanValue: types.StringUnknown(), + }, + { + name: "should do nothing when the plan value is not empty", + planValue: types.StringValue("hello"), + expectedPlanValue: types.StringValue("hello"), + }, + { + name: "should set the plan value to null when the plan value is empty", + planValue: types.StringValue(""), + expectedPlanValue: types.StringNull(), + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + modifier := planmodifiers.SetNullWhenEmptyString() + + resp := planmodifier.StringResponse{ + PlanValue: tt.planValue, + } + modifier.PlanModifyString(context.Background(), planmodifier.StringRequest{ + PlanValue: tt.planValue, + }, &resp) + + require.Equal(t, tt.expectedPlanValue, resp.PlanValue) + }) + } +}