Merge pull request #8200 from hashicorp/fix-state-rm
core: Add `terraform state rm` command
This commit is contained in:
commit
f933b2cf16
|
@ -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.`
|
|
@ -0,0 +1,108 @@
|
|||
package command
|
||||
|
||||
import (
|
||||
"path/filepath"
|
||||
"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
|
||||
`
|
|
@ -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,
|
||||
|
|
|
@ -94,6 +94,11 @@ func (f *StateFilter) filterSingle(a *ResourceAddress) []*StateFilterResult {
|
|||
continue
|
||||
}
|
||||
|
||||
if a.Name != "" && a.Name != key.Name {
|
||||
// Name doesn't match
|
||||
continue
|
||||
}
|
||||
|
||||
if a.Index >= 0 && key.Index != a.Index {
|
||||
// Index doesn't match
|
||||
continue
|
||||
|
|
|
@ -38,6 +38,15 @@ func TestStateFilterFilter(t *testing.T) {
|
|||
},
|
||||
},
|
||||
|
||||
"single resource with similar names": {
|
||||
"small_test_instance.tfstate",
|
||||
[]string{"test_instance.foo"},
|
||||
[]string{
|
||||
"*terraform.ResourceState: test_instance.foo",
|
||||
"*terraform.InstanceState: test_instance.foo",
|
||||
},
|
||||
},
|
||||
|
||||
"single instance": {
|
||||
"small.tfstate",
|
||||
[]string{"aws_key_pair.onprem.primary"},
|
||||
|
|
|
@ -600,6 +600,13 @@ func TestStateRemove(t *testing.T) {
|
|||
ID: "foo",
|
||||
},
|
||||
},
|
||||
|
||||
"test_instance.bar": &ResourceState{
|
||||
Type: "test_instance",
|
||||
Primary: &InstanceState{
|
||||
ID: "foo",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
|
@ -607,8 +614,15 @@ func TestStateRemove(t *testing.T) {
|
|||
&State{
|
||||
Modules: []*ModuleState{
|
||||
&ModuleState{
|
||||
Path: rootModulePath,
|
||||
Resources: map[string]*ResourceState{},
|
||||
Path: rootModulePath,
|
||||
Resources: map[string]*ResourceState{
|
||||
"test_instance.bar": &ResourceState{
|
||||
Type: "test_instance",
|
||||
Primary: &InstanceState{
|
||||
ID: "foo",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
|
|
|
@ -0,0 +1,27 @@
|
|||
{
|
||||
"version": 1,
|
||||
"serial": 12,
|
||||
"modules": [
|
||||
{
|
||||
"path": [
|
||||
"root"
|
||||
],
|
||||
"resources": {
|
||||
"test_instance.foo": {
|
||||
"type": "test_instance",
|
||||
"primary": {
|
||||
"id": "foo"
|
||||
}
|
||||
}
|
||||
},
|
||||
"resources": {
|
||||
"test_instance.bar": {
|
||||
"type": "test_instance",
|
||||
"primary": {
|
||||
"id": "foo"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
|
@ -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
|
||||
```
|
Loading…
Reference in New Issue