diff --git a/terraform/graph_config_node.go b/terraform/graph_config_node.go index abe1e58c1..723526c36 100644 --- a/terraform/graph_config_node.go +++ b/terraform/graph_config_node.go @@ -137,6 +137,10 @@ func (n *GraphNodeConfigOutput) ConfigType() GraphNodeConfigType { return GraphNodeConfigTypeOutput } +func (n *GraphNodeConfigOutput) OutputName() string { + return n.Output.Name +} + func (n *GraphNodeConfigOutput) DependableName() []string { return []string{n.Name()} } diff --git a/terraform/graph_config_node_test.go b/terraform/graph_config_node_test.go index 33390fc24..13848663d 100644 --- a/terraform/graph_config_node_test.go +++ b/terraform/graph_config_node_test.go @@ -41,6 +41,13 @@ func TestGraphNodeConfigModuleExpand(t *testing.T) { } } +func TestGraphNodeConfigOutput_impl(t *testing.T) { + var _ dag.Vertex = new(GraphNodeConfigOutput) + var _ dag.NamedVertex = new(GraphNodeConfigOutput) + var _ graphNodeConfig = new(GraphNodeConfigOutput) + var _ GraphNodeOutput = new(GraphNodeConfigOutput) +} + func TestGraphNodeConfigProvider_impl(t *testing.T) { var _ dag.Vertex = new(GraphNodeConfigProvider) var _ dag.NamedVertex = new(GraphNodeConfigProvider) diff --git a/terraform/test-fixtures/transform-orphan-output-basic/main.tf b/terraform/test-fixtures/transform-orphan-output-basic/main.tf new file mode 100644 index 000000000..70619c4e3 --- /dev/null +++ b/terraform/test-fixtures/transform-orphan-output-basic/main.tf @@ -0,0 +1 @@ +output "foo" { value = "bar" } diff --git a/terraform/transform_output.go b/terraform/transform_output.go new file mode 100644 index 000000000..62e127d38 --- /dev/null +++ b/terraform/transform_output.go @@ -0,0 +1,59 @@ +package terraform + +import ( + "fmt" +) + +// GraphNodeOutput is an interface that nodes that are outputs must +// implement. The OutputName returned is the name of the output key +// that they manage. +type GraphNodeOutput interface { + OutputName() string +} + +// 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. +type AddOutputOrphanTransformer struct { + State *State +} + +func (t *AddOutputOrphanTransformer) Transform(g *Graph) error { + // Get the state for this module. If we have no state, we have no orphans + state := t.State.ModuleByPath(g.Path) + if state == nil { + return nil + } + + // Create the set of outputs we do have in the graph + found := make(map[string]struct{}) + for _, v := range g.Vertices() { + on, ok := v.(GraphNodeOutput) + if !ok { + continue + } + + found[on.OutputName()] = struct{}{} + } + + // Go over all the outputs. If we don't have a graph node for it, + // create it. It doesn't need to depend on anything, since its just + // setting it empty. + for k, _ := range state.Outputs { + if _, ok := found[k]; ok { + continue + } + + g.Add(&graphNodeOrphanOutput{OutputName: k}) + } + + return nil +} + +type graphNodeOrphanOutput struct { + OutputName string +} + +func (n *graphNodeOrphanOutput) Name() string { + return fmt.Sprintf("output.%s (orphan)", n.OutputName) +} diff --git a/terraform/transform_output_test.go b/terraform/transform_output_test.go new file mode 100644 index 000000000..dc9ea0a76 --- /dev/null +++ b/terraform/transform_output_test.go @@ -0,0 +1,45 @@ +package terraform + +import ( + "strings" + "testing" +) + +func TestAddOutputOrphanTransformer(t *testing.T) { + mod := testModule(t, "transform-orphan-output-basic") + state := &State{ + Modules: []*ModuleState{ + &ModuleState{ + Path: RootModulePath, + Outputs: map[string]string{ + "foo": "bar", + "bar": "baz", + }, + }, + }, + } + + g := Graph{Path: RootModulePath} + { + tf := &ConfigTransformer{Module: mod} + if err := tf.Transform(&g); err != nil { + t.Fatalf("err: %s", err) + } + } + + transform := &AddOutputOrphanTransformer{State: state} + if err := transform.Transform(&g); err != nil { + t.Fatalf("err: %s", err) + } + + actual := strings.TrimSpace(g.String()) + expected := strings.TrimSpace(testTransformOrphanOutputBasicStr) + if actual != expected { + t.Fatalf("bad:\n\n%s", actual) + } +} + +const testTransformOrphanOutputBasicStr = ` +output.bar (orphan) +output.foo +`