opentofu/terraform/transform_module_variable.go
Martin Atkins 39e609d5fd vendor: switch to HCL 2.0 in the HCL repository
Previously we were using the experimental HCL 2 repository, but now we'll
shift over to the v2 import path within the main HCL repository as part of
actually releasing HCL 2.0 as stable.

This is a mechanical search/replace to the new import paths. It also
switches to the v2.0.0 release of HCL, which includes some new code that
Terraform didn't previously have but should not change any behavior that
matters for Terraform's purposes.

For the moment the experimental HCL2 repository is still an indirect
dependency via terraform-config-inspect, so it remains in our go.sum and
vendor directories for the moment. Because terraform-config-inspect uses
a much smaller subset of the HCL2 functionality, this does still manage
to prune the vendor directory a little. A subsequent release of
terraform-config-inspect should allow us to completely remove that old
repository in a future commit.
2019-10-02 15:10:21 -07:00

127 lines
4.1 KiB
Go

package terraform
import (
"fmt"
"github.com/hashicorp/hcl/v2/hclsyntax"
"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
}
}
// For now we treat all module variables as "applyable", even though
// such nodes are valid to use on other walks too. We may specialize
// this in future if we find reasons to employ different behaviors
// in different scenarios.
node := &NodeApplyableModuleVariable{
Addr: path.InputVariable(v.Name),
Config: v,
Expr: expr,
}
g.Add(node)
}
return nil
}