From 0d7674b0791037918d5347b52c1789448c6c9776 Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Thu, 15 Sep 2016 23:20:35 -0700 Subject: [PATCH] terraform: apply builder adds outputs to graphs --- terraform/graph_builder_apply.go | 3 ++ terraform/node_output.go | 57 ++++++++++++++++++++ terraform/transform_output.go | 55 ++++++++++++++++++++ terraform/transform_reference.go | 89 ++++++++++++++++++++++---------- 4 files changed, 176 insertions(+), 28 deletions(-) create mode 100644 terraform/node_output.go diff --git a/terraform/graph_builder_apply.go b/terraform/graph_builder_apply.go index 1deea69c5..580dc7f31 100644 --- a/terraform/graph_builder_apply.go +++ b/terraform/graph_builder_apply.go @@ -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{}, diff --git a/terraform/node_output.go b/terraform/node_output.go new file mode 100644 index 000000000..517a795af --- /dev/null +++ b/terraform/node_output.go @@ -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, + }, + }, + }, + } +} diff --git a/terraform/transform_output.go b/terraform/transform_output.go index 0aed7b017..3e5f33986 100644 --- a/terraform/transform_output.go +++ b/terraform/transform_output.go @@ -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 } diff --git a/terraform/transform_reference.go b/terraform/transform_reference.go index d0c15c118..0f061dda6 100644 --- a/terraform/transform_reference.go +++ b/terraform/transform_reference.go @@ -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