-
Notifications
You must be signed in to change notification settings - Fork 207
test: Ensures project withDefaultAlertsSettings
works with import and introduce create_only plan modifier
#3105
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from 26 commits
53de463
26f7353
5e67e14
57ddae6
7bc763e
de2589d
7934ad0
af84e38
66e55e9
e11fc3f
bd76a93
d0cb772
57f8e64
4ae2ccc
8c7f8c6
c8abacd
078b07c
fae76d4
9bb557b
6a551b7
954229a
8a03248
bb9028e
d4d7669
f5f2311
9e309a0
a7b8145
538686a
8418405
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,91 @@ | ||
package customplanmodifier | ||
|
||
import ( | ||
"context" | ||
"fmt" | ||
|
||
"github.com/hashicorp/terraform-plugin-framework/attr" | ||
"github.com/hashicorp/terraform-plugin-framework/diag" | ||
"github.com/hashicorp/terraform-plugin-framework/path" | ||
planmodifier "github.com/hashicorp/terraform-plugin-framework/resource/schema/planmodifier" | ||
"github.com/hashicorp/terraform-plugin-framework/tfsdk" | ||
"github.com/hashicorp/terraform-plugin-framework/types" | ||
) | ||
|
||
type Modifier interface { | ||
planmodifier.String | ||
planmodifier.Bool | ||
} | ||
|
||
// CreateOnlyAttributePlanModifier returns a plan modifier that ensures that update operations fails when the attribute is changed. | ||
// This is useful for attributes only supported in create and not in update. | ||
// It shows a helpful error message helping the user to update their config to match the state. | ||
// 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. | ||
// Implement CopyFromPlan if the attribute is not in the API Response. | ||
func CreateOnlyAttributePlanModifier() Modifier { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Can we add a comment on what this plan modifier does & why it's needed? |
||
return &createOnlyAttributePlanModifier{} | ||
} | ||
|
||
func CreateOnlyAttributePlanModifierWithBoolDefault(b bool) Modifier { | ||
return &createOnlyAttributePlanModifier{defaultBool: &b} | ||
} | ||
|
||
type createOnlyAttributePlanModifier struct { | ||
defaultBool *bool | ||
} | ||
|
||
func (d *createOnlyAttributePlanModifier) Description(ctx context.Context) string { | ||
return d.MarkdownDescription(ctx) | ||
} | ||
|
||
func (d *createOnlyAttributePlanModifier) MarkdownDescription(ctx context.Context) string { | ||
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." | ||
} | ||
|
||
func isCreate(t *tfsdk.State) bool { | ||
return t.Raw.IsNull() | ||
} | ||
|
||
func (d *createOnlyAttributePlanModifier) UseDefault() bool { | ||
return d.defaultBool != nil | ||
} | ||
|
||
func (d *createOnlyAttributePlanModifier) PlanModifyBool(ctx context.Context, req planmodifier.BoolRequest, resp *planmodifier.BoolResponse) { | ||
if isCreate(&req.State) { | ||
if !IsKnown(req.PlanValue) && d.UseDefault() { | ||
resp.PlanValue = types.BoolPointerValue(d.defaultBool) | ||
} | ||
return | ||
} | ||
Comment on lines
+58
to
+63
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. q: Is there a reason we have logic for setting the default value as part of the plan modifier, and not in the read operation? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yes, by setting this in the plan modifier we make sure the default is visible in the plan and only applied during the AFAIK: The import operation doesn't use the plan modifier, it only calls read and stores it in the state. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Makes sense. Would suggest adding additional clarification (as comment in There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Good point. Added in 8418405 |
||
if isUpdated(req.StateValue, req.PlanValue) { | ||
d.addDiags(&resp.Diagnostics, req.Path, req.StateValue) | ||
} | ||
if !IsKnown(req.PlanValue) { | ||
resp.PlanValue = req.StateValue | ||
} | ||
} | ||
|
||
func (d *createOnlyAttributePlanModifier) PlanModifyString(ctx context.Context, req planmodifier.StringRequest, resp *planmodifier.StringResponse) { | ||
if isCreate(&req.State) { | ||
return | ||
} | ||
if isUpdated(req.StateValue, req.PlanValue) { | ||
d.addDiags(&resp.Diagnostics, req.Path, req.StateValue) | ||
} | ||
if !IsKnown(req.PlanValue) { | ||
resp.PlanValue = req.StateValue | ||
} | ||
} | ||
|
||
func isUpdated(state, plan attr.Value) bool { | ||
if !IsKnown(plan) { | ||
return false | ||
} | ||
return !state.Equal(plan) | ||
} | ||
|
||
func (d *createOnlyAttributePlanModifier) addDiags(diags *diag.Diagnostics, attrPath path.Path, stateValue attr.Value) { | ||
message := fmt.Sprintf("%s cannot be updated or set after import, remove it from the configuration or use the state value (see below).", attrPath) | ||
EspenAlbert marked this conversation as resolved.
Show resolved
Hide resolved
|
||
detail := fmt.Sprintf("The current state value is %s", stateValue) | ||
diags.AddError(message, detail) | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,8 @@ | ||
package customplanmodifier | ||
|
||
import "github.com/hashicorp/terraform-plugin-framework/attr" | ||
|
||
// IsKnown returns true if the attribute is known (not null or unknown). Note that !IsKnown is not the same as IsUnknown because null is !IsKnown but not IsUnknown. | ||
func IsKnown(attribute attr.Value) bool { | ||
return !attribute.IsNull() && !attribute.IsUnknown() | ||
} |
This file was deleted.
oarbusi marked this conversation as resolved.
Show resolved
Hide resolved
|
Uh oh!
There was an error while loading. Please reload this page.