Skip to content
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

tftypes: Introduce unknown value refinement support for Value #448

Draft
wants to merge 21 commits into
base: main
Choose a base branch
from

Conversation

austinvalle
Copy link
Member

@austinvalle austinvalle commented Nov 25, 2024

Ref: hashicorp/terraform#30937
Ref: hashicorp/terraform-plugin-framework#869


This PR introduces value refinement support to the tftypes package. This allows Terraform providers to communicate unknown value refinements to/from Terraform core (Terraform 1.6+).

Value refinements are additional constraints that can be applied to unknown values in Terraform that can be used to produce known results from unknown values. For example, with value refinements, providers can now indicate specific attributes will "definitely not be null" during PlanResourceChange, which would allow Terraform core to successfully evaluate expressions like count during plan, rather than returning an error like:

│ Error: Invalid count argument
│
│ ...
│
│ The "count" value depends on resource attributes that cannot be
│ determined until apply, so Terraform cannot predict how many
│ instances will be created. To work around this, use the -target
│ argument to first apply only the resources that the count depends
│ on.

No changes to existing provider code will be necessary as partially unknown values will still evaluate the same as a wholly unknown value today.

TODOs left on this PR

  • Design is still under review
  • Changelogs

@austinvalle austinvalle added the enhancement New feature or request label Nov 25, 2024
Comment on lines +1901 to +1953
"unknown-bool": {
in: NewValue(Bool, UnknownValue),
expected: "tftypes.Bool<unknown>",
},
"unknown-bool-with-nullness-refinement": {
in: NewValue(Bool, UnknownValue).Refine(refinement.Refinements{
refinement.KeyNullness: refinement.NewNullness(false),
}),
expected: `tftypes.Bool<unknown, not null>`,
},
"unknown-string": {
in: NewValue(String, UnknownValue),
expected: "tftypes.String<unknown>",
},
"unknown-string-with-multiple-refinements": {
in: NewValue(String, UnknownValue).Refine(refinement.Refinements{
refinement.KeyNullness: refinement.NewNullness(false),
refinement.KeyStringPrefix: refinement.NewStringPrefix("str://"),
}),
expected: `tftypes.String<unknown, not null, prefix = "str://">`,
},
"unknown-number": {
in: NewValue(Number, UnknownValue),
expected: "tftypes.Number<unknown>",
},
"unknown-number-with-multiple-refinements": {
in: NewValue(Number, UnknownValue).Refine(refinement.Refinements{
refinement.KeyNullness: refinement.NewNullness(false),
refinement.KeyNumberLowerBound: refinement.NewNumberLowerBound(big.NewFloat(5), true),
refinement.KeyNumberUpperBound: refinement.NewNumberUpperBound(big.NewFloat(10), true),
}),
expected: `tftypes.Number<unknown, not null, lower bound = 5 (inclusive), upper bound = 10 (inclusive)>`,
},
"unknown-number-float-with-multiple-refinements": {
in: NewValue(Number, UnknownValue).Refine(refinement.Refinements{
refinement.KeyNullness: refinement.NewNullness(false),
refinement.KeyNumberLowerBound: refinement.NewNumberLowerBound(big.NewFloat(5.67), true),
refinement.KeyNumberUpperBound: refinement.NewNumberUpperBound(big.NewFloat(10.123), true),
}),
expected: `tftypes.Number<unknown, not null, lower bound = 5.67 (inclusive), upper bound = 10.123 (inclusive)>`,
},
"unknown-list": {
in: NewValue(List{ElementType: String}, UnknownValue),
expected: "tftypes.List[tftypes.String]<unknown>",
},
"unknown-list-with-multiple-refinements": {
in: NewValue(List{ElementType: String}, UnknownValue).Refine(refinement.Refinements{
refinement.KeyNullness: refinement.NewNullness(false),
refinement.KeyCollectionLengthLowerBound: refinement.NewCollectionLengthLowerBound(2),
refinement.KeyCollectionLengthUpperBound: refinement.NewCollectionLengthUpperBound(7),
}),
expected: `tftypes.List[tftypes.String]<unknown, not null, length lower bound = 2, length upper bound = 7>`,
},
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We can bikeshed the display of these attributes since there is no established pattern ATM (core displays the cty methods when showing data consistency errors, but doesn't have a "user friendly" version of the data defined). Other thoughts:

tftypes.String<unknown, refinements = [not null, prefix = "str://"]>
tftypes.String<unknown, will not be null, will have prefix "str://">

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
enhancement New feature or request
Projects
None yet
Development

Successfully merging this pull request may close these issues.

1 participant