From 9373baca3a362f665332deb3f5b2a3c509ed7c54 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 44c8d8a0536f..f116731c8881 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 559ed09a999a..4a9a41c227a9 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)