github.com/opentofu/opentofu@v1.7.1/internal/tofu/transform_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  
    11  	"github.com/zclconf/go-cty/cty"
    12  
    13  	"github.com/opentofu/opentofu/internal/addrs"
    14  	"github.com/opentofu/opentofu/internal/tfdiags"
    15  
    16  	"github.com/hashicorp/hcl/v2"
    17  
    18  	"github.com/opentofu/opentofu/internal/configs"
    19  )
    20  
    21  // ModuleVariableTransformer is a GraphTransformer that adds all the variables
    22  // in the configuration to the graph.
    23  //
    24  // Any "variable" block present in any non-root module is included here, even
    25  // if a particular variable is not referenced from anywhere.
    26  //
    27  // The transform will produce errors if a call to a module does not conform
    28  // to the expected set of arguments, but this transformer is not in a good
    29  // position to return errors and so the validate walk should include specific
    30  // steps for validating module blocks, separate from this transform.
    31  type ModuleVariableTransformer struct {
    32  	Config *configs.Config
    33  }
    34  
    35  func (t *ModuleVariableTransformer) Transform(g *Graph) error {
    36  	return t.transform(g, nil, t.Config)
    37  }
    38  
    39  func (t *ModuleVariableTransformer) transform(g *Graph, parent, c *configs.Config) error {
    40  	// We can have no variables if we have no configuration.
    41  	if c == nil {
    42  		return nil
    43  	}
    44  
    45  	// Transform all the children first.
    46  	for _, cc := range c.Children {
    47  		if err := t.transform(g, c, cc); err != nil {
    48  			return err
    49  		}
    50  	}
    51  
    52  	// If we're processing anything other than the root module then we'll
    53  	// add graph nodes for variables defined inside. (Variables for the root
    54  	// module are dealt with in RootVariableTransformer).
    55  	// If we have a parent, we can determine if a module variable is being
    56  	// used, so we transform this.
    57  	if parent != nil {
    58  		if err := t.transformSingle(g, parent, c); err != nil {
    59  			return err
    60  		}
    61  	}
    62  
    63  	return nil
    64  }
    65  
    66  func (t *ModuleVariableTransformer) transformSingle(g *Graph, parent, c *configs.Config) error {
    67  	_, call := c.Path.Call()
    68  
    69  	// Find the call in the parent module configuration, so we can get the
    70  	// expressions given for each input variable at the call site.
    71  	callConfig, exists := parent.Module.ModuleCalls[call.Name]
    72  	if !exists {
    73  		// This should never happen, since it indicates an improperly-constructed
    74  		// configuration tree.
    75  		panic(fmt.Errorf("no module call block found for %s", c.Path))
    76  	}
    77  
    78  	// We need to construct a schema for the expected call arguments based on
    79  	// the configured variables in our config, which we can then use to
    80  	// decode the content of the call block.
    81  	schema := &hcl.BodySchema{}
    82  	for _, v := range c.Module.Variables {
    83  		schema.Attributes = append(schema.Attributes, hcl.AttributeSchema{
    84  			Name:     v.Name,
    85  			Required: v.Default == cty.NilVal,
    86  		})
    87  	}
    88  
    89  	content, contentDiags := callConfig.Config.Content(schema)
    90  	if contentDiags.HasErrors() {
    91  		// Validation code elsewhere should deal with any errors before we
    92  		// get in here, but we'll report them out here just in case, to
    93  		// avoid crashes.
    94  		var diags tfdiags.Diagnostics
    95  		diags = diags.Append(contentDiags)
    96  		return diags.Err()
    97  	}
    98  
    99  	for _, v := range c.Module.Variables {
   100  		var expr hcl.Expression
   101  		if attr := content.Attributes[v.Name]; attr != nil {
   102  			expr = attr.Expr
   103  		}
   104  
   105  		// Add a plannable node, as the variable may expand
   106  		// during module expansion
   107  		node := &nodeExpandModuleVariable{
   108  			Addr: addrs.InputVariable{
   109  				Name: v.Name,
   110  			},
   111  			Module: c.Path,
   112  			Config: v,
   113  			Expr:   expr,
   114  		}
   115  		g.Add(node)
   116  	}
   117  
   118  	return nil
   119  }