terraform/internal/namedvals/values.go
2026-02-17 13:56:34 +00:00

141 lines
5.4 KiB
Go

// Copyright IBM Corp. 2014, 2026
// SPDX-License-Identifier: BUSL-1.1
package namedvals
import (
"fmt"
"github.com/zclconf/go-cty/cty"
"github.com/hashicorp/terraform/internal/addrs"
)
// Values is a type we use internally to track values that were already
// resolved.
//
// This container encapsulates the problem of dealing with placeholder values
// in modules whose instance addresses are not yet fully known due to unknown
// values in count or for_each. Callers can register values in module paths
// of different levels of "known-ness" and then when queried the result will
// be the value from the most specific registration seen so far.
//
// Values is not concurrency-safe, because we expect to use it only indirectly
// through the public-facing [State] type and it should be the one to ensure
// concurrency safety.
type values[LocalType namedValueAddr, AbsType namedValueAddr] struct {
// exact are values for objects in fully-qualified module instances.
exact addrs.Map[AbsType, cty.Value]
// placeholder are placeholder values to use for objects under an
// unexpanded module prefix. These placeholders will contain known
// values only in positions that are guaranteed to have equal values
// across all module instances under a given prefix, thereby allowing
// similar placeholder evaluation for other objects downstream of
// the partially-evaluated ones.
//
// This one is more awkward because there can be conflicting placeholders
// for the same object at different levels of specificity, and so we'll
// need to scan over elements to find ones that match the most. To slightly
// optimize that we have the values bucketed by the static module address
// they belong to; maybe we'll optimize this more later if it seems like
// a bottleneck, but we're assuming a relatively small number of declared
// objects of each type in each module.
placeholder addrs.Map[addrs.Module, addrs.Map[addrs.InPartialExpandedModule[LocalType], cty.Value]]
}
type inputVariableValues = values[addrs.InputVariable, addrs.AbsInputVariableInstance]
type localValues = values[addrs.LocalValue, addrs.AbsLocalValue]
type outputValues = values[addrs.OutputValue, addrs.AbsOutputValue]
// namedValueAddr describes the address behaviors we need for address types
// we'll use with type [values] as defined above.
type namedValueAddr interface {
addrs.UniqueKeyer
fmt.Stringer
}
func newValues[LocalType namedValueAddr, AbsType namedValueAddr]() values[LocalType, AbsType] {
return values[LocalType, AbsType]{
exact: addrs.MakeMap[AbsType, cty.Value](),
placeholder: addrs.MakeMap[addrs.Module, addrs.Map[addrs.InPartialExpandedModule[LocalType], cty.Value]](),
}
}
func (v *values[LocalType, AbsType]) SetExactResult(addr AbsType, val cty.Value) {
if v.exact.Has(addr) {
// This is always a bug in the caller, because Terraform Core should
// use its graph to ensure that each value gets set exactly once and
// the values get registered in the correct order.
panic(fmt.Sprintf("value for %s was already set by an earlier caller", addr))
}
v.exact.Put(addr, val)
}
func (v *values[LocalType, AbsType]) HasExactResult(addr AbsType) bool {
return v.exact.Has(addr)
}
func (v *values[LocalType, AbsType]) GetExactResult(addr AbsType) cty.Value {
// TODO: Do we need to handle placeholder results in here too? Seems like
// callers should not end up holding AbsType addresses if they are dealing
// with unexpanded objects because they wouldn't have instance keys to
// fill in to the address, so assuming not for now.
if !v.exact.Has(addr) {
// This is always a bug in the caller, because Terraform Core should
// use its graph to ensure that each value gets set exactly once and
// the values get registered in the correct order.
panic(fmt.Sprintf("value for %s was requested before it was provided", addr))
}
return v.exact.Get(addr)
}
func (v *values[LocalType, AbsType]) GetExactResults() addrs.Map[AbsType, cty.Value] {
return v.exact
}
func (v *values[LocalType, AbsType]) SetPlaceholderResult(addr addrs.InPartialExpandedModule[LocalType], val cty.Value) {
modAddr := addr.Module.Module()
if !v.placeholder.Has(modAddr) {
v.placeholder.Put(modAddr, addrs.MakeMap[addrs.InPartialExpandedModule[LocalType], cty.Value]())
}
placeholders := v.placeholder.Get(modAddr)
if placeholders.Has(addr) {
// This is always a bug in the caller, because Terraform Core should
// use its graph to ensure that each value gets set exactly once and
// the values get registered in the correct order.
panic(fmt.Sprintf("placeholder value for %s was already set by an earlier caller", addr))
}
placeholders.Put(addr, val)
}
func (v *values[LocalType, AbsType]) GetPlaceholderResult(addr addrs.InPartialExpandedModule[LocalType]) cty.Value {
modAddr := addr.Module.Module()
if !v.placeholder.Has(modAddr) {
return cty.DynamicVal
}
placeholders := v.placeholder.Get(modAddr)
// We'll now search the placeholders for just the ones that match the
// given address, and take the one that has the longest known module prefix.
longestVal := cty.DynamicVal
longestLen := -1
for _, elem := range placeholders.Elems {
candidate := elem.Key
lenKnown := candidate.ModuleLevelsKnown()
if lenKnown < longestLen {
continue
}
if !addrs.Equivalent(candidate.Local, addr.Local) {
continue
}
if !candidate.Module.MatchesPartial(addr.Module) {
continue
}
longestVal = elem.Value
longestLen = lenKnown
}
return longestVal
}