From 3fdc08a9eb0aa5ec68d2547e332e7677cb1f34ba Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Thu, 31 Mar 2016 09:29:39 -0700 Subject: [PATCH] core: Add `terraform state rm` command and docs This commit adds the `state rm` command for removing an address from state. It is the result of a rebase from pull-request #5953 which was lost at some point during the Terraform 0.7 feature branch merges. --- command/state_rm.go | 102 ++++++++++++++++ command/state_rm_test.go | 109 ++++++++++++++++++ commands.go | 6 + website/source/docs/commands/state/rm.html.md | 65 +++++++++++ website/source/layouts/commands-state.erb | 2 +- 5 files changed, 283 insertions(+), 1 deletion(-) create mode 100644 command/state_rm.go create mode 100644 command/state_rm_test.go create mode 100644 website/source/docs/commands/state/rm.html.md diff --git a/command/state_rm.go b/command/state_rm.go new file mode 100644 index 000000000..273fd46aa --- /dev/null +++ b/command/state_rm.go @@ -0,0 +1,102 @@ +package command + +import ( + "fmt" + "strings" + + "github.com/mitchellh/cli" +) + +// StateRmCommand is a Command implementation that shows a single resource. +type StateRmCommand struct { + Meta + StateMeta +} + +func (c *StateRmCommand) Run(args []string) int { + args = c.Meta.process(args, true) + + var backupPath string + cmdFlags := c.Meta.flagSet("state show") + cmdFlags.StringVar(&backupPath, "backup", "", "backup") + cmdFlags.StringVar(&c.Meta.statePath, "state", DefaultStateFilename, "path") + if err := cmdFlags.Parse(args); err != nil { + return cli.RunResultHelp + } + args = cmdFlags.Args() + + state, err := c.StateMeta.State(&c.Meta) + if err != nil { + c.Ui.Error(fmt.Sprintf(errStateLoadingState, err)) + return cli.RunResultHelp + } + + stateReal := state.State() + if stateReal == nil { + c.Ui.Error(fmt.Sprintf(errStateNotFound)) + return 1 + } + + if err := stateReal.Remove(args...); err != nil { + c.Ui.Error(fmt.Sprintf(errStateRm, err)) + return 1 + } + + if err := state.WriteState(stateReal); err != nil { + c.Ui.Error(fmt.Sprintf(errStateRmPersist, err)) + return 1 + } + + if err := state.PersistState(); err != nil { + c.Ui.Error(fmt.Sprintf(errStateRmPersist, err)) + return 1 + } + + c.Ui.Output("Item removal successful.") + return 0 +} + +func (c *StateRmCommand) Help() string { + helpText := ` +Usage: terraform state rm [options] ADDRESS... + + Remove one or more items from the Terraform state. + + This command removes one or more items from the Terraform state based + on the address given. You can view and list the available resources + with "terraform state list". + + This command creates a timestamped backup of the state on every invocation. + This can't be disabled. Due to the destructive nature of this command, + the backup is ensured by Terraform for safety reasons. + +Options: + + -backup=PATH Path where Terraform should write the backup + state. This can't be disabled. If not set, Terraform + will write it to the same path as the statefile with + a backup extension. + + -state=statefile Path to a Terraform state file to use to look + up Terraform-managed resources. By default it will + use the state "terraform.tfstate" if it exists. + +` + return strings.TrimSpace(helpText) +} + +func (c *StateRmCommand) Synopsis() string { + return "Remove an item from the state" +} + +const errStateRm = `Error removing items from the state: %s + +The state was not saved. No items were removed from the persisted +state. No backup was created since no modification occurred. Please +resolve the issue above and try again.` + +const errStateRmPersist = `Error saving the state: %s + +The state was not saved. No items were removed from the persisted +state. No backup was created since no modification occurred. Please +resolve the issue above and try again.` diff --git a/command/state_rm_test.go b/command/state_rm_test.go new file mode 100644 index 000000000..6f0b14633 --- /dev/null +++ b/command/state_rm_test.go @@ -0,0 +1,109 @@ +package command + +import ( + "path/filepath" + "sort" + "testing" + + "github.com/hashicorp/terraform/terraform" + "github.com/mitchellh/cli" +) + +func TestStateRm(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{ + ContextOpts: testCtxConfig(p), + Ui: ui, + }, + } + + args := []string{ + "-state", statePath, + "test_instance.foo", + } + if code := c.Run(args); code != 0 { + t.Fatalf("bad: %d\n\n%s", code, ui.ErrorWriter.String()) + } + + // Test it is correct + testStateOutput(t, statePath, testStateRmOutput) + + // Test we have backups + backups := testStateBackups(t, filepath.Dir(statePath)) + if len(backups) != 1 { + t.Fatalf("bad: %#v", backups) + } + testStateOutput(t, backups[0], testStateRmOutputOriginal) +} + +func TestStateRm_noState(t *testing.T) { + tmp, cwd := testCwd(t) + defer testFixCwd(t, tmp, cwd) + + p := testProvider() + ui := new(cli.MockUi) + c := &StateRmCommand{ + Meta: Meta{ + ContextOpts: testCtxConfig(p), + Ui: ui, + }, + } + + args := []string{} + if code := c.Run(args); code != 1 { + t.Fatalf("bad: %d\n\n%s", code, ui.ErrorWriter.String()) + } +} + +const testStateRmOutputOriginal = ` +test_instance.bar: + ID = foo + bar = value + foo = value +test_instance.foo: + ID = bar + bar = value + foo = value +` + +const testStateRmOutput = ` +test_instance.bar: + ID = foo + bar = value + foo = value +` diff --git a/commands.go b/commands.go index e33e55540..13e05ab6e 100644 --- a/commands.go +++ b/commands.go @@ -171,6 +171,12 @@ func init() { }, nil }, + "state rm": func() (cli.Command, error) { + return &command.StateRmCommand{ + Meta: meta, + }, nil + }, + "state mv": func() (cli.Command, error) { return &command.StateMvCommand{ Meta: meta, diff --git a/website/source/docs/commands/state/rm.html.md b/website/source/docs/commands/state/rm.html.md new file mode 100644 index 000000000..206d49695 --- /dev/null +++ b/website/source/docs/commands/state/rm.html.md @@ -0,0 +1,65 @@ +--- +layout: "commands-state" +page_title: "Command: state rm" +sidebar_current: "docs-state-sub-rm" +description: |- + The `terraform state rm` command removes items from the Terraform state. +--- + +# Command: state rm + +The `terraform state rm` command is used to remove items from the +[Terraform state](/docs/state/index.html). This command can remove +single resources, since instances of a resource, entire modules, +and more. + +## Usage + +Usage: `terraform state rm [options] ADDRESS...` + +The command will remove all the items matched by the addresses given. + +Items removed from the Terraform state are _not physically destroyed_. +Items removed from the Terraform state are only no longer managed by +Terraform. For example, if you remove an AWS instance from the state, the AWS +instance will continue running, but `terraform plan` will no longer see that +instance. + +There are various use cases for removing items from a Terraform state +file. The most common is refactoring a configuration to no longer manage +that resource (perhaps moving it to another Terraform configuration/state). + +The state will only be saved on successful removal of all addresses. +If any specific address errors for any reason (such as a syntax error), +the state will not be modified at all. + +This command will output a backup copy of the state prior to saving any +changes. The backup cannot be disabled. Due to the destructive nature +of this command, backups are required. + +This command requires one or more addresses that point to a resources in the +state. Addresses are +in [resource addressing format](/docs/commands/state/addressing.html). + +The command-line flags are all optional. The list of available flags are: + +* `-backup=path` - Path to a backup file Defaults to the state path plus + a timestamp with the ".backup" extension. + +* `-state=path` - Path to the state file. Defaults to "terraform.tfstate". + +## Example: Remove a Resource + +The example below removes a single resource in a module: + +``` +$ terraform state rm module.foo.packet_device.worker[0] +``` + +## Example: Remove a Module + +The example below removes an entire module: + +``` +$ terraform state rm module.foo +``` diff --git a/website/source/layouts/commands-state.erb b/website/source/layouts/commands-state.erb index 14cc64416..e012e6f61 100644 --- a/website/source/layouts/commands-state.erb +++ b/website/source/layouts/commands-state.erb @@ -24,7 +24,7 @@ > mv - + > rm