diff --git a/managed/plan_modifier/plan_modifier.go b/managed/plan_modifier/plan_modifier.go new file mode 100644 index 0000000..d6abe51 --- /dev/null +++ b/managed/plan_modifier/plan_modifier.go @@ -0,0 +1,41 @@ +package planmodifier + +import ( + "context" + "fmt" + + "github.com/hashicorp/terraform-plugin-framework/tfsdk" +) + +// ImmutableFieldModifier is a plan modifier that enforces immutability of an attribute. +// This should be used with attribute's that can't be changed after resource creation and you +// want to show error to user during `tf plan` rather than `tf apply`. +// +// It should NOT be used for attribute's having "Computed: true" as they are already Read-Only. +// +// For nested attribute types, applying this plan modifier at root attribute is enough. +type ImmutableFieldModifier struct{} + +func (m ImmutableFieldModifier) Modify(ctx context.Context, req tfsdk.ModifyAttributePlanRequest, resp *tfsdk.ModifyAttributePlanResponse) { + if req.AttributeState == nil || req.AttributeState.IsNull() { + return + } + + if !req.AttributeConfig.IsNull() && !req.AttributeConfig.Equal(req.AttributeState) { + resp.Diagnostics.AddError( + "Immutable Field Error", + fmt.Sprintf( + "Field '%s' is immutable and cannot be updated. Please destroy and recreate the resource if changes are needed.", + req.AttributePath.String(), + ), + ) + } +} + +func (m ImmutableFieldModifier) Description(ctx context.Context) string { + return "Errors if the field is changed after resource creation" +} + +func (m ImmutableFieldModifier) MarkdownDescription(ctx context.Context) string { + return "Errors if the field is changed after resource creation" +} diff --git a/managed/resource_integration.go b/managed/resource_integration.go index c082d37..93bc813 100644 --- a/managed/resource_integration.go +++ b/managed/resource_integration.go @@ -17,6 +17,7 @@ import ( "github.com/hashicorp/terraform-plugin-framework/types" "github.com/hashicorp/terraform-plugin-log/tflog" "github.com/yugabyte/terraform-provider-ybm/managed/fflags" + planmodifier "github.com/yugabyte/terraform-provider-ybm/managed/plan_modifier" openapiclient "github.com/yugabyte/yugabytedb-managed-go-client-internal" ) @@ -53,12 +54,18 @@ func (r resourceIntegrationType) getSchemaAttributes() map[string]tfsdk.Attribut Description: "The name of the integration", Type: types.StringType, Required: true, + PlanModifiers: []tfsdk.AttributePlanModifier{ + planmodifier.ImmutableFieldModifier{}, + }, }, "type": { Description: "Defines different exporter destination types.", Type: types.StringType, Required: true, Validators: []tfsdk.AttributeValidator{stringvalidator.OneOf("DATADOG", "GRAFANA", "SUMOLOGIC", "GOOGLECLOUD", "PROMETHEUS", "VICTORIAMETRICS")}, + PlanModifiers: []tfsdk.AttributePlanModifier{ + planmodifier.ImmutableFieldModifier{}, + }, }, "is_valid": { Description: "Signifies whether the integration configuration is valid or not", @@ -68,7 +75,10 @@ func (r resourceIntegrationType) getSchemaAttributes() map[string]tfsdk.Attribut "datadog_spec": { Description: "The specifications of a Datadog integration.", Optional: true, - Validators: onlyContainsPath("datadog_spec"), + PlanModifiers: []tfsdk.AttributePlanModifier{ + planmodifier.ImmutableFieldModifier{}, + }, + Validators: onlyContainsPath("datadog_spec"), Attributes: tfsdk.SingleNestedAttributes(map[string]tfsdk.Attribute{ "api_key": { Description: "Datadog Api Key", @@ -80,13 +90,15 @@ func (r resourceIntegrationType) getSchemaAttributes() map[string]tfsdk.Attribut Description: "Datadog site.", Type: types.StringType, Required: true, - }, - }), + }}), }, "prometheus_spec": { Description: "The specifications of a Prometheus integration.", Optional: true, - Validators: onlyContainsPath("prometheus_spec"), + PlanModifiers: []tfsdk.AttributePlanModifier{ + planmodifier.ImmutableFieldModifier{}, + }, + Validators: onlyContainsPath("prometheus_spec"), Attributes: tfsdk.SingleNestedAttributes(map[string]tfsdk.Attribute{ "endpoint": { Description: "Prometheus OTLP endpoint URL e.g. http://my-prometheus-endpoint/api/v1/otlp", @@ -98,7 +110,10 @@ func (r resourceIntegrationType) getSchemaAttributes() map[string]tfsdk.Attribut "victoriametrics_spec": { Description: "The specifications of a VictoriaMetrics integration.", Optional: true, - Validators: onlyContainsPath("victoriametrics_spec"), + PlanModifiers: []tfsdk.AttributePlanModifier{ + planmodifier.ImmutableFieldModifier{}, + }, + Validators: onlyContainsPath("victoriametrics_spec"), Attributes: tfsdk.SingleNestedAttributes(map[string]tfsdk.Attribute{ "endpoint": { Description: "VictoriaMetrics OTLP endpoint URL e.g. http://my-victoria-metrics-endpoint/opentelemetry", @@ -110,7 +125,10 @@ func (r resourceIntegrationType) getSchemaAttributes() map[string]tfsdk.Attribut "grafana_spec": { Description: "The specifications of a Grafana integration.", Optional: true, - Validators: onlyContainsPath("grafana_spec"), + PlanModifiers: []tfsdk.AttributePlanModifier{ + planmodifier.ImmutableFieldModifier{}, + }, + Validators: onlyContainsPath("grafana_spec"), Attributes: tfsdk.SingleNestedAttributes(map[string]tfsdk.Attribute{ "access_policy_token": { Description: "Grafana Access Policy Token", @@ -138,7 +156,10 @@ func (r resourceIntegrationType) getSchemaAttributes() map[string]tfsdk.Attribut "sumologic_spec": { Description: "The specifications of a Sumo Logic integration.", Optional: true, - Validators: onlyContainsPath("sumologic_spec"), + PlanModifiers: []tfsdk.AttributePlanModifier{ + planmodifier.ImmutableFieldModifier{}, + }, + Validators: onlyContainsPath("sumologic_spec"), Attributes: tfsdk.SingleNestedAttributes(map[string]tfsdk.Attribute{ "access_id": { Description: "Sumo Logic Access Key ID", @@ -151,8 +172,7 @@ func (r resourceIntegrationType) getSchemaAttributes() map[string]tfsdk.Attribut Type: types.StringType, Required: true, Sensitive: true, - }, - "installation_token": { + }, "installation_token": { Description: "A Sumo Logic installation token to export telemetry to Grafana with", Type: types.StringType, Required: true, @@ -163,7 +183,10 @@ func (r resourceIntegrationType) getSchemaAttributes() map[string]tfsdk.Attribut "googlecloud_spec": { Description: "The specifications of a Google Cloud integration.", Optional: true, - Validators: onlyContainsPath("googlecloud_spec"), + PlanModifiers: []tfsdk.AttributePlanModifier{ + planmodifier.ImmutableFieldModifier{}, + }, + Validators: onlyContainsPath("googlecloud_spec"), Attributes: tfsdk.SingleNestedAttributes(map[string]tfsdk.Attribute{ "type": { Description: "Service Account Type",