From fee1197cf999adb49fb96371f3c19b9c32afe8ae Mon Sep 17 00:00:00 2001 From: Martin Atkins Date: Wed, 5 Jul 2017 14:58:08 -0700 Subject: [PATCH] command: terraform state rm to require at least one argument Due to how the state filter machinery works, passing no arguments is valid and matches _all_ resources. It is very unlikely that someone wants to remove everything from state, so this ends up being a very dangerous default for the "terraform state rm" command, and surprising for someone who perhaps runs it looking for the usage information. So we'll be pragmatic here and reject the no-arguments case for this command, accepting that it makes the unlikely case of intentionally deleting all resources harder in order to make it less likely that it will happen _unintentionally_. If someone does really want to remove all resources from the state, they can provide an explicit empty string argument, but this isn't documented because it's a weird case that doesn't seem worth mentioning. This fixes #15283. --- command/state_rm.go | 5 ++++ command/state_rm_test.go | 57 ++++++++++++++++++++++++++++++++++++++++ 2 files changed, 62 insertions(+) diff --git a/command/state_rm.go b/command/state_rm.go index 44c8d8a05..f116731c8 100644 --- a/command/state_rm.go +++ b/command/state_rm.go @@ -24,6 +24,11 @@ func (c *StateRmCommand) Run(args []string) int { } args = cmdFlags.Args() + if len(args) < 1 { + c.Ui.Error("At least one resource address is required.") + return 1 + } + state, err := c.StateMeta.State(&c.Meta) if err != nil { c.Ui.Error(fmt.Sprintf(errStateLoadingState, err)) diff --git a/command/state_rm_test.go b/command/state_rm_test.go index 559ed09a9..4a9a41c22 100644 --- a/command/state_rm_test.go +++ b/command/state_rm_test.go @@ -3,6 +3,7 @@ package command import ( "os" "path/filepath" + "strings" "testing" "github.com/hashicorp/terraform/terraform" @@ -71,6 +72,62 @@ func TestStateRm(t *testing.T) { testStateOutput(t, backups[0], testStateRmOutputOriginal) } +func TestStateRmNoArgs(t *testing.T) { + state := &terraform.State{ + Modules: []*terraform.ModuleState{ + &terraform.ModuleState{ + Path: []string{"root"}, + Resources: map[string]*terraform.ResourceState{ + "test_instance.foo": &terraform.ResourceState{ + Type: "test_instance", + Primary: &terraform.InstanceState{ + ID: "bar", + Attributes: map[string]string{ + "foo": "value", + "bar": "value", + }, + }, + }, + + "test_instance.bar": &terraform.ResourceState{ + Type: "test_instance", + Primary: &terraform.InstanceState{ + ID: "foo", + Attributes: map[string]string{ + "foo": "value", + "bar": "value", + }, + }, + }, + }, + }, + }, + } + + statePath := testStateFile(t, state) + + p := testProvider() + ui := new(cli.MockUi) + c := &StateRmCommand{ + Meta: Meta{ + testingOverrides: metaOverridesForProvider(p), + Ui: ui, + }, + } + + args := []string{ + "-state", statePath, + } + if code := c.Run(args); code != 1 { + t.Errorf("wrong exit status %d; want %d", code, 1) + } + + if msg := ui.ErrorWriter.String(); !strings.Contains(msg, "At least one resource address") { + t.Errorf("not the error we were looking for:\n%s", msg) + } + +} + func TestStateRm_backupExplicit(t *testing.T) { td := tempDir(t) defer os.RemoveAll(td)