Skip to content

Commit 6e7ffbb

Browse files
committed
refactor: Replace CreateOnlyAttributePlanModifier with CreateOnlyStringPlanModifier and add CreateOnlyBoolPlanModifier
1 parent 6909929 commit 6e7ffbb

File tree

3 files changed

+67
-28
lines changed

3 files changed

+67
-28
lines changed

internal/common/customplanmodifier/create_only.go renamed to internal/common/customplanmodifier/create_only_bool.go

Lines changed: 17 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -12,59 +12,45 @@ import (
1212
"github.com/hashicorp/terraform-plugin-framework/types"
1313
)
1414

15-
// CreateOnlyStringPlanModifier creates a plan modifier that prevents updates to string attributes.
16-
func CreateOnlyStringPlanModifier() planmodifier.String {
17-
return &createOnlyAttributePlanModifier{}
18-
}
19-
2015
// CreateOnlyBoolPlanModifier creates a plan modifier that prevents updates to boolean attributes.
21-
func CreateOnlyBoolPlanModifier() planmodifier.Bool {
22-
return &createOnlyAttributePlanModifier{}
23-
}
24-
25-
type CreateOnlyModifier interface {
26-
planmodifier.String
27-
planmodifier.Bool
28-
}
29-
30-
// CreateOnlyAttributePlanModifier returns a plan modifier that ensures that update operations fails when the attribute is changed.
3116
// This is useful for attributes only supported in create and not in update.
3217
// It shows a helpful error message helping the user to update their config to match the state.
33-
// Never use a schema.Default for create only attributes, instead use WithXXXDefault, the default will lead to plan changes that are not expected after import.
18+
// Never use a schema.Default for create only attributes, instead use `WithDefault`, the default will lead to plan changes that are not expected after import.
3419
// Implement CopyFromPlan if the attribute is not in the API Response.
35-
func CreateOnlyAttributePlanModifier() CreateOnlyModifier {
36-
return &createOnlyAttributePlanModifier{}
20+
func CreateOnlyBoolPlanModifier() planmodifier.Bool {
21+
return &createOnlyBoolPlanModifier{}
3722
}
3823

3924
// CreateOnlyBoolWithDefaultPlanModifier sets a default value on create operation that will show in the plan.
4025
// This avoids any custom logic in the resource "Create" handler.
4126
// On update the default has no impact and the UseStateForUnknown behavior is observed instead.
4227
// Always use Optional+Computed when using a default value.
43-
func CreateOnlyBoolWithDefaultPlanModifier(b bool) CreateOnlyModifier {
44-
return &createOnlyAttributePlanModifier{defaultBool: &b}
28+
func CreateOnlyBoolWithDefaultPlanModifier(b bool) planmodifier.Bool {
29+
return &createOnlyBoolPlanModifier{defaultBool: &b}
4530
}
4631

47-
type createOnlyAttributePlanModifier struct {
32+
type createOnlyBoolPlanModifier struct {
4833
defaultBool *bool
4934
}
5035

51-
func (d *createOnlyAttributePlanModifier) Description(ctx context.Context) string {
36+
func (d *createOnlyBoolPlanModifier) Description(ctx context.Context) string {
5237
return d.MarkdownDescription(ctx)
5338
}
5439

55-
func (d *createOnlyAttributePlanModifier) MarkdownDescription(ctx context.Context) string {
40+
func (d *createOnlyBoolPlanModifier) MarkdownDescription(ctx context.Context) string {
5641
return "Ensures the update operation fails when updating an attribute. If the read after import don't equal the configuration value it will also raise an error."
5742
}
5843

44+
// isCreate uses the full state to check if this is a create operation
5945
func isCreate(t *tfsdk.State) bool {
6046
return t.Raw.IsNull()
6147
}
6248

63-
func (d *createOnlyAttributePlanModifier) UseDefault() bool {
49+
func (d *createOnlyBoolPlanModifier) UseDefault() bool {
6450
return d.defaultBool != nil
6551
}
6652

67-
func (d *createOnlyAttributePlanModifier) PlanModifyBool(ctx context.Context, req planmodifier.BoolRequest, resp *planmodifier.BoolResponse) {
53+
func (d *createOnlyBoolPlanModifier) PlanModifyBool(ctx context.Context, req planmodifier.BoolRequest, resp *planmodifier.BoolResponse) {
6854
if isCreate(&req.State) {
6955
if !IsKnown(req.PlanValue) && d.UseDefault() {
7056
resp.PlanValue = types.BoolPointerValue(d.defaultBool)
@@ -79,7 +65,7 @@ func (d *createOnlyAttributePlanModifier) PlanModifyBool(ctx context.Context, re
7965
}
8066
}
8167

82-
func (d *createOnlyAttributePlanModifier) PlanModifyString(ctx context.Context, req planmodifier.StringRequest, resp *planmodifier.StringResponse) {
68+
func (d *createOnlyBoolPlanModifier) PlanModifyString(ctx context.Context, req planmodifier.StringRequest, resp *planmodifier.StringResponse) {
8369
if isCreate(&req.State) {
8470
return
8571
}
@@ -91,14 +77,18 @@ func (d *createOnlyAttributePlanModifier) PlanModifyString(ctx context.Context,
9177
}
9278
}
9379

80+
// isUpdated checks if the attribute was updated.
81+
// Special case when the attribute is removed/set to null in the plan:
82+
// Computed Attribute: returns false (unknown in the plan)
83+
// Optional Attribute: returns true if the state has a value
9484
func isUpdated(state, plan attr.Value) bool {
9585
if !IsKnown(plan) {
9686
return false
9787
}
9888
return !state.Equal(plan)
9989
}
10090

101-
func (d *createOnlyAttributePlanModifier) addDiags(diags *diag.Diagnostics, attrPath path.Path, stateValue attr.Value) {
91+
func (d *createOnlyBoolPlanModifier) addDiags(diags *diag.Diagnostics, attrPath path.Path, stateValue attr.Value) {
10292
message := fmt.Sprintf("%s cannot be updated or set after import, remove it from the configuration or use the state value (see below).", attrPath)
10393
detail := fmt.Sprintf("The current state value is %s", stateValue)
10494
diags.AddError(message, detail)
Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
package customplanmodifier
2+
3+
import (
4+
"context"
5+
"fmt"
6+
7+
"github.com/hashicorp/terraform-plugin-framework/attr"
8+
"github.com/hashicorp/terraform-plugin-framework/diag"
9+
"github.com/hashicorp/terraform-plugin-framework/path"
10+
planmodifier "github.com/hashicorp/terraform-plugin-framework/resource/schema/planmodifier"
11+
)
12+
13+
// CreateOnlyStringPlanModifier creates a plan modifier that prevents updates to string attributes.
14+
// This is useful for attributes only supported in create and not in update.
15+
// It shows a helpful error message helping the user to update their config to match the state.
16+
// Never use a schema.Default for create only attributes, instead use `WithDefault`, the default will lead to plan changes that are not expected after import.
17+
// No default value implemented for string until we have a use case.
18+
// Implement CopyFromPlan if the attribute is not in the API Response.
19+
func CreateOnlyStringPlanModifier() planmodifier.String {
20+
return &createOnlyStringPlanModifier{}
21+
}
22+
23+
type createOnlyStringPlanModifier struct{}
24+
25+
func (d *createOnlyStringPlanModifier) Description(ctx context.Context) string {
26+
return d.MarkdownDescription(ctx)
27+
}
28+
29+
func (d *createOnlyStringPlanModifier) MarkdownDescription(ctx context.Context) string {
30+
return "Ensures the update operation fails when updating an attribute. If the read after import don't equal the configuration value it will also raise an error."
31+
}
32+
33+
func (d *createOnlyStringPlanModifier) PlanModifyString(ctx context.Context, req planmodifier.StringRequest, resp *planmodifier.StringResponse) {
34+
if isCreate(&req.State) {
35+
return
36+
}
37+
if isUpdated(req.StateValue, req.PlanValue) {
38+
d.addDiags(&resp.Diagnostics, req.Path, req.StateValue)
39+
}
40+
if !IsKnown(req.PlanValue) {
41+
resp.PlanValue = req.StateValue
42+
}
43+
}
44+
45+
func (d *createOnlyStringPlanModifier) addDiags(diags *diag.Diagnostics, attrPath path.Path, stateValue attr.Value) {
46+
message := fmt.Sprintf("%s cannot be updated or set after import, remove it from the configuration or use the state value (see below).", attrPath)
47+
detail := fmt.Sprintf("The current state value is %s", stateValue)
48+
diags.AddError(message, detail)
49+
}

internal/service/flexcluster/resource_schema.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -59,7 +59,7 @@ func ResourceSchema(ctx context.Context) schema.Schema {
5959
"region_name": schema.StringAttribute{
6060
Required: true,
6161
PlanModifiers: []planmodifier.String{
62-
customplanmodifier.CreateOnlyAttributePlanModifier(),
62+
customplanmodifier.CreateOnlyStringPlanModifier(),
6363
},
6464
MarkdownDescription: "Human-readable label that identifies the geographic location of your MongoDB flex cluster. The region you choose can affect network latency for clients accessing your databases. For a complete list of region names, see [AWS](https://docs.atlas.mongodb.com/reference/amazon-aws/#std-label-amazon-aws), [GCP](https://docs.atlas.mongodb.com/reference/google-gcp/), and [Azure](https://docs.atlas.mongodb.com/reference/microsoft-azure/).",
6565
},

0 commit comments

Comments
 (0)