* website: v0.15 upgrade guide for sensitive resource attributes
Our earlier draft of this guide didn't include a section about the
stabilization of the "provider_sensitive_attrs" language experiment. This
new section aims to address the situation where a module might previously
have been returning a sensitive value without having marked it as such,
and thus that module will begin returning an error after upgrading to
Terraform v0.15.
As part of that, I also reviewed the existing documentation about these
features and made some edits aiming to make these four different sections
work well together if users refer to them all at once, as they are likely
to do if they follow the new links from the upgrade guide. I aimed to
retain all of the content we had before, but some of it is now in a new
location.
In particular, I moved the discussion about the v0.14 language experiment
into the upgrade guide, because it seems like a topic only really relevant
to those upgrading from an earlier version and not something folks need to
know about if they are using Terraform for the first time in v0.15 or
later.
* minor fixups
Co-authored-by: Kristin Laemmert <mildwonkey@users.noreply.github.com>
In the Terraform language we typically use lists of zero or one values in
some sense interchangably with single values that might be null, because
various Terraform language constructs are designed to work with
collections rather than with nullable values.
In Terraform v0.12 we made the splat operator [*] have a "special power"
of concisely converting from a possibly-null single value into a
zero-or-one list as a way to make that common operation more concise.
In a sense this "one" function is the opposite operation to that special
power: it goes from a zero-or-one collection (list, set, or tuple) to a
possibly-null single value.
This is a concise alternative to the following clunky conditional
expression, with the additional benefit that the following expression is
also not viable for set values, and it also properly handles the case
where there's unexpectedly more than one value:
length(var.foo) != 0 ? var.foo[0] : null
Instead, we can write:
one(var.foo)
As with the splat operator, this is a tricky tradeoff because it could be
argued that it's not something that'd be immediately intuitive to someone
unfamiliar with Terraform. However, I think that's justified given how
often zero-or-one collections arise in typical Terraform configurations.
Unlike the splat operator, it should at least be easier to search for its
name and find its documentation the first time you see it in a
configuration.
My expectation that this will become a common pattern is also my
justification for giving it a short, concise name. Arguably it could be
better named something like "oneornull", but that's a pretty clunky name
and I'm not convinced it really adds any clarity for someone who isn't
already familiar with it.
Calling the nonsensitive function with values which are not sensitive
will result in an error. This restriction was added with the goal of
preventing confusingly redundant use of this function.
Unfortunately, this breaks when using nonsensitive to reveal the value of
sensitive resource attributes. This is because the validate walk does
not (and cannot) mark attributes as sensitive based on the schema,
because the resource value itself is unknown.
This commit therefore alters this restriction such that it permits
nonsensitive unknown values, and adds a test case to cover this specific
scenario.
When returning generic grpc errors from a provider, use
WholeContainingBody so that callers can annotate the error with all the
available contextual information. This can help troubleshoot problems by
narrowing down problems to a particular configuration or specific
resource instance.
Add an address argument to tfdiags.InConfigBody, and store the address
string the diagnostics details. Since nearly every place where we want
to annotate the diagnostics with the config context we also have some
sort of address, we can use the same call to insert them both into the
diagnostic.
Perhaps we should rename InConfigBody and ElaborateFromConfigBody to
reflect the additional address parameter, but for now we can verify this
is a pattern that suits us.
* Optimize (m ModuleInstance) String()
Optimize (m ModuleInstance) String() to preallocate the buffer and use strings.Builder instead of bytes.Buffer
This leads to a common case only doing a single allocation as opposed to a few allocations which the bytes.Buffer is doing.
* adding a benchmark test
Result:
```
$ go test -bench=String ./addrs -benchmem
BenchmarkStringShort-12 18271692 56.52 ns/op 16 B/op 1 allocs/op
BenchmarkStringLong-12 8057071 158.5 ns/op 96 B/op 1 allocs/op
PASS
$ git checkout main addrs/module_instance.go
$ go test -bench=String ./addrs -benchmem
BenchmarkStringShort-12 7690818 162.0 ns/op 80 B/op 2 allocs/op
BenchmarkStringLong-12 2922117 414.1 ns/op 288 B/op 3 allocs/op
```
* Update module_instance_test.go
switch spaces to tabs
Dependencies are tracked via configuration addresses, but when dealing
with depends_on references they can only apply to resources within the
same module instance. When determining if a data source can be read
during planning, verify that the dependency change is coming from the
same module instance.
When rendering the JSON plan sensitivity output, if the plan contained
unknown collection or structural types, Terraform would crash. We need
to detect unknown values before attempting to iterate them.
Unknown collection or structural values cannot have sensitive contents
accidentally displayed, as those values are not known until after apply.
As a result we return an empty value of the appropriate type for the
sensitivity mapping.
When applying sensitivity marks to resources, we previously would first
mark any provider-denoted sensitive attributes, then apply the set of
planned-change sensitive value marks. This would cause a panic if a
provider marked an iterable value as sensitive, because it is invalid to
call `MarkWithPaths` against a marked iterable value.
Instead, we now merge the marks from the provider schema and the planned
change into a single set, and apply them with one call. The included
test panics without this change.
We previously added a hint to both resource for_each and dynamic blocks
about using the "flatten" and "setproduct" situations to construct
suitable collections to repeat over.
However, we used the same text in both places which ended up stating that
dynamic blocks can only accept map or set values, which is a constraint
that applies to resource for_each (because we need to assign a unique
identifier to each instance) and not to dynamic blocks (which don't have
any uniqueness enforced by Terraform Core itself).
To remove that contradiction with the text above which talks about what
is valid here, I've just generalized this to say "collection", because
the primary point of this paragraph is the "one element per desired nested
block" part, not specifically what sort of collections are permitted in
this location. (Text further up describes the supported types.)
If the provider locks have not changed, there is no need to rewrite the
locks file. Preventing this needless rewrite should allow Terraform to
operate in a read-only directory, so long as the provider requirements
don't change.
The resource configuration was always being used to determine
dependencies during refresh, because if there were no changes to a
resource, there was no chance to replace any incorrect stored
dependencies. Now that we are refreshing during plan, we can determine
that a resource has no changes and opt to store the new dependencies
immediately.
Here we clean up the writeResourceInstanceState calls to no longer
modify the resource instance state, removing the `dependencies`
argument. Callers are now expected to set the Dependencies field as
needed.
When an output value changes, we have a small amount of information we
can convey about its sensitivity. If either the output was previously
marked sensitive, or is currently marked sensitive in the config, this
is tracked in the output change data.
This commit encodes this boolean in the change struct's
`before_sensitive` and `after_sensitive` fields, in the a way which
matches resource value sensitivity. Since we have so little information
to work with, these two values will always be booleans, and always equal
each.
This is logically consistent with how else we want to obscure sensitive
data: a changing output which was or is marked sensitive should not have
the value shown in human-readable output.
Similar to `after_unknown`, `before_sensitive` and `after_sensitive` are
values with similar structure to `before` and `after` which encode the
presence of sensitive values in a planned change. These should be used
to obscure sensitive values from human-readable output.
These values follow the same structure as the `before` and `after`
values, replacing sensitive values with `true`, and non-sensitive values
with `false`. Following the `after_unknown` precedent, we omit
non-sensitive `false` values for object attributes/map values, to make
serialization more compact.
One difference from `after_unknown` is that a sensitive complex value
(collection or structural type) is replaced with `true`. If the complex
value itself is sensitive, all of its contents should be obscured.
We have these funny extra options that date back to before Terraform even
had remote state, which we've preserved along the way by most recently
incorporating them as special-case overrides for the local backend.
The documentation we had for these has grown less accurate over time as
the details have shifted, and was in many cases missing the requisite
caveats that they are only for the local backend and that backend
configuration is the modern, preferred way to deal with the use-cases they
were intended for.
We always have a bit of a tension with this sort of legacy option because
we want to keep them documented just enough to be useful to someone who
finds an existing script/etc using them and wants to know what they do,
but not to take up so much space that they might distract users from
finding the modern alternative they should consider instead.
As a compromise in that vein here I've created a new section about these
options under the local backend documentation, which then gives us the
space to go into some detail about the various behaviors and interactions
and also to discuss their history and our recommended alternatives. I then
simplified all of the other mentions of these in command documentation
to just link to or refer to the local backend documentation. My hope then
is that folks who need to know what these do can still find the docs, but
that information can be kept out of the direct path of new users so they
can focus on learning about remote backends instead.
This is certainly not the most ideal thing ever, but it seemed like the
best compromise between the competing priorities I described above.