Merge pull request #19197 from hashicorp/f-state-mv

command/state: update and fix the state mv command
This commit is contained in:
Sander van Harmelen 2018-10-27 15:03:42 +02:00 committed by GitHub
commit 292ec47e66
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
9 changed files with 282 additions and 1202 deletions

View File

@ -2,6 +2,7 @@ package command
import (
"fmt"
"sort"
"time"
"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
}

View File

@ -4,6 +4,7 @@ import (
"fmt"
"strings"
"github.com/hashicorp/terraform/addrs"
"github.com/hashicorp/terraform/states"
"github.com/mitchellh/cli"
)
@ -22,7 +23,9 @@ func (c *StateMvCommand) Run(args []string) int {
// We create two metas to track the two states
var backupPathOut, statePathOut string
var dryRun bool
cmdFlags := c.Meta.flagSet("state mv")
cmdFlags.BoolVar(&dryRun, "dry-run", false, "dry run")
cmdFlags.StringVar(&c.backupPath, "backup", "-", "backup")
cmdFlags.StringVar(&c.statePath, "state", "", "path")
cmdFlags.StringVar(&backupPathOut, "backup-out", "-", "backup")
@ -37,127 +40,228 @@ func (c *StateMvCommand) Run(args []string) int {
}
// Read the from state
stateFrom, err := c.State()
stateFromMgr, err := c.State()
if err != nil {
c.Ui.Error(fmt.Sprintf(errStateLoadingState, err))
return 1
}
if err := stateFrom.RefreshState(); err != nil {
c.Ui.Error(fmt.Sprintf("Failed to load state: %s", err))
if err := stateFromMgr.RefreshState(); err != nil {
c.Ui.Error(fmt.Sprintf("Failed to refresh state: %s", err))
return 1
}
stateFromReal := stateFrom.State()
if stateFromReal == nil {
stateFrom := stateFromMgr.State()
if stateFrom == nil {
c.Ui.Error(fmt.Sprintf(errStateNotFound))
return 1
}
// Read the destination state
stateToMgr := stateFromMgr
stateTo := stateFrom
stateToReal := stateFromReal
if statePathOut != "" {
c.statePath = statePathOut
c.backupPath = backupPathOut
stateTo, err = c.State()
stateToMgr, err = c.State()
if err != nil {
c.Ui.Error(fmt.Sprintf(errStateLoadingState, err))
return 1
}
if err := stateTo.RefreshState(); err != nil {
c.Ui.Error(fmt.Sprintf("Failed to load state: %s", err))
if err := stateToMgr.RefreshState(); err != nil {
c.Ui.Error(fmt.Sprintf("Failed to refresh state: %s", err))
return 1
}
stateToReal = stateTo.State()
if stateToReal == nil {
stateToReal = states.NewState()
stateTo = stateToMgr.State()
if stateTo == nil {
stateTo = states.NewState()
}
}
c.Ui.Error("state mv command not yet updated for new state types")
return 1
/*
// 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
}
// Filter what we are moving.
results, err := c.filter(stateFrom, []string{args[0]})
if err != nil {
c.Ui.Error(fmt.Sprintf(errStateFilter, err))
return cli.RunResultHelp
}
// Get the item to add to the state
add := c.addableResult(results)
// Do the actual move
if err := stateFromReal.Remove(args[0]); err != nil {
c.Ui.Error(fmt.Sprintf(errStateMv, err))
return 1
// If we have no results, exit early as we're not going to do anything.
if len(results) == 0 {
if dryRun {
c.Ui.Output("Would have moved nothing.")
} else {
c.Ui.Output("No matching objects found.")
}
return 0
}
if err := stateToReal.Add(args[0], args[1], add); err != nil {
c.Ui.Error(fmt.Sprintf(errStateMv, err))
return 1
}
prefix := "Move"
if dryRun {
prefix = "Would move"
}
// 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))
var moved int
ssFrom := stateFrom.SyncWrapper()
for _, result := range c.moveableResult(results) {
switch addrFrom := result.Address.(type) {
case addrs.ModuleInstance:
search, err := addrs.ParseModuleInstanceStr(args[0])
if err != nil {
c.Ui.Error(fmt.Sprintf(errStateMv, err))
return 1
}
addrTo, err := addrs.ParseModuleInstanceStr(args[1])
if err != nil {
c.Ui.Error(fmt.Sprintf(errStateMv, err))
return 1
}
if err := stateFrom.PersistState(); err != nil {
c.Ui.Error(fmt.Sprintf(errStateMvPersist, err))
if len(search) < len(addrFrom) {
addrTo = append(addrTo, addrFrom[len(search):]...)
}
if stateTo.Module(addrTo) != nil {
c.Ui.Error(fmt.Sprintf(errStateMv, "destination module already exists"))
return 1
}
}
*/
c.Ui.Output(fmt.Sprintf(
"Moved %s to %s", args[0], args[1]))
moved++
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
}
// addableResult takes the result from a filter operation and returns what to
// call State.Add with. The reason we do this is because in the module case
// moveableResult takes the result from a filter operation and returns what
// 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.
func (c *StateMvCommand) addableResult(results []*states.FilterResult) interface{} {
switch v := results[0].Value.(type) {
case *states.Module:
// If a state module then we should add the full list of modules
result := []*states.Module{v}
if len(results) > 1 {
func (c *StateMvCommand) moveableResult(results []*states.FilterResult) []*states.FilterResult {
result := results[:1]
if len(results) > 1 {
// If a state module then we should add the full list of modules.
if _, ok := result[0].Address.(addrs.ModuleInstance); ok {
for _, r := range results[1:] {
if ms, ok := r.Value.(*states.Module); ok {
result = append(result, ms)
if _, ok := r.Address.(addrs.ModuleInstance); ok {
result = append(result, r)
}
}
}
return result
default:
// By default just add the first result
return v
}
return result
}
func (c *StateMvCommand) Help() string {
@ -182,6 +286,9 @@ Usage: terraform state mv [options] SOURCE DESTINATION
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
state. This can't be disabled. If not set, Terraform
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"
}
const errStateMv = `Error moving state: %[1]s
const errStateMv = `Error moving state: %s
Please ensure your addresses and state paths are valid. No
state was persisted. Your existing states are untouched.`

View File

@ -4,6 +4,7 @@ import (
"fmt"
"os"
"path/filepath"
"strings"
"testing"
"github.com/mitchellh/cli"
@ -74,6 +75,48 @@ func TestStateMv(t *testing.T) {
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
func TestStateMv_explicitWithBackend(t *testing.T) {
td := tempDir(t)
@ -152,10 +195,6 @@ func TestStateMv_explicitWithBackend(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) {
s.SetResourceInstanceCurrent(
addrs.Resource{
@ -183,6 +222,7 @@ func TestStateMv_backupExplicit(t *testing.T) {
)
})
statePath := testStateFile(t, state)
backupPath := statePath + ".backup.test"
p := testProvider()
ui := new(cli.MockUi)
@ -913,8 +953,6 @@ test_instance.foo.10:
const testStateMvNestedModule_stateOut = `
<no state>
module.bar:
<no state>
module.bar.child1:
test_instance.foo:
ID = bar
@ -935,8 +973,6 @@ const testStateMvNestedModule_stateOutSrc = `
const testStateMvNestedModule_stateOutOriginal = `
<no state>
module.foo:
<no state>
module.foo.child1:
test_instance.foo:
ID = bar
@ -983,6 +1019,7 @@ test_instance.bar:
foo = value
test_instance.qux:
ID = bar
provider = provider.test
`
const testStateMvExisting_stateSrcOriginal = `

View File

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

View File

@ -115,11 +115,11 @@ func TestStateRmNoArgs(t *testing.T) {
args := []string{
"-state", statePath,
}
if code := c.Run(args); code != 1 {
t.Errorf("wrong exit status %d; want %d", code, 1)
if code := c.Run(args); code == 0 {
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)
}
@ -207,7 +207,7 @@ func TestStateRm_backupExplicit(t *testing.T) {
)
})
statePath := testStateFile(t, state)
backupPath := statePath + ".mybackup"
backupPath := statePath + ".backup.test"
p := testProvider()
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 {
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)
}
// Sort them and return
sort.Sort(FilterResultSlice(results))
// Sort the 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
}
@ -155,7 +166,7 @@ func (r *FilterResult) String() string {
return fmt.Sprintf("%T: %s", r.Value, r.Address)
}
func (r *FilterResult) sortedType() int {
func (r *FilterResult) SortedType() int {
switch r.Value.(type) {
case *Module:
return 0
@ -167,22 +178,3 @@ func (r *FilterResult) sortedType() int {
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
boot, _ := addrs.ParseModuleInstanceStr("module.boot")
state := BuildState(func(s *SyncState) {
return BuildState(func(s *SyncState) {
s.SetResourceInstanceCurrent(
addrs.Resource{
Mode: addrs.ManagedResourceMode,
@ -457,9 +457,6 @@ func testStateSmall() *State {
}.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.

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())
}
})
}
}