diff --git a/terraform/context.go b/terraform/context.go index 813a4c5ca..10d9c100f 100644 --- a/terraform/context.go +++ b/terraform/context.go @@ -592,6 +592,11 @@ func (c *Context) applyWalkFn() depgraph.WalkFunc { handleHook(h.PreApply(r.Id, is, diff)) } + // We create a new instance if there was no ID + // previously or the diff requires re-creating the + // underlying instance + createNew := is.ID == "" || diff.RequiresNew() + // With the completed diff, apply! log.Printf("[DEBUG] %s: Executing Apply", r.Id) is, applyerr := r.Provider.Apply(r.Info, is, diff) @@ -633,7 +638,7 @@ func (c *Context) applyWalkFn() depgraph.WalkFunc { // Additionally, we need to be careful to not run this if there // was an error during the provider apply. tainted := false - if applyerr == nil && is.ID != "" && len(r.Provisioners) > 0 { + if applyerr == nil && createNew && len(r.Provisioners) > 0 { for _, h := range c.hooks { handleHook(h.PreProvisionResource(r.Id, is)) } diff --git a/terraform/context_test.go b/terraform/context_test.go index 28f23bb1f..e39f582c3 100644 --- a/terraform/context_test.go +++ b/terraform/context_test.go @@ -683,6 +683,83 @@ func TestContextApply_provisionerResourceRef(t *testing.T) { } } +// Provisioner should NOT run on a diff, only create +func TestContextApply_Provisioner_Diff(t *testing.T) { + c := testConfig(t, "apply-provisioner-diff") + p := testProvider("aws") + pr := testProvisioner() + p.ApplyFn = testApplyFn + p.DiffFn = testDiffFn + pr.ApplyFn = func(rs *InstanceState, c *ResourceConfig) error { + return nil + } + ctx := testContext(t, &ContextOpts{ + Config: c, + Providers: map[string]ResourceProviderFactory{ + "aws": testProviderFuncFixed(p), + }, + Provisioners: map[string]ResourceProvisionerFactory{ + "shell": testProvisionerFuncFixed(pr), + }, + }) + + if _, err := ctx.Plan(nil); 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(testTerraformApplyProvisionerDiffStr) + if actual != expected { + t.Fatalf("bad: \n%s", actual) + } + + // Verify apply was invoked + if !pr.ApplyCalled { + t.Fatalf("provisioner not invoked") + } + pr.ApplyCalled = false + + // Change the state to force a diff + mod := state.RootModule() + mod.Resources["aws_instance.bar"].Primary.Attributes["foo"] = "baz" + + // Re-create context with state + ctx = testContext(t, &ContextOpts{ + Config: c, + Providers: map[string]ResourceProviderFactory{ + "aws": testProviderFuncFixed(p), + }, + Provisioners: map[string]ResourceProvisionerFactory{ + "shell": testProvisionerFuncFixed(pr), + }, + State: state, + }) + + if _, err := ctx.Plan(nil); err != nil { + t.Fatalf("err: %s", err) + } + + state2, err := ctx.Apply() + if err != nil { + t.Fatalf("err: %s", err) + } + + actual = strings.TrimSpace(state2.String()) + if actual != expected { + t.Fatalf("bad: \n%s", actual) + } + + // Verify apply was NOT invoked + if pr.ApplyCalled { + t.Fatalf("provisioner invoked") + } +} + func TestContextApply_outputDiffVars(t *testing.T) { c := testConfig(t, "apply-good") p := testProvider("aws") diff --git a/terraform/terraform_test.go b/terraform/terraform_test.go index c8df4c2a1..54c4ac927 100644 --- a/terraform/terraform_test.go +++ b/terraform/terraform_test.go @@ -153,6 +153,13 @@ aws_instance.bar: type = aws_instance ` +const testTerraformApplyProvisionerDiffStr = ` +aws_instance.bar: + ID = foo + foo = bar + type = aws_instance +` + const testTerraformApplyDestroyStr = ` ` diff --git a/terraform/test-fixtures/apply-provisioner-diff/main.tf b/terraform/test-fixtures/apply-provisioner-diff/main.tf new file mode 100644 index 000000000..ac4f38e97 --- /dev/null +++ b/terraform/test-fixtures/apply-provisioner-diff/main.tf @@ -0,0 +1,4 @@ +resource "aws_instance" "bar" { + foo = "bar" + provisioner "shell" {} +}