commit
5e127a62c1
|
@ -224,21 +224,7 @@ func testProvider() *terraform.MockResourceProvider {
|
|||
}
|
||||
|
||||
func testTempFile(t *testing.T) string {
|
||||
tf, err := ioutil.TempFile("", "tf")
|
||||
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
|
||||
return filepath.Join(testTempDir(t), "state.tfstate")
|
||||
}
|
||||
|
||||
func testTempDir(t *testing.T) string {
|
||||
|
|
|
@ -108,6 +108,9 @@ func TestPlan_destroy(t *testing.T) {
|
|||
}
|
||||
|
||||
func TestPlan_noState(t *testing.T) {
|
||||
tmp, cwd := testCwd(t)
|
||||
defer testFixCwd(t, tmp, cwd)
|
||||
|
||||
p := testProvider()
|
||||
ui := new(cli.MockUi)
|
||||
c := &PlanCommand{
|
||||
|
|
|
@ -2,13 +2,46 @@ package command
|
|||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"github.com/hashicorp/terraform/state"
|
||||
"github.com/hashicorp/terraform/terraform"
|
||||
)
|
||||
|
||||
// StateMeta is the meta struct that should be embedded in state subcommands.
|
||||
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.
|
||||
func (c *StateMeta) filterInstance(rs []*terraform.StateFilterResult) (*terraform.StateFilterResult, error) {
|
||||
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()
|
||||
|
||||
state, err := c.State()
|
||||
state, err := c.Meta.State()
|
||||
if err != nil {
|
||||
c.Ui.Error(fmt.Sprintf(errStateLoadingState, err))
|
||||
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 != "" {
|
||||
name := r.Name
|
||||
switch r.InstanceType {
|
||||
case TypeDeposed:
|
||||
name += ".deposed"
|
||||
case TypeTainted:
|
||||
name += ".tainted"
|
||||
if r.InstanceTypeSet {
|
||||
switch r.InstanceType {
|
||||
case TypePrimary:
|
||||
name += ".primary"
|
||||
case TypeDeposed:
|
||||
name += ".deposed"
|
||||
case TypeTainted:
|
||||
name += ".tainted"
|
||||
}
|
||||
}
|
||||
|
||||
if r.Index >= 0 {
|
||||
|
|
|
@ -50,7 +50,7 @@ func TestParseResourceAddress(t *testing.T) {
|
|||
InstanceTypeSet: true,
|
||||
Index: 2,
|
||||
},
|
||||
"aws_instance.foo[2]",
|
||||
"",
|
||||
},
|
||||
"tainted": {
|
||||
"aws_instance.foo.tainted",
|
||||
|
|
|
@ -417,12 +417,8 @@ func (s *State) init() {
|
|||
if s.Version == 0 {
|
||||
s.Version = StateVersion
|
||||
}
|
||||
if len(s.Modules) == 0 {
|
||||
root := &ModuleState{
|
||||
Path: rootModulePath,
|
||||
}
|
||||
root.init()
|
||||
s.Modules = []*ModuleState{root}
|
||||
if s.ModuleByPath(rootModulePath) == nil {
|
||||
s.AddModule(rootModulePath)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -677,11 +673,13 @@ func (m *ModuleState) deepcopy() *ModuleState {
|
|||
return nil
|
||||
}
|
||||
n := &ModuleState{
|
||||
Path: make([]string, len(m.Path)),
|
||||
Outputs: make(map[string]interface{}, len(m.Outputs)),
|
||||
Resources: make(map[string]*ResourceState, len(m.Resources)),
|
||||
Path: make([]string, len(m.Path)),
|
||||
Outputs: make(map[string]interface{}, len(m.Outputs)),
|
||||
Resources: make(map[string]*ResourceState, len(m.Resources)),
|
||||
Dependencies: make([]string, len(m.Dependencies)),
|
||||
}
|
||||
copy(n.Path, m.Path)
|
||||
copy(n.Dependencies, m.Dependencies)
|
||||
for k, v := range m.Outputs {
|
||||
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
|
||||
if r.Primary != nil {
|
||||
addr.InstanceType = TypePrimary
|
||||
addr.InstanceTypeSet = true
|
||||
addr.InstanceTypeSet = false
|
||||
results = append(results, &StateFilterResult{
|
||||
Path: addr.Path,
|
||||
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>
|
||||
</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") %>>
|
||||
<a href="/docs/commands/state/show.html">show</a>
|
||||
</li>
|
||||
|
|
Loading…
Reference in New Issue