backend/local: validate module exists for plan

Fixes #11504

The local backend should error if `terraform plan` is called in a
directory with no Terraform config files (same behavior as 0.8.x).
**New behavior:** We now allow `terraform plan -destroy` with no
configuration files since that seems reasonable.
This commit is contained in:
Mitchell Hashimoto 2017-01-29 19:51:54 -08:00
parent 9183be4c83
commit a424203ea3
No known key found for this signature in database
GPG Key ID: 744E147AA52F5B0A
2 changed files with 89 additions and 0 deletions

View File

@ -10,6 +10,7 @@ import (
"github.com/hashicorp/errwrap" "github.com/hashicorp/errwrap"
"github.com/hashicorp/terraform/backend" "github.com/hashicorp/terraform/backend"
"github.com/hashicorp/terraform/command/format" "github.com/hashicorp/terraform/command/format"
"github.com/hashicorp/terraform/config/module"
"github.com/hashicorp/terraform/terraform" "github.com/hashicorp/terraform/terraform"
) )
@ -28,6 +29,18 @@ func (b *Local) opPlan(
"directory as an argument.\n\n")) "directory as an argument.\n\n"))
} }
// A local plan requires either a plan or a module
if op.Plan == nil && op.Module == nil && !op.Destroy {
runningOp.Err = fmt.Errorf(strings.TrimSpace(planErrNoConfig))
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 // Setup our count hook that keeps track of resource changes
countHook := new(CountHook) countHook := new(CountHook)
if b.ContextOpts == nil { if b.ContextOpts == nil {
@ -120,6 +133,16 @@ func (b *Local) opPlan(
} }
} }
const planErrNoConfig = `
No configuration files found!
Plan requires configuration to be present. Planning without a configuration
would mark everything for destruction, which is normally not what is desired.
If you would like to destroy everything, please run plan with the "-destroy"
flag or create a single empty configuration file. Otherwise, please create
a Terraform configuration file in the path being executed and try again.
`
const planHeaderNoOutput = ` const planHeaderNoOutput = `
The Terraform execution plan has been generated and is shown below. The Terraform execution plan has been generated and is shown below.
Resources are shown in alphabetical order for quick scanning. Green resources Resources are shown in alphabetical order for quick scanning. Green resources

View File

@ -4,6 +4,7 @@ import (
"context" "context"
"os" "os"
"path/filepath" "path/filepath"
"strings"
"testing" "testing"
"github.com/hashicorp/terraform/backend" "github.com/hashicorp/terraform/backend"
@ -36,6 +37,29 @@ func TestLocal_planBasic(t *testing.T) {
} }
} }
func TestLocal_planNoConfig(t *testing.T) {
b := TestLocal(t)
TestLocalProvider(t, b, "test")
op := testOperationPlan()
op.Module = nil
op.PlanRefresh = true
run, err := b.Operation(context.Background(), op)
if err != nil {
t.Fatalf("bad: %s", err)
}
<-run.Done()
err = run.Err
if err == nil {
t.Fatal("should error")
}
if !strings.Contains(err.Error(), "configuration") {
t.Fatalf("bad: %s", err)
}
}
func TestLocal_planRefreshFalse(t *testing.T) { func TestLocal_planRefreshFalse(t *testing.T) {
b := TestLocal(t) b := TestLocal(t)
p := TestLocalProvider(t, b, "test") p := TestLocalProvider(t, b, "test")
@ -110,6 +134,48 @@ func TestLocal_planDestroy(t *testing.T) {
} }
} }
func TestLocal_planDestroyNoConfig(t *testing.T) {
b := TestLocal(t)
p := TestLocalProvider(t, b, "test")
terraform.TestStateFile(t, b.StatePath, testPlanState())
outDir := testTempDir(t)
defer os.RemoveAll(outDir)
planPath := filepath.Join(outDir, "plan.tfplan")
op := testOperationPlan()
op.Destroy = true
op.PlanRefresh = true
op.Module = nil
op.PlanOutPath = planPath
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.RefreshCalled {
t.Fatal("refresh should be called")
}
if run.PlanEmpty {
t.Fatal("plan should not be empty")
}
plan := testReadPlan(t, planPath)
for _, m := range plan.Diff.Modules {
for _, r := range m.Resources {
if !r.Destroy {
t.Fatalf("bad: %#v", r)
}
}
}
}
func TestLocal_planOutPathNoChange(t *testing.T) { func TestLocal_planOutPathNoChange(t *testing.T) {
b := TestLocal(t) b := TestLocal(t)
TestLocalProvider(t, b, "test") TestLocalProvider(t, b, "test")