kubeform.dev/terraform-backend-sdk@v0.0.0-20220310143633-45f07fe731c5/terraform/node_module_variable.go (about)

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