Skip to content

Commit a424203

Browse files
committed
backend/local: validate module exists for plan
Fixes hashicorp#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.
1 parent 9183be4 commit a424203

2 files changed

Lines changed: 89 additions & 0 deletions

File tree

backend/local/backend_plan.go

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import (
1010
"github.com/hashicorp/errwrap"
1111
"github.com/hashicorp/terraform/backend"
1212
"github.com/hashicorp/terraform/command/format"
13+
"github.com/hashicorp/terraform/config/module"
1314
"github.com/hashicorp/terraform/terraform"
1415
)
1516

@@ -28,6 +29,18 @@ func (b *Local) opPlan(
2829
"directory as an argument.\n\n"))
2930
}
3031

32+
// A local plan requires either a plan or a module
33+
if op.Plan == nil && op.Module == nil && !op.Destroy {
34+
runningOp.Err = fmt.Errorf(strings.TrimSpace(planErrNoConfig))
35+
return
36+
}
37+
38+
// If we have a nil module at this point, then set it to an empty tree
39+
// to avoid any potential crashes.
40+
if op.Module == nil {
41+
op.Module = module.NewEmptyTree()
42+
}
43+
3144
// Setup our count hook that keeps track of resource changes
3245
countHook := new(CountHook)
3346
if b.ContextOpts == nil {
@@ -120,6 +133,16 @@ func (b *Local) opPlan(
120133
}
121134
}
122135

136+
const planErrNoConfig = `
137+
No configuration files found!
138+
139+
Plan requires configuration to be present. Planning without a configuration
140+
would mark everything for destruction, which is normally not what is desired.
141+
If you would like to destroy everything, please run plan with the "-destroy"
142+
flag or create a single empty configuration file. Otherwise, please create
143+
a Terraform configuration file in the path being executed and try again.
144+
`
145+
123146
const planHeaderNoOutput = `
124147
The Terraform execution plan has been generated and is shown below.
125148
Resources are shown in alphabetical order for quick scanning. Green resources

backend/local/backend_plan_test.go

Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import (
44
"context"
55
"os"
66
"path/filepath"
7+
"strings"
78
"testing"
89

910
"github.com/hashicorp/terraform/backend"
@@ -36,6 +37,29 @@ func TestLocal_planBasic(t *testing.T) {
3637
}
3738
}
3839

40+
func TestLocal_planNoConfig(t *testing.T) {
41+
b := TestLocal(t)
42+
TestLocalProvider(t, b, "test")
43+
44+
op := testOperationPlan()
45+
op.Module = nil
46+
op.PlanRefresh = true
47+
48+
run, err := b.Operation(context.Background(), op)
49+
if err != nil {
50+
t.Fatalf("bad: %s", err)
51+
}
52+
<-run.Done()
53+
54+
err = run.Err
55+
if err == nil {
56+
t.Fatal("should error")
57+
}
58+
if !strings.Contains(err.Error(), "configuration") {
59+
t.Fatalf("bad: %s", err)
60+
}
61+
}
62+
3963
func TestLocal_planRefreshFalse(t *testing.T) {
4064
b := TestLocal(t)
4165
p := TestLocalProvider(t, b, "test")
@@ -110,6 +134,48 @@ func TestLocal_planDestroy(t *testing.T) {
110134
}
111135
}
112136

137+
func TestLocal_planDestroyNoConfig(t *testing.T) {
138+
b := TestLocal(t)
139+
p := TestLocalProvider(t, b, "test")
140+
terraform.TestStateFile(t, b.StatePath, testPlanState())
141+
142+
outDir := testTempDir(t)
143+
defer os.RemoveAll(outDir)
144+
planPath := filepath.Join(outDir, "plan.tfplan")
145+
146+
op := testOperationPlan()
147+
op.Destroy = true
148+
op.PlanRefresh = true
149+
op.Module = nil
150+
op.PlanOutPath = planPath
151+
152+
run, err := b.Operation(context.Background(), op)
153+
if err != nil {
154+
t.Fatalf("bad: %s", err)
155+
}
156+
<-run.Done()
157+
if run.Err != nil {
158+
t.Fatalf("err: %s", err)
159+
}
160+
161+
if !p.RefreshCalled {
162+
t.Fatal("refresh should be called")
163+
}
164+
165+
if run.PlanEmpty {
166+
t.Fatal("plan should not be empty")
167+
}
168+
169+
plan := testReadPlan(t, planPath)
170+
for _, m := range plan.Diff.Modules {
171+
for _, r := range m.Resources {
172+
if !r.Destroy {
173+
t.Fatalf("bad: %#v", r)
174+
}
175+
}
176+
}
177+
}
178+
113179
func TestLocal_planOutPathNoChange(t *testing.T) {
114180
b := TestLocal(t)
115181
TestLocalProvider(t, b, "test")

0 commit comments

Comments
 (0)