-
Notifications
You must be signed in to change notification settings - Fork 91
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
Add resource schema-based support for static default values #674
Conversation
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Drive-by comments before we chat. 😄 Good stuff so far.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This looks awesome! 🎉
One general question, are we planning on adding a new section to the plugin framework docs about default values and the relevant interfaces? Similar to the plan modification docs
Excellent question. Yep, we definitely should add to the docs. I'll attend to this. Thanks for flagging. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Overall I think this is pretty close! Some considerations and we'll likely want to figure out the best naming/strategy for the built-in default functions since those details would immediately be protected by compatibility promises.
// StaticValue returns a static number value default handler. | ||
// | ||
// Use StaticValue if a static default value for a number should be set. | ||
func StaticValue(defaultVal types.Number) defaults.Number { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
To prevent provider developer errors and keep this similar to the other primitive type functions, it may be good to use *big.Float
here to represent a known value, instead of types.Number
which could be set to null/unknown. We could consider different naming for these if we want to ensure there's room to support a types
type variant -- e.g. calling this StaticBigFloat()
, calling the int64
one StaticInt64()
, etc, then leaving "StaticValue" for the types
ones.
That said, we may want to consider adding error diagnostics from the default handling methods if the given default value is null or unknown. I'm not sure its valid to ever have either -- for null it should have Computed
and Default
removed, for unknown it could result permanent plan differences for practitioners.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I've switched to using *big.Float
and updated the primitives to use StaticBigFloat()
, StaticInt()
etc.
I've not added the error diagnostics for the handling methods if the default is null or unknown. Happy to do this, but I was wondering if we might want to discuss this first?
resource/schema/schema.go
Outdated
return diag.NewAttributeErrorDiagnostic( | ||
path, | ||
"Schema Using Attribute Default For Non-Computed Attribute", | ||
fmt.Sprintf("attribute %q must be computed when using default", path.String()), |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nit: It'd be good to include our typical verbiage about "This is an issue with the provider and should be reported to the provider developers" in case a practitioner happens to receive it.
// DefaultString runs the logic of the default. Access to the path is available in `req`, while | ||
// `resp` contains fields for updating the planned value, and returning diagnostics. | ||
func (m timeNowDefaultValue) DefaultString(_ context.Context, req defaults.StringRequest, resp *defaults.StringResponse) | ||
resp.PlanValue = types.StringValue(time.Now().String()) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
How would you feel about showing a generic time.Time
default similar to our "StaticValue" functions instead of hardcoding time.Now()
?
<Note> | ||
Any Defaults that have been set are processed before any plan modifiers. | ||
</Note> | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Should this just be added to the list below instead?
// If the value is unknown or known, do not set default value. | ||
if !req.PlanValue.IsNull() { | ||
return | ||
} | ||
|
||
resp.PlanValue = types.StringValue(m.Default) | ||
resp.PlanValue = types.StringValue(time.Now().String()) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
How do you feel about adjusting this to be a plan modifier that normalizes JSON-encoded strings (keeping the prior state if they match when normalized) so its separate from the default value use case?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
As discussed, have used one of the predefined UseStateForUnknown
plan modifiers as an example.
defaultValue := a.BoolDefaultValue() | ||
if defaultValue != nil { | ||
resp := defaults.BoolResponse{} | ||
defaultValue.DefaultBool(ctx, defaults.BoolRequest{}, &resp) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It'd be good to have trace/debug logging for each of these that we're calling provider-defined logic.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Perfect, thanks!
}, | ||
}, | ||
map[string]tftypes.Value{ | ||
"bool_attribute": tftypes.NewValue(tftypes.Bool, false), // value in state |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Should the Description be updated to reflect this instead a code comment?
Co-authored-by: Brian Flad <bflad417@gmail.com>
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Excellent! Nice work. 🚀
website/docs/plugin/framework/migrating/attributes-blocks/default-values.mdx
Outdated
Show resolved
Hide resolved
"github.com/hashicorp/terraform-plugin-framework/types" | ||
) | ||
|
||
func TestStaticValueDefaultNumber(t *testing.T) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nit: Should this be TestStaticBigFloat
to match the function name?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I've updated the naming of the /resource/schema
static default test functions for primitives to make them consistent.
defaultValue := a.BoolDefaultValue() | ||
if defaultValue != nil { | ||
resp := defaults.BoolResponse{} | ||
defaultValue.DefaultBool(ctx, defaults.BoolRequest{}, &resp) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Perfect, thanks!
Co-authored-by: Brian Flad <bflad417@gmail.com>
I'm going to lock this pull request because it has been closed for 30 days ⏳. This helps our maintainers find and focus on the active contributions. |
Closes: #668