terraform: first stap at module variables, going to redo some things

This commit is contained in:
Mitchell Hashimoto 2016-09-16 13:56:10 -07:00
parent 6376c4ca9b
commit 4dfdc52ba0
No known key found for this signature in database
GPG Key ID: 744E147AA52F5B0A
4 changed files with 250 additions and 17 deletions

View File

@ -63,6 +63,9 @@ func (b *ApplyGraphBuilder) Steps() []GraphTransformer {
&MissingProvisionerTransformer{Provisioners: b.Provisioners},
&ProvisionerTransformer{},
// Add module variables
&VariableTransformer{Module: b.Module},
// Add the outputs
&OutputTransformer{Module: b.Module},

View File

@ -0,0 +1,93 @@
package terraform
import (
"fmt"
"github.com/hashicorp/terraform/config"
"github.com/hashicorp/terraform/config/module"
)
// NodeApplyableVariable represents a variable during the apply step.
type NodeApplyableVariable struct {
PathValue []string
Config *config.Variable // Config is the var in the config
Value *config.RawConfig // Value is the value that is set
Module *module.Tree // Antiquated, want to remove
}
func (n *NodeApplyableVariable) Name() string {
result := fmt.Sprintf("var.%s", n.Config.Name)
if len(n.PathValue) > 1 {
result = fmt.Sprintf("%s.%s", modulePrefixStr(n.PathValue), result)
}
return result
}
// GraphNodeSubPath
func (n *NodeApplyableVariable) Path() []string {
// We execute in the parent scope (above our own module) so that
// we can access the proper interpolations.
if len(n.PathValue) > 2 {
return n.PathValue[:len(n.PathValue)-1]
}
return nil
}
// GraphNodeReferenceGlobal
func (n *NodeApplyableVariable) ReferenceGlobal() bool {
// We have to create fully qualified references because we cross
// boundaries here: our ReferenceableName is in one path and our
// References are from another path.
return true
}
// GraphNodeReferenceable
func (n *NodeApplyableVariable) ReferenceableName() []string {
return []string{n.Name()}
}
// GraphNodeEvalable
func (n *NodeApplyableVariable) EvalTree() EvalNode {
// If we have no value, do nothing
if n.Value == nil {
return &EvalNoop{}
}
// Otherwise, interpolate the value of this variable and set it
// within the variables mapping.
var config *ResourceConfig
variables := make(map[string]interface{})
return &EvalSequence{
Nodes: []EvalNode{
&EvalInterpolate{
Config: n.Value,
Output: &config,
},
&EvalVariableBlock{
Config: &config,
VariableValues: variables,
},
&EvalCoerceMapVariable{
Variables: variables,
ModulePath: n.PathValue,
ModuleTree: n.Module,
},
&EvalTypeCheckVariable{
Variables: variables,
ModulePath: n.PathValue,
ModuleTree: n.Module,
},
&EvalSetVariables{
Module: &n.PathValue[len(n.PathValue)-1],
Variables: variables,
},
},
}
}

View File

@ -25,6 +25,22 @@ type GraphNodeReferencer interface {
References() []string
}
// GraphNodeReferenceGlobal is an interface that can optionally be
// implemented. If ReferenceGlobal returns true, then the References()
// and ReferenceableName() must be _fully qualified_ with "module.foo.bar"
// etc.
//
// This allows a node to reference and be referenced by a specific name
// that may cross module boundaries. This can be very dangerous so use
// this wisely.
//
// The primary use case for this is module boundaries (variables coming in).
type GraphNodeReferenceGlobal interface {
// Set to true to signal that references and name are fully
// qualified. See the above docs for more information.
ReferenceGlobal() bool
}
// ReferenceTransformer is a GraphTransformer that connects all the
// nodes that reference each other in order to form the proper ordering.
type ReferenceTransformer struct{}
@ -61,16 +77,9 @@ func (m *ReferenceMap) References(v dag.Vertex) ([]dag.Vertex, []string) {
return nil, nil
}
// If this node represents a sub path then we prefix
var prefix string
if pn, ok := v.(GraphNodeSubPath); ok {
if path := normalizeModulePath(pn.Path()); len(path) > 1 {
prefix = modulePrefixStr(path) + "."
}
}
var matches []dag.Vertex
var missing []string
prefix := m.prefix(v)
for _, n := range rn.References() {
n = prefix + n
parents, ok := m.m[n]
@ -85,9 +94,29 @@ func (m *ReferenceMap) References(v dag.Vertex) ([]dag.Vertex, []string) {
return matches, missing
}
func (m *ReferenceMap) prefix(v dag.Vertex) string {
// If the node is stating it is already fully qualified then
// we don't have to create the prefix!
if gn, ok := v.(GraphNodeReferenceGlobal); ok && gn.ReferenceGlobal() {
return ""
}
// Create the prefix based on the path
var prefix string
if pn, ok := v.(GraphNodeSubPath); ok {
if path := normalizeModulePath(pn.Path()); len(path) > 1 {
prefix = modulePrefixStr(path) + "."
}
}
return prefix
}
// NewReferenceMap is used to create a new reference map for the
// given set of vertices.
func NewReferenceMap(vs []dag.Vertex) *ReferenceMap {
var m ReferenceMap
// Build the lookup table
refMap := make(map[string][]dag.Vertex)
for _, v := range vs {
@ -97,22 +126,16 @@ func NewReferenceMap(vs []dag.Vertex) *ReferenceMap {
continue
}
// If this node represents a sub path then we prefix
var prefix string
if pn, ok := v.(GraphNodeSubPath); ok {
if path := normalizeModulePath(pn.Path()); len(path) > 1 {
prefix = modulePrefixStr(path) + "."
}
}
// Go through and cache them
prefix := m.prefix(v)
for _, n := range rn.ReferenceableName() {
n = prefix + n
refMap[n] = append(refMap[n], v)
}
}
return &ReferenceMap{m: refMap}
m.m = refMap
return &m
}
// ReferencesFromConfig returns the references that a configuration has

View File

@ -0,0 +1,114 @@
package terraform
import (
"log"
"github.com/hashicorp/terraform/config"
"github.com/hashicorp/terraform/config/module"
"github.com/hashicorp/terraform/dag"
)
// VariableTransformer is a GraphTransformer that adds all the variables
// in the configuration to the graph.
//
// This only adds variables that either have no dependencies (and therefore
// always succeed) or has dependencies that are 100% represented in the
// graph.
type VariableTransformer struct {
Module *module.Tree
}
func (t *VariableTransformer) Transform(g *Graph) error {
return t.transform(g, nil, t.Module)
}
func (t *VariableTransformer) transform(g *Graph, parent, m *module.Tree) error {
// If no config, no variables
if m == nil {
return nil
}
// Transform all the children.
for _, c := range m.Children() {
if err := t.transform(g, m, c); err != nil {
return err
}
}
// If we have no parent, then don't do anything. This is because
// we need to be able to get the set value from the module declaration.
if parent == nil {
return nil
}
// If we have no vars, we're done!
vars := m.Config().Variables
if len(vars) == 0 {
return nil
}
// Look for usage of this module
var mod *config.Module
for _, modUse := range parent.Config().Modules {
if modUse.Name == m.Name() {
mod = modUse
break
}
}
if mod == nil {
log.Printf("[INFO] Module %q not used, not adding variables", m.Name())
return nil
}
// 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)
}
}
// 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.
node := &NodeApplyableVariable{
PathValue: normalizeModulePath(m.Path()),
Config: v,
Value: value,
Module: t.Module,
}
// If the node references something, then we check to make sure
// that the thing it references is in the graph. If it isn't, then
// we don't add it because we may not be able to compute the output.
//
// If the node references nothing, we always include it since there
// is no other clear time to compute it.
matches, missing := refMap.References(node)
if len(missing) > 0 {
log.Printf(
"[INFO] Not including %q in graph, matches: %v, missing: %s",
dag.VertexName(node), matches, missing)
continue
}
// Add it!
g.Add(node)
}
return nil
}