Merge pull request #15652 from hashicorp/jbardin/state-command
state commands with remote state backends
This commit is contained in:
commit
eadda50f02
|
@ -11,30 +11,32 @@ import (
|
||||||
)
|
)
|
||||||
|
|
||||||
// StateMeta is the meta struct that should be embedded in state subcommands.
|
// StateMeta is the meta struct that should be embedded in state subcommands.
|
||||||
type StateMeta struct{}
|
type StateMeta struct {
|
||||||
|
Meta
|
||||||
|
}
|
||||||
|
|
||||||
// State returns the state for this meta. This gets the appropriate state from
|
// State returns the state for this meta. This gets the appropriate state from
|
||||||
// the backend, but changes the way that backups are done. This configures
|
// the backend, but changes the way that backups are done. This configures
|
||||||
// backups to be timestamped rather than just the original state path plus a
|
// backups to be timestamped rather than just the original state path plus a
|
||||||
// backup path.
|
// backup path.
|
||||||
func (c *StateMeta) State(m *Meta) (state.State, error) {
|
func (c *StateMeta) State() (state.State, error) {
|
||||||
var realState state.State
|
var realState state.State
|
||||||
backupPath := m.backupPath
|
backupPath := c.backupPath
|
||||||
stateOutPath := m.statePath
|
stateOutPath := c.statePath
|
||||||
|
|
||||||
// use the specified state
|
// use the specified state
|
||||||
if m.statePath != "" {
|
if c.statePath != "" {
|
||||||
realState = &state.LocalState{
|
realState = &state.LocalState{
|
||||||
Path: m.statePath,
|
Path: c.statePath,
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// Load the backend
|
// Load the backend
|
||||||
b, err := m.Backend(nil)
|
b, err := c.Backend(nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
env := m.Workspace()
|
env := c.Workspace()
|
||||||
// Get the state
|
// Get the state
|
||||||
s, err := b.State(env)
|
s, err := b.State(env)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -42,7 +44,7 @@ func (c *StateMeta) State(m *Meta) (state.State, error) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get a local backend
|
// Get a local backend
|
||||||
localRaw, err := m.Backend(&BackendOpts{ForceLocal: true})
|
localRaw, err := c.Backend(&BackendOpts{ForceLocal: true})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
// This should never fail
|
// This should never fail
|
||||||
panic(err)
|
panic(err)
|
||||||
|
|
|
@ -10,7 +10,6 @@ import (
|
||||||
|
|
||||||
// StateMvCommand is a Command implementation that shows a single resource.
|
// StateMvCommand is a Command implementation that shows a single resource.
|
||||||
type StateMvCommand struct {
|
type StateMvCommand struct {
|
||||||
Meta
|
|
||||||
StateMeta
|
StateMeta
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -21,12 +20,13 @@ func (c *StateMvCommand) Run(args []string) int {
|
||||||
}
|
}
|
||||||
|
|
||||||
// We create two metas to track the two states
|
// We create two metas to track the two states
|
||||||
var meta1, meta2 Meta
|
var backupPathOut, statePathOut string
|
||||||
|
|
||||||
cmdFlags := c.Meta.flagSet("state mv")
|
cmdFlags := c.Meta.flagSet("state mv")
|
||||||
cmdFlags.StringVar(&meta1.backupPath, "backup", "-", "backup")
|
cmdFlags.StringVar(&c.backupPath, "backup", "-", "backup")
|
||||||
cmdFlags.StringVar(&meta1.statePath, "state", DefaultStateFilename, "path")
|
cmdFlags.StringVar(&c.statePath, "state", "", "path")
|
||||||
cmdFlags.StringVar(&meta2.backupPath, "backup-out", "-", "backup")
|
cmdFlags.StringVar(&backupPathOut, "backup-out", "-", "backup")
|
||||||
cmdFlags.StringVar(&meta2.statePath, "state-out", "", "path")
|
cmdFlags.StringVar(&statePathOut, "state-out", "", "path")
|
||||||
if err := cmdFlags.Parse(args); err != nil {
|
if err := cmdFlags.Parse(args); err != nil {
|
||||||
return cli.RunResultHelp
|
return cli.RunResultHelp
|
||||||
}
|
}
|
||||||
|
@ -36,16 +36,11 @@ func (c *StateMvCommand) Run(args []string) int {
|
||||||
return cli.RunResultHelp
|
return cli.RunResultHelp
|
||||||
}
|
}
|
||||||
|
|
||||||
// Copy the `-state` flag for output if we weren't given a custom one
|
|
||||||
if meta2.statePath == "" {
|
|
||||||
meta2.statePath = meta1.statePath
|
|
||||||
}
|
|
||||||
|
|
||||||
// Read the from state
|
// Read the from state
|
||||||
stateFrom, err := c.StateMeta.State(&meta1)
|
stateFrom, err := c.State()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
c.Ui.Error(fmt.Sprintf(errStateLoadingState, err))
|
c.Ui.Error(fmt.Sprintf(errStateLoadingState, err))
|
||||||
return cli.RunResultHelp
|
return 1
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := stateFrom.RefreshState(); err != nil {
|
if err := stateFrom.RefreshState(); err != nil {
|
||||||
|
@ -62,11 +57,14 @@ func (c *StateMvCommand) Run(args []string) int {
|
||||||
// Read the destination state
|
// Read the destination state
|
||||||
stateTo := stateFrom
|
stateTo := stateFrom
|
||||||
stateToReal := stateFromReal
|
stateToReal := stateFromReal
|
||||||
if meta2.statePath != meta1.statePath {
|
|
||||||
stateTo, err = c.StateMeta.State(&meta2)
|
if statePathOut != "" {
|
||||||
|
c.statePath = statePathOut
|
||||||
|
c.backupPath = backupPathOut
|
||||||
|
stateTo, err = c.State()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
c.Ui.Error(fmt.Sprintf(errStateLoadingState, err))
|
c.Ui.Error(fmt.Sprintf(errStateLoadingState, err))
|
||||||
return cli.RunResultHelp
|
return 1
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := stateTo.RefreshState(); err != nil {
|
if err := stateTo.RefreshState(); err != nil {
|
||||||
|
@ -185,28 +183,30 @@ func (c *StateMvCommand) addableResult(results []*terraform.StateFilterResult) i
|
||||||
|
|
||||||
func (c *StateMvCommand) Help() string {
|
func (c *StateMvCommand) Help() string {
|
||||||
helpText := `
|
helpText := `
|
||||||
Usage: terraform state mv [options] ADDRESS ADDRESS
|
Usage: terraform state mv [options] SOURCE DESTINATION
|
||||||
|
|
||||||
Move an item in the state to another location or to a completely different
|
This command will move an item matched by the address given to the
|
||||||
state file.
|
destination address. This command can also move to a destination address
|
||||||
|
in a completely different state file.
|
||||||
|
|
||||||
This command is useful for module refactors (moving items into a module),
|
This can be used for simple resource renaming, moving items to and from
|
||||||
configuration refactors (moving items to a completely different or new
|
a module, moving entire modules, and more. And because this command can also
|
||||||
state file), or generally renaming of resources.
|
move data to a completely new state, it can also be used for refactoring
|
||||||
|
one configuration into multiple separately managed Terraform configurations.
|
||||||
|
|
||||||
This command creates a timestamped backup of the state on every invocation.
|
This command will output a backup copy of the state prior to saving any
|
||||||
This can't be disabled. Due to the destructive nature of this command,
|
changes. The backup cannot be disabled. Due to the destructive nature
|
||||||
the backup is ensured by Terraform for safety reasons.
|
of this command, backups are required.
|
||||||
|
|
||||||
If you're moving from one state file to a different state file, a backup
|
If you're moving an item to a different state file, a backup will be created
|
||||||
will be created for each state file.
|
for each state file.
|
||||||
|
|
||||||
Options:
|
Options:
|
||||||
|
|
||||||
-backup=PATH Path where Terraform should write the backup for the original
|
-backup=PATH Path where Terraform should write the backup for the original
|
||||||
state. This can't be disabled. If not set, Terraform
|
state. This can't be disabled. If not set, Terraform
|
||||||
will write it to the same path as the statefile with
|
will write it to the same path as the statefile with
|
||||||
a backup extension.
|
a ".backup" extension.
|
||||||
|
|
||||||
-backup-out=PATH Path where Terraform should write the backup for the destination
|
-backup-out=PATH Path where Terraform should write the backup for the destination
|
||||||
state. This can't be disabled. If not set, Terraform
|
state. This can't be disabled. If not set, Terraform
|
||||||
|
@ -215,13 +215,12 @@ Options:
|
||||||
to be specified if -state-out is set to a different path
|
to be specified if -state-out is set to a different path
|
||||||
than -state.
|
than -state.
|
||||||
|
|
||||||
-state=PATH Path to a Terraform state file to use to look
|
-state=PATH Path to the source state file. Defaults to the configured
|
||||||
up Terraform-managed resources. By default it will
|
backend, or "terraform.tfstate"
|
||||||
use the state "terraform.tfstate" if it exists.
|
|
||||||
|
|
||||||
-state-out=PATH Path to the destination state file to move the item
|
-state-out=PATH Path to the destination state file to write to. If this
|
||||||
to. This defaults to the same statefile. This will
|
isn't specified, the source state file will be used. This
|
||||||
overwrite the destination state file.
|
can be a new or existing path.
|
||||||
|
|
||||||
`
|
`
|
||||||
return strings.TrimSpace(helpText)
|
return strings.TrimSpace(helpText)
|
||||||
|
|
|
@ -47,9 +47,11 @@ func TestStateMv(t *testing.T) {
|
||||||
p := testProvider()
|
p := testProvider()
|
||||||
ui := new(cli.MockUi)
|
ui := new(cli.MockUi)
|
||||||
c := &StateMvCommand{
|
c := &StateMvCommand{
|
||||||
Meta: Meta{
|
StateMeta{
|
||||||
testingOverrides: metaOverridesForProvider(p),
|
Meta: Meta{
|
||||||
Ui: ui,
|
testingOverrides: metaOverridesForProvider(p),
|
||||||
|
Ui: ui,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -133,9 +135,11 @@ func TestStateMv_explicitWithBackend(t *testing.T) {
|
||||||
p := testProvider()
|
p := testProvider()
|
||||||
ui = new(cli.MockUi)
|
ui = new(cli.MockUi)
|
||||||
c := &StateMvCommand{
|
c := &StateMvCommand{
|
||||||
Meta: Meta{
|
StateMeta{
|
||||||
testingOverrides: metaOverridesForProvider(p),
|
Meta: Meta{
|
||||||
Ui: ui,
|
testingOverrides: metaOverridesForProvider(p),
|
||||||
|
Ui: ui,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -194,9 +198,11 @@ func TestStateMv_backupExplicit(t *testing.T) {
|
||||||
p := testProvider()
|
p := testProvider()
|
||||||
ui := new(cli.MockUi)
|
ui := new(cli.MockUi)
|
||||||
c := &StateMvCommand{
|
c := &StateMvCommand{
|
||||||
Meta: Meta{
|
StateMeta{
|
||||||
testingOverrides: metaOverridesForProvider(p),
|
Meta: Meta{
|
||||||
Ui: ui,
|
testingOverrides: metaOverridesForProvider(p),
|
||||||
|
Ui: ui,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -244,9 +250,11 @@ func TestStateMv_stateOutNew(t *testing.T) {
|
||||||
p := testProvider()
|
p := testProvider()
|
||||||
ui := new(cli.MockUi)
|
ui := new(cli.MockUi)
|
||||||
c := &StateMvCommand{
|
c := &StateMvCommand{
|
||||||
Meta: Meta{
|
StateMeta{
|
||||||
testingOverrides: metaOverridesForProvider(p),
|
Meta: Meta{
|
||||||
Ui: ui,
|
testingOverrides: metaOverridesForProvider(p),
|
||||||
|
Ui: ui,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -316,9 +324,11 @@ func TestStateMv_stateOutExisting(t *testing.T) {
|
||||||
p := testProvider()
|
p := testProvider()
|
||||||
ui := new(cli.MockUi)
|
ui := new(cli.MockUi)
|
||||||
c := &StateMvCommand{
|
c := &StateMvCommand{
|
||||||
Meta: Meta{
|
StateMeta{
|
||||||
testingOverrides: metaOverridesForProvider(p),
|
Meta: Meta{
|
||||||
Ui: ui,
|
testingOverrides: metaOverridesForProvider(p),
|
||||||
|
Ui: ui,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -357,9 +367,11 @@ func TestStateMv_noState(t *testing.T) {
|
||||||
p := testProvider()
|
p := testProvider()
|
||||||
ui := new(cli.MockUi)
|
ui := new(cli.MockUi)
|
||||||
c := &StateMvCommand{
|
c := &StateMvCommand{
|
||||||
Meta: Meta{
|
StateMeta{
|
||||||
testingOverrides: metaOverridesForProvider(p),
|
Meta: Meta{
|
||||||
Ui: ui,
|
testingOverrides: metaOverridesForProvider(p),
|
||||||
|
Ui: ui,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -418,9 +430,11 @@ func TestStateMv_stateOutNew_count(t *testing.T) {
|
||||||
p := testProvider()
|
p := testProvider()
|
||||||
ui := new(cli.MockUi)
|
ui := new(cli.MockUi)
|
||||||
c := &StateMvCommand{
|
c := &StateMvCommand{
|
||||||
Meta: Meta{
|
StateMeta{
|
||||||
testingOverrides: metaOverridesForProvider(p),
|
Meta: Meta{
|
||||||
Ui: ui,
|
testingOverrides: metaOverridesForProvider(p),
|
||||||
|
Ui: ui,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -596,9 +610,11 @@ func TestStateMv_stateOutNew_largeCount(t *testing.T) {
|
||||||
p := testProvider()
|
p := testProvider()
|
||||||
ui := new(cli.MockUi)
|
ui := new(cli.MockUi)
|
||||||
c := &StateMvCommand{
|
c := &StateMvCommand{
|
||||||
Meta: Meta{
|
StateMeta{
|
||||||
testingOverrides: metaOverridesForProvider(p),
|
Meta: Meta{
|
||||||
Ui: ui,
|
testingOverrides: metaOverridesForProvider(p),
|
||||||
|
Ui: ui,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -677,9 +693,11 @@ func TestStateMv_stateOutNew_nestedModule(t *testing.T) {
|
||||||
p := testProvider()
|
p := testProvider()
|
||||||
ui := new(cli.MockUi)
|
ui := new(cli.MockUi)
|
||||||
c := &StateMvCommand{
|
c := &StateMvCommand{
|
||||||
Meta: Meta{
|
StateMeta{
|
||||||
testingOverrides: metaOverridesForProvider(p),
|
Meta: Meta{
|
||||||
Ui: ui,
|
testingOverrides: metaOverridesForProvider(p),
|
||||||
|
Ui: ui,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -705,6 +723,160 @@ func TestStateMv_stateOutNew_nestedModule(t *testing.T) {
|
||||||
testStateOutput(t, backups[0], testStateMvNestedModule_stateOutOriginal)
|
testStateOutput(t, backups[0], testStateMvNestedModule_stateOutOriginal)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestStateMv_withinBackend(t *testing.T) {
|
||||||
|
td := tempDir(t)
|
||||||
|
copy.CopyDir(testFixturePath("backend-unchanged"), td)
|
||||||
|
defer os.RemoveAll(td)
|
||||||
|
defer testChdir(t, td)()
|
||||||
|
|
||||||
|
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.baz": &terraform.ResourceState{
|
||||||
|
Type: "test_instance",
|
||||||
|
Primary: &terraform.InstanceState{
|
||||||
|
ID: "foo",
|
||||||
|
Attributes: map[string]string{
|
||||||
|
"foo": "value",
|
||||||
|
"bar": "value",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
// the local backend state file is "foo"
|
||||||
|
statePath := "local-state.tfstate"
|
||||||
|
backupPath := "local-state.backup"
|
||||||
|
|
||||||
|
f, err := os.Create(statePath)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
defer f.Close()
|
||||||
|
|
||||||
|
if err := terraform.WriteState(state, f); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
p := testProvider()
|
||||||
|
ui := new(cli.MockUi)
|
||||||
|
c := &StateMvCommand{
|
||||||
|
StateMeta{
|
||||||
|
Meta: Meta{
|
||||||
|
testingOverrides: metaOverridesForProvider(p),
|
||||||
|
Ui: ui,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
args := []string{
|
||||||
|
"-backup", backupPath,
|
||||||
|
"test_instance.foo",
|
||||||
|
"test_instance.bar",
|
||||||
|
}
|
||||||
|
if code := c.Run(args); code != 0 {
|
||||||
|
t.Fatalf("bad: %d\n\n%s", code, ui.ErrorWriter.String())
|
||||||
|
}
|
||||||
|
|
||||||
|
testStateOutput(t, statePath, testStateMvOutput)
|
||||||
|
testStateOutput(t, backupPath, testStateMvOutputOriginal)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestStateMv_fromBackendToLocal(t *testing.T) {
|
||||||
|
td := tempDir(t)
|
||||||
|
copy.CopyDir(testFixturePath("backend-unchanged"), td)
|
||||||
|
defer os.RemoveAll(td)
|
||||||
|
defer testChdir(t, td)()
|
||||||
|
|
||||||
|
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.baz": &terraform.ResourceState{
|
||||||
|
Type: "test_instance",
|
||||||
|
Primary: &terraform.InstanceState{
|
||||||
|
ID: "foo",
|
||||||
|
Attributes: map[string]string{
|
||||||
|
"foo": "value",
|
||||||
|
"bar": "value",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
// the local backend state file is "foo"
|
||||||
|
statePath := "local-state.tfstate"
|
||||||
|
|
||||||
|
// real "local" state file
|
||||||
|
statePathOut := "real-local.tfstate"
|
||||||
|
|
||||||
|
f, err := os.Create(statePath)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
defer f.Close()
|
||||||
|
|
||||||
|
if err := terraform.WriteState(state, f); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
p := testProvider()
|
||||||
|
ui := new(cli.MockUi)
|
||||||
|
c := &StateMvCommand{
|
||||||
|
StateMeta{
|
||||||
|
Meta: Meta{
|
||||||
|
testingOverrides: metaOverridesForProvider(p),
|
||||||
|
Ui: ui,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
args := []string{
|
||||||
|
"-state-out", statePathOut,
|
||||||
|
"test_instance.foo",
|
||||||
|
"test_instance.bar",
|
||||||
|
}
|
||||||
|
if code := c.Run(args); code != 0 {
|
||||||
|
t.Fatalf("bad: %d\n\n%s", code, ui.ErrorWriter.String())
|
||||||
|
}
|
||||||
|
|
||||||
|
testStateOutput(t, statePathOut, testStateMvCount_stateOutSrc)
|
||||||
|
|
||||||
|
// the backend state should be left with only baz
|
||||||
|
testStateOutput(t, statePath, testStateMvOriginal_backend)
|
||||||
|
}
|
||||||
|
|
||||||
const testStateMvOutputOriginal = `
|
const testStateMvOutputOriginal = `
|
||||||
test_instance.baz:
|
test_instance.baz:
|
||||||
ID = foo
|
ID = foo
|
||||||
|
@ -943,3 +1115,10 @@ const testStateMvExisting_stateDstOriginal = `
|
||||||
test_instance.qux:
|
test_instance.qux:
|
||||||
ID = bar
|
ID = bar
|
||||||
`
|
`
|
||||||
|
|
||||||
|
const testStateMvOriginal_backend = `
|
||||||
|
test_instance.baz:
|
||||||
|
ID = foo
|
||||||
|
bar = value
|
||||||
|
foo = value
|
||||||
|
`
|
||||||
|
|
|
@ -9,7 +9,6 @@ import (
|
||||||
|
|
||||||
// StateRmCommand is a Command implementation that shows a single resource.
|
// StateRmCommand is a Command implementation that shows a single resource.
|
||||||
type StateRmCommand struct {
|
type StateRmCommand struct {
|
||||||
Meta
|
|
||||||
StateMeta
|
StateMeta
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -20,8 +19,8 @@ func (c *StateRmCommand) Run(args []string) int {
|
||||||
}
|
}
|
||||||
|
|
||||||
cmdFlags := c.Meta.flagSet("state show")
|
cmdFlags := c.Meta.flagSet("state show")
|
||||||
cmdFlags.StringVar(&c.Meta.backupPath, "backup", "-", "backup")
|
cmdFlags.StringVar(&c.backupPath, "backup", "-", "backup")
|
||||||
cmdFlags.StringVar(&c.Meta.statePath, "state", DefaultStateFilename, "path")
|
cmdFlags.StringVar(&c.statePath, "state", "", "path")
|
||||||
if err := cmdFlags.Parse(args); err != nil {
|
if err := cmdFlags.Parse(args); err != nil {
|
||||||
return cli.RunResultHelp
|
return cli.RunResultHelp
|
||||||
}
|
}
|
||||||
|
@ -32,10 +31,10 @@ func (c *StateRmCommand) Run(args []string) int {
|
||||||
return 1
|
return 1
|
||||||
}
|
}
|
||||||
|
|
||||||
state, err := c.StateMeta.State(&c.Meta)
|
state, err := c.State()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
c.Ui.Error(fmt.Sprintf(errStateLoadingState, err))
|
c.Ui.Error(fmt.Sprintf(errStateLoadingState, err))
|
||||||
return cli.RunResultHelp
|
return 1
|
||||||
}
|
}
|
||||||
if err := state.RefreshState(); err != nil {
|
if err := state.RefreshState(); err != nil {
|
||||||
c.Ui.Error(fmt.Sprintf("Failed to load state: %s", err))
|
c.Ui.Error(fmt.Sprintf("Failed to load state: %s", err))
|
||||||
|
|
|
@ -6,6 +6,7 @@ import (
|
||||||
"strings"
|
"strings"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
|
"github.com/hashicorp/terraform/helper/copy"
|
||||||
"github.com/hashicorp/terraform/terraform"
|
"github.com/hashicorp/terraform/terraform"
|
||||||
"github.com/mitchellh/cli"
|
"github.com/mitchellh/cli"
|
||||||
)
|
)
|
||||||
|
@ -47,9 +48,11 @@ func TestStateRm(t *testing.T) {
|
||||||
p := testProvider()
|
p := testProvider()
|
||||||
ui := new(cli.MockUi)
|
ui := new(cli.MockUi)
|
||||||
c := &StateRmCommand{
|
c := &StateRmCommand{
|
||||||
Meta: Meta{
|
StateMeta{
|
||||||
testingOverrides: metaOverridesForProvider(p),
|
Meta: Meta{
|
||||||
Ui: ui,
|
testingOverrides: metaOverridesForProvider(p),
|
||||||
|
Ui: ui,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -109,9 +112,11 @@ func TestStateRmNoArgs(t *testing.T) {
|
||||||
p := testProvider()
|
p := testProvider()
|
||||||
ui := new(cli.MockUi)
|
ui := new(cli.MockUi)
|
||||||
c := &StateRmCommand{
|
c := &StateRmCommand{
|
||||||
Meta: Meta{
|
StateMeta{
|
||||||
testingOverrides: metaOverridesForProvider(p),
|
Meta: Meta{
|
||||||
Ui: ui,
|
testingOverrides: metaOverridesForProvider(p),
|
||||||
|
Ui: ui,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -169,9 +174,11 @@ func TestStateRm_backupExplicit(t *testing.T) {
|
||||||
p := testProvider()
|
p := testProvider()
|
||||||
ui := new(cli.MockUi)
|
ui := new(cli.MockUi)
|
||||||
c := &StateRmCommand{
|
c := &StateRmCommand{
|
||||||
Meta: Meta{
|
StateMeta{
|
||||||
testingOverrides: metaOverridesForProvider(p),
|
Meta: Meta{
|
||||||
Ui: ui,
|
testingOverrides: metaOverridesForProvider(p),
|
||||||
|
Ui: ui,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -198,9 +205,11 @@ func TestStateRm_noState(t *testing.T) {
|
||||||
p := testProvider()
|
p := testProvider()
|
||||||
ui := new(cli.MockUi)
|
ui := new(cli.MockUi)
|
||||||
c := &StateRmCommand{
|
c := &StateRmCommand{
|
||||||
Meta: Meta{
|
StateMeta{
|
||||||
testingOverrides: metaOverridesForProvider(p),
|
Meta: Meta{
|
||||||
Ui: ui,
|
testingOverrides: metaOverridesForProvider(p),
|
||||||
|
Ui: ui,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -210,6 +219,110 @@ func TestStateRm_noState(t *testing.T) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestStateRm_needsInit(t *testing.T) {
|
||||||
|
td := tempDir(t)
|
||||||
|
copy.CopyDir(testFixturePath("backend-change"), td)
|
||||||
|
defer os.RemoveAll(td)
|
||||||
|
defer testChdir(t, td)()
|
||||||
|
|
||||||
|
p := testProvider()
|
||||||
|
ui := new(cli.MockUi)
|
||||||
|
c := &StateRmCommand{
|
||||||
|
StateMeta{
|
||||||
|
Meta: Meta{
|
||||||
|
testingOverrides: metaOverridesForProvider(p),
|
||||||
|
Ui: ui,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
args := []string{"foo"}
|
||||||
|
if code := c.Run(args); code == 0 {
|
||||||
|
t.Fatal("expected error\noutput:", ui.OutputWriter)
|
||||||
|
}
|
||||||
|
|
||||||
|
if !strings.Contains(ui.ErrorWriter.String(), "Initialization") {
|
||||||
|
t.Fatal("expected initialization error, got:\n", ui.ErrorWriter)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestStateRm_backendState(t *testing.T) {
|
||||||
|
td := tempDir(t)
|
||||||
|
copy.CopyDir(testFixturePath("backend-unchanged"), td)
|
||||||
|
defer os.RemoveAll(td)
|
||||||
|
defer testChdir(t, td)()
|
||||||
|
|
||||||
|
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",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
// the local backend state file is "foo"
|
||||||
|
statePath := "local-state.tfstate"
|
||||||
|
backupPath := "local-state.backup"
|
||||||
|
|
||||||
|
f, err := os.Create(statePath)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
defer f.Close()
|
||||||
|
|
||||||
|
if err := terraform.WriteState(state, f); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
p := testProvider()
|
||||||
|
ui := new(cli.MockUi)
|
||||||
|
c := &StateRmCommand{
|
||||||
|
StateMeta{
|
||||||
|
Meta: Meta{
|
||||||
|
testingOverrides: metaOverridesForProvider(p),
|
||||||
|
Ui: ui,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
args := []string{
|
||||||
|
"-backup", backupPath,
|
||||||
|
"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 backup
|
||||||
|
testStateOutput(t, backupPath, testStateRmOutputOriginal)
|
||||||
|
}
|
||||||
|
|
||||||
const testStateRmOutputOriginal = `
|
const testStateRmOutputOriginal = `
|
||||||
test_instance.bar:
|
test_instance.bar:
|
||||||
ID = foo
|
ID = foo
|
||||||
|
|
|
@ -28,7 +28,7 @@ func TestStateDefaultBackupExtension(t *testing.T) {
|
||||||
tmp, cwd := testCwd(t)
|
tmp, cwd := testCwd(t)
|
||||||
defer testFixCwd(t, tmp, cwd)
|
defer testFixCwd(t, tmp, cwd)
|
||||||
|
|
||||||
s, err := (&StateMeta{}).State(&Meta{})
|
s, err := (&StateMeta{}).State()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
|
|
|
@ -276,13 +276,17 @@ func init() {
|
||||||
|
|
||||||
"state rm": func() (cli.Command, error) {
|
"state rm": func() (cli.Command, error) {
|
||||||
return &command.StateRmCommand{
|
return &command.StateRmCommand{
|
||||||
Meta: meta,
|
StateMeta: command.StateMeta{
|
||||||
|
Meta: meta,
|
||||||
|
},
|
||||||
}, nil
|
}, nil
|
||||||
},
|
},
|
||||||
|
|
||||||
"state mv": func() (cli.Command, error) {
|
"state mv": func() (cli.Command, error) {
|
||||||
return &command.StateMvCommand{
|
return &command.StateMvCommand{
|
||||||
Meta: meta,
|
StateMeta: command.StateMeta{
|
||||||
|
Meta: meta,
|
||||||
|
},
|
||||||
}, nil
|
}, nil
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|
|
@ -40,19 +40,22 @@ in [resource addressing format](/docs/commands/state/addressing.html).
|
||||||
|
|
||||||
The command-line flags are all optional. The list of available flags are:
|
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
|
* `-backup=path` - Path where Terraform should write the backup for the
|
||||||
a timestamp with the ".backup" extension.
|
original state. This can't be disabled. If not set, Terraform will write it
|
||||||
|
to the same path as the statefile with a ".backup" extension.
|
||||||
|
|
||||||
* `-backup-out=path` - Path to the backup file for the output state.
|
* `-backup-out=path` - Path where Terraform should write the backup for the
|
||||||
This is only necessary if `-state-out` is specified.
|
destination state. This can't be disabled. If not set, Terraform will write
|
||||||
|
it to the same path as the destination state file with a backup extension.
|
||||||
|
This only needs to be specified if -state-out is set to a different path than
|
||||||
|
-state.
|
||||||
|
|
||||||
* `-state=path` - Path to the state file. Defaults to "terraform.tfstate".
|
* `-state=path` - Path to the source state file to read from. Defaults to the
|
||||||
Ignored when [remote state](/docs/state/remote.html) is used.
|
configured backend, or "terraform.tfstate".
|
||||||
|
|
||||||
* `-state-out=path` - Path to the state file to write to. If this isn't specified
|
* `-state-out=path` - Path to the destination state file to write to. If this
|
||||||
the state specified by `-state` will be used. This can be
|
isn't specified the source state file will be used. This can be a new or
|
||||||
a new or existing path. Ignored when
|
existing path.
|
||||||
[remote state](/docs/state/remote.html) is used.
|
|
||||||
|
|
||||||
## Example: Rename a Resource
|
## Example: Rename a Resource
|
||||||
|
|
||||||
|
|
|
@ -17,7 +17,7 @@ and more.
|
||||||
|
|
||||||
Usage: `terraform state rm [options] ADDRESS...`
|
Usage: `terraform state rm [options] ADDRESS...`
|
||||||
|
|
||||||
The command will remove all the items matched by the addresses given.
|
Remove one or more items from the Terraform state.
|
||||||
|
|
||||||
Items removed from the Terraform state are _not physically destroyed_.
|
Items removed from the Terraform state are _not physically destroyed_.
|
||||||
Items removed from the Terraform state are only no longer managed by
|
Items removed from the Terraform state are only no longer managed by
|
||||||
|
@ -43,10 +43,13 @@ in [resource addressing format](/docs/commands/state/addressing.html).
|
||||||
|
|
||||||
The command-line flags are all optional. The list of available flags are:
|
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
|
* `-backup=path` - Path where Terraform should write the backup state. This
|
||||||
a timestamp with the ".backup" extension.
|
can't be disabled. If not set, Terraform will write it to the same path as
|
||||||
|
the statefile with a backup extension.
|
||||||
|
|
||||||
* `-state=path` - Path to the state file. Defaults to "terraform.tfstate".
|
* `-state=path` - Path to a Terraform state file to use to look up
|
||||||
|
Terraform-managed resources. By default it will use the configured backend,
|
||||||
|
or the default "terraform.tfstate" if it exists.
|
||||||
|
|
||||||
## Example: Remove a Resource
|
## Example: Remove a Resource
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue