Skip to content

Commit 17af3d7

Browse files
[CLOUDGA-24889] Show error during TF plan for immutable field change (#145)
1 parent 79634ed commit 17af3d7

File tree

2 files changed

+74
-10
lines changed

2 files changed

+74
-10
lines changed
Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
package planmodifier
2+
3+
import (
4+
"context"
5+
"fmt"
6+
7+
"github.com/hashicorp/terraform-plugin-framework/tfsdk"
8+
)
9+
10+
// ImmutableFieldModifier is a plan modifier that enforces immutability of an attribute.
11+
// This should be used with attribute's that can't be changed after resource creation and you
12+
// want to show error to user during `tf plan` rather than `tf apply`.
13+
//
14+
// It should NOT be used for attribute's having "Computed: true" as they are already Read-Only.
15+
//
16+
// For nested attribute types, applying this plan modifier at root attribute is enough.
17+
type ImmutableFieldModifier struct{}
18+
19+
func (m ImmutableFieldModifier) Modify(ctx context.Context, req tfsdk.ModifyAttributePlanRequest, resp *tfsdk.ModifyAttributePlanResponse) {
20+
if req.AttributeState == nil || req.AttributeState.IsNull() {
21+
return
22+
}
23+
24+
if !req.AttributeConfig.IsNull() && !req.AttributeConfig.Equal(req.AttributeState) {
25+
resp.Diagnostics.AddError(
26+
"Immutable Field Error",
27+
fmt.Sprintf(
28+
"Field '%s' is immutable and cannot be updated. Please destroy and recreate the resource if changes are needed.",
29+
req.AttributePath.String(),
30+
),
31+
)
32+
}
33+
}
34+
35+
func (m ImmutableFieldModifier) Description(ctx context.Context) string {
36+
return "Errors if the field is changed after resource creation"
37+
}
38+
39+
func (m ImmutableFieldModifier) MarkdownDescription(ctx context.Context) string {
40+
return "Errors if the field is changed after resource creation"
41+
}

managed/resource_integration.go

Lines changed: 33 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ import (
1717
"github.com/hashicorp/terraform-plugin-framework/types"
1818
"github.com/hashicorp/terraform-plugin-log/tflog"
1919
"github.com/yugabyte/terraform-provider-ybm/managed/fflags"
20+
planmodifier "github.com/yugabyte/terraform-provider-ybm/managed/plan_modifier"
2021
openapiclient "github.com/yugabyte/yugabytedb-managed-go-client-internal"
2122
)
2223

@@ -53,12 +54,18 @@ func (r resourceIntegrationType) getSchemaAttributes() map[string]tfsdk.Attribut
5354
Description: "The name of the integration",
5455
Type: types.StringType,
5556
Required: true,
57+
PlanModifiers: []tfsdk.AttributePlanModifier{
58+
planmodifier.ImmutableFieldModifier{},
59+
},
5660
},
5761
"type": {
5862
Description: "Defines different exporter destination types.",
5963
Type: types.StringType,
6064
Required: true,
6165
Validators: []tfsdk.AttributeValidator{stringvalidator.OneOf("DATADOG", "GRAFANA", "SUMOLOGIC", "GOOGLECLOUD", "PROMETHEUS", "VICTORIAMETRICS")},
66+
PlanModifiers: []tfsdk.AttributePlanModifier{
67+
planmodifier.ImmutableFieldModifier{},
68+
},
6269
},
6370
"is_valid": {
6471
Description: "Signifies whether the integration configuration is valid or not",
@@ -68,7 +75,10 @@ func (r resourceIntegrationType) getSchemaAttributes() map[string]tfsdk.Attribut
6875
"datadog_spec": {
6976
Description: "The specifications of a Datadog integration.",
7077
Optional: true,
71-
Validators: onlyContainsPath("datadog_spec"),
78+
PlanModifiers: []tfsdk.AttributePlanModifier{
79+
planmodifier.ImmutableFieldModifier{},
80+
},
81+
Validators: onlyContainsPath("datadog_spec"),
7282
Attributes: tfsdk.SingleNestedAttributes(map[string]tfsdk.Attribute{
7383
"api_key": {
7484
Description: "Datadog Api Key",
@@ -80,13 +90,15 @@ func (r resourceIntegrationType) getSchemaAttributes() map[string]tfsdk.Attribut
8090
Description: "Datadog site.",
8191
Type: types.StringType,
8292
Required: true,
83-
},
84-
}),
93+
}}),
8594
},
8695
"prometheus_spec": {
8796
Description: "The specifications of a Prometheus integration.",
8897
Optional: true,
89-
Validators: onlyContainsPath("prometheus_spec"),
98+
PlanModifiers: []tfsdk.AttributePlanModifier{
99+
planmodifier.ImmutableFieldModifier{},
100+
},
101+
Validators: onlyContainsPath("prometheus_spec"),
90102
Attributes: tfsdk.SingleNestedAttributes(map[string]tfsdk.Attribute{
91103
"endpoint": {
92104
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
98110
"victoriametrics_spec": {
99111
Description: "The specifications of a VictoriaMetrics integration.",
100112
Optional: true,
101-
Validators: onlyContainsPath("victoriametrics_spec"),
113+
PlanModifiers: []tfsdk.AttributePlanModifier{
114+
planmodifier.ImmutableFieldModifier{},
115+
},
116+
Validators: onlyContainsPath("victoriametrics_spec"),
102117
Attributes: tfsdk.SingleNestedAttributes(map[string]tfsdk.Attribute{
103118
"endpoint": {
104119
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
110125
"grafana_spec": {
111126
Description: "The specifications of a Grafana integration.",
112127
Optional: true,
113-
Validators: onlyContainsPath("grafana_spec"),
128+
PlanModifiers: []tfsdk.AttributePlanModifier{
129+
planmodifier.ImmutableFieldModifier{},
130+
},
131+
Validators: onlyContainsPath("grafana_spec"),
114132
Attributes: tfsdk.SingleNestedAttributes(map[string]tfsdk.Attribute{
115133
"access_policy_token": {
116134
Description: "Grafana Access Policy Token",
@@ -138,7 +156,10 @@ func (r resourceIntegrationType) getSchemaAttributes() map[string]tfsdk.Attribut
138156
"sumologic_spec": {
139157
Description: "The specifications of a Sumo Logic integration.",
140158
Optional: true,
141-
Validators: onlyContainsPath("sumologic_spec"),
159+
PlanModifiers: []tfsdk.AttributePlanModifier{
160+
planmodifier.ImmutableFieldModifier{},
161+
},
162+
Validators: onlyContainsPath("sumologic_spec"),
142163
Attributes: tfsdk.SingleNestedAttributes(map[string]tfsdk.Attribute{
143164
"access_id": {
144165
Description: "Sumo Logic Access Key ID",
@@ -151,8 +172,7 @@ func (r resourceIntegrationType) getSchemaAttributes() map[string]tfsdk.Attribut
151172
Type: types.StringType,
152173
Required: true,
153174
Sensitive: true,
154-
},
155-
"installation_token": {
175+
}, "installation_token": {
156176
Description: "A Sumo Logic installation token to export telemetry to Grafana with",
157177
Type: types.StringType,
158178
Required: true,
@@ -163,7 +183,10 @@ func (r resourceIntegrationType) getSchemaAttributes() map[string]tfsdk.Attribut
163183
"googlecloud_spec": {
164184
Description: "The specifications of a Google Cloud integration.",
165185
Optional: true,
166-
Validators: onlyContainsPath("googlecloud_spec"),
186+
PlanModifiers: []tfsdk.AttributePlanModifier{
187+
planmodifier.ImmutableFieldModifier{},
188+
},
189+
Validators: onlyContainsPath("googlecloud_spec"),
167190
Attributes: tfsdk.SingleNestedAttributes(map[string]tfsdk.Attribute{
168191
"type": {
169192
Description: "Service Account Type",

0 commit comments

Comments
 (0)