github.com/eliastor/durgaform@v0.0.0-20220816172711-d0ab2d17673e/internal/durgaform/node_module_variable.go (about)

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