From 1480d0c5b8888f1bd4b274cc1e679f51c87a6b8d Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Wed, 15 Feb 2017 16:00:59 -0800 Subject: [PATCH 1/2] backend/local: check for empty config on apply This prevents Terraform from crashing on apply/destroy with a directory with no Terraform configuration files. We allow a destroy with no files but not an apply. --- backend/local/backend_apply.go | 24 +++++++++ backend/local/backend_apply_test.go | 54 +++++++++++++++++++ .../local/test-fixtures/apply-empty/hello.txt | 1 + 3 files changed, 79 insertions(+) create mode 100644 backend/local/test-fixtures/apply-empty/hello.txt diff --git a/backend/local/backend_apply.go b/backend/local/backend_apply.go index 466bce468..30cbe0cf6 100644 --- a/backend/local/backend_apply.go +++ b/backend/local/backend_apply.go @@ -4,11 +4,13 @@ import ( "context" "fmt" "log" + "strings" "github.com/hashicorp/errwrap" "github.com/hashicorp/go-multierror" "github.com/hashicorp/terraform/backend" clistate "github.com/hashicorp/terraform/command/state" + "github.com/hashicorp/terraform/config/module" "github.com/hashicorp/terraform/state" "github.com/hashicorp/terraform/terraform" ) @@ -19,6 +21,19 @@ func (b *Local) opApply( runningOp *backend.RunningOperation) { log.Printf("[INFO] backend/local: starting Apply operation") + // If we have a nil module at this point, then set it to an empty tree + // to avoid any potential crashes. + if op.Module == nil && !op.Destroy { + runningOp.Err = fmt.Errorf(strings.TrimSpace(applyErrNoConfig)) + return + } + + // If we have a nil module at this point, then set it to an empty tree + // to avoid any potential crashes. + if op.Module == nil { + op.Module = module.NewEmptyTree() + } + // Setup our count hook that keeps track of resource changes countHook := new(CountHook) stateHook := new(StateHook) @@ -165,3 +180,12 @@ func (b *Local) opApply( } } } + +const applyErrNoConfig = ` +No configuration files found! + +Apply requires configuration to be present. Applying without a configuration +would mark everything for destruction, which is normally not what is desired. +If you would like to destroy everything, please run 'terraform destroy' instead +which does not require any configuration files. +` diff --git a/backend/local/backend_apply_test.go b/backend/local/backend_apply_test.go index 7424faa6b..c761d1538 100644 --- a/backend/local/backend_apply_test.go +++ b/backend/local/backend_apply_test.go @@ -3,6 +3,7 @@ package local import ( "context" "fmt" + "os" "sync" "testing" @@ -50,6 +51,59 @@ test_instance.foo: `) } +func TestLocal_applyEmptyDir(t *testing.T) { + b := TestLocal(t) + p := TestLocalProvider(t, b, "test") + + p.ApplyReturn = &terraform.InstanceState{ID: "yes"} + + op := testOperationApply() + op.Module = nil + + run, err := b.Operation(context.Background(), op) + if err != nil { + t.Fatalf("bad: %s", err) + } + <-run.Done() + if run.Err == nil { + t.Fatal("should error") + } + + if p.ApplyCalled { + t.Fatal("apply should not be called") + } + + if _, err := os.Stat(b.StateOutPath); err == nil { + t.Fatal("should not exist") + } +} + +func TestLocal_applyEmptyDirDestroy(t *testing.T) { + b := TestLocal(t) + p := TestLocalProvider(t, b, "test") + + p.ApplyReturn = nil + + op := testOperationApply() + op.Module = nil + op.Destroy = true + + run, err := b.Operation(context.Background(), op) + if err != nil { + t.Fatalf("bad: %s", err) + } + <-run.Done() + if run.Err != nil { + t.Fatalf("err: %s", err) + } + + if p.ApplyCalled { + t.Fatal("apply should not be called") + } + + checkState(t, b.StateOutPath, ``) +} + func TestLocal_applyError(t *testing.T) { b := TestLocal(t) p := TestLocalProvider(t, b, "test") diff --git a/backend/local/test-fixtures/apply-empty/hello.txt b/backend/local/test-fixtures/apply-empty/hello.txt new file mode 100644 index 000000000..7dcfcbb4a --- /dev/null +++ b/backend/local/test-fixtures/apply-empty/hello.txt @@ -0,0 +1 @@ +This is an empty dir From d443bf1b565cdb139ed00354d821d0df5bb8ca70 Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Thu, 16 Feb 2017 10:56:39 -0800 Subject: [PATCH 2/2] backend/local: allow nil modules (no config) if executing a plan --- backend/local/backend_apply.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/backend/local/backend_apply.go b/backend/local/backend_apply.go index 30cbe0cf6..6b80aac34 100644 --- a/backend/local/backend_apply.go +++ b/backend/local/backend_apply.go @@ -23,7 +23,7 @@ func (b *Local) opApply( // If we have a nil module at this point, then set it to an empty tree // to avoid any potential crashes. - if op.Module == nil && !op.Destroy { + if op.Plan == nil && op.Module == nil && !op.Destroy { runningOp.Err = fmt.Errorf(strings.TrimSpace(applyErrNoConfig)) return }