terraform: apply builder adds outputs to graphs

This commit is contained in:
Mitchell Hashimoto 2016-09-15 23:20:35 -07:00
parent ba51295267
commit 0d7674b079
No known key found for this signature in database
GPG Key ID: 744E147AA52F5B0A
4 changed files with 176 additions and 28 deletions

View File

@ -63,6 +63,9 @@ func (b *ApplyGraphBuilder) Steps() []GraphTransformer {
&MissingProvisionerTransformer{Provisioners: b.Provisioners},
&ProvisionerTransformer{},
// Add the outputs
&OutputTransformer{Module: b.Module},
// Connect references so ordering is correct
&ReferenceTransformer{},

57
terraform/node_output.go Normal file
View File

@ -0,0 +1,57 @@
package terraform
import (
"fmt"
"github.com/hashicorp/terraform/config"
)
// NodeApplyableOutput represents an output that is "applyable":
// it is ready to be applied.
type NodeApplyableOutput struct {
PathValue []string
Config *config.Output // Config is the output in the config
}
func (n *NodeApplyableOutput) Name() string {
result := fmt.Sprintf("output.%s", n.Config.Name)
if len(n.PathValue) > 1 {
result = fmt.Sprintf("%s.%s", modulePrefixStr(n.PathValue), result)
}
return result
}
// GraphNodeSubPath
func (n *NodeApplyableOutput) Path() []string {
return n.PathValue
}
// GraphNodeReferenceable
func (n *NodeApplyableOutput) ReferenceableName() []string {
return []string{n.Name()}
}
// GraphNodeReferencer
func (n *NodeApplyableOutput) References() []string {
var result []string
result = append(result, ReferencesFromConfig(n.Config.RawConfig)...)
return result
}
// GraphNodeEvalable
func (n *NodeApplyableOutput) EvalTree() EvalNode {
return &EvalOpFilter{
Ops: []walkOperation{walkRefresh, walkPlan, walkApply,
walkDestroy, walkInput, walkValidate},
Node: &EvalSequence{
Nodes: []EvalNode{
&EvalWriteOutput{
Name: n.Config.Name,
Sensitive: n.Config.Sensitive,
Value: n.Config.RawConfig,
},
},
},
}
}

View File

@ -1,6 +1,8 @@
package terraform
import (
"log"
"github.com/hashicorp/terraform/config/module"
)
@ -15,5 +17,58 @@ type OutputTransformer struct {
}
func (t *OutputTransformer) Transform(g *Graph) error {
return t.transform(g, t.Module)
}
func (t *OutputTransformer) transform(g *Graph, m *module.Tree) error {
// If no config, no outputs
if m == nil {
return nil
}
// Transform all the children. We must do this first because
// we can reference module outputs and they must show up in the
// reference map.
for _, c := range m.Children() {
if err := t.transform(g, c); err != nil {
return err
}
}
// If we have no outputs, we're done!
os := m.Config().Outputs
if len(os) == 0 {
return nil
}
// Build the reference map so we can determine if we're referencing things.
refMap := NewReferenceMap(g.Vertices())
// Add all outputs here
for _, o := range os {
// Build the node
node := &NodeApplyableOutput{
PathValue: m.Path(),
Config: o,
}
// 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(matches) == 0 || len(missing) > 0 {
log.Printf(
"[INFO] Not including %q in graph, matches: %v, missing: %s",
node, matches, missing)
continue
}
// Add it!
g.Add(node)
}
return nil
}

View File

@ -30,9 +30,67 @@ type GraphNodeReferencer interface {
type ReferenceTransformer struct{}
func (t *ReferenceTransformer) Transform(g *Graph) error {
// Build the mapping of reference => vertex for efficient lookups.
// Build a reference map so we can efficiently look up the references
vs := g.Vertices()
m := NewReferenceMap(vs)
// Find the things that reference things and connect them
for _, v := range vs {
parents, _ := m.References(v)
for _, parent := range parents {
g.Connect(dag.BasicEdge(v, parent))
}
}
return nil
}
// ReferenceMap is a structure that can be used to efficiently check
// for references on a graph.
type ReferenceMap struct {
// m is the mapping of referenceable name to list of verticies that
// implement that name. This is built on initialization.
m map[string][]dag.Vertex
}
// References returns the list of vertices that this vertex
// references along with any missing references.
func (m *ReferenceMap) References(v dag.Vertex) ([]dag.Vertex, []string) {
rn, ok := v.(GraphNodeReferencer)
if !ok {
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[1:]) + "."
}
}
var matches []dag.Vertex
var missing []string
for _, n := range rn.References() {
n = prefix + n
parents, ok := m.m[n]
if !ok {
missing = append(missing, n)
continue
}
matches = append(matches, parents...)
}
return matches, missing
}
// NewReferenceMap is used to create a new reference map for the
// given set of vertices.
func NewReferenceMap(vs []dag.Vertex) *ReferenceMap {
// Build the lookup table
refMap := make(map[string][]dag.Vertex)
for _, v := range g.Vertices() {
for _, v := range vs {
// We're only looking for referenceable nodes
rn, ok := v.(GraphNodeReferenceable)
if !ok {
@ -54,32 +112,7 @@ func (t *ReferenceTransformer) Transform(g *Graph) error {
}
}
// Find the things that reference things and connect them
for _, v := range g.Vertices() {
rn, ok := v.(GraphNodeReferencer)
if !ok {
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[1:]) + "."
}
}
for _, n := range rn.References() {
n = prefix + n
if parents, ok := refMap[n]; ok {
for _, parent := range parents {
g.Connect(dag.BasicEdge(v, parent))
}
}
}
}
return nil
return &ReferenceMap{m: refMap}
}
// ReferencesFromConfig returns the references that a configuration has