opentofu/terraform/transform_module_variable.go
Pam Selle c249943360
Module Expansion: Part 2 (#24154)
* WIP: dynamic expand

* WIP: add variable and local support

* WIP: outputs

* WIP: Add referencer

* String representation, fixing tests it impacts

* Fixes TestContext2Apply_outputOrphanModule

* Fix TestContext2Apply_plannedDestroyInterpolatedCount

* Update DestroyOutputTransformer and associated types to reflect PlannableOutputs

* Remove comment about locals

* Remove module count enablement

* Removes allowing count for modules, and reverts the test,
while adding a Skip()'d test that works when you re-enable
the config

* update TargetDownstream signature to match master

* remove unnecessary method

Co-authored-by: James Bardin <j.bardin@gmail.com>
2020-02-24 17:42:32 -05:00

130 lines
4.0 KiB
Go

package terraform
import (
"fmt"
"github.com/hashicorp/hcl/v2/hclsyntax"
"github.com/hashicorp/terraform/addrs"
"github.com/hashicorp/terraform/tfdiags"
"github.com/zclconf/go-cty/cty"
"github.com/hashicorp/hcl/v2"
"github.com/hashicorp/terraform/configs"
)
// ModuleVariableTransformer is a GraphTransformer that adds all the variables
// in the configuration to the graph.
//
// Any "variable" block present in any non-root module is included here, even
// if a particular variable is not referenced from anywhere.
//
// The transform will produce errors if a call to a module does not conform
// to the expected set of arguments, but this transformer is not in a good
// position to return errors and so the validate walk should include specific
// steps for validating module blocks, separate from this transform.
type ModuleVariableTransformer struct {
Config *configs.Config
}
func (t *ModuleVariableTransformer) Transform(g *Graph) error {
return t.transform(g, nil, t.Config)
}
func (t *ModuleVariableTransformer) transform(g *Graph, parent, c *configs.Config) error {
// We can have no variables if we have no configuration.
if c == nil {
return nil
}
// Transform all the children first.
for _, cc := range c.Children {
if err := t.transform(g, c, cc); err != nil {
return err
}
}
// If we're processing anything other than the root module then we'll
// add graph nodes for variables defined inside. (Variables for the root
// module are dealt with in RootVariableTransformer).
// If we have a parent, we can determine if a module variable is being
// used, so we transform this.
if parent != nil {
if err := t.transformSingle(g, parent, c); err != nil {
return err
}
}
return nil
}
func (t *ModuleVariableTransformer) transformSingle(g *Graph, parent, c *configs.Config) error {
// Our addressing system distinguishes between modules and module instances,
// but we're not yet ready to make that distinction here (since we don't
// support "count"/"for_each" on modules) and so we just do a naive
// transform of the module path into a module instance path, assuming that
// no keys are in use. This should be removed when "count" and "for_each"
// are implemented for modules.
path := c.Path.UnkeyedInstanceShim()
_, call := path.Call()
// Find the call in the parent module configuration, so we can get the
// expressions given for each input variable at the call site.
callConfig, exists := parent.Module.ModuleCalls[call.Name]
if !exists {
// This should never happen, since it indicates an improperly-constructed
// configuration tree.
panic(fmt.Errorf("no module call block found for %s", path))
}
// We need to construct a schema for the expected call arguments based on
// the configured variables in our config, which we can then use to
// decode the content of the call block.
schema := &hcl.BodySchema{}
for _, v := range c.Module.Variables {
schema.Attributes = append(schema.Attributes, hcl.AttributeSchema{
Name: v.Name,
Required: v.Default == cty.NilVal,
})
}
content, contentDiags := callConfig.Config.Content(schema)
if contentDiags.HasErrors() {
// Validation code elsewhere should deal with any errors before we
// get in here, but we'll report them out here just in case, to
// avoid crashes.
var diags tfdiags.Diagnostics
diags = diags.Append(contentDiags)
return diags.Err()
}
for _, v := range c.Module.Variables {
var expr hcl.Expression
if attr := content.Attributes[v.Name]; attr != nil {
expr = attr.Expr
} else {
// No expression provided for this variable, so we'll make a
// synthetic one using the variable's default value.
expr = &hclsyntax.LiteralValueExpr{
Val: v.Default,
SrcRange: v.DeclRange, // This is not exact, but close enough
}
}
// Add a plannable node, as the variable may expand
// during module expansion
node := &NodePlannableModuleVariable{
Addr: addrs.InputVariable{
Name: v.Name,
},
Module: c.Path,
Config: v,
Expr: expr,
}
g.Add(node)
}
return nil
}