This upstream library (which I wrote, independently of my work on OpenTofu)
came about because "go-spew" tended to produce unreadable representations
of certain types commonly used in OpenTofu, whereas "go-dump" is really
just a pretty-printer for whatever a type might produce when formatted
using the %#v verb in package fmt.
Over time the uses of this seem to have decreased only to some leftover
situations where we wanted to pretty-print a cty.Value in a test, but
we already depend on go-cty-debug that has a more specialized
implementation of that behavior and so switching the few remaining callers
over to that allows us to remove one dependency.
(And, FWIW, that upstream dependency is effectively unmaintained; I don't
know of any callers of it other than OpenTofu itself, and after merging
this even OpenTofu won't depend on it anymore.)
Signed-off-by: Martin Atkins <mart@degeneration.co.uk>
In a future commit we will adopt golangci-lint v1.64.5, which now triggers
lint warnings for some code that was previously not detected. This commit
is the smallest change to address those differences.
Unfortunately the "cloud" package and the "remote" backend both rely on
non-idiomatic error message formatting because they emit the returned error
message text directly into the UI, so to avoid changing the UI output but
also avoid significant refactoring this just adds nolint comments to those
for now. A future commit might address this by reworking things so that
the UI takes care of its own presentation concerns instead of relying on
the main implementation to directly generate UI-appropriate error strings.
This also completely disables the exportloopref linter, because that was
for a loop scoping hazard that was already addressed by a language change
in Go 1.22. This linter is therefore completely removed in newer versions
of golangci-lint and would thus generate an error if left enabled after
upgrading.
Signed-off-by: Martin Atkins <mart@degeneration.co.uk>
Unlike the other nesting modes, we need to use some quite different code
for comparing object-backed vs. map-backed blocks, which are both possible
interpretations of the NestingMap mode depending on whether the inner
block type has any dynamically-typed attributes.
Therefore we split that case into two parts depending on what type kind
we find, so that each of the parts can then be shaped more like the other
type-specific variants of assertNestedBlockCompatible. (This also removes
one level of if nesting to placate the nestif linter.)
Signed-off-by: Martin Atkins <mart@degeneration.co.uk>
This function starts with a general part that deals with conditions that
are common to all types, but then dispatches into different codepaths
depending on the type kind.
To keep the main function shorter, here we decompose the type-kind-specific
handling into separate functions, making assertValueCompatible now end
with a simpler dispatch table.
Signed-off-by: Martin Atkins <mart@degeneration.co.uk>
The main function is now just a jump table into a separate function for
each nesting mode. The observable behavior is unchanged.
Signed-off-by: Martin Atkins <mart@degeneration.co.uk>
This splits out the handling of individual attributes and individual nested
block types into separate functions, thereby reducing the length and
complexity of the top-level function.
As of this commit, assertNestedBlockCompatible is still too long to pass
our current function length linting limit, but we'll address that in a
later commit to avoid changing too much at once.
Signed-off-by: Martin Atkins <mart@degeneration.co.uk>
Passing the result of fmt.Sprintf as the format string to path.NewErrorf
is redundant. It can also potentially cause problems if the result would
also contain formatting verbs, although in this case the input is under
this function's full control so this was just a waste of time rather than
a behavior problem.
Go error strings also conventionally start with lowercase letters and act
as sentence fragments rather than full sentences, so the prefix used for
a zero-length path is now "root object" instead of "Root object".
Signed-off-by: Martin Atkins <mart@degeneration.co.uk>
* Rename module name from "github.com/hashicorp/terraform" to "github.com/placeholderplaceholderplaceholder/opentf".
Signed-off-by: Jakub Martin <kubam@spacelift.io>
* Gofmt.
Signed-off-by: Jakub Martin <kubam@spacelift.io>
* Regenerate protobuf.
Signed-off-by: Jakub Martin <kubam@spacelift.io>
* Fix comments.
Signed-off-by: Jakub Martin <kubam@spacelift.io>
* Undo issue and pull request link changes.
Signed-off-by: Jakub Martin <kubam@spacelift.io>
* Undo comment changes.
Signed-off-by: Jakub Martin <kubam@spacelift.io>
* Fix comment.
Signed-off-by: Jakub Martin <kubam@spacelift.io>
* Undo some link changes.
Signed-off-by: Jakub Martin <kubam@spacelift.io>
* make generate && make protobuf
Signed-off-by: Jakub Martin <kubam@spacelift.io>
---------
Signed-off-by: Jakub Martin <kubam@spacelift.io>
Several parts of the objchange logic incorrectly use cty.Value.RawEquals
for value comparison, instead of more appropriate comparison methods like
cty.Value.Equals or c.Value.Range().Includes. That makes them incorrectly
consider two unknown values with the same type but different refinements
as always non-equal, rather than evaluating based on the overlap between
the refinements (if any).
As a short-term fix for that we previously added this unrefinedValue shim
that just strips away the refinements for comparison, thus allowing
callers to continue using RawEquals as long as they've already taken care
of all of the other things that can make that go wrong, such as value
marks.
Unfortunately the shim was too simplistic and only supported direct
unknown values. Unknown values with refinements can also appear nested
inside known container values such as collections, so the shim needs to
recursively un-refine the entire data structure in that case.
This is still intended only as a temporary fix until we have time to
revisit all of the callers and make them use cty's own logic for
comparison. Using cty's own logic will make the results more precise,
because e.g. it can notice if two unknown strings have different known
prefixes and therefore cannot possibly be equal despite not being fully
known. For now this shim will accept any pair of unknown values of the
same type as equal, regardless of refinement.
If a set contains partially known values the length is unknown which
causes assertPlannedObjectValid to fail valid plans.
Revert to the old method if using LengthInt for the set lengths, which
returns the maximum number of possible elements, with a guard for
entirely unknown set values.
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.
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.
It is not valid for a provider to return an unknown value for a
configured nested collection, but we need to check for unknowns before
comparing the number of values in the collection.
The existing set comparison method uses the prior elements with the computed
portions nulled out to find candidates to match the configuration. This
has the shortcoming of always removing optional+computed attributes,
because we have not yet found the configuration to know if attribute was
set or not.
Rather than having to take the most pessimistic value before comparison
to precompute the nulled values, we can compare each candidate directly,
walking the values in tandem. Each prior value is compared against the
config and checked to see if it could have been derived from that
configuration value, which allows us to treat optional+computed as
optional if there is config and computed if there is not.
This removes the ambiguity from having optional+computed attributes
within sets, giving us consistent plans when all values are known.
Unknown values of course are still undecidable, as are edge cases were
providers refresh with altered values or retained changed prior values
plan that were deemed not functionally significant.
Unify the ProposedNew paths for Blocks and Objects. Break out the
individual case blocks into functions, then use a common interface to
dispatch the object creation to the correct function based on schema
type. This cuts the code in half, and prevents the block and object
behavior from diverging.
NestingMap structures are not well tested, and we panic in many
situations when null crops up. Fix the first test cases and start
refactoring best we can. This probably won't go so far as making all the
objchange functions generic over Block and Object, but we can simplify a
lot and verify parity in implementations for now.
We can check if an object in state must have at least partially come
from configuration, by seeing if the prior value has any non-null
attributes which are not computed in the schema.
This is used when the configuration contains a null optional+computed
value, and we want to know if we should plan to send the null value or
the prior state.
Combine and simplify the set comparison functions for NestingSet blocks
and attribute types.
The set handling for structural attributes was not recursing into nested
values. Once a simplified method for comparing set elements was devised
for nested types, it turns out the same method could be applied to
nested set blocks as well.
When structural attributes were added, optional+computed were not
correctly handled when containing nested values which could themselves
be computed. This would cause terraform to ignore previously computed
values from state when generating the proposed plan.
The special case for optional+computed was incorrect, but isn't needed
in the context of planning new values anyway. Attributes are either
computed, or not computed. When optional+computed is set and there is
no configuration, the attribute is treated as computed. It is up to the
provider to determine how and when to deal with any changes to that
computed value.
* pause implementation
* change -> diff, value -> change
* add support for json and multiline strings to the primitive renderer
* goimports
* remove unused function
* go fmt
* address comments
NestingSingle blocks removed from from the config were causing a plan to
error out with "... planned for existence but config wants absence".
Terraform core was proposing an incorrect value in this case, taking the
prior instead as a fallback because a null value was not expected.
Unlike other collection nesting modes, a NestingSingle block not present
in the configuration is a null value, and should be allowed when
planning a new value rather than building an empty object or falling
back to the prior value.