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

Refined Unknown Values, allowing some operations with unknown values to produce known results #33234

Merged
merged 8 commits into from
May 24, 2023

Conversation

apparentlymart
Copy link
Contributor

@apparentlymart apparentlymart commented May 22, 2023

This introduces a new concept called Refinements from upstream cty, which is the foundation of the Terraform language type system.

For example, this allows an known value to be refined as "not null", which means that v != null and v == null can return a known true or false even if the actual value isn't yet known. There are also various other refinements to help with some other common situations, such as testing whether the length of a collection is greater than zero, whether the length of a string is greater than zero, etc.

The individual commits have more detailed commit messages, so it might help to review this on a per-commit basis rather than as a large flat diff.

I'm proposing this now -- very early in the v1.6 development period -- so that there will be as much opportunity as possible for these changes to "soak" throughout later development and any alpha releases we'll produce during that development period.

This is a change in cty's behavior within the bounds of its compatibility policy, which allows unknown values to become "more known". As noted in those docs, anything using cty.Value.RawEquals is a potential hazard under that policy and so that function is supposed to be used only in test code. Terraform does incorrectly use it in non-test code in a few select spots, so this PR either replaces those with Equals calls (where it was relatively non-invasive) or strips the refinements before comparing to avoid a change in behavior.

This is the sort of thing that can get more precise over time as we learn where further annotation will be useful, so this PR is mostly focused just on getting the new concept wired in but it does include some initial refinements in addition to those provided automatically by cty:

  • Various functions annotate their result as definitely not being null.
  • The startswith function can take the "string prefix" refinement into account.

Because cty's MessagePack serialization now supports refinements the Terraform provider protocol effectively does too, and so this includes some tweaks to the plans/objchange rules to take that into account and some initial documentation on the serialization of refinements. However, I don't recommend that the provider framework start supporting this just yet until we have some more experience with it just within the core language. Eventually it would be useful for the plugin framework to allow providers to at least signal that certain attributes can never be null, but it'll be much harder to tweak the design of that once it's deployed in real providers so best to wait to see.

I have a separate commit in HCL that also makes HCL's own operators refine unknown values in their results, such as refining arithmetic operations to represent that they never produce null results, but I've intentionally not included that here because this PR is already quite broad. I'll work on getting the HCL side of this merged later on, and then open a separate PR to incorporate the changes from HCL.

Closes #31078
Closes #15498
Closes #31035

cty's new "refinements" concept allows us to reduce the range of unknown
values from our functions. This initial changeset focuses only on
declaring which functions are guaranteed to return a non-null result,
which is a helpful baseline refinement because it allows "== null" and
"!= null" tests to produce known results even when the given value is
otherwise unknown.

This commit also includes some updates to test results that are now
refined based on cty's own built-in refinement behaviors, just as a
result of us having updated cty in the previous commit.
The "id" attribute of this resource type is generated by the provider
itself and can never be null, so we'll refine the range of its unknown
result in case that helps downstream expressions to produce known results
even when the exact value hasn't yet been planned.
If the string to be tested is an unknown value that's been refined with
a prefix and the prefix we're being asked to test is in turn a prefix of
that known prefix then we can return a known answer despite the inputs
not being fully known.

There are also some other similar deductions we can make about other
combinations of inputs.

This extra analysis could be useful in a custom condition check that
requires a string with a particular prefix, since it can allow the
condition to fail even on partially-unknown input, thereby giving earlier
feedback about a problem.
If the original value was unknown but its range was refined then the
provider must return a value that is within the refined range, because
otherwise downstream planning decisions could be invalidated.

This relies on cty's definition of whether a value is in a refined range,
which has pretty good coverage for the "false" case and so should give a
pretty good signal, but it'll probably improve over time and so providers
must not rely on any loopholes in the current implementation and must
keep their promises even if Terraform can't currently check them.
Providers that existed prior to refinements (all of them, at the time of
writing) cannot preserve refinements sent in unknown values in the
configuration, and even if one day providers _are_ aware of refinements
there we might add new ones that existing providers don't know how to
handle.

For that reason we'll absolve providers of the responsibility of
preserving refinements from config into plan by fixing some cases where
we were incorrectly using RawEquals to compare values; that function isn't
appropriate for comparing values that might be unknown.

However, to avoid a disruptive change right now this initial fix just
strips off the refinements before comparing. Ideally this should be using
Value.Equals and handling unknown values more explicitly, but we'll save
that for a possible later improvement.

This does not include a similar exception for validating whether a final
value conforms to a plan because the plan value and the final value are
both produced by the same provider and so providers ought to be able to
be consistent with their _own_ treatment of refinements, if any.
Configuration is special because Terraform itself generates that, and so
it can potentially contain refinements that a particular provider has no
awareness of.
This is actually a description of the "cty" library's encoding of refined
values, but from the perspective of the plugin protocol it's an
implementation detail that Terraform Core outsources that to a third-party
library, and current server-side implementations of the protocol use an
independent implementation of this format which will need to be compatible
with what cty does.
@apparentlymart apparentlymart merged commit b3f99ce into main May 24, 2023
@apparentlymart apparentlymart deleted the f-cty-refinements branch May 24, 2023 21:11
@github-actions
Copy link

Reminder for the merging maintainer: if this is a user-visible change, please update the changelog on the appropriate release branch.

@github-actions
Copy link

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.
If you have found a problem that seems related to this change, please open a new issue and complete the issue template so we can capture all the details necessary to investigate further.

@github-actions github-actions bot locked as resolved and limited conversation to collaborators Jun 24, 2023
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Projects
None yet
2 participants