package terraform
import (
- "log"
+ "fmt"
- "github.com/hashicorp/terraform/config"
- "github.com/hashicorp/terraform/config/module"
- "github.com/hashicorp/terraform/dag"
+ "github.com/hashicorp/hcl2/hcl/hclsyntax"
+ "github.com/hashicorp/terraform/tfdiags"
+ "github.com/zclconf/go-cty/cty"
+
+ "github.com/hashicorp/hcl2/hcl"
+ "github.com/hashicorp/terraform/configs"
)
// ModuleVariableTransformer is a GraphTransformer that adds all the variables
// in the configuration to the graph.
//
-// This only adds variables that are referenced by other things in the graph.
-// If a module variable is not referenced, it won't be added 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 {
- Module *module.Tree
-
- DisablePrune bool // True if pruning unreferenced should be disabled
+ Config *configs.Config
}
func (t *ModuleVariableTransformer) Transform(g *Graph) error {
- return t.transform(g, nil, t.Module)
+ return t.transform(g, nil, t.Config)
}
-func (t *ModuleVariableTransformer) transform(g *Graph, parent, m *module.Tree) error {
- // If no config, no variables
- if m == nil {
+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. This must be done BEFORE the transform
- // above since child module variables can reference parent module variables.
- for _, c := range m.Children() {
- if err := t.transform(g, m, c); err != 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, m); err != nil {
+ if err := t.transformSingle(g, parent, c); err != nil {
return err
}
}
return nil
}
-func (t *ModuleVariableTransformer) transformSingle(g *Graph, parent, m *module.Tree) error {
- // If we have no vars, we're done!
- vars := m.Config().Variables
- if len(vars) == 0 {
- log.Printf("[TRACE] Module %#v has no variables, skipping.", m.Path())
- 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))
}
- // Look for usage of this module
- var mod *config.Module
- for _, modUse := range parent.Config().Modules {
- if modUse.Name == m.Name() {
- mod = modUse
- break
- }
+ // 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,
+ })
}
- if mod == nil {
- log.Printf("[INFO] Module %#v not used, not adding variables", m.Path())
- return nil
+
+ 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()
}
- // Build the reference map so we can determine if we're referencing things.
- refMap := NewReferenceMap(g.Vertices())
-
- // Add all variables here
- for _, v := range vars {
- // Determine the value of the variable. If it isn't in the
- // configuration then it was never set and that's not a problem.
- var value *config.RawConfig
- if raw, ok := mod.RawConfig.Raw[v.Name]; ok {
- var err error
- value, err = config.NewRawConfig(map[string]interface{}{
- v.Name: raw,
- })
- if err != nil {
- // This shouldn't happen because it is already in
- // a RawConfig above meaning it worked once before.
- panic(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
}
}
- // Build the node.
- //
- // NOTE: For now this is just an "applyable" variable. As we build
- // new graph builders for the other operations I suspect we'll
- // find a way to parameterize this, require new transforms, etc.
+ // 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{
- PathValue: normalizeModulePath(m.Path()),
- Config: v,
- Value: value,
- Module: t.Module,
+ Addr: path.InputVariable(v.Name),
+ Config: v,
+ Expr: expr,
}
-
- if !t.DisablePrune {
- // If the node is not referenced by anything, then we don't need
- // to include it since it won't be used.
- if matches := refMap.ReferencedBy(node); len(matches) == 0 {
- log.Printf(
- "[INFO] Not including %q in graph, nothing depends on it",
- dag.VertexName(node))
- continue
- }
- }
-
- // Add it!
g.Add(node)
}