diff --git a/terraform/context_test.go b/terraform/context_test.go index d4597f11f..a85e77db6 100644 --- a/terraform/context_test.go +++ b/terraform/context_test.go @@ -4128,6 +4128,48 @@ func TestContext2Apply_nilDiff(t *testing.T) { } } +func TestContext2Apply_outputOrphan(t *testing.T) { + m := testModule(t, "apply-output-orphan") + p := testProvider("aws") + p.ApplyFn = testApplyFn + p.DiffFn = testDiffFn + + state := &State{ + Modules: []*ModuleState{ + &ModuleState{ + Path: rootModulePath, + Outputs: map[string]string{ + "foo": "bar", + "bar": "baz", + }, + }, + }, + } + + ctx := testContext2(t, &ContextOpts{ + Module: m, + Providers: map[string]ResourceProviderFactory{ + "aws": testProviderFuncFixed(p), + }, + State: state, + }) + + if _, err := ctx.Plan(); err != nil { + t.Fatalf("err: %s", err) + } + + state, err := ctx.Apply() + if err != nil { + t.Fatalf("err: %s", err) + } + + actual := strings.TrimSpace(state.String()) + expected := strings.TrimSpace(testTerraformApplyOutputOrphanStr) + if actual != expected { + t.Fatalf("bad: \n%s", actual) + } +} + func TestContext2Apply_Provisioner_compute(t *testing.T) { m := testModule(t, "apply-provisioner-compute") p := testProvider("aws") diff --git a/terraform/eval_output.go b/terraform/eval_output.go index 0d9056d70..acdc268c3 100644 --- a/terraform/eval_output.go +++ b/terraform/eval_output.go @@ -6,6 +6,34 @@ import ( "github.com/hashicorp/terraform/config" ) +// EvalDeleteOutput is an EvalNode implementation that deletes an output +// from the state. +type EvalDeleteOutput struct { + Name string +} + +// TODO: test +func (n *EvalDeleteOutput) Eval(ctx EvalContext) (interface{}, error) { + state, lock := ctx.State() + if state == nil { + return nil, nil + } + + // Get a write lock so we can access this instance + lock.Lock() + defer lock.Unlock() + + // Look for the module state. If we don't have one, create it. + mod := state.ModuleByPath(ctx.Path()) + if mod == nil { + return nil, nil + } + + delete(mod.Outputs, n.Name) + + return nil, nil +} + // EvalWriteOutput is an EvalNode implementation that writes the output // for the given name to the current state. type EvalWriteOutput struct { diff --git a/terraform/graph_builder.go b/terraform/graph_builder.go index f08e20f16..425d2ef13 100644 --- a/terraform/graph_builder.go +++ b/terraform/graph_builder.go @@ -95,6 +95,9 @@ func (b *BuiltinGraphBuilder) Steps() []GraphTransformer { Targeting: (len(b.Targets) > 0), }, + // Output-related transformations + &AddOutputOrphanTransformer{State: b.State}, + // Provider-related transformations &MissingProviderTransformer{Providers: b.Providers}, &ProviderTransformer{}, diff --git a/terraform/terraform_test.go b/terraform/terraform_test.go index 38be7b9fd..09c330792 100644 --- a/terraform/terraform_test.go +++ b/terraform/terraform_test.go @@ -373,6 +373,13 @@ do_instance.foo: type = do_instance ` +const testTerraformApplyOutputOrphanStr = ` + +Outputs: + +foo = bar +` + const testTerraformApplyProvisionerStr = ` aws_instance.bar: ID = foo diff --git a/terraform/test-fixtures/apply-output-orphan/main.tf b/terraform/test-fixtures/apply-output-orphan/main.tf new file mode 100644 index 000000000..70619c4e3 --- /dev/null +++ b/terraform/test-fixtures/apply-output-orphan/main.tf @@ -0,0 +1 @@ +output "foo" { value = "bar" } diff --git a/terraform/transform_output.go b/terraform/transform_output.go index 62e127d38..7741f9d3d 100644 --- a/terraform/transform_output.go +++ b/terraform/transform_output.go @@ -57,3 +57,12 @@ type graphNodeOrphanOutput struct { func (n *graphNodeOrphanOutput) Name() string { return fmt.Sprintf("output.%s (orphan)", n.OutputName) } + +func (n *graphNodeOrphanOutput) EvalTree() EvalNode { + return &EvalOpFilter{ + Ops: []walkOperation{walkApply, walkRefresh}, + Node: &EvalDeleteOutput{ + Name: n.OutputName, + }, + } +}