github.com/opentofu/opentofu@v1.7.1/internal/tofu/node_module_variable.go (about)

     1  // Copyright (c) The OpenTofu Authors
     2  // SPDX-License-Identifier: MPL-2.0
     3  // Copyright (c) 2023 HashiCorp, Inc.
     4  // SPDX-License-Identifier: MPL-2.0
     5  
     6  package tofu
     7  
     8  import (
     9  	"fmt"
    10  	"log"
    11  
    12  	"github.com/hashicorp/hcl/v2"
    13  	"github.com/zclconf/go-cty/cty"
    14  
    15  	"github.com/opentofu/opentofu/internal/addrs"
    16  	"github.com/opentofu/opentofu/internal/configs"
    17  	"github.com/opentofu/opentofu/internal/dag"
    18  	"github.com/opentofu/opentofu/internal/instances"
    19  	"github.com/opentofu/opentofu/internal/lang"
    20  	"github.com/opentofu/opentofu/internal/tfdiags"
    21  )
    22  
    23  // nodeExpandModuleVariable is the placeholder for an variable that has not yet had
    24  // its module path expanded.
    25  type nodeExpandModuleVariable struct {
    26  	Addr   addrs.InputVariable
    27  	Module addrs.Module
    28  	Config *configs.Variable
    29  	Expr   hcl.Expression
    30  }
    31  
    32  var (
    33  	_ GraphNodeDynamicExpandable = (*nodeExpandModuleVariable)(nil)
    34  	_ GraphNodeReferenceOutside  = (*nodeExpandModuleVariable)(nil)
    35  	_ GraphNodeReferenceable     = (*nodeExpandModuleVariable)(nil)
    36  	_ GraphNodeReferencer        = (*nodeExpandModuleVariable)(nil)
    37  	_ graphNodeTemporaryValue    = (*nodeExpandModuleVariable)(nil)
    38  	_ graphNodeExpandsInstances  = (*nodeExpandModuleVariable)(nil)
    39  )
    40  
    41  func (n *nodeExpandModuleVariable) expandsInstances() {}
    42  
    43  func (n *nodeExpandModuleVariable) temporaryValue() bool {
    44  	return true
    45  }
    46  
    47  func (n *nodeExpandModuleVariable) DynamicExpand(ctx EvalContext) (*Graph, error) {
    48  	var g Graph
    49  
    50  	// If this variable has preconditions, we need to report these checks now.
    51  	//
    52  	// We should only do this during planning as the apply phase starts with
    53  	// all the same checkable objects that were registered during the plan.
    54  	var checkableAddrs addrs.Set[addrs.Checkable]
    55  	if checkState := ctx.Checks(); checkState.ConfigHasChecks(n.Addr.InModule(n.Module)) {
    56  		checkableAddrs = addrs.MakeSet[addrs.Checkable]()
    57  	}
    58  
    59  	expander := ctx.InstanceExpander()
    60  	for _, module := range expander.ExpandModule(n.Module) {
    61  		addr := n.Addr.Absolute(module)
    62  		if checkableAddrs != nil {
    63  			checkableAddrs.Add(addr)
    64  		}
    65  
    66  		o := &nodeModuleVariable{
    67  			Addr:           addr,
    68  			Config:         n.Config,
    69  			Expr:           n.Expr,
    70  			ModuleInstance: module,
    71  		}
    72  		g.Add(o)
    73  	}
    74  	addRootNodeToGraph(&g)
    75  
    76  	if checkableAddrs != nil {
    77  		ctx.Checks().ReportCheckableObjects(n.Addr.InModule(n.Module), checkableAddrs)
    78  	}
    79  
    80  	return &g, nil
    81  }
    82  
    83  func (n *nodeExpandModuleVariable) Name() string {
    84  	return fmt.Sprintf("%s.%s (expand)", n.Module, n.Addr.String())
    85  }
    86  
    87  // GraphNodeModulePath
    88  func (n *nodeExpandModuleVariable) ModulePath() addrs.Module {
    89  	return n.Module
    90  }
    91  
    92  // GraphNodeReferencer
    93  func (n *nodeExpandModuleVariable) References() []*addrs.Reference {
    94  
    95  	// If we have no value expression, we cannot depend on anything.
    96  	if n.Expr == nil {
    97  		return nil
    98  	}
    99  
   100  	// Variables in the root don't depend on anything, because their values
   101  	// are gathered prior to the graph walk and recorded in the context.
   102  	if len(n.Module) == 0 {
   103  		return nil
   104  	}
   105  
   106  	// Otherwise, we depend on anything referenced by our value expression.
   107  	// We ignore diagnostics here under the assumption that we'll re-eval
   108  	// all these things later and catch them then; for our purposes here,
   109  	// we only care about valid references.
   110  	//
   111  	// Due to our GraphNodeReferenceOutside implementation, the addresses
   112  	// returned by this function are interpreted in the _parent_ module from
   113  	// where our associated variable was declared, which is correct because
   114  	// our value expression is assigned within a "module" block in the parent
   115  	// module.
   116  	refs, _ := lang.ReferencesInExpr(addrs.ParseRef, n.Expr)
   117  	return refs
   118  }
   119  
   120  // GraphNodeReferenceOutside implementation
   121  func (n *nodeExpandModuleVariable) ReferenceOutside() (selfPath, referencePath addrs.Module) {
   122  	return n.Module, n.Module.Parent()
   123  }
   124  
   125  // GraphNodeReferenceable
   126  func (n *nodeExpandModuleVariable) ReferenceableAddrs() []addrs.Referenceable {
   127  	return []addrs.Referenceable{n.Addr}
   128  }
   129  
   130  // nodeModuleVariable represents a module variable input during
   131  // the apply step.
   132  type nodeModuleVariable struct {
   133  	Addr   addrs.AbsInputVariableInstance
   134  	Config *configs.Variable // Config is the var in the config
   135  	Expr   hcl.Expression    // Expr is the value expression given in the call
   136  	// ModuleInstance in order to create the appropriate context for evaluating
   137  	// ModuleCallArguments, ex. so count.index and each.key can resolve
   138  	ModuleInstance addrs.ModuleInstance
   139  }
   140  
   141  // Ensure that we are implementing all of the interfaces we think we are
   142  // implementing.
   143  var (
   144  	_ GraphNodeModuleInstance = (*nodeModuleVariable)(nil)
   145  	_ GraphNodeExecutable     = (*nodeModuleVariable)(nil)
   146  	_ graphNodeTemporaryValue = (*nodeModuleVariable)(nil)
   147  	_ dag.GraphNodeDotter     = (*nodeModuleVariable)(nil)
   148  )
   149  
   150  func (n *nodeModuleVariable) temporaryValue() bool {
   151  	return true
   152  }
   153  
   154  func (n *nodeModuleVariable) Name() string {
   155  	return n.Addr.String()
   156  }
   157  
   158  // GraphNodeModuleInstance
   159  func (n *nodeModuleVariable) Path() addrs.ModuleInstance {
   160  	// We execute in the parent scope (above our own module) because
   161  	// expressions in our value are resolved in that context.
   162  	return n.Addr.Module.Parent()
   163  }
   164  
   165  // GraphNodeModulePath
   166  func (n *nodeModuleVariable) ModulePath() addrs.Module {
   167  	return n.Addr.Module.Module()
   168  }
   169  
   170  // GraphNodeExecutable
   171  func (n *nodeModuleVariable) Execute(ctx EvalContext, op walkOperation) (diags tfdiags.Diagnostics) {
   172  	log.Printf("[TRACE] nodeModuleVariable: evaluating %s", n.Addr)
   173  
   174  	var val cty.Value
   175  	var err error
   176  
   177  	switch op {
   178  	case walkValidate:
   179  		val, err = n.evalModuleVariable(ctx, true)
   180  		diags = diags.Append(err)
   181  	default:
   182  		val, err = n.evalModuleVariable(ctx, false)
   183  		diags = diags.Append(err)
   184  	}
   185  	if diags.HasErrors() {
   186  		return diags
   187  	}
   188  
   189  	// Set values for arguments of a child module call, for later retrieval
   190  	// during expression evaluation.
   191  	_, call := n.Addr.Module.CallInstance()
   192  	ctx.SetModuleCallArgument(call, n.Addr.Variable, val)
   193  
   194  	return evalVariableValidations(n.Addr, n.Config, n.Expr, ctx)
   195  }
   196  
   197  // dag.GraphNodeDotter impl.
   198  func (n *nodeModuleVariable) DotNode(name string, opts *dag.DotOpts) *dag.DotNode {
   199  	return &dag.DotNode{
   200  		Name: name,
   201  		Attrs: map[string]string{
   202  			"label": n.Name(),
   203  			"shape": "note",
   204  		},
   205  	}
   206  }
   207  
   208  // evalModuleVariable produces the value for a particular variable as will
   209  // be used by a child module instance.
   210  //
   211  // The result is written into a map, with its key set to the local name of the
   212  // variable, disregarding the module instance address. A map is returned instead
   213  // of a single value as a result of trying to be convenient for use with
   214  // EvalContext.SetModuleCallArguments, which expects a map to merge in with any
   215  // existing arguments.
   216  //
   217  // validateOnly indicates that this evaluation is only for config
   218  // validation, and we will not have any expansion module instance
   219  // repetition data.
   220  func (n *nodeModuleVariable) evalModuleVariable(ctx EvalContext, validateOnly bool) (cty.Value, error) {
   221  	var diags tfdiags.Diagnostics
   222  	var givenVal cty.Value
   223  	var errSourceRange tfdiags.SourceRange
   224  	if expr := n.Expr; expr != nil {
   225  		var moduleInstanceRepetitionData instances.RepetitionData
   226  
   227  		switch {
   228  		case validateOnly:
   229  			// the instance expander does not track unknown expansion values, so we
   230  			// have to assume all RepetitionData is unknown.
   231  			moduleInstanceRepetitionData = instances.RepetitionData{
   232  				CountIndex: cty.UnknownVal(cty.Number),
   233  				EachKey:    cty.UnknownVal(cty.String),
   234  				EachValue:  cty.DynamicVal,
   235  			}
   236  
   237  		default:
   238  			// Get the repetition data for this module instance,
   239  			// so we can create the appropriate scope for evaluating our expression
   240  			moduleInstanceRepetitionData = ctx.InstanceExpander().GetModuleInstanceRepetitionData(n.ModuleInstance)
   241  		}
   242  
   243  		scope := ctx.EvaluationScope(nil, nil, moduleInstanceRepetitionData)
   244  		val, moreDiags := scope.EvalExpr(expr, cty.DynamicPseudoType)
   245  		diags = diags.Append(moreDiags)
   246  		if moreDiags.HasErrors() {
   247  			return cty.DynamicVal, diags.ErrWithWarnings()
   248  		}
   249  		givenVal = val
   250  		errSourceRange = tfdiags.SourceRangeFromHCL(expr.Range())
   251  	} else {
   252  		// We'll use cty.NilVal to represent the variable not being set at all.
   253  		givenVal = cty.NilVal
   254  		errSourceRange = tfdiags.SourceRangeFromHCL(n.Config.DeclRange) // we use the declaration range as a fallback for an undefined variable
   255  	}
   256  
   257  	// We construct a synthetic InputValue here to pretend as if this were
   258  	// a root module variable set from outside, just as a convenience so we
   259  	// can reuse the InputValue type for this.
   260  	rawVal := &InputValue{
   261  		Value:       givenVal,
   262  		SourceType:  ValueFromConfig,
   263  		SourceRange: errSourceRange,
   264  	}
   265  
   266  	finalVal, moreDiags := prepareFinalInputVariableValue(n.Addr, rawVal, n.Config)
   267  	diags = diags.Append(moreDiags)
   268  
   269  	return finalVal, diags.ErrWithWarnings()
   270  }