From 7f87a0109bfdbf431eedb0bf26cb96c4dbce96a2 Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Fri, 9 Dec 2016 11:34:38 -0500 Subject: [PATCH 1/2] command/plan: user friendly error if plan file given to plan command --- command/plan.go | 9 ++++++++- command/plan_test.go | 23 +++++++++++++++++++++++ 2 files changed, 31 insertions(+), 1 deletion(-) diff --git a/command/plan.go b/command/plan.go index c9a275ade67c..e15f11aca3c4 100644 --- a/command/plan.go +++ b/command/plan.go @@ -61,7 +61,7 @@ func (c *PlanCommand) Run(args []string) int { // This is going to keep track of shadow errors var shadowErr error - ctx, _, err := c.Context(contextOpts{ + ctx, planned, err := c.Context(contextOpts{ Destroy: destroy, Path: path, StatePath: c.Meta.statePath, @@ -71,6 +71,13 @@ func (c *PlanCommand) Run(args []string) int { c.Ui.Error(err.Error()) return 1 } + if planned { + c.Ui.Error( + "The plan command cannot be called with a saved plan file.\n\n" + + "The plan command expects a configuration directory as an argument.", + ) + return 1 + } err = terraform.SetDebugInfo(DefaultDataDir) if err != nil { diff --git a/command/plan_test.go b/command/plan_test.go index c4af9c1418fe..c3805af622e5 100644 --- a/command/plan_test.go +++ b/command/plan_test.go @@ -37,6 +37,29 @@ func TestPlan(t *testing.T) { } } +func TestPlan_plan(t *testing.T) { + tmp, cwd := testCwd(t) + defer testFixCwd(t, tmp, cwd) + + planPath := testPlanFile(t, &terraform.Plan{ + Module: testModule(t, "apply"), + }) + + p := testProvider() + ui := new(cli.MockUi) + c := &PlanCommand{ + Meta: Meta{ + ContextOpts: testCtxConfig(p), + Ui: ui, + }, + } + + args := []string{planPath} + if code := c.Run(args); code != 1 { + t.Fatalf("bad: %d\n\n%s", code, ui.ErrorWriter.String()) + } +} + func TestPlan_destroy(t *testing.T) { originalState := &terraform.State{ Modules: []*terraform.ModuleState{ From 36ff8b3c0273b5ae7563d25da53e2fd8ec61e893 Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Mon, 12 Dec 2016 10:45:26 -0800 Subject: [PATCH 2/2] command/plan: show a warning when a plan file is given --- command/plan.go | 19 +++++++++++++------ command/plan_test.go | 6 +++++- .../source/docs/commands/plan.html.markdown | 7 ++++++- 3 files changed, 24 insertions(+), 8 deletions(-) diff --git a/command/plan.go b/command/plan.go index e15f11aca3c4..09ce4778a439 100644 --- a/command/plan.go +++ b/command/plan.go @@ -72,11 +72,15 @@ func (c *PlanCommand) Run(args []string) int { return 1 } if planned { - c.Ui.Error( - "The plan command cannot be called with a saved plan file.\n\n" + - "The plan command expects a configuration directory as an argument.", - ) - return 1 + c.Ui.Output(c.Colorize().Color( + "[reset][bold][yellow]" + + "The plan command received a saved plan file as input. This command\n" + + "will output the saved plan. This will not modify the already-existing\n" + + "plan. If you wish to generate a new plan, please pass in a configuration\n" + + "directory as an argument.\n\n")) + + // Disable refreshing no matter what since we only want to show the plan + refresh = false } err = terraform.SetDebugInfo(DefaultDataDir) @@ -178,7 +182,7 @@ func (c *PlanCommand) Run(args []string) int { func (c *PlanCommand) Help() string { helpText := ` -Usage: terraform plan [options] [dir] +Usage: terraform plan [options] [DIR-OR-PLAN] Generates an execution plan for Terraform. @@ -187,6 +191,9 @@ Usage: terraform plan [options] [dir] a Terraform plan file, and apply can take this plan file to execute this plan exactly. + If a saved plan is passed as an argument, this command will output + the saved plan contents. It will not modify the given plan. + Options: -destroy If set, a plan will be generated to destroy all resources diff --git a/command/plan_test.go b/command/plan_test.go index c3805af622e5..8e9537e2f447 100644 --- a/command/plan_test.go +++ b/command/plan_test.go @@ -55,9 +55,13 @@ func TestPlan_plan(t *testing.T) { } args := []string{planPath} - if code := c.Run(args); code != 1 { + if code := c.Run(args); code != 0 { t.Fatalf("bad: %d\n\n%s", code, ui.ErrorWriter.String()) } + + if p.RefreshCalled { + t.Fatal("refresh should not be called") + } } func TestPlan_destroy(t *testing.T) { diff --git a/website/source/docs/commands/plan.html.markdown b/website/source/docs/commands/plan.html.markdown index 84732a5ccbcc..48d5374736c1 100644 --- a/website/source/docs/commands/plan.html.markdown +++ b/website/source/docs/commands/plan.html.markdown @@ -16,11 +16,16 @@ to `terraform apply` to ensure only the pre-planned actions are executed. ## Usage -Usage: `terraform plan [options] [dir]` +Usage: `terraform plan [options] [dir-or-plan]` By default, `plan` requires no flags and looks in the current directory for the configuration and state file to refresh. +If the command is given an existing saved plan as an argument, the +command will output the contents of the saved plan. In this scenario, +the `plan` command will not modify the given plan. This can be used to +inspect a planfile. + The command-line flags are all optional. The list of available flags are: * `-destroy` - If set, generates a plan to destroy all the known resources.