commit
5e127a62c1
|
@ -224,21 +224,7 @@ func testProvider() *terraform.MockResourceProvider {
|
||||||
}
|
}
|
||||||
|
|
||||||
func testTempFile(t *testing.T) string {
|
func testTempFile(t *testing.T) string {
|
||||||
tf, err := ioutil.TempFile("", "tf")
|
return filepath.Join(testTempDir(t), "state.tfstate")
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("err: %s", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
result := tf.Name()
|
|
||||||
|
|
||||||
if err := tf.Close(); err != nil {
|
|
||||||
t.Fatalf("err: %s", err)
|
|
||||||
}
|
|
||||||
if err := os.Remove(result); err != nil {
|
|
||||||
t.Fatalf("err: %s", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
return result
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func testTempDir(t *testing.T) string {
|
func testTempDir(t *testing.T) string {
|
||||||
|
|
|
@ -108,6 +108,9 @@ func TestPlan_destroy(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestPlan_noState(t *testing.T) {
|
func TestPlan_noState(t *testing.T) {
|
||||||
|
tmp, cwd := testCwd(t)
|
||||||
|
defer testFixCwd(t, tmp, cwd)
|
||||||
|
|
||||||
p := testProvider()
|
p := testProvider()
|
||||||
ui := new(cli.MockUi)
|
ui := new(cli.MockUi)
|
||||||
c := &PlanCommand{
|
c := &PlanCommand{
|
||||||
|
|
|
@ -2,13 +2,46 @@ package command
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"errors"
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/hashicorp/terraform/state"
|
||||||
"github.com/hashicorp/terraform/terraform"
|
"github.com/hashicorp/terraform/terraform"
|
||||||
)
|
)
|
||||||
|
|
||||||
// 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{}
|
||||||
|
|
||||||
|
// State returns the state for this meta. This is different then Meta.State
|
||||||
|
// in the way that backups are done. This configures backups to be timestamped
|
||||||
|
// rather than just the original state path plus a backup path.
|
||||||
|
func (c *StateMeta) State(m *Meta) (state.State, error) {
|
||||||
|
// Disable backups since we wrap it manually below
|
||||||
|
m.backupPath = "-"
|
||||||
|
|
||||||
|
// Get the state (shouldn't be wrapped in a backup)
|
||||||
|
s, err := m.State()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Determine the backup path. stateOutPath is set to the resulting
|
||||||
|
// file where state is written (cached in the case of remote state)
|
||||||
|
backupPath := fmt.Sprintf(
|
||||||
|
"%s.%d.%s",
|
||||||
|
m.stateOutPath,
|
||||||
|
time.Now().UTC().Unix(),
|
||||||
|
DefaultBackupExtension)
|
||||||
|
|
||||||
|
// Wrap it for backups
|
||||||
|
s = &state.BackupState{
|
||||||
|
Real: s,
|
||||||
|
Path: backupPath,
|
||||||
|
}
|
||||||
|
|
||||||
|
return s, nil
|
||||||
|
}
|
||||||
|
|
||||||
// filterInstance filters a single instance out of filter results.
|
// filterInstance filters a single instance out of filter results.
|
||||||
func (c *StateMeta) filterInstance(rs []*terraform.StateFilterResult) (*terraform.StateFilterResult, error) {
|
func (c *StateMeta) filterInstance(rs []*terraform.StateFilterResult) (*terraform.StateFilterResult, error) {
|
||||||
var result *terraform.StateFilterResult
|
var result *terraform.StateFilterResult
|
||||||
|
|
|
@ -0,0 +1,179 @@
|
||||||
|
package command
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/hashicorp/terraform/terraform"
|
||||||
|
"github.com/mitchellh/cli"
|
||||||
|
)
|
||||||
|
|
||||||
|
// StateMvCommand is a Command implementation that shows a single resource.
|
||||||
|
type StateMvCommand struct {
|
||||||
|
Meta
|
||||||
|
StateMeta
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *StateMvCommand) Run(args []string) int {
|
||||||
|
args = c.Meta.process(args, true)
|
||||||
|
|
||||||
|
// We create two metas to track the two states
|
||||||
|
var meta1, meta2 Meta
|
||||||
|
cmdFlags := c.Meta.flagSet("state show")
|
||||||
|
cmdFlags.StringVar(&meta1.stateOutPath, "backup", "", "backup")
|
||||||
|
cmdFlags.StringVar(&meta1.statePath, "state", DefaultStateFilename, "path")
|
||||||
|
cmdFlags.StringVar(&meta2.stateOutPath, "backup-out", "", "backup")
|
||||||
|
cmdFlags.StringVar(&meta2.statePath, "state-out", "", "path")
|
||||||
|
if err := cmdFlags.Parse(args); err != nil {
|
||||||
|
return cli.RunResultHelp
|
||||||
|
}
|
||||||
|
args = cmdFlags.Args()
|
||||||
|
if len(args) != 2 {
|
||||||
|
c.Ui.Error("Exactly two arguments expected.\n")
|
||||||
|
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
|
||||||
|
stateFrom, err := c.StateMeta.State(&meta1)
|
||||||
|
if err != nil {
|
||||||
|
c.Ui.Error(fmt.Sprintf(errStateLoadingState, err))
|
||||||
|
return cli.RunResultHelp
|
||||||
|
}
|
||||||
|
|
||||||
|
stateFromReal := stateFrom.State()
|
||||||
|
if stateFromReal == nil {
|
||||||
|
c.Ui.Error(fmt.Sprintf(errStateNotFound))
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
|
||||||
|
// Read the destination state
|
||||||
|
stateTo := stateFrom
|
||||||
|
stateToReal := stateFromReal
|
||||||
|
if meta2.statePath != meta1.statePath {
|
||||||
|
stateTo, err = c.StateMeta.State(&meta2)
|
||||||
|
if err != nil {
|
||||||
|
c.Ui.Error(fmt.Sprintf(errStateLoadingState, err))
|
||||||
|
return cli.RunResultHelp
|
||||||
|
}
|
||||||
|
|
||||||
|
stateToReal = stateTo.State()
|
||||||
|
if stateToReal == nil {
|
||||||
|
stateToReal = terraform.NewState()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Filter what we're moving
|
||||||
|
filter := &terraform.StateFilter{State: stateFromReal}
|
||||||
|
results, err := filter.Filter(args[0])
|
||||||
|
if err != nil {
|
||||||
|
c.Ui.Error(fmt.Sprintf(errStateMv, err))
|
||||||
|
return cli.RunResultHelp
|
||||||
|
}
|
||||||
|
if len(results) == 0 {
|
||||||
|
c.Ui.Output(fmt.Sprintf("Item to move doesn't exist: %s", args[0]))
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
|
||||||
|
// Do the actual move
|
||||||
|
if err := stateFromReal.Remove(args[0]); err != nil {
|
||||||
|
c.Ui.Error(fmt.Sprintf(errStateMv, err))
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := stateToReal.Add(args[0], args[1], results[0].Value); err != nil {
|
||||||
|
c.Ui.Error(fmt.Sprintf(errStateMv, err))
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
|
||||||
|
// Write the new state
|
||||||
|
if err := stateTo.WriteState(stateToReal); err != nil {
|
||||||
|
c.Ui.Error(fmt.Sprintf(errStateMvPersist, err))
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := stateTo.PersistState(); err != nil {
|
||||||
|
c.Ui.Error(fmt.Sprintf(errStateMvPersist, err))
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
|
||||||
|
// Write the old state if it is different
|
||||||
|
if stateTo != stateFrom {
|
||||||
|
if err := stateFrom.WriteState(stateFromReal); err != nil {
|
||||||
|
c.Ui.Error(fmt.Sprintf(errStateMvPersist, err))
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := stateFrom.PersistState(); err != nil {
|
||||||
|
c.Ui.Error(fmt.Sprintf(errStateMvPersist, err))
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
c.Ui.Output(fmt.Sprintf(
|
||||||
|
"Moved %s to %s", args[0], args[1]))
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *StateMvCommand) Help() string {
|
||||||
|
helpText := `
|
||||||
|
Usage: terraform state mv [options] ADDRESS ADDRESS
|
||||||
|
|
||||||
|
Move an item in the state to another location or to a completely different
|
||||||
|
state file.
|
||||||
|
|
||||||
|
This command is useful for module refactors (moving items into a module),
|
||||||
|
configuration refactors (moving items to a completely different or new
|
||||||
|
state file), or generally renaming of resources.
|
||||||
|
|
||||||
|
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.
|
||||||
|
|
||||||
|
If you're moving from one state file to a different state file, a backup
|
||||||
|
will be created for each state file.
|
||||||
|
|
||||||
|
Options:
|
||||||
|
|
||||||
|
-backup=PATH Path where Terraform should write the backup for the 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 where Terraform should write the backup for the 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 a Terraform state file to use to look
|
||||||
|
up Terraform-managed resources. By default it will
|
||||||
|
use the state "terraform.tfstate" if it exists.
|
||||||
|
|
||||||
|
-state-out=PATH Path to the destination state file to move the item
|
||||||
|
to. This defaults to the same statefile. This will
|
||||||
|
overwrite the destination state file.
|
||||||
|
|
||||||
|
`
|
||||||
|
return strings.TrimSpace(helpText)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *StateMvCommand) Synopsis() string {
|
||||||
|
return "Move an item in the state"
|
||||||
|
}
|
||||||
|
|
||||||
|
const errStateMv = `Error moving state: %[1]s
|
||||||
|
|
||||||
|
Please ensure your addresses and state paths are valid. No
|
||||||
|
state was persisted. Your existing states are untouched.`
|
||||||
|
|
||||||
|
const errStateMvPersist = `Error saving the state: %s
|
||||||
|
|
||||||
|
The state wasn't saved properly. If the error happening after a partial
|
||||||
|
write occurred, a backup file will have been created. Otherwise, the state
|
||||||
|
is in the same state it was when the operation started.`
|
|
@ -0,0 +1,289 @@
|
||||||
|
package command
|
||||||
|
|
||||||
|
import (
|
||||||
|
"path/filepath"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/hashicorp/terraform/terraform"
|
||||||
|
"github.com/mitchellh/cli"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestStateMv(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.baz": &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 := &StateMvCommand{
|
||||||
|
Meta: Meta{
|
||||||
|
ContextOpts: testCtxConfig(p),
|
||||||
|
Ui: ui,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
args := []string{
|
||||||
|
"-state", statePath,
|
||||||
|
"test_instance.foo",
|
||||||
|
"test_instance.bar",
|
||||||
|
}
|
||||||
|
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, testStateMvOutput)
|
||||||
|
|
||||||
|
// Test we have backups
|
||||||
|
backups := testStateBackups(t, filepath.Dir(statePath))
|
||||||
|
if len(backups) != 1 {
|
||||||
|
t.Fatalf("bad: %#v", backups)
|
||||||
|
}
|
||||||
|
testStateOutput(t, backups[0], testStateMvOutputOriginal)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestStateMv_stateOutNew(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",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
statePath := testStateFile(t, state)
|
||||||
|
stateOutPath := statePath + ".out"
|
||||||
|
|
||||||
|
p := testProvider()
|
||||||
|
ui := new(cli.MockUi)
|
||||||
|
c := &StateMvCommand{
|
||||||
|
Meta: Meta{
|
||||||
|
ContextOpts: testCtxConfig(p),
|
||||||
|
Ui: ui,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
args := []string{
|
||||||
|
"-state", statePath,
|
||||||
|
"-state-out", stateOutPath,
|
||||||
|
"test_instance.foo",
|
||||||
|
"test_instance.bar",
|
||||||
|
}
|
||||||
|
if code := c.Run(args); code != 0 {
|
||||||
|
t.Fatalf("bad: %d\n\n%s", code, ui.ErrorWriter.String())
|
||||||
|
}
|
||||||
|
|
||||||
|
// Test it is correct
|
||||||
|
testStateOutput(t, stateOutPath, testStateMvOutput_stateOut)
|
||||||
|
testStateOutput(t, statePath, testStateMvOutput_stateOutSrc)
|
||||||
|
|
||||||
|
// Test we have backups
|
||||||
|
backups := testStateBackups(t, filepath.Dir(statePath))
|
||||||
|
if len(backups) != 1 {
|
||||||
|
t.Fatalf("bad: %#v", backups)
|
||||||
|
}
|
||||||
|
testStateOutput(t, backups[0], testStateMvOutput_stateOutOriginal)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestStateMv_stateOutExisting(t *testing.T) {
|
||||||
|
stateSrc := &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",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
statePath := testStateFile(t, stateSrc)
|
||||||
|
|
||||||
|
stateDst := &terraform.State{
|
||||||
|
Modules: []*terraform.ModuleState{
|
||||||
|
&terraform.ModuleState{
|
||||||
|
Path: []string{"root"},
|
||||||
|
Resources: map[string]*terraform.ResourceState{
|
||||||
|
"test_instance.qux": &terraform.ResourceState{
|
||||||
|
Type: "test_instance",
|
||||||
|
Primary: &terraform.InstanceState{
|
||||||
|
ID: "bar",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
stateOutPath := testStateFile(t, stateDst)
|
||||||
|
|
||||||
|
p := testProvider()
|
||||||
|
ui := new(cli.MockUi)
|
||||||
|
c := &StateMvCommand{
|
||||||
|
Meta: Meta{
|
||||||
|
ContextOpts: testCtxConfig(p),
|
||||||
|
Ui: ui,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
args := []string{
|
||||||
|
"-state", statePath,
|
||||||
|
"-state-out", stateOutPath,
|
||||||
|
"test_instance.foo",
|
||||||
|
"test_instance.bar",
|
||||||
|
}
|
||||||
|
if code := c.Run(args); code != 0 {
|
||||||
|
t.Fatalf("bad: %d\n\n%s", code, ui.ErrorWriter.String())
|
||||||
|
}
|
||||||
|
|
||||||
|
// Test it is correct
|
||||||
|
testStateOutput(t, stateOutPath, testStateMvExisting_stateDst)
|
||||||
|
testStateOutput(t, statePath, testStateMvExisting_stateSrc)
|
||||||
|
|
||||||
|
// Test we have backups
|
||||||
|
backups := testStateBackups(t, filepath.Dir(statePath))
|
||||||
|
if len(backups) != 1 {
|
||||||
|
t.Fatalf("bad: %#v", backups)
|
||||||
|
}
|
||||||
|
testStateOutput(t, backups[0], testStateMvExisting_stateSrcOriginal)
|
||||||
|
|
||||||
|
backups = testStateBackups(t, filepath.Dir(stateOutPath))
|
||||||
|
if len(backups) != 1 {
|
||||||
|
t.Fatalf("bad: %#v", backups)
|
||||||
|
}
|
||||||
|
testStateOutput(t, backups[0], testStateMvExisting_stateDstOriginal)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestStateMv_noState(t *testing.T) {
|
||||||
|
tmp, cwd := testCwd(t)
|
||||||
|
defer testFixCwd(t, tmp, cwd)
|
||||||
|
|
||||||
|
p := testProvider()
|
||||||
|
ui := new(cli.MockUi)
|
||||||
|
c := &StateMvCommand{
|
||||||
|
Meta: Meta{
|
||||||
|
ContextOpts: testCtxConfig(p),
|
||||||
|
Ui: ui,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
args := []string{"from", "to"}
|
||||||
|
if code := c.Run(args); code != 1 {
|
||||||
|
t.Fatalf("bad: %d\n\n%s", code, ui.ErrorWriter.String())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const testStateMvOutputOriginal = `
|
||||||
|
test_instance.baz:
|
||||||
|
ID = foo
|
||||||
|
bar = value
|
||||||
|
foo = value
|
||||||
|
test_instance.foo:
|
||||||
|
ID = bar
|
||||||
|
bar = value
|
||||||
|
foo = value
|
||||||
|
`
|
||||||
|
|
||||||
|
const testStateMvOutput = `
|
||||||
|
test_instance.bar:
|
||||||
|
ID = bar
|
||||||
|
bar = value
|
||||||
|
foo = value
|
||||||
|
test_instance.baz:
|
||||||
|
ID = foo
|
||||||
|
bar = value
|
||||||
|
foo = value
|
||||||
|
`
|
||||||
|
|
||||||
|
const testStateMvOutput_stateOut = `
|
||||||
|
test_instance.bar:
|
||||||
|
ID = bar
|
||||||
|
bar = value
|
||||||
|
foo = value
|
||||||
|
`
|
||||||
|
|
||||||
|
const testStateMvOutput_stateOutSrc = `
|
||||||
|
<no state>
|
||||||
|
`
|
||||||
|
|
||||||
|
const testStateMvOutput_stateOutOriginal = `
|
||||||
|
test_instance.foo:
|
||||||
|
ID = bar
|
||||||
|
bar = value
|
||||||
|
foo = value
|
||||||
|
`
|
||||||
|
|
||||||
|
const testStateMvExisting_stateSrc = `
|
||||||
|
<no state>
|
||||||
|
`
|
||||||
|
|
||||||
|
const testStateMvExisting_stateDst = `
|
||||||
|
test_instance.bar:
|
||||||
|
ID = bar
|
||||||
|
bar = value
|
||||||
|
foo = value
|
||||||
|
test_instance.qux:
|
||||||
|
ID = bar
|
||||||
|
`
|
||||||
|
|
||||||
|
const testStateMvExisting_stateSrcOriginal = `
|
||||||
|
test_instance.foo:
|
||||||
|
ID = bar
|
||||||
|
bar = value
|
||||||
|
foo = value
|
||||||
|
`
|
||||||
|
|
||||||
|
const testStateMvExisting_stateDstOriginal = `
|
||||||
|
test_instance.qux:
|
||||||
|
ID = bar
|
||||||
|
`
|
|
@ -26,7 +26,7 @@ func (c *StateShowCommand) Run(args []string) int {
|
||||||
}
|
}
|
||||||
args = cmdFlags.Args()
|
args = cmdFlags.Args()
|
||||||
|
|
||||||
state, err := c.State()
|
state, err := c.Meta.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 cli.RunResultHelp
|
||||||
|
|
|
@ -0,0 +1,22 @@
|
||||||
|
package command
|
||||||
|
|
||||||
|
import (
|
||||||
|
"path/filepath"
|
||||||
|
"sort"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
// testStateBackups returns the list of backups in order of creation
|
||||||
|
// (oldest first) in the given directory.
|
||||||
|
func testStateBackups(t *testing.T, dir string) []string {
|
||||||
|
// Find all the backups
|
||||||
|
list, err := filepath.Glob(filepath.Join(dir, "*"+DefaultBackupExtension))
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("err: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Sort them which will put them naturally in the right order
|
||||||
|
sort.Strings(list)
|
||||||
|
|
||||||
|
return list
|
||||||
|
}
|
|
@ -52,11 +52,15 @@ func (r *ResourceAddress) String() string {
|
||||||
|
|
||||||
if r.Name != "" {
|
if r.Name != "" {
|
||||||
name := r.Name
|
name := r.Name
|
||||||
switch r.InstanceType {
|
if r.InstanceTypeSet {
|
||||||
case TypeDeposed:
|
switch r.InstanceType {
|
||||||
name += ".deposed"
|
case TypePrimary:
|
||||||
case TypeTainted:
|
name += ".primary"
|
||||||
name += ".tainted"
|
case TypeDeposed:
|
||||||
|
name += ".deposed"
|
||||||
|
case TypeTainted:
|
||||||
|
name += ".tainted"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if r.Index >= 0 {
|
if r.Index >= 0 {
|
||||||
|
|
|
@ -50,7 +50,7 @@ func TestParseResourceAddress(t *testing.T) {
|
||||||
InstanceTypeSet: true,
|
InstanceTypeSet: true,
|
||||||
Index: 2,
|
Index: 2,
|
||||||
},
|
},
|
||||||
"aws_instance.foo[2]",
|
"",
|
||||||
},
|
},
|
||||||
"tainted": {
|
"tainted": {
|
||||||
"aws_instance.foo.tainted",
|
"aws_instance.foo.tainted",
|
||||||
|
|
|
@ -417,12 +417,8 @@ func (s *State) init() {
|
||||||
if s.Version == 0 {
|
if s.Version == 0 {
|
||||||
s.Version = StateVersion
|
s.Version = StateVersion
|
||||||
}
|
}
|
||||||
if len(s.Modules) == 0 {
|
if s.ModuleByPath(rootModulePath) == nil {
|
||||||
root := &ModuleState{
|
s.AddModule(rootModulePath)
|
||||||
Path: rootModulePath,
|
|
||||||
}
|
|
||||||
root.init()
|
|
||||||
s.Modules = []*ModuleState{root}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -677,11 +673,13 @@ func (m *ModuleState) deepcopy() *ModuleState {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
n := &ModuleState{
|
n := &ModuleState{
|
||||||
Path: make([]string, len(m.Path)),
|
Path: make([]string, len(m.Path)),
|
||||||
Outputs: make(map[string]interface{}, len(m.Outputs)),
|
Outputs: make(map[string]interface{}, len(m.Outputs)),
|
||||||
Resources: make(map[string]*ResourceState, len(m.Resources)),
|
Resources: make(map[string]*ResourceState, len(m.Resources)),
|
||||||
|
Dependencies: make([]string, len(m.Dependencies)),
|
||||||
}
|
}
|
||||||
copy(n.Path, m.Path)
|
copy(n.Path, m.Path)
|
||||||
|
copy(n.Dependencies, m.Dependencies)
|
||||||
for k, v := range m.Outputs {
|
for k, v := range m.Outputs {
|
||||||
n.Outputs[k] = v
|
n.Outputs[k] = v
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,315 @@
|
||||||
|
package terraform
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Add adds the item in the state at the given address.
|
||||||
|
//
|
||||||
|
// The item can be a ModuleState, ResourceState, or InstanceState. Depending
|
||||||
|
// on the item type, the address may or may not be valid. For example, a
|
||||||
|
// module cannot be moved to a resource address, however a resource can be
|
||||||
|
// moved to a module address (it retains the same name, under that resource).
|
||||||
|
//
|
||||||
|
// The full semantics of Add:
|
||||||
|
//
|
||||||
|
// ┌───────────────────────┬───────────────────────┬───────────────────────┐
|
||||||
|
// │ Module Address │ Resource Address │ Instance Address │
|
||||||
|
// ┌───────────────────────┼───────────────────────┼───────────────────────┼───────────────────────┤
|
||||||
|
// │ ModuleState │ ✓ │ x │ x │
|
||||||
|
// ├───────────────────────┼───────────────────────┼───────────────────────┼───────────────────────┤
|
||||||
|
// │ ResourceState │ ✓ │ ✓ │ maybe* │
|
||||||
|
// ├───────────────────────┼───────────────────────┼───────────────────────┼───────────────────────┤
|
||||||
|
// │ Instance State │ ✓ │ ✓ │ ✓ │
|
||||||
|
// └───────────────────────┴───────────────────────┴───────────────────────┴───────────────────────┘
|
||||||
|
//
|
||||||
|
// *maybe - Resources can be added at an instance address only if the resource
|
||||||
|
// represents a single instance (primary). Example:
|
||||||
|
// "aws_instance.foo" can be moved to "aws_instance.bar.tainted"
|
||||||
|
//
|
||||||
|
func (s *State) Add(fromAddrRaw string, toAddrRaw string, raw interface{}) error {
|
||||||
|
// Parse the address
|
||||||
|
toAddr, err := ParseResourceAddress(toAddrRaw)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Parse the from address
|
||||||
|
fromAddr, err := ParseResourceAddress(fromAddrRaw)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Determine the types
|
||||||
|
from := detectValueAddLoc(raw)
|
||||||
|
to := detectAddrAddLoc(toAddr)
|
||||||
|
|
||||||
|
// Find the function to do this
|
||||||
|
fromMap, ok := stateAddFuncs[from]
|
||||||
|
if !ok {
|
||||||
|
return fmt.Errorf("invalid source to add to state: %T", raw)
|
||||||
|
}
|
||||||
|
f, ok := fromMap[to]
|
||||||
|
if !ok {
|
||||||
|
return fmt.Errorf("invalid destination: %s (%d)", toAddr, to)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Call the migrator
|
||||||
|
if err := f(s, fromAddr, toAddr, raw); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Prune the state
|
||||||
|
s.prune()
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func stateAddFunc_Module_Module(s *State, fromAddr, addr *ResourceAddress, raw interface{}) error {
|
||||||
|
src := raw.(*ModuleState).deepcopy()
|
||||||
|
|
||||||
|
// If the target module exists, it is an error
|
||||||
|
path := append([]string{"root"}, addr.Path...)
|
||||||
|
if s.ModuleByPath(path) != nil {
|
||||||
|
return fmt.Errorf("module target is not empty: %s", addr)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create it and copy our outputs and dependencies
|
||||||
|
mod := s.AddModule(path)
|
||||||
|
mod.Outputs = src.Outputs
|
||||||
|
mod.Dependencies = src.Dependencies
|
||||||
|
|
||||||
|
// Go through the resources perform an add for each of those
|
||||||
|
for k, v := range src.Resources {
|
||||||
|
resourceKey, err := ParseResourceStateKey(k)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update the resource address for this
|
||||||
|
addrCopy := *addr
|
||||||
|
addrCopy.Type = resourceKey.Type
|
||||||
|
addrCopy.Name = resourceKey.Name
|
||||||
|
addrCopy.Index = resourceKey.Index
|
||||||
|
|
||||||
|
// Perform an add
|
||||||
|
if err := s.Add(fromAddr.String(), addrCopy.String(), v); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func stateAddFunc_Resource_Module(
|
||||||
|
s *State, from, to *ResourceAddress, raw interface{}) error {
|
||||||
|
// Build the more specific to addr
|
||||||
|
addr := *to
|
||||||
|
addr.Type = from.Type
|
||||||
|
addr.Name = from.Name
|
||||||
|
|
||||||
|
return s.Add(from.String(), addr.String(), raw)
|
||||||
|
}
|
||||||
|
|
||||||
|
func stateAddFunc_Resource_Resource(s *State, fromAddr, addr *ResourceAddress, raw interface{}) error {
|
||||||
|
src := raw.(*ResourceState).deepcopy()
|
||||||
|
|
||||||
|
// Initialize the resource
|
||||||
|
resourceRaw, exists := stateAddInitAddr(s, addr)
|
||||||
|
if exists {
|
||||||
|
return fmt.Errorf("resource exists and not empty: %s", addr)
|
||||||
|
}
|
||||||
|
resource := resourceRaw.(*ResourceState)
|
||||||
|
resource.Type = src.Type
|
||||||
|
resource.Dependencies = src.Dependencies
|
||||||
|
resource.Provider = src.Provider
|
||||||
|
|
||||||
|
// Move the primary
|
||||||
|
if src.Primary != nil {
|
||||||
|
addrCopy := *addr
|
||||||
|
addrCopy.InstanceType = TypePrimary
|
||||||
|
addrCopy.InstanceTypeSet = true
|
||||||
|
if err := s.Add(fromAddr.String(), addrCopy.String(), src.Primary); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Move all tainted
|
||||||
|
if len(src.Tainted) > 0 {
|
||||||
|
resource.Tainted = src.Tainted
|
||||||
|
}
|
||||||
|
|
||||||
|
// Move all deposed
|
||||||
|
if len(src.Deposed) > 0 {
|
||||||
|
resource.Deposed = src.Deposed
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func stateAddFunc_Instance_Instance(s *State, fromAddr, addr *ResourceAddress, raw interface{}) error {
|
||||||
|
src := raw.(*InstanceState).deepcopy()
|
||||||
|
|
||||||
|
// Create the instance
|
||||||
|
instanceRaw, _ := stateAddInitAddr(s, addr)
|
||||||
|
instance := instanceRaw.(*InstanceState)
|
||||||
|
|
||||||
|
// Set it
|
||||||
|
*instance = *src
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func stateAddFunc_Instance_Module(
|
||||||
|
s *State, from, to *ResourceAddress, raw interface{}) error {
|
||||||
|
addr := *to
|
||||||
|
addr.Type = from.Type
|
||||||
|
addr.Name = from.Name
|
||||||
|
|
||||||
|
return s.Add(from.String(), addr.String(), raw)
|
||||||
|
}
|
||||||
|
|
||||||
|
func stateAddFunc_Instance_Resource(
|
||||||
|
s *State, from, to *ResourceAddress, raw interface{}) error {
|
||||||
|
addr := *to
|
||||||
|
addr.InstanceType = TypePrimary
|
||||||
|
addr.InstanceTypeSet = true
|
||||||
|
|
||||||
|
return s.Add(from.String(), addr.String(), raw)
|
||||||
|
}
|
||||||
|
|
||||||
|
// stateAddFunc is the type of function for adding an item to a state
|
||||||
|
type stateAddFunc func(s *State, from, to *ResourceAddress, item interface{}) error
|
||||||
|
|
||||||
|
// stateAddFuncs has the full matrix mapping of the state adders.
|
||||||
|
var stateAddFuncs map[stateAddLoc]map[stateAddLoc]stateAddFunc
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
stateAddFuncs = map[stateAddLoc]map[stateAddLoc]stateAddFunc{
|
||||||
|
stateAddModule: {
|
||||||
|
stateAddModule: stateAddFunc_Module_Module,
|
||||||
|
},
|
||||||
|
stateAddResource: {
|
||||||
|
stateAddModule: stateAddFunc_Resource_Module,
|
||||||
|
stateAddResource: stateAddFunc_Resource_Resource,
|
||||||
|
},
|
||||||
|
stateAddInstance: {
|
||||||
|
stateAddInstance: stateAddFunc_Instance_Instance,
|
||||||
|
stateAddModule: stateAddFunc_Instance_Module,
|
||||||
|
stateAddResource: stateAddFunc_Instance_Resource,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// stateAddLoc is an enum to represent the location where state is being
|
||||||
|
// moved from/to. We use this for quick lookups in a function map.
|
||||||
|
type stateAddLoc uint
|
||||||
|
|
||||||
|
const (
|
||||||
|
stateAddInvalid stateAddLoc = iota
|
||||||
|
stateAddModule
|
||||||
|
stateAddResource
|
||||||
|
stateAddInstance
|
||||||
|
)
|
||||||
|
|
||||||
|
// detectAddrAddLoc detects the state type for the given address. This
|
||||||
|
// function is specifically not unit tested since we consider the State.Add
|
||||||
|
// functionality to be comprehensive enough to cover this.
|
||||||
|
func detectAddrAddLoc(addr *ResourceAddress) stateAddLoc {
|
||||||
|
if addr.Name == "" {
|
||||||
|
return stateAddModule
|
||||||
|
}
|
||||||
|
|
||||||
|
if !addr.InstanceTypeSet {
|
||||||
|
return stateAddResource
|
||||||
|
}
|
||||||
|
|
||||||
|
return stateAddInstance
|
||||||
|
}
|
||||||
|
|
||||||
|
// detectValueAddLoc determines the stateAddLoc value from the raw value
|
||||||
|
// that is some State structure.
|
||||||
|
func detectValueAddLoc(raw interface{}) stateAddLoc {
|
||||||
|
switch raw.(type) {
|
||||||
|
case *ModuleState:
|
||||||
|
return stateAddModule
|
||||||
|
case *ResourceState:
|
||||||
|
return stateAddResource
|
||||||
|
case *InstanceState:
|
||||||
|
return stateAddInstance
|
||||||
|
default:
|
||||||
|
return stateAddInvalid
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// stateAddInitAddr takes a ResourceAddress and creates the non-existing
|
||||||
|
// resources up to that point, returning the empty (or existing) interface
|
||||||
|
// at that address.
|
||||||
|
func stateAddInitAddr(s *State, addr *ResourceAddress) (interface{}, bool) {
|
||||||
|
addType := detectAddrAddLoc(addr)
|
||||||
|
|
||||||
|
// Get the module
|
||||||
|
path := append([]string{"root"}, addr.Path...)
|
||||||
|
exists := true
|
||||||
|
mod := s.ModuleByPath(path)
|
||||||
|
if mod == nil {
|
||||||
|
mod = s.AddModule(path)
|
||||||
|
exists = false
|
||||||
|
}
|
||||||
|
if addType == stateAddModule {
|
||||||
|
return mod, exists
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add the resource
|
||||||
|
resourceKey := (&ResourceStateKey{
|
||||||
|
Name: addr.Name,
|
||||||
|
Type: addr.Type,
|
||||||
|
Index: addr.Index,
|
||||||
|
}).String()
|
||||||
|
exists = true
|
||||||
|
resource, ok := mod.Resources[resourceKey]
|
||||||
|
if !ok {
|
||||||
|
resource = &ResourceState{Type: addr.Type}
|
||||||
|
resource.init()
|
||||||
|
mod.Resources[resourceKey] = resource
|
||||||
|
exists = false
|
||||||
|
}
|
||||||
|
if addType == stateAddResource {
|
||||||
|
return resource, exists
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get the instance
|
||||||
|
exists = true
|
||||||
|
instance := &InstanceState{}
|
||||||
|
switch addr.InstanceType {
|
||||||
|
case TypePrimary:
|
||||||
|
if v := resource.Primary; v != nil {
|
||||||
|
instance = resource.Primary
|
||||||
|
} else {
|
||||||
|
exists = false
|
||||||
|
}
|
||||||
|
case TypeTainted:
|
||||||
|
idx := addr.Index
|
||||||
|
if addr.Index < 0 {
|
||||||
|
idx = 0
|
||||||
|
}
|
||||||
|
if len(resource.Tainted) > idx {
|
||||||
|
instance = resource.Tainted[idx]
|
||||||
|
} else {
|
||||||
|
resource.Tainted = append(resource.Tainted, instance)
|
||||||
|
exists = false
|
||||||
|
}
|
||||||
|
case TypeDeposed:
|
||||||
|
idx := addr.Index
|
||||||
|
if addr.Index < 0 {
|
||||||
|
idx = 0
|
||||||
|
}
|
||||||
|
if len(resource.Deposed) > idx {
|
||||||
|
instance = resource.Deposed[idx]
|
||||||
|
} else {
|
||||||
|
resource.Deposed = append(resource.Deposed, instance)
|
||||||
|
exists = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return instance, exists
|
||||||
|
}
|
|
@ -0,0 +1,422 @@
|
||||||
|
package terraform
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestStateAdd(t *testing.T) {
|
||||||
|
cases := map[string]struct {
|
||||||
|
Err bool
|
||||||
|
From, To string
|
||||||
|
Value interface{}
|
||||||
|
One, Two *State
|
||||||
|
}{
|
||||||
|
"ModuleState => Module Addr (new)": {
|
||||||
|
false,
|
||||||
|
"",
|
||||||
|
"module.foo",
|
||||||
|
&ModuleState{
|
||||||
|
Path: rootModulePath,
|
||||||
|
Resources: map[string]*ResourceState{
|
||||||
|
"test_instance.foo": &ResourceState{
|
||||||
|
Type: "test_instance",
|
||||||
|
Primary: &InstanceState{
|
||||||
|
ID: "foo",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
"test_instance.bar": &ResourceState{
|
||||||
|
Type: "test_instance",
|
||||||
|
Primary: &InstanceState{
|
||||||
|
ID: "foo",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
&State{},
|
||||||
|
&State{
|
||||||
|
Modules: []*ModuleState{
|
||||||
|
&ModuleState{
|
||||||
|
Path: []string{"root", "foo"},
|
||||||
|
Resources: map[string]*ResourceState{
|
||||||
|
"test_instance.foo": &ResourceState{
|
||||||
|
Type: "test_instance",
|
||||||
|
Primary: &InstanceState{
|
||||||
|
ID: "foo",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
"test_instance.bar": &ResourceState{
|
||||||
|
Type: "test_instance",
|
||||||
|
Primary: &InstanceState{
|
||||||
|
ID: "foo",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
"ModuleState => Nested Module Addr (new)": {
|
||||||
|
false,
|
||||||
|
"",
|
||||||
|
"module.foo.module.bar",
|
||||||
|
&ModuleState{
|
||||||
|
Path: rootModulePath,
|
||||||
|
Resources: map[string]*ResourceState{
|
||||||
|
"test_instance.foo": &ResourceState{
|
||||||
|
Type: "test_instance",
|
||||||
|
Primary: &InstanceState{
|
||||||
|
ID: "foo",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
"test_instance.bar": &ResourceState{
|
||||||
|
Type: "test_instance",
|
||||||
|
Primary: &InstanceState{
|
||||||
|
ID: "foo",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
&State{},
|
||||||
|
&State{
|
||||||
|
Modules: []*ModuleState{
|
||||||
|
&ModuleState{
|
||||||
|
Path: []string{"root", "foo", "bar"},
|
||||||
|
Resources: map[string]*ResourceState{
|
||||||
|
"test_instance.foo": &ResourceState{
|
||||||
|
Type: "test_instance",
|
||||||
|
Primary: &InstanceState{
|
||||||
|
ID: "foo",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
"test_instance.bar": &ResourceState{
|
||||||
|
Type: "test_instance",
|
||||||
|
Primary: &InstanceState{
|
||||||
|
ID: "foo",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
"ModuleState w/ outputs and deps => Module Addr (new)": {
|
||||||
|
false,
|
||||||
|
"",
|
||||||
|
"module.foo",
|
||||||
|
&ModuleState{
|
||||||
|
Path: rootModulePath,
|
||||||
|
Outputs: map[string]interface{}{
|
||||||
|
"foo": "bar",
|
||||||
|
},
|
||||||
|
Dependencies: []string{"foo"},
|
||||||
|
Resources: map[string]*ResourceState{
|
||||||
|
"test_instance.foo": &ResourceState{
|
||||||
|
Type: "test_instance",
|
||||||
|
Primary: &InstanceState{
|
||||||
|
ID: "foo",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
"test_instance.bar": &ResourceState{
|
||||||
|
Type: "test_instance",
|
||||||
|
Primary: &InstanceState{
|
||||||
|
ID: "foo",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
&State{},
|
||||||
|
&State{
|
||||||
|
Modules: []*ModuleState{
|
||||||
|
&ModuleState{
|
||||||
|
Path: []string{"root", "foo"},
|
||||||
|
Outputs: map[string]interface{}{
|
||||||
|
"foo": "bar",
|
||||||
|
},
|
||||||
|
Dependencies: []string{"foo"},
|
||||||
|
Resources: map[string]*ResourceState{
|
||||||
|
"test_instance.foo": &ResourceState{
|
||||||
|
Type: "test_instance",
|
||||||
|
Primary: &InstanceState{
|
||||||
|
ID: "foo",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
"test_instance.bar": &ResourceState{
|
||||||
|
Type: "test_instance",
|
||||||
|
Primary: &InstanceState{
|
||||||
|
ID: "foo",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
"ModuleState => Module Addr (existing)": {
|
||||||
|
true,
|
||||||
|
"",
|
||||||
|
"module.foo",
|
||||||
|
&ModuleState{},
|
||||||
|
&State{
|
||||||
|
Modules: []*ModuleState{
|
||||||
|
&ModuleState{
|
||||||
|
Path: []string{"root", "foo"},
|
||||||
|
Resources: map[string]*ResourceState{
|
||||||
|
"test_instance.baz": &ResourceState{
|
||||||
|
Type: "test_instance",
|
||||||
|
Primary: &InstanceState{
|
||||||
|
ID: "foo",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
nil,
|
||||||
|
},
|
||||||
|
|
||||||
|
"ResourceState => Resource Addr (new)": {
|
||||||
|
false,
|
||||||
|
"aws_instance.bar",
|
||||||
|
"aws_instance.foo",
|
||||||
|
&ResourceState{
|
||||||
|
Type: "test_instance",
|
||||||
|
Primary: &InstanceState{
|
||||||
|
ID: "foo",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
&State{},
|
||||||
|
&State{
|
||||||
|
Modules: []*ModuleState{
|
||||||
|
&ModuleState{
|
||||||
|
Path: []string{"root"},
|
||||||
|
Resources: map[string]*ResourceState{
|
||||||
|
"aws_instance.foo": &ResourceState{
|
||||||
|
Type: "test_instance",
|
||||||
|
Primary: &InstanceState{
|
||||||
|
ID: "foo",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
"ResourceState w/ deps, provider => Resource Addr (new)": {
|
||||||
|
false,
|
||||||
|
"aws_instance.bar",
|
||||||
|
"aws_instance.foo",
|
||||||
|
&ResourceState{
|
||||||
|
Type: "test_instance",
|
||||||
|
Provider: "foo",
|
||||||
|
Dependencies: []string{"bar"},
|
||||||
|
Primary: &InstanceState{
|
||||||
|
ID: "foo",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
&State{},
|
||||||
|
&State{
|
||||||
|
Modules: []*ModuleState{
|
||||||
|
&ModuleState{
|
||||||
|
Path: []string{"root"},
|
||||||
|
Resources: map[string]*ResourceState{
|
||||||
|
"aws_instance.foo": &ResourceState{
|
||||||
|
Type: "test_instance",
|
||||||
|
Provider: "foo",
|
||||||
|
Dependencies: []string{"bar"},
|
||||||
|
Primary: &InstanceState{
|
||||||
|
ID: "foo",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
"ResourceState w/ tainted => Resource Addr (new)": {
|
||||||
|
false,
|
||||||
|
"aws_instance.bar",
|
||||||
|
"aws_instance.foo",
|
||||||
|
&ResourceState{
|
||||||
|
Type: "test_instance",
|
||||||
|
Tainted: []*InstanceState{
|
||||||
|
&InstanceState{
|
||||||
|
ID: "foo",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
&State{},
|
||||||
|
&State{
|
||||||
|
Modules: []*ModuleState{
|
||||||
|
&ModuleState{
|
||||||
|
Path: []string{"root"},
|
||||||
|
Resources: map[string]*ResourceState{
|
||||||
|
"aws_instance.foo": &ResourceState{
|
||||||
|
Type: "test_instance",
|
||||||
|
Primary: &InstanceState{},
|
||||||
|
Tainted: []*InstanceState{
|
||||||
|
&InstanceState{
|
||||||
|
ID: "foo",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
"ResourceState => Resource Addr (existing)": {
|
||||||
|
true,
|
||||||
|
"aws_instance.bar",
|
||||||
|
"aws_instance.foo",
|
||||||
|
&ResourceState{
|
||||||
|
Type: "test_instance",
|
||||||
|
Primary: &InstanceState{
|
||||||
|
ID: "foo",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
&State{
|
||||||
|
Modules: []*ModuleState{
|
||||||
|
&ModuleState{
|
||||||
|
Path: []string{"root"},
|
||||||
|
Resources: map[string]*ResourceState{
|
||||||
|
"aws_instance.foo": &ResourceState{
|
||||||
|
Type: "test_instance",
|
||||||
|
Primary: &InstanceState{
|
||||||
|
ID: "foo",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
nil,
|
||||||
|
},
|
||||||
|
|
||||||
|
"ResourceState => Module (new)": {
|
||||||
|
false,
|
||||||
|
"aws_instance.bar",
|
||||||
|
"module.foo",
|
||||||
|
&ResourceState{
|
||||||
|
Type: "test_instance",
|
||||||
|
Primary: &InstanceState{
|
||||||
|
ID: "foo",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
&State{},
|
||||||
|
&State{
|
||||||
|
Modules: []*ModuleState{
|
||||||
|
&ModuleState{
|
||||||
|
Path: []string{"root", "foo"},
|
||||||
|
Resources: map[string]*ResourceState{
|
||||||
|
"aws_instance.bar": &ResourceState{
|
||||||
|
Type: "test_instance",
|
||||||
|
Primary: &InstanceState{
|
||||||
|
ID: "foo",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
"InstanceState => Resource (new)": {
|
||||||
|
false,
|
||||||
|
"aws_instance.bar.primary",
|
||||||
|
"aws_instance.baz",
|
||||||
|
&InstanceState{
|
||||||
|
ID: "foo",
|
||||||
|
},
|
||||||
|
|
||||||
|
&State{},
|
||||||
|
&State{
|
||||||
|
Modules: []*ModuleState{
|
||||||
|
&ModuleState{
|
||||||
|
Path: []string{"root"},
|
||||||
|
Resources: map[string]*ResourceState{
|
||||||
|
"aws_instance.baz": &ResourceState{
|
||||||
|
Type: "aws_instance",
|
||||||
|
Primary: &InstanceState{
|
||||||
|
ID: "foo",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
"InstanceState => Module (new)": {
|
||||||
|
false,
|
||||||
|
"aws_instance.bar.primary",
|
||||||
|
"module.foo",
|
||||||
|
&InstanceState{
|
||||||
|
ID: "foo",
|
||||||
|
},
|
||||||
|
|
||||||
|
&State{},
|
||||||
|
&State{
|
||||||
|
Modules: []*ModuleState{
|
||||||
|
&ModuleState{
|
||||||
|
Path: []string{"root", "foo"},
|
||||||
|
Resources: map[string]*ResourceState{
|
||||||
|
"aws_instance.bar": &ResourceState{
|
||||||
|
Type: "aws_instance",
|
||||||
|
Primary: &InstanceState{
|
||||||
|
ID: "foo",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for k, tc := range cases {
|
||||||
|
// Make sure they're both initialized as normal
|
||||||
|
tc.One.init()
|
||||||
|
if tc.Two != nil {
|
||||||
|
tc.Two.init()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add the value
|
||||||
|
err := tc.One.Add(tc.From, tc.To, tc.Value)
|
||||||
|
if (err != nil) != tc.Err {
|
||||||
|
t.Fatalf("bad: %s\n\n%s", k, err)
|
||||||
|
}
|
||||||
|
if tc.Err {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
// Prune them both to be sure
|
||||||
|
tc.One.prune()
|
||||||
|
tc.Two.prune()
|
||||||
|
|
||||||
|
// Verify equality
|
||||||
|
if !tc.One.Equal(tc.Two) {
|
||||||
|
t.Fatalf("Bad: %s\n\n%#v\n\n%#v", k, tc.One, tc.Two)
|
||||||
|
//t.Fatalf("Bad: %s\n\n%s\n\n%s", k, tc.One.String(), tc.Two.String())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -124,7 +124,7 @@ func (f *StateFilter) filterSingle(a *ResourceAddress) []*StateFilterResult {
|
||||||
// Add the instances
|
// Add the instances
|
||||||
if r.Primary != nil {
|
if r.Primary != nil {
|
||||||
addr.InstanceType = TypePrimary
|
addr.InstanceType = TypePrimary
|
||||||
addr.InstanceTypeSet = true
|
addr.InstanceTypeSet = false
|
||||||
results = append(results, &StateFilterResult{
|
results = append(results, &StateFilterResult{
|
||||||
Path: addr.Path,
|
Path: addr.Path,
|
||||||
Address: addr.String(),
|
Address: addr.String(),
|
||||||
|
|
|
@ -0,0 +1,89 @@
|
||||||
|
---
|
||||||
|
layout: "commands-state"
|
||||||
|
page_title: "Command: state mv"
|
||||||
|
sidebar_current: "docs-state-sub-mv"
|
||||||
|
description: |-
|
||||||
|
The `terraform state rm` command removes items from the Terraform state.
|
||||||
|
---
|
||||||
|
|
||||||
|
# Command: state mv
|
||||||
|
|
||||||
|
The `terraform state mv` command is used to move items in a
|
||||||
|
[Terraform state](/docs/state/index.html). This command can move
|
||||||
|
single resources, single instances of a resource, entire modules, and more.
|
||||||
|
This command can also move items to a completely different state file,
|
||||||
|
enabling efficient refactoring.
|
||||||
|
|
||||||
|
## Usage
|
||||||
|
|
||||||
|
Usage: `terraform state mv [options] SOURCE DESTINATION`
|
||||||
|
|
||||||
|
This command will move an item matched by the address given to the
|
||||||
|
destination address. This command can also move to a destination address
|
||||||
|
in a completely different state file.
|
||||||
|
|
||||||
|
This can be used for simple resource renaming, moving items to and from
|
||||||
|
a module, moving entire modules, and more. And because this command can also
|
||||||
|
move data to a completely new state, it can also be used for refactoring
|
||||||
|
one configuration into multiple separately managed Terraform configurations.
|
||||||
|
|
||||||
|
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.
|
||||||
|
|
||||||
|
If you're moving an item to a different state file, a backup will be created
|
||||||
|
for each state file.
|
||||||
|
|
||||||
|
This command requires a source and destination address of the item to move.
|
||||||
|
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.
|
||||||
|
|
||||||
|
* `-backup-out=path` - Path to the backup file for the output state.
|
||||||
|
This is only necessary if `-state-out` is specified.
|
||||||
|
|
||||||
|
* `-state=path` - Path to the state file. Defaults to "terraform.tfstate".
|
||||||
|
|
||||||
|
* `-state-out=path` - Path to the state file to write to. If this isn't specified
|
||||||
|
the state specified by `-state` will be used. This can be
|
||||||
|
a new or existing path.
|
||||||
|
|
||||||
|
## Example: Rename a Resource
|
||||||
|
|
||||||
|
The example below renames a single resource:
|
||||||
|
|
||||||
|
```
|
||||||
|
$ terraform state mv aws_instance.foo aws_instance.bar
|
||||||
|
```
|
||||||
|
|
||||||
|
## Example: Move a Resource Into a Module
|
||||||
|
|
||||||
|
The example below moves a resource into a module. The module will be
|
||||||
|
created if it doesn't exist.
|
||||||
|
|
||||||
|
```
|
||||||
|
$ terraform state mv aws_instance.foo module.web
|
||||||
|
```
|
||||||
|
|
||||||
|
## Example: Move a Module Into a Module
|
||||||
|
|
||||||
|
The example below moves a module into another module.
|
||||||
|
|
||||||
|
```
|
||||||
|
$ terraform state mv module.foo module.parent.module.foo
|
||||||
|
```
|
||||||
|
|
||||||
|
## Example: Move a Module to Another State
|
||||||
|
|
||||||
|
The example below moves a module into another state file. This removes
|
||||||
|
the module from the original state file and adds it to the destination.
|
||||||
|
The source and destination are the same meaning we're keeping the same name.
|
||||||
|
|
||||||
|
```
|
||||||
|
$ terraform state mv -state-out=other.tfstate \
|
||||||
|
module.web module.web
|
||||||
|
```
|
|
@ -21,6 +21,14 @@
|
||||||
<a href="/docs/commands/state/list.html">list</a>
|
<a href="/docs/commands/state/list.html">list</a>
|
||||||
</li>
|
</li>
|
||||||
|
|
||||||
|
<li<%= sidebar_current("docs-state-sub-mv") %>>
|
||||||
|
<a href="/docs/commands/state/mv.html">mv</a>
|
||||||
|
</li>
|
||||||
|
|
||||||
|
<li<%= sidebar_current("docs-state-sub-rm") %>>
|
||||||
|
<a href="/docs/commands/state/rm.html">rm</a>
|
||||||
|
</li>
|
||||||
|
|
||||||
<li<%= sidebar_current("docs-state-sub-show") %>>
|
<li<%= sidebar_current("docs-state-sub-show") %>>
|
||||||
<a href="/docs/commands/state/show.html">show</a>
|
<a href="/docs/commands/state/show.html">show</a>
|
||||||
</li>
|
</li>
|
||||||
|
|
Loading…
Reference in New Issue