mirror of
https://github.com/hashicorp/terraform.git
synced 2026-03-21 10:00:09 -04:00
141 lines
5.4 KiB
Go
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
|
|
}
|