command/state: update and fix the state mv command

This commit is contained in:
Sander van Harmelen 2018-10-25 15:41:00 +02:00
parent b54cc1d95f
commit 7ec3f96e3a
9 changed files with 282 additions and 1202 deletions

View File

@ -2,6 +2,7 @@ package command
import ( import (
"fmt" "fmt"
"sort"
"time" "time"
"github.com/hashicorp/terraform/addrs" "github.com/hashicorp/terraform/addrs"
@ -111,6 +112,25 @@ func (c *StateMeta) filter(state *states.State, args []string) ([]*states.Filter
} }
} }
// Sort the results
sort.Slice(results, func(i, j int) bool {
a, b := results[i], results[j]
// If the length is different, sort on the length so that the
// best match is the first result.
if len(a.Address.String()) != len(b.Address.String()) {
return len(a.Address.String()) < len(b.Address.String())
}
// If the addresses are different it is just lexographic sorting
if a.Address.String() != b.Address.String() {
return a.Address.String() < b.Address.String()
}
// Addresses are the same, which means it matters on the type
return a.SortedType() < b.SortedType()
})
return results, nil return results, nil
} }

View File

@ -4,6 +4,7 @@ import (
"fmt" "fmt"
"strings" "strings"
"github.com/hashicorp/terraform/addrs"
"github.com/hashicorp/terraform/states" "github.com/hashicorp/terraform/states"
"github.com/mitchellh/cli" "github.com/mitchellh/cli"
) )
@ -22,7 +23,9 @@ func (c *StateMvCommand) Run(args []string) int {
// We create two metas to track the two states // We create two metas to track the two states
var backupPathOut, statePathOut string var backupPathOut, statePathOut string
var dryRun bool
cmdFlags := c.Meta.flagSet("state mv") cmdFlags := c.Meta.flagSet("state mv")
cmdFlags.BoolVar(&dryRun, "dry-run", false, "dry run")
cmdFlags.StringVar(&c.backupPath, "backup", "-", "backup") cmdFlags.StringVar(&c.backupPath, "backup", "-", "backup")
cmdFlags.StringVar(&c.statePath, "state", "", "path") cmdFlags.StringVar(&c.statePath, "state", "", "path")
cmdFlags.StringVar(&backupPathOut, "backup-out", "-", "backup") cmdFlags.StringVar(&backupPathOut, "backup-out", "-", "backup")
@ -37,127 +40,228 @@ func (c *StateMvCommand) Run(args []string) int {
} }
// Read the from state // Read the from state
stateFrom, err := c.State() stateFromMgr, err := c.State()
if err != nil { if err != nil {
c.Ui.Error(fmt.Sprintf(errStateLoadingState, err)) c.Ui.Error(fmt.Sprintf(errStateLoadingState, err))
return 1 return 1
} }
if err := stateFromMgr.RefreshState(); err != nil {
if err := stateFrom.RefreshState(); err != nil { c.Ui.Error(fmt.Sprintf("Failed to refresh state: %s", err))
c.Ui.Error(fmt.Sprintf("Failed to load state: %s", err))
return 1 return 1
} }
stateFromReal := stateFrom.State() stateFrom := stateFromMgr.State()
if stateFromReal == nil { if stateFrom == nil {
c.Ui.Error(fmt.Sprintf(errStateNotFound)) c.Ui.Error(fmt.Sprintf(errStateNotFound))
return 1 return 1
} }
// Read the destination state // Read the destination state
stateToMgr := stateFromMgr
stateTo := stateFrom stateTo := stateFrom
stateToReal := stateFromReal
if statePathOut != "" { if statePathOut != "" {
c.statePath = statePathOut c.statePath = statePathOut
c.backupPath = backupPathOut c.backupPath = backupPathOut
stateTo, err = c.State()
stateToMgr, err = c.State()
if err != nil { if err != nil {
c.Ui.Error(fmt.Sprintf(errStateLoadingState, err)) c.Ui.Error(fmt.Sprintf(errStateLoadingState, err))
return 1 return 1
} }
if err := stateToMgr.RefreshState(); err != nil {
if err := stateTo.RefreshState(); err != nil { c.Ui.Error(fmt.Sprintf("Failed to refresh state: %s", err))
c.Ui.Error(fmt.Sprintf("Failed to load state: %s", err))
return 1 return 1
} }
stateToReal = stateTo.State() stateTo = stateToMgr.State()
if stateToReal == nil { if stateTo == nil {
stateToReal = states.NewState() stateTo = states.NewState()
} }
} }
c.Ui.Error("state mv command not yet updated for new state types") // Filter what we are moving.
return 1 results, err := c.filter(stateFrom, []string{args[0]})
/* if err != nil {
// Filter what we're moving c.Ui.Error(fmt.Sprintf(errStateFilter, err))
filter := &terraform.StateFilter{State: stateFromReal} return cli.RunResultHelp
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
}
// Get the item to add to the state // If we have no results, exit early as we're not going to do anything.
add := c.addableResult(results) if len(results) == 0 {
if dryRun {
// Do the actual move c.Ui.Output("Would have moved nothing.")
if err := stateFromReal.Remove(args[0]); err != nil { } else {
c.Ui.Error(fmt.Sprintf(errStateMv, err)) c.Ui.Output("No matching objects found.")
return 1
} }
return 0
}
if err := stateToReal.Add(args[0], args[1], add); err != nil { prefix := "Move"
c.Ui.Error(fmt.Sprintf(errStateMv, err)) if dryRun {
return 1 prefix = "Would move"
} }
// Write the new state var moved int
if err := stateTo.WriteState(stateToReal); err != nil { ssFrom := stateFrom.SyncWrapper()
c.Ui.Error(fmt.Sprintf(errStateMvPersist, err)) for _, result := range c.moveableResult(results) {
return 1 switch addrFrom := result.Address.(type) {
} case addrs.ModuleInstance:
search, err := addrs.ParseModuleInstanceStr(args[0])
if err := stateTo.PersistState(); err != nil { if err != nil {
c.Ui.Error(fmt.Sprintf(errStateMvPersist, err)) c.Ui.Error(fmt.Sprintf(errStateMv, err))
return 1 return 1
} }
addrTo, err := addrs.ParseModuleInstanceStr(args[1])
// Write the old state if it is different if err != nil {
if stateTo != stateFrom { c.Ui.Error(fmt.Sprintf(errStateMv, err))
if err := stateFrom.WriteState(stateFromReal); err != nil {
c.Ui.Error(fmt.Sprintf(errStateMvPersist, err))
return 1 return 1
} }
if err := stateFrom.PersistState(); err != nil { if len(search) < len(addrFrom) {
c.Ui.Error(fmt.Sprintf(errStateMvPersist, err)) addrTo = append(addrTo, addrFrom[len(search):]...)
}
if stateTo.Module(addrTo) != nil {
c.Ui.Error(fmt.Sprintf(errStateMv, "destination module already exists"))
return 1 return 1
} }
}
*/
c.Ui.Output(fmt.Sprintf( moved++
"Moved %s to %s", args[0], args[1])) c.Ui.Output(fmt.Sprintf("%s %q to %q", prefix, addrFrom.String(), addrTo.String()))
if !dryRun {
ssFrom.RemoveModule(addrFrom)
// Update the address before adding it to the state.
m := result.Value.(*states.Module)
m.Addr = addrTo
stateTo.Modules[addrTo.String()] = m
}
case addrs.AbsResource:
addrTo, err := addrs.ParseAbsResourceStr(args[1])
if err != nil {
c.Ui.Error(fmt.Sprintf(errStateMv, err))
return 1
}
if addrFrom.Resource.Type != addrTo.Resource.Type {
c.Ui.Error(fmt.Sprintf(
errStateMv, "resource types do not match"))
return 1
}
if stateTo.Module(addrTo.Module) == nil {
c.Ui.Error(fmt.Sprintf(
errStateMv, "destination module does not exist"))
return 1
}
if stateTo.Resource(addrTo) != nil {
c.Ui.Error(fmt.Sprintf(
errStateMv, "destination resource already exists"))
return 1
}
moved++
c.Ui.Output(fmt.Sprintf("%s %q to %q", prefix, addrFrom.String(), addrTo.String()))
if !dryRun {
ssFrom.RemoveResource(addrFrom)
// Update the address before adding it to the state.
rs := result.Value.(*states.Resource)
rs.Addr = addrTo.Resource
stateTo.Module(addrTo.Module).Resources[addrTo.Resource.String()] = rs
}
case addrs.AbsResourceInstance:
addrTo, err := addrs.ParseAbsResourceInstanceStr(args[1])
if err != nil {
c.Ui.Error(fmt.Sprintf(errStateMv, err))
return 1
}
if stateTo.Module(addrTo.Module) == nil {
c.Ui.Error(fmt.Sprintf(
errStateMv, "destination module does not exist"))
return 1
}
if stateTo.Resource(addrTo.ContainingResource()) == nil {
c.Ui.Error(fmt.Sprintf(
errStateMv, "destination resource does not exist"))
return 1
}
if stateTo.ResourceInstance(addrTo) != nil {
c.Ui.Error(fmt.Sprintf(
errStateMv, "destination resource instance already exists"))
return 1
}
moved++
c.Ui.Output(fmt.Sprintf("%s %q to %q", prefix, addrFrom.String(), args[1]))
if !dryRun {
ssFrom.ForgetResourceInstanceAll(addrFrom)
ssFrom.RemoveResourceIfEmpty(addrFrom.ContainingResource())
rs := stateTo.Resource(addrTo.ContainingResource())
rs.Instances[addrTo.Resource.Key] = result.Value.(*states.ResourceInstance)
}
}
}
if dryRun {
if moved == 0 {
c.Ui.Output("Would have moved nothing.")
}
return 0 // This is as far as we go in dry-run mode
}
// Write the new state
if err := stateToMgr.WriteState(stateTo); err != nil {
c.Ui.Error(fmt.Sprintf(errStateRmPersist, err))
return 1
}
if err := stateToMgr.PersistState(); err != nil {
c.Ui.Error(fmt.Sprintf(errStateRmPersist, err))
return 1
}
// Write the old state if it is different
if stateTo != stateFrom {
if err := stateFromMgr.WriteState(stateFrom); err != nil {
c.Ui.Error(fmt.Sprintf(errStateRmPersist, err))
return 1
}
if err := stateFromMgr.PersistState(); err != nil {
c.Ui.Error(fmt.Sprintf(errStateRmPersist, err))
return 1
}
}
if moved == 0 {
c.Ui.Output("No matching objects found.")
} else {
c.Ui.Output(fmt.Sprintf("Successfully moved %d object(s).", moved))
}
return 0 return 0
} }
// addableResult takes the result from a filter operation and returns what to // moveableResult takes the result from a filter operation and returns what
// call State.Add with. The reason we do this is because in the module case // object(s) to move. The reason we do this is because in the module case
// we must add the list of all modules returned versus just the root module. // we must add the list of all modules returned versus just the root module.
func (c *StateMvCommand) addableResult(results []*states.FilterResult) interface{} { func (c *StateMvCommand) moveableResult(results []*states.FilterResult) []*states.FilterResult {
switch v := results[0].Value.(type) { result := results[:1]
case *states.Module:
// If a state module then we should add the full list of modules if len(results) > 1 {
result := []*states.Module{v} // If a state module then we should add the full list of modules.
if len(results) > 1 { if _, ok := result[0].Address.(addrs.ModuleInstance); ok {
for _, r := range results[1:] { for _, r := range results[1:] {
if ms, ok := r.Value.(*states.Module); ok { if _, ok := r.Address.(addrs.ModuleInstance); ok {
result = append(result, ms) result = append(result, r)
} }
} }
} }
return result
default:
// By default just add the first result
return v
} }
return result
} }
func (c *StateMvCommand) Help() string { func (c *StateMvCommand) Help() string {
@ -182,6 +286,9 @@ Usage: terraform state mv [options] SOURCE DESTINATION
Options: Options:
-dry-run If set, prints out what would've been moved but doesn't
actually move anything.
-backup=PATH Path where Terraform should write the backup for the original -backup=PATH Path where Terraform should write the backup for the original
state. This can't be disabled. If not set, Terraform state. This can't be disabled. If not set, Terraform
will write it to the same path as the statefile with will write it to the same path as the statefile with
@ -209,7 +316,7 @@ func (c *StateMvCommand) Synopsis() string {
return "Move an item in the state" return "Move an item in the state"
} }
const errStateMv = `Error moving state: %[1]s const errStateMv = `Error moving state: %s
Please ensure your addresses and state paths are valid. No Please ensure your addresses and state paths are valid. No
state was persisted. Your existing states are untouched.` state was persisted. Your existing states are untouched.`

View File

@ -4,6 +4,7 @@ import (
"fmt" "fmt"
"os" "os"
"path/filepath" "path/filepath"
"strings"
"testing" "testing"
"github.com/mitchellh/cli" "github.com/mitchellh/cli"
@ -74,6 +75,48 @@ func TestStateMv(t *testing.T) {
testStateOutput(t, backups[0], testStateMvOutputOriginal) testStateOutput(t, backups[0], testStateMvOutputOriginal)
} }
func TestStateMv_differentResourceTypes(t *testing.T) {
state := states.BuildState(func(s *states.SyncState) {
s.SetResourceInstanceCurrent(
addrs.Resource{
Mode: addrs.ManagedResourceMode,
Type: "test_instance",
Name: "foo",
}.Instance(addrs.NoKey).Absolute(addrs.RootModuleInstance),
&states.ResourceInstanceObjectSrc{
AttrsJSON: []byte(`{"id":"bar","foo":"value","bar":"value"}`),
Status: states.ObjectReady,
},
addrs.ProviderConfig{Type: "test"}.Absolute(addrs.RootModuleInstance),
)
})
statePath := testStateFile(t, state)
p := testProvider()
ui := new(cli.MockUi)
c := &StateMvCommand{
StateMeta{
Meta: Meta{
testingOverrides: metaOverridesForProvider(p),
Ui: ui,
},
},
}
args := []string{
"-state", statePath,
"test_instance.foo",
"test_network.bar",
}
if code := c.Run(args); code == 0 {
t.Fatalf("expected error output, got:\n%s", ui.OutputWriter.String())
}
if !strings.Contains(ui.ErrorWriter.String(), "resource types do not match") {
t.Fatalf("expected initialization error, got:\n%s", ui.ErrorWriter.String())
}
}
// don't modify backend state is we supply a -state flag // don't modify backend state is we supply a -state flag
func TestStateMv_explicitWithBackend(t *testing.T) { func TestStateMv_explicitWithBackend(t *testing.T) {
td := tempDir(t) td := tempDir(t)
@ -152,10 +195,6 @@ func TestStateMv_explicitWithBackend(t *testing.T) {
} }
func TestStateMv_backupExplicit(t *testing.T) { func TestStateMv_backupExplicit(t *testing.T) {
td := tempDir(t)
defer os.RemoveAll(td)
backupPath := filepath.Join(td, "backup")
state := states.BuildState(func(s *states.SyncState) { state := states.BuildState(func(s *states.SyncState) {
s.SetResourceInstanceCurrent( s.SetResourceInstanceCurrent(
addrs.Resource{ addrs.Resource{
@ -183,6 +222,7 @@ func TestStateMv_backupExplicit(t *testing.T) {
) )
}) })
statePath := testStateFile(t, state) statePath := testStateFile(t, state)
backupPath := statePath + ".backup.test"
p := testProvider() p := testProvider()
ui := new(cli.MockUi) ui := new(cli.MockUi)
@ -913,8 +953,6 @@ test_instance.foo.10:
const testStateMvNestedModule_stateOut = ` const testStateMvNestedModule_stateOut = `
<no state> <no state>
module.bar:
<no state>
module.bar.child1: module.bar.child1:
test_instance.foo: test_instance.foo:
ID = bar ID = bar
@ -935,8 +973,6 @@ const testStateMvNestedModule_stateOutSrc = `
const testStateMvNestedModule_stateOutOriginal = ` const testStateMvNestedModule_stateOutOriginal = `
<no state> <no state>
module.foo:
<no state>
module.foo.child1: module.foo.child1:
test_instance.foo: test_instance.foo:
ID = bar ID = bar
@ -983,6 +1019,7 @@ test_instance.bar:
foo = value foo = value
test_instance.qux: test_instance.qux:
ID = bar ID = bar
provider = provider.test
` `
const testStateMvExisting_stateSrcOriginal = ` const testStateMvExisting_stateSrcOriginal = `

View File

@ -5,10 +5,9 @@ import (
"sort" "sort"
"strings" "strings"
"github.com/mitchellh/cli"
"github.com/hashicorp/terraform/addrs" "github.com/hashicorp/terraform/addrs"
"github.com/hashicorp/terraform/states" "github.com/hashicorp/terraform/states"
"github.com/mitchellh/cli"
) )
// StateRmCommand is a Command implementation that shows a single resource. // StateRmCommand is a Command implementation that shows a single resource.
@ -22,18 +21,19 @@ func (c *StateRmCommand) Run(args []string) int {
return 1 return 1
} }
var dryRun bool
cmdFlags := c.Meta.flagSet("state show") cmdFlags := c.Meta.flagSet("state show")
cmdFlags.BoolVar(&dryRun, "dry-run", false, "dry run")
cmdFlags.StringVar(&c.backupPath, "backup", "-", "backup") cmdFlags.StringVar(&c.backupPath, "backup", "-", "backup")
cmdFlags.StringVar(&c.statePath, "state", "", "path") cmdFlags.StringVar(&c.statePath, "state", "", "path")
dryRun := cmdFlags.Bool("dry-run", false, "dry run")
if err := cmdFlags.Parse(args); err != nil { if err := cmdFlags.Parse(args); err != nil {
return cli.RunResultHelp return cli.RunResultHelp
} }
args = cmdFlags.Args() args = cmdFlags.Args()
if len(args) < 1 { if len(args) < 1 {
c.Ui.Error("At least one resource address is required.") c.Ui.Error("At least one address is required.\n")
return 1 return cli.RunResultHelp
} }
// Get the state // Get the state
@ -53,18 +53,16 @@ func (c *StateRmCommand) Run(args []string) int {
return 1 return 1
} }
// Filter what we are removing.
results, err := c.filter(state, args) results, err := c.filter(state, args)
if err != nil { if err != nil {
c.Ui.Error(fmt.Sprintf(errStateFilter, err)) c.Ui.Error(fmt.Sprintf(errStateFilter, err))
return cli.RunResultHelp return cli.RunResultHelp
} }
// If we have no results, just exit early, we're not going to do anything. // If we have no results, exit early as we're not going to do anything.
// While what happens below is fairly fast, this is an important early
// exit since the prune below might modify the state more and we don't
// want to modify the state if we don't have to.
if len(results) == 0 { if len(results) == 0 {
if *dryRun { if dryRun {
c.Ui.Output("Would have removed nothing.") c.Ui.Output("Would have removed nothing.")
} else { } else {
c.Ui.Output("No matching resources found.") c.Ui.Output("No matching resources found.")
@ -73,7 +71,7 @@ func (c *StateRmCommand) Run(args []string) int {
} }
prefix := "Remove resource " prefix := "Remove resource "
if *dryRun { if dryRun {
prefix = "Would remove resource " prefix = "Would remove resource "
} }
@ -92,7 +90,7 @@ func (c *StateRmCommand) Run(args []string) int {
if len(output) > 0 { if len(output) > 0 {
c.Ui.Output(strings.Join(sort.StringSlice(output), "\n")) c.Ui.Output(strings.Join(sort.StringSlice(output), "\n"))
} }
if !*dryRun { if !dryRun {
ss.RemoveModule(addr) ss.RemoveModule(addr)
} }
@ -105,29 +103,27 @@ func (c *StateRmCommand) Run(args []string) int {
if len(output) > 0 { if len(output) > 0 {
c.Ui.Output(strings.Join(sort.StringSlice(output), "\n")) c.Ui.Output(strings.Join(sort.StringSlice(output), "\n"))
} }
if !*dryRun { if !dryRun {
ss.RemoveResource(addr) ss.RemoveResource(addr)
} }
case addrs.AbsResourceInstance: case addrs.AbsResourceInstance:
isCount++ isCount++
c.Ui.Output(prefix + addr.String()) c.Ui.Output(prefix + addr.String())
if !*dryRun { if !dryRun {
ss.ForgetResourceInstanceAll(addr) ss.ForgetResourceInstanceAll(addr)
ss.RemoveResourceIfEmpty(addr.ContainingResource())
} }
} }
} }
if *dryRun { if dryRun {
if isCount == 0 { if isCount == 0 {
c.Ui.Output("Would have removed nothing.") c.Ui.Output("Would have removed nothing.")
} }
return 0 // This is as far as we go in dry-run mode return 0 // This is as far as we go in dry-run mode
} }
// Prune the state before writing and persisting it.
state.PruneResourceHusks()
if err := stateMgr.WriteState(state); err != nil { if err := stateMgr.WriteState(state); err != nil {
c.Ui.Error(fmt.Sprintf(errStateRmPersist, err)) c.Ui.Error(fmt.Sprintf(errStateRmPersist, err))
return 1 return 1

View File

@ -115,11 +115,11 @@ func TestStateRmNoArgs(t *testing.T) {
args := []string{ args := []string{
"-state", statePath, "-state", statePath,
} }
if code := c.Run(args); code != 1 { if code := c.Run(args); code == 0 {
t.Errorf("wrong exit status %d; want %d", code, 1) t.Errorf("expected non-zero exit code, got: %d", code)
} }
if msg := ui.ErrorWriter.String(); !strings.Contains(msg, "At least one resource address") { if msg := ui.ErrorWriter.String(); !strings.Contains(msg, "At least one address") {
t.Errorf("not the error we were looking for:\n%s", msg) t.Errorf("not the error we were looking for:\n%s", msg)
} }
@ -207,7 +207,7 @@ func TestStateRm_backupExplicit(t *testing.T) {
) )
}) })
statePath := testStateFile(t, state) statePath := testStateFile(t, state)
backupPath := statePath + ".mybackup" backupPath := statePath + ".backup.test"
p := testProvider() p := testProvider()
ui := new(cli.MockUi) ui := new(cli.MockUi)
@ -251,7 +251,7 @@ func TestStateRm_noState(t *testing.T) {
}, },
} }
args := []string{} args := []string{"foo"}
if code := c.Run(args); code != 1 { if code := c.Run(args); code != 1 {
t.Fatalf("bad: %d\n\n%s", code, ui.ErrorWriter.String()) t.Fatalf("bad: %d\n\n%s", code, ui.ErrorWriter.String())
} }

View File

@ -63,8 +63,19 @@ func (f *Filter) Filter(fs ...string) ([]*FilterResult, error) {
results = append(results, v) results = append(results, v)
} }
// Sort them and return // Sort the results
sort.Sort(FilterResultSlice(results)) sort.Slice(results, func(i, j int) bool {
a, b := results[i], results[j]
// If the addresses are different it is just lexographic sorting
if a.Address.String() != b.Address.String() {
return a.Address.String() < b.Address.String()
}
// Addresses are the same, which means it matters on the type
return a.SortedType() < b.SortedType()
})
return results, nil return results, nil
} }
@ -155,7 +166,7 @@ func (r *FilterResult) String() string {
return fmt.Sprintf("%T: %s", r.Value, r.Address) return fmt.Sprintf("%T: %s", r.Value, r.Address)
} }
func (r *FilterResult) sortedType() int { func (r *FilterResult) SortedType() int {
switch r.Value.(type) { switch r.Value.(type) {
case *Module: case *Module:
return 0 return 0
@ -167,22 +178,3 @@ func (r *FilterResult) sortedType() int {
return 50 return 50
} }
} }
// FilterResultSlice is a slice of results that implements
// sort.Interface. The sorting goal is what is most appealing to
// human output.
type FilterResultSlice []*FilterResult
func (s FilterResultSlice) Len() int { return len(s) }
func (s FilterResultSlice) Swap(i, j int) { s[i], s[j] = s[j], s[i] }
func (s FilterResultSlice) Less(i, j int) bool {
a, b := s[i], s[j]
// If the addresses are different it is just lexographic sorting
if a.Address.String() != b.Address.String() {
return a.Address.String() < b.Address.String()
}
// Addresses are the same, which means it matters on the type
return a.sortedType() < b.sortedType()
}

View File

@ -399,7 +399,7 @@ func testStateSmall() *State {
root := addrs.RootModuleInstance root := addrs.RootModuleInstance
boot, _ := addrs.ParseModuleInstanceStr("module.boot") boot, _ := addrs.ParseModuleInstanceStr("module.boot")
state := BuildState(func(s *SyncState) { return BuildState(func(s *SyncState) {
s.SetResourceInstanceCurrent( s.SetResourceInstanceCurrent(
addrs.Resource{ addrs.Resource{
Mode: addrs.ManagedResourceMode, Mode: addrs.ManagedResourceMode,
@ -457,9 +457,6 @@ func testStateSmall() *State {
}.Absolute(boot), }.Absolute(boot),
) )
}) })
// fmt.Printf("mods: %#v\n", state.Modules)
// fmt.Printf("boot: %#+v\n", state.Modules["module.boot"])
return state
} }
// testStateSmallTestInstance returns a test State structure. // testStateSmallTestInstance returns a test State structure.

View File

@ -1,374 +0,0 @@
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 item can also be a []*ModuleState, which is the case for nested
// modules. In this case, Add will expect the zero-index to be the top-most
// module to add and will only nest children from there. For semantics, this
// is equivalent to module => module.
//
// 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 {
// raw can be either *ModuleState or []*ModuleState. The former means
// we're moving just one module. The latter means we're moving a module
// and children.
root := raw
var rest []*ModuleState
if list, ok := raw.([]*ModuleState); ok {
// We need at least one item
if len(list) == 0 {
return fmt.Errorf("module move with no value to: %s", addr)
}
// The first item is always the root
root = list[0]
if len(list) > 1 {
rest = list[1:]
}
}
// Get the actual module state
src := root.(*ModuleState).deepcopy()
// If the target module exists, it is an error
path := normalizeModulePath(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
addrCopy.Mode = resourceKey.Mode
// Perform an add
if err := s.Add(fromAddr.String(), addrCopy.String(), v); err != nil {
return err
}
}
// Add all the children if we have them
for _, item := range rest {
// If item isn't a descendent of our root, then ignore it
if !src.IsDescendent(item) {
continue
}
// It is! Strip the leading prefix and attach that to our address
extra := item.Path[len(src.Path):]
addrCopy := addr.Copy()
addrCopy.Path = append(addrCopy.Path, extra...)
// Add it
s.Add(fromAddr.String(), addrCopy.String(), item)
}
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 {
// raw can be either *ResourceState or []*ResourceState. The former means
// we're moving just one resource. The latter means we're moving a count
// of resources.
if list, ok := raw.([]*ResourceState); ok {
// We need at least one item
if len(list) == 0 {
return fmt.Errorf("resource move with no value to: %s", addr)
}
// If there is an index, this is an error since we can't assign
// a set of resources to a single index
if addr.Index >= 0 && len(list) > 1 {
return fmt.Errorf(
"multiple resources can't be moved to a single index: "+
"%s => %s", fromAddr, addr)
}
// Add each with a specific index
for i, rs := range list {
addrCopy := addr.Copy()
addrCopy.Index = i
if err := s.Add(fromAddr.String(), addrCopy.String(), rs); err != nil {
return err
}
}
return nil
}
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 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.Set(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 []*ModuleState:
return stateAddModule
case *ResourceState:
return stateAddResource
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 := normalizeModulePath(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,
Mode: addr.Mode,
}).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, TypeTainted:
if v := resource.Primary; v != nil {
instance = resource.Primary
} else {
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
}

View File

@ -1,695 +0,0 @@
package terraform
import (
"fmt"
"testing"
)
func TestStateAdd(t *testing.T) {
cases := []struct {
Name string
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]*OutputState{
"foo": &OutputState{
Type: "string",
Sensitive: false,
Value: "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]*OutputState{
"foo": &OutputState{
Type: "string",
Sensitive: false,
Value: "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,
},
{
"ModuleState with children => Module Addr (new)",
false,
"module.foo",
"module.bar",
[]*ModuleState{
&ModuleState{
Path: []string{"root", "foo"},
Resources: map[string]*ResourceState{},
},
&ModuleState{
Path: []string{"root", "foo", "child1"},
Resources: map[string]*ResourceState{
"test_instance.foo": &ResourceState{
Type: "test_instance",
Primary: &InstanceState{
ID: "foo",
},
},
},
},
&ModuleState{
Path: []string{"root", "foo", "child2"},
Resources: map[string]*ResourceState{
"test_instance.foo": &ResourceState{
Type: "test_instance",
Primary: &InstanceState{
ID: "foo",
},
},
},
},
// Should be ignored
&ModuleState{
Path: []string{"root", "baz", "child2"},
Resources: map[string]*ResourceState{
"test_instance.foo": &ResourceState{
Type: "test_instance",
Primary: &InstanceState{
ID: "foo",
},
},
},
},
},
&State{},
&State{
Modules: []*ModuleState{
&ModuleState{
Path: []string{"root", "bar"},
Resources: map[string]*ResourceState{},
},
&ModuleState{
Path: []string{"root", "bar", "child1"},
Resources: map[string]*ResourceState{
"test_instance.foo": &ResourceState{
Type: "test_instance",
Primary: &InstanceState{
ID: "foo",
},
},
},
},
&ModuleState{
Path: []string{"root", "bar", "child2"},
Resources: map[string]*ResourceState{
"test_instance.foo": &ResourceState{
Type: "test_instance",
Primary: &InstanceState{
ID: "foo",
},
},
},
},
},
},
},
{
"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 tainted => Resource Addr (new)",
false,
"aws_instance.bar",
"aws_instance.foo",
&ResourceState{
Type: "test_instance",
Primary: &InstanceState{
ID: "foo",
Tainted: true,
},
},
&State{},
&State{
Modules: []*ModuleState{
&ModuleState{
Path: []string{"root"},
Resources: map[string]*ResourceState{
"aws_instance.foo": &ResourceState{
Type: "test_instance",
Primary: &InstanceState{
ID: "foo",
Tainted: true,
},
},
},
},
},
},
},
{
"ResourceState with count unspecified => Resource Addr (new)",
false,
"aws_instance.bar",
"aws_instance.foo",
[]*ResourceState{
&ResourceState{
Type: "test_instance",
Primary: &InstanceState{
ID: "foo",
},
},
&ResourceState{
Type: "test_instance",
Primary: &InstanceState{
ID: "bar",
},
},
},
&State{},
&State{
Modules: []*ModuleState{
&ModuleState{
Path: []string{"root"},
Resources: map[string]*ResourceState{
"aws_instance.foo.0": &ResourceState{
Type: "test_instance",
Primary: &InstanceState{
ID: "foo",
},
},
"aws_instance.foo.1": &ResourceState{
Type: "test_instance",
Primary: &InstanceState{
ID: "bar",
},
},
},
},
},
},
},
{
"ResourceState with count unspecified => Resource Addr (new with count)",
true,
"aws_instance.bar",
"aws_instance.foo[0]",
[]*ResourceState{
&ResourceState{
Type: "test_instance",
Primary: &InstanceState{
ID: "foo",
},
},
&ResourceState{
Type: "test_instance",
Primary: &InstanceState{
ID: "bar",
},
},
},
&State{},
nil,
},
{
"ResourceState with single count unspecified => Resource Addr (new with count)",
false,
"aws_instance.bar",
"aws_instance.foo[0]",
[]*ResourceState{
&ResourceState{
Type: "test_instance",
Primary: &InstanceState{
ID: "foo",
},
},
},
&State{},
&State{
Modules: []*ModuleState{
&ModuleState{
Path: []string{"root"},
Resources: map[string]*ResourceState{
"aws_instance.foo.0": &ResourceState{
Type: "test_instance",
Primary: &InstanceState{
ID: "foo",
},
},
},
},
},
},
},
{
"ResourceState => Resource Addr (new with count)",
false,
"aws_instance.bar",
"aws_instance.foo[0]",
&ResourceState{
Type: "test_instance",
Primary: &InstanceState{
ID: "foo",
},
},
&State{},
&State{
Modules: []*ModuleState{
&ModuleState{
Path: []string{"root"},
Resources: map[string]*ResourceState{
"aws_instance.foo.0": &ResourceState{
Type: "test_instance",
Primary: &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",
},
},
},
},
},
},
},
{
"ModuleState => Module Addr (new with data source)",
false,
"",
"module.foo",
&ModuleState{
Path: rootModulePath,
Resources: map[string]*ResourceState{
"data.test_instance.foo": &ResourceState{
Type: "test_instance",
Primary: &InstanceState{
ID: "foo",
},
},
},
},
&State{},
&State{
Modules: []*ModuleState{
&ModuleState{
Path: []string{"root", "foo"},
Resources: map[string]*ResourceState{
"data.test_instance.foo": &ResourceState{
Type: "test_instance",
Primary: &InstanceState{
ID: "foo",
},
},
},
},
},
},
},
}
for i, tc := range cases {
t.Run(fmt.Sprintf("%d-%s", i, tc.Name), func(t *testing.T) {
// 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.Fatal(err)
}
if tc.Err {
return
}
// 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: \n\n%s\n\n%s", tc.One.String(), tc.Two.String())
}
})
}
}