diff --git a/terraform/graph_builder_apply.go b/terraform/graph_builder_apply.go index fe415c49f..7a6e735ca 100644 --- a/terraform/graph_builder_apply.go +++ b/terraform/graph_builder_apply.go @@ -54,6 +54,9 @@ func (b *ApplyGraphBuilder) Steps() []GraphTransformer { State: b.State, }, + // Create orphan output nodes + &OrphanOutputTransformer{Module: b.Module, State: b.State}, + // Attach the state &AttachStateTransformer{State: b.State}, diff --git a/terraform/node_output_orphan.go b/terraform/node_output_orphan.go new file mode 100644 index 000000000..651638d7e --- /dev/null +++ b/terraform/node_output_orphan.go @@ -0,0 +1,32 @@ +package terraform + +import ( + "fmt" +) + +// NodeOutputOrphan represents an output that is an orphan. +type NodeOutputOrphan struct { + OutputName string + PathValue []string +} + +func (n *NodeOutputOrphan) Name() string { + result := fmt.Sprintf("output.%s (orphan)", n.OutputName) + if len(n.PathValue) > 1 { + result = fmt.Sprintf("%s.%s", modulePrefixStr(n.PathValue), result) + } + + return result +} + +// GraphNodeSubPath +func (n *NodeOutputOrphan) Path() []string { + return n.PathValue +} + +// GraphNodeEvalable +func (n *NodeOutputOrphan) EvalTree() EvalNode { + return &EvalDeleteOutput{ + Name: n.OutputName, + } +} diff --git a/terraform/transform_orphan_output.go b/terraform/transform_orphan_output.go new file mode 100644 index 000000000..49568d5bc --- /dev/null +++ b/terraform/transform_orphan_output.go @@ -0,0 +1,64 @@ +package terraform + +import ( + "log" + + "github.com/hashicorp/terraform/config" + "github.com/hashicorp/terraform/config/module" +) + +// OrphanOutputTransformer finds the outputs that aren't present +// in the given config that are in the state and adds them to the graph +// for deletion. +type OrphanOutputTransformer struct { + Module *module.Tree // Root module + State *State // State is the root state +} + +func (t *OrphanOutputTransformer) Transform(g *Graph) error { + if t.State == nil { + log.Printf("[DEBUG] No state, no orphan outputs") + return nil + } + + return t.transform(g, t.Module) +} + +func (t *OrphanOutputTransformer) transform(g *Graph, m *module.Tree) error { + // Get our configuration, and recurse into children + var c *config.Config + if m != nil { + c = m.Config() + for _, child := range m.Children() { + if err := t.transform(g, child); err != nil { + return err + } + } + } + + // Get the state. If there is no state, then we have no orphans! + path := normalizeModulePath(m.Path()) + state := t.State.ModuleByPath(path) + if state == nil { + return nil + } + + // Make a map of the valid outputs + valid := make(map[string]struct{}) + for _, o := range c.Outputs { + valid[o.Name] = struct{}{} + } + + // Go through the outputs and find the ones that aren't in our config. + for n, _ := range state.Outputs { + // If it is in the valid map, then ignore + if _, ok := valid[n]; ok { + continue + } + + // Orphan! + g.Add(&NodeOutputOrphan{OutputName: n, PathValue: path}) + } + + return nil +} diff --git a/terraform/transform_output_orphan.go b/terraform/transform_output_orphan.go index d3e839ce1..ffaa0b7e2 100644 --- a/terraform/transform_output_orphan.go +++ b/terraform/transform_output_orphan.go @@ -16,6 +16,9 @@ type GraphNodeOutput interface { // AddOutputOrphanTransformer is a transformer that adds output orphans // to the graph. Output orphans are outputs that are no longer in the // configuration and therefore need to be removed from the state. +// +// NOTE: This is the _old_ way to add output orphans that is used with +// legacy graph builders. The new way is OrphanOutputTransformer. type AddOutputOrphanTransformer struct { State *State }