diff --git a/terraform/context.go b/terraform/context.go index bf1980045..72f6a8813 100644 --- a/terraform/context.go +++ b/terraform/context.go @@ -112,8 +112,11 @@ func (c *Context) Apply() (*State, error) { // Walk err = g.Walk(c.applyWalkFn()) + // Prune the state so that we have as clean a state as possible + c.state.prune() + // If we have no errors, then calculate the outputs if we have any - if err == nil && len(c.config.Outputs) > 0 { + if err == nil && len(c.config.Outputs) > 0 && len(c.state.Resources) > 0 { c.state.Outputs = make(map[string]string) for _, o := range c.config.Outputs { if err = c.computeVars(o.RawConfig); err != nil { diff --git a/terraform/context_test.go b/terraform/context_test.go index 65e472de9..4b27e5017 100644 --- a/terraform/context_test.go +++ b/terraform/context_test.go @@ -557,6 +557,46 @@ func TestContextApply_destroy(t *testing.T) { } } +func TestContextApply_destroyOutputs(t *testing.T) { + c := testConfig(t, "apply-destroy-outputs") + h := new(HookRecordApplyOrder) + p := testProvider("aws") + p.ApplyFn = testApplyFn + p.DiffFn = testDiffFn + ctx := testContext(t, &ContextOpts{ + Config: c, + Hooks: []Hook{h}, + Providers: map[string]ResourceProviderFactory{ + "aws": testProviderFuncFixed(p), + }, + }) + + // First plan and apply a create operation + if _, err := ctx.Plan(nil); err != nil { + t.Fatalf("err: %s", err) + } + + if _, err := ctx.Apply(); err != nil { + t.Fatalf("err: %s", err) + } + + // Next, plan and apply a destroy operation + if _, err := ctx.Plan(&PlanOpts{Destroy: true}); err != nil { + t.Fatalf("err: %s", err) + } + + h.Active = true + + state, err := ctx.Apply() + if err != nil { + t.Fatalf("err: %s", err) + } + + if len(state.Resources) > 0 { + t.Fatalf("bad: %#v", state) + } +} + func TestContextApply_destroyOrphan(t *testing.T) { c := testConfig(t, "apply-error") p := testProvider("aws") diff --git a/terraform/state.go b/terraform/state.go index 8fef99889..f8972364a 100644 --- a/terraform/state.go +++ b/terraform/state.go @@ -40,6 +40,16 @@ func (s *State) deepcopy() *State { return result } +// prune is a helper that removes any empty IDs from the state +// and cleans it up in general. +func (s *State) prune() { + for k, v := range s.Resources { + if v.ID == "" { + delete(s.Resources, k) + } + } +} + // Orphans returns a list of keys of resources that are in the State // but aren't present in the configuration itself. Hence, these keys // represent the state of resources that are orphans. diff --git a/terraform/test-fixtures/apply-destroy-outputs/main.tf b/terraform/test-fixtures/apply-destroy-outputs/main.tf new file mode 100644 index 000000000..98c3476ef --- /dev/null +++ b/terraform/test-fixtures/apply-destroy-outputs/main.tf @@ -0,0 +1,14 @@ +resource "aws_instance" "foo" { + id = "foo" + num = "2" +} + +resource "aws_instance" "bar" { + id = "bar" + foo = "{aws_instance.foo.num}" + dep = "foo" +} + +output "foo" { + value = "${aws_instance.foo.id}" +}