command: Fix various issues in the "terraform state ..." subcommands

In earlier refactoring we updated these commands to support the new
address and state types, but attempted to partially retain the old-style
"StateFilter" abstraction that originally lived in the Terraform package,
even though that was no longer being used for any other functionality.

Unfortunately the adaptation of the existing filtering to the new types
wasn't exact and so these commands ended up having a few bugs that were
not covered by the existing tests.

Since the old StateFilter behavior was the source of various misbehavior
anyway, here it's removed altogether and replaced with some simpler
functions in the state_meta.go file that are tailored to the use-cases of
these sub-commands.

As well as just generally behaving more consistently with the other
parts of Terraform that use the new resource address types, this commit
fixes the following bugs:

- A resource address of aws_instance.foo would previously match an
  resource of that type and name in any module, which disagreed with the
  expected interpretation elsewhere of meaning a single resource in the
  root module.

- The "terraform state mv" command was not supporting moves from a single
  resource address to an indexed address and vice-versa, because the old
  logic didn't need to make that distinction while they are two separate
  address types in the new logic. Now we allow resources that do not have
  count/for_each to be treated as if they are instances for the purposes
  of this command, which is a better match for likely user intent and for
  the old behavior.

Finally, we also clean up a little some of the usage output from these
commands, which hasn't been updated for some time and so had both some
stale information and some inaccurate terminology.
This commit is contained in:
Martin Atkins 2019-03-15 19:51:26 -07:00
parent 04cbf249aa
commit c39905e1a8
8 changed files with 654 additions and 906 deletions

View File

@ -4,7 +4,9 @@ import (
"fmt"
"strings"
"github.com/hashicorp/terraform/addrs"
"github.com/hashicorp/terraform/states"
"github.com/hashicorp/terraform/tfdiags"
"github.com/mitchellh/cli"
)
@ -54,49 +56,61 @@ func (c *StateListCommand) Run(args []string) int {
return 1
}
filter := &states.Filter{State: state}
results, err := filter.Filter(args...)
if err != nil {
c.Ui.Error(fmt.Sprintf(errStateFilter, err))
return cli.RunResultHelp
var addrs []addrs.AbsResourceInstance
var diags tfdiags.Diagnostics
if len(args) == 0 {
addrs, diags = c.lookupAllResourceInstanceAddrs(state)
} else {
addrs, diags = c.lookupResourceInstanceAddrs(state, args...)
}
if diags.HasErrors() {
c.showDiagnostics(diags)
return 1
}
for _, result := range results {
if is, ok := result.Value.(*states.ResourceInstance); ok {
for _, addr := range addrs {
if is := state.ResourceInstance(addr); is != nil {
if *lookupId == "" || *lookupId == states.LegacyInstanceObjectID(is.Current) {
c.Ui.Output(result.Address.String())
c.Ui.Output(addr.String())
}
}
}
c.showDiagnostics(diags)
return 0
}
func (c *StateListCommand) Help() string {
helpText := `
Usage: terraform state list [options] [pattern...]
Usage: terraform state list [options] [address...]
List resources in the Terraform state.
This command lists resources in the Terraform state. The pattern argument
can be used to filter the resources by resource or module. If no pattern
is given, all resources are listed.
This command lists resource instances in the Terraform state. The address
argument can be used to filter the instances by resource or module. If
no pattern is given, all resource instances are listed.
The pattern argument is meant to provide very simple filtering. For
advanced filtering, please use tools such as "grep". The output of this
command is designed to be friendly for this usage.
The addresses must either be module addresses or absolute resource
addresses, such as:
aws_instance.example
module.example
module.example.module.child
module.example.aws_instance.example
The pattern argument accepts any resource targeting syntax. Please
refer to the documentation on resource targeting syntax for more
information.
An error will be returned if any of the resources or modules given as
filter addresses do not exist in the state.
Options:
-state=statefile 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.
up Terraform-managed resources. By default, Terraform
will consult the state of the currently-selected
workspace.
-id=ID Restricts the output to objects whose id is ID.
-id=ID Filters the results to include only instances whose
resource types have an attribute named "id" whose value
equals the given id string.
`
return strings.TrimSpace(helpText)

View File

@ -9,6 +9,7 @@ import (
"github.com/hashicorp/terraform/state"
"github.com/hashicorp/terraform/states"
"github.com/hashicorp/terraform/states/statemgr"
"github.com/hashicorp/terraform/tfdiags"
backendLocal "github.com/hashicorp/terraform/backend/local"
)
@ -80,57 +81,118 @@ func (c *StateMeta) State() (state.State, error) {
return realState, nil
}
func (c *StateMeta) filter(state *states.State, args []string) ([]*states.FilterResult, error) {
var results []*states.FilterResult
filter := &states.Filter{State: state}
for _, arg := range args {
filtered, err := filter.Filter(arg)
if err != nil {
return nil, err
}
filtered:
for _, result := range filtered {
switch result.Address.(type) {
case addrs.ModuleInstance:
for _, result := range filtered {
if _, ok := result.Address.(addrs.ModuleInstance); ok {
results = append(results, result)
}
}
break filtered
case addrs.AbsResource:
for _, result := range filtered {
if _, ok := result.Address.(addrs.AbsResource); ok {
results = append(results, result)
}
}
break filtered
case addrs.AbsResourceInstance:
results = append(results, result)
}
}
func (c *StateMeta) lookupResourceInstanceAddr(state *states.State, allowMissing bool, addrStr string) ([]addrs.AbsResourceInstance, tfdiags.Diagnostics) {
target, diags := addrs.ParseTargetStr(addrStr)
if diags.HasErrors() {
return nil, diags
}
// 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())
targetAddr := target.Subject
var ret []addrs.AbsResourceInstance
switch addr := targetAddr.(type) {
case addrs.ModuleInstance:
// Matches all instances within the indicated module and all of its
// descendent modules.
ms := state.Module(addr)
if ms == nil {
if !allowMissing {
diags = diags.Append(tfdiags.Sourceless(
tfdiags.Error,
"Unknown module",
fmt.Sprintf(`The current state contains no module at %s. If you've just added this module to the configuration, you must run "terraform apply" first to create the module's entry in the state.`, addr),
))
}
break
}
// If the addresses are different it is just lexographic sorting
if a.Address.String() != b.Address.String() {
return a.Address.String() < b.Address.String()
ret = append(ret, c.collectModuleResourceInstances(ms)...)
for _, cms := range state.Modules {
candidateAddr := ms.Addr
if len(candidateAddr) > len(addr) && candidateAddr[:len(addr)].Equal(addr) {
ret = append(ret, c.collectModuleResourceInstances(cms)...)
}
}
// Addresses are the same, which means it matters on the type
return a.SortedType() < b.SortedType()
case addrs.AbsResource:
// Matches all instances of the specific selected resource.
rs := state.Resource(addr)
if rs == nil {
if !allowMissing {
diags = diags.Append(tfdiags.Sourceless(
tfdiags.Error,
"Unknown resource",
fmt.Sprintf(`The current state contains no resource %s. If you've just added this resource to the configuration, you must run "terraform apply" first to create the resource's entry in the state.`, addr),
))
}
break
}
ret = append(ret, c.collectResourceInstances(addr.Module, rs)...)
case addrs.AbsResourceInstance:
is := state.ResourceInstance(addr)
if is == nil {
if !allowMissing {
diags = diags.Append(tfdiags.Sourceless(
tfdiags.Error,
"Unknown resource instance",
fmt.Sprintf(`The current state contains no resource instance %s. If you've just added its resource to the configuration or have changed the count or for_each arguments, you must run "terraform apply" first to update the resource's entry in the state.`, addr),
))
}
break
}
ret = append(ret, addr)
}
sort.Slice(ret, func(i, j int) bool {
return ret[i].Less(ret[j])
})
return results, nil
return ret, diags
}
func (c *StateMeta) lookupSingleResourceInstanceAddr(state *states.State, addrStr string) (addrs.AbsResourceInstance, tfdiags.Diagnostics) {
return addrs.ParseAbsResourceInstanceStr(addrStr)
}
func (c *StateMeta) lookupSingleStateObjectAddr(state *states.State, addrStr string) (addrs.Targetable, tfdiags.Diagnostics) {
target, diags := addrs.ParseTargetStr(addrStr)
if diags.HasErrors() {
return nil, diags
}
return target.Subject, diags
}
func (c *StateMeta) lookupResourceInstanceAddrs(state *states.State, addrStrs ...string) ([]addrs.AbsResourceInstance, tfdiags.Diagnostics) {
var ret []addrs.AbsResourceInstance
var diags tfdiags.Diagnostics
for _, addrStr := range addrStrs {
moreAddrs, moreDiags := c.lookupResourceInstanceAddr(state, false, addrStr)
ret = append(ret, moreAddrs...)
diags = diags.Append(moreDiags)
}
return ret, diags
}
func (c *StateMeta) lookupAllResourceInstanceAddrs(state *states.State) ([]addrs.AbsResourceInstance, tfdiags.Diagnostics) {
var ret []addrs.AbsResourceInstance
var diags tfdiags.Diagnostics
for _, ms := range state.Modules {
ret = append(ret, c.collectModuleResourceInstances(ms)...)
}
sort.Slice(ret, func(i, j int) bool {
return ret[i].Less(ret[j])
})
return ret, diags
}
func (c *StateMeta) collectModuleResourceInstances(ms *states.Module) []addrs.AbsResourceInstance {
var ret []addrs.AbsResourceInstance
for _, rs := range ms.Resources {
ret = append(ret, c.collectResourceInstances(ms.Addr, rs)...)
}
return ret
}
func (c *StateMeta) collectResourceInstances(moduleAddr addrs.ModuleInstance, rs *states.Resource) []addrs.AbsResourceInstance {
var ret []addrs.AbsResourceInstance
for key := range rs.Instances {
ret = append(ret, rs.Addr.Instance(key).Absolute(moduleAddr))
}
return ret
}

View File

@ -8,6 +8,7 @@ import (
"github.com/hashicorp/terraform/addrs"
"github.com/hashicorp/terraform/command/clistate"
"github.com/hashicorp/terraform/states"
"github.com/hashicorp/terraform/tfdiags"
"github.com/mitchellh/cli"
)
@ -104,21 +105,14 @@ func (c *StateMvCommand) Run(args []string) int {
}
}
// 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
}
// 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
var diags tfdiags.Diagnostics
sourceAddr, moreDiags := c.lookupSingleStateObjectAddr(stateFrom, args[0])
diags = diags.Append(moreDiags)
destAddr, moreDiags := c.lookupSingleStateObjectAddr(stateFrom, args[1])
diags = diags.Append(moreDiags)
if diags.HasErrors() {
c.showDiagnostics(diags)
return 1
}
prefix := "Move"
@ -126,24 +120,39 @@ func (c *StateMvCommand) Run(args []string) int {
prefix = "Would move"
}
const msgInvalidSource = "Invalid source address"
const msgInvalidTarget = "Invalid target address"
var moved int
ssFrom := stateFrom.SyncWrapper()
for _, result := range c.moveableResult(results) {
switch addrFrom := result.Address.(type) {
sourceAddrs := c.sourceObjectAddrs(stateFrom, sourceAddr)
if len(sourceAddrs) == 0 {
diags = diags.Append(tfdiags.Sourceless(
tfdiags.Error,
msgInvalidSource,
fmt.Sprintf("Cannot move %s: does not match anything in the current state.", sourceAddr),
))
}
for _, rawAddrFrom := range sourceAddrs {
switch addrFrom := rawAddrFrom.(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))
search := sourceAddr.(addrs.ModuleInstance)
addrTo, ok := destAddr.(addrs.ModuleInstance)
if !ok {
diags = diags.Append(tfdiags.Sourceless(
tfdiags.Error,
msgInvalidTarget,
fmt.Sprintf("Cannot move %s to %s: the target must also be a module.", addrFrom, addrTo),
))
c.showDiagnostics(diags)
return 1
}
if len(search) < len(addrFrom) {
addrTo = append(addrTo, addrFrom[len(search):]...)
n := make(addrs.ModuleInstance, 0, len(addrTo)+len(addrFrom)-len(search))
n = append(n, addrTo...)
n = append(n, addrFrom[len(search):]...)
addrTo = n
}
if stateTo.Module(addrTo) != nil {
@ -151,37 +160,65 @@ func (c *StateMvCommand) Run(args []string) int {
return 1
}
ms := ssFrom.Module(addrFrom)
if ms == nil {
diags = diags.Append(tfdiags.Sourceless(
tfdiags.Error,
msgInvalidSource,
fmt.Sprintf("The current state does not contain %s.", addrFrom),
))
c.showDiagnostics(diags)
return 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
ms.Addr = addrTo
stateTo.Modules[addrTo.String()] = ms
}
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"))
addrTo, ok := destAddr.(addrs.AbsResource)
if !ok {
diags = diags.Append(tfdiags.Sourceless(
tfdiags.Error,
msgInvalidTarget,
fmt.Sprintf("Cannot move %s to %s: the target must also be a whole resource.", addrFrom, addrTo),
))
c.showDiagnostics(diags)
return 1
}
diags = diags.Append(c.validateResourceMove(addrFrom, addrTo))
if stateTo.Module(addrTo.Module) == nil {
c.Ui.Error(fmt.Sprintf(
errStateMv, "destination module does not exist"))
return 1
diags = diags.Append(tfdiags.Sourceless(
tfdiags.Error,
msgInvalidTarget,
fmt.Sprintf("Cannot move to %s: %s does not exist in the current state.", addrTo, addrTo.Module),
))
}
if stateTo.Resource(addrTo) != nil {
c.Ui.Error(fmt.Sprintf(
errStateMv, "destination resource already exists"))
diags = diags.Append(tfdiags.Sourceless(
tfdiags.Error,
msgInvalidTarget,
fmt.Sprintf("Cannot move to %s: there is already a resource at that address in the current state.", addrTo),
))
}
rs := ssFrom.Resource(addrFrom)
if rs == nil {
diags = diags.Append(tfdiags.Sourceless(
tfdiags.Error,
msgInvalidSource,
fmt.Sprintf("The current state does not contain %s.", addrFrom),
))
}
if diags.HasErrors() {
c.showDiagnostics(diags)
return 1
}
@ -191,43 +228,99 @@ func (c *StateMvCommand) Run(args []string) int {
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
addrTo, ok := destAddr.(addrs.AbsResourceInstance)
if !ok {
ra, ok := destAddr.(addrs.AbsResource)
if !ok {
diags = diags.Append(tfdiags.Sourceless(
tfdiags.Error,
msgInvalidTarget,
fmt.Sprintf("Cannot move %s to %s: the target must also be a resource instance.", addrFrom, addrTo),
))
c.showDiagnostics(diags)
return 1
}
addrTo = ra.Instance(addrs.NoKey)
}
diags = diags.Append(c.validateResourceMove(addrFrom.ContainingResource(), addrTo.ContainingResource()))
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
diags = diags.Append(tfdiags.Sourceless(
tfdiags.Error,
msgInvalidTarget,
fmt.Sprintf("Cannot move to %s: %s does not exist in the current state.", addrTo, addrTo.Module),
))
}
if stateTo.ResourceInstance(addrTo) != nil {
c.Ui.Error(fmt.Sprintf(
errStateMv, "destination resource instance already exists"))
diags = diags.Append(tfdiags.Sourceless(
tfdiags.Error,
msgInvalidTarget,
fmt.Sprintf("Cannot move to %s: there is already a resource instance at that address in the current state.", addrTo),
))
}
is := ssFrom.ResourceInstance(addrFrom)
if is == nil {
diags = diags.Append(tfdiags.Sourceless(
tfdiags.Error,
msgInvalidSource,
fmt.Sprintf("The current state does not contain %s.", addrFrom),
))
}
if diags.HasErrors() {
c.showDiagnostics(diags)
return 1
}
moved++
c.Ui.Output(fmt.Sprintf("%s %q to %q", prefix, addrFrom.String(), args[1]))
if !dryRun {
fromResourceAddr := addrFrom.ContainingResource()
fromProviderAddr := ssFrom.Resource(fromResourceAddr).ProviderConfig
ssFrom.ForgetResourceInstanceAll(addrFrom)
ssFrom.RemoveResourceIfEmpty(addrFrom.ContainingResource())
ssFrom.RemoveResourceIfEmpty(fromResourceAddr)
rs := stateTo.Resource(addrTo.ContainingResource())
rs.Instances[addrTo.Resource.Key] = result.Value.(*states.ResourceInstance)
if rs == nil {
// If we're moving to an address without an index then that
// suggests the user's intent is to establish both the
// resource and the instance at the same time (since the
// address covers both), but if there's an index in the
// target then the resource must already exist.
if addrTo.Resource.Key != addrs.NoKey {
diags = diags.Append(tfdiags.Sourceless(
tfdiags.Error,
msgInvalidTarget,
fmt.Sprintf("Cannot move to %s: %s does not exist in the current state.", addrTo, addrTo.ContainingResource()),
))
c.showDiagnostics(diags)
return 1
}
resourceAddr := addrTo.ContainingResource()
stateTo.SyncWrapper().SetResourceMeta(
resourceAddr,
states.NoEach,
fromProviderAddr, // in this case, we bring the provider along as if we were moving the whole resource
)
rs = stateTo.Resource(resourceAddr)
}
rs.Instances[addrTo.Resource.Key] = is
}
default:
diags = diags.Append(tfdiags.Sourceless(
tfdiags.Error,
msgInvalidSource,
fmt.Sprintf("Cannot move %s: Terraform doesn't know how to move this object.", rawAddrFrom),
))
}
}
@ -260,6 +353,8 @@ func (c *StateMvCommand) Run(args []string) int {
}
}
c.showDiagnostics(diags)
if moved == 0 {
c.Ui.Output("No matching objects found.")
} else {
@ -268,24 +363,82 @@ func (c *StateMvCommand) Run(args []string) int {
return 0
}
// 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) moveableResult(results []*states.FilterResult) []*states.FilterResult {
result := results[:1]
// sourceObjectAddrs takes a single source object address and expands it to
// potentially multiple objects that need to be handled within it.
//
// In particular, this handles the case where a module is requested directly:
// if it has any child modules, then they must also be moved. It also resolves
// the ambiguity that an index-less resource address could either be a resource
// address or a resource instance address, by making a decision about which
// is intended based on the current state of the resource in question.
func (c *StateMvCommand) sourceObjectAddrs(state *states.State, matched addrs.Targetable) []addrs.Targetable {
var ret []addrs.Targetable
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 _, ok := r.Address.(addrs.ModuleInstance); ok {
result = append(result, r)
}
switch addr := matched.(type) {
case addrs.ModuleInstance:
for _, mod := range state.Modules {
if len(mod.Addr) < len(addr) {
continue // can't possibly be our selection or a child of it
}
if !mod.Addr[:len(addr)].Equal(addr) {
continue
}
ret = append(ret, mod.Addr)
}
case addrs.AbsResource:
// If this refers to a resource without "count" or "for_each" set then
// we'll assume the user intended it to be a resource instance
// address instead, to allow for requests like this:
// terraform state mv aws_instance.foo aws_instance.bar[1]
// That wouldn't be allowed if aws_instance.foo had multiple instances
// since we can't move multiple instances into one.
if rs := state.Resource(addr); rs != nil && rs.EachMode == states.NoEach {
ret = append(ret, addr.Instance(addrs.NoKey))
} else {
ret = append(ret, addr)
}
default:
ret = append(ret, matched)
}
return result
return ret
}
func (c *StateMvCommand) validateResourceMove(addrFrom, addrTo addrs.AbsResource) tfdiags.Diagnostics {
const msgInvalidRequest = "Invalid state move request"
var diags tfdiags.Diagnostics
if addrFrom.Resource.Mode != addrTo.Resource.Mode {
switch addrFrom.Resource.Mode {
case addrs.ManagedResourceMode:
diags = diags.Append(tfdiags.Sourceless(
tfdiags.Error,
msgInvalidRequest,
fmt.Sprintf("Cannot move %s to %s: a managed resource can be moved only to another managed resource address.", addrFrom, addrTo),
))
case addrs.DataResourceMode:
diags = diags.Append(tfdiags.Sourceless(
tfdiags.Error,
msgInvalidRequest,
fmt.Sprintf("Cannot move %s to %s: a data resource can be moved only to another data resource address.", addrFrom, addrTo),
))
default:
// In case a new mode is added in future, this unhelpful error is better than nothing.
diags = diags.Append(tfdiags.Sourceless(
tfdiags.Error,
msgInvalidRequest,
fmt.Sprintf("Cannot move %s to %s: cannot change resource mode.", addrFrom, addrTo),
))
}
}
if addrFrom.Resource.Type != addrTo.Resource.Type {
diags = diags.Append(tfdiags.Sourceless(
tfdiags.Error,
msgInvalidRequest,
fmt.Sprintf("Cannot move %s to %s: resource types don't match.", addrFrom, addrTo),
))
}
return diags
}
func (c *StateMvCommand) Help() string {

View File

@ -75,6 +75,168 @@ func TestStateMv(t *testing.T) {
testStateOutput(t, backups[0], testStateMvOutputOriginal)
}
func TestStateMv_resourceToInstance(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),
)
s.SetResourceInstanceCurrent(
addrs.Resource{
Mode: addrs.ManagedResourceMode,
Type: "test_instance",
Name: "baz",
}.Instance(addrs.NoKey).Absolute(addrs.RootModuleInstance),
&states.ResourceInstanceObjectSrc{
AttrsJSON: []byte(`{"id":"foo","foo":"value","bar":"value"}`),
Status: states.ObjectReady,
},
addrs.ProviderConfig{Type: "test"}.Absolute(addrs.RootModuleInstance),
)
s.SetResourceMeta(
addrs.Resource{
Mode: addrs.ManagedResourceMode,
Type: "test_instance",
Name: "bar",
}.Absolute(addrs.RootModuleInstance),
states.EachList,
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_instance.bar[0]",
}
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, `
test_instance.bar.0:
ID = bar
provider = provider.test
bar = value
foo = value
test_instance.baz:
ID = foo
provider = provider.test
bar = value
foo = value
`)
// 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_instanceToResource(t *testing.T) {
state := states.BuildState(func(s *states.SyncState) {
s.SetResourceInstanceCurrent(
addrs.Resource{
Mode: addrs.ManagedResourceMode,
Type: "test_instance",
Name: "foo",
}.Instance(addrs.IntKey(0)).Absolute(addrs.RootModuleInstance),
&states.ResourceInstanceObjectSrc{
AttrsJSON: []byte(`{"id":"bar","foo":"value","bar":"value"}`),
Status: states.ObjectReady,
},
addrs.ProviderConfig{Type: "test"}.Absolute(addrs.RootModuleInstance),
)
s.SetResourceInstanceCurrent(
addrs.Resource{
Mode: addrs.ManagedResourceMode,
Type: "test_instance",
Name: "baz",
}.Instance(addrs.NoKey).Absolute(addrs.RootModuleInstance),
&states.ResourceInstanceObjectSrc{
AttrsJSON: []byte(`{"id":"foo","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[0]",
"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, `
test_instance.bar:
ID = bar
provider = provider.test
bar = value
foo = value
test_instance.baz:
ID = foo
provider = provider.test
bar = value
foo = value
`)
// Test we have backups
backups := testStateBackups(t, filepath.Dir(statePath))
if len(backups) != 1 {
t.Fatalf("bad: %#v", backups)
}
testStateOutput(t, backups[0], `
test_instance.baz:
ID = foo
provider = provider.test
bar = value
foo = value
test_instance.foo.0:
ID = bar
provider = provider.test
bar = value
foo = value
`)
}
func TestStateMv_differentResourceTypes(t *testing.T) {
state := states.BuildState(func(s *states.SyncState) {
s.SetResourceInstanceCurrent(
@ -112,7 +274,7 @@ func TestStateMv_differentResourceTypes(t *testing.T) {
t.Fatalf("expected error output, got:\n%s", ui.OutputWriter.String())
}
if !strings.Contains(ui.ErrorWriter.String(), "resource types do not match") {
if !strings.Contains(ui.ErrorWriter.String(), "resource types don't match") {
t.Fatalf("expected initialization error, got:\n%s", ui.ErrorWriter.String())
}
}

View File

@ -3,12 +3,11 @@ package command
import (
"context"
"fmt"
"sort"
"strings"
"github.com/hashicorp/terraform/addrs"
"github.com/hashicorp/terraform/command/clistate"
"github.com/hashicorp/terraform/states"
"github.com/hashicorp/terraform/tfdiags"
"github.com/mitchellh/cli"
)
@ -67,67 +66,33 @@ 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
// This command primarily works with resource instances, though it will
// also clean up any modules and resources left empty by actions it takes.
var addrs []addrs.AbsResourceInstance
var diags tfdiags.Diagnostics
for _, addrStr := range args {
moreAddrs, moreDiags := c.lookupResourceInstanceAddr(state, true, addrStr)
addrs = append(addrs, moreAddrs...)
diags = diags.Append(moreDiags)
}
if diags.HasErrors() {
c.showDiagnostics(diags)
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 removed nothing.")
} else {
c.Ui.Output("No matching resources found.")
}
return 0
}
prefix := "Remove resource "
prefix := "Removed "
if dryRun {
prefix = "Would remove resource "
prefix = "Would remove "
}
var isCount int
ss := state.SyncWrapper()
for _, result := range results {
switch addr := result.Address.(type) {
case addrs.ModuleInstance:
var output []string
for _, rs := range result.Value.(*states.Module).Resources {
for k := range rs.Instances {
isCount++
output = append(output, prefix+rs.Addr.Absolute(addr).Instance(k).String())
}
}
if len(output) > 0 {
c.Ui.Output(strings.Join(sort.StringSlice(output), "\n"))
}
if !dryRun {
ss.RemoveModule(addr)
}
case addrs.AbsResource:
var output []string
for k := range result.Value.(*states.Resource).Instances {
isCount++
output = append(output, prefix+addr.Instance(k).String())
}
if len(output) > 0 {
c.Ui.Output(strings.Join(sort.StringSlice(output), "\n"))
}
if !dryRun {
ss.RemoveResource(addr)
}
case addrs.AbsResourceInstance:
isCount++
c.Ui.Output(prefix + addr.String())
if !dryRun {
ss.ForgetResourceInstanceAll(addr)
ss.RemoveResourceIfEmpty(addr.ContainingResource())
}
for _, addr := range addrs {
isCount++
c.Ui.Output(prefix + addr.String())
if !dryRun {
ss.ForgetResourceInstanceAll(addr)
ss.RemoveResourceIfEmpty(addr.ContainingResource())
}
}
@ -147,10 +112,14 @@ func (c *StateRmCommand) Run(args []string) int {
return 1
}
if len(diags) > 0 {
c.showDiagnostics(diags)
}
if isCount == 0 {
c.Ui.Output("No matching resources found.")
c.Ui.Output("No matching resource instances found.")
} else {
c.Ui.Output(fmt.Sprintf("Successfully removed %d resource(s).", isCount))
c.Ui.Output(fmt.Sprintf("Successfully removed %d resource instance(s).", isCount))
}
return 0
}
@ -159,15 +128,18 @@ func (c *StateRmCommand) Help() string {
helpText := `
Usage: terraform state rm [options] ADDRESS...
Remove one or more items from the Terraform state.
Remove one or more items from the Terraform state, causing Terraform to
"forget" those items without first destroying them in the remote system.
This command removes one or more resource instances from the Terraform state
based on the addresses given. You can view and list the available instances
with "terraform state list".
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 give the address of an entire module then all of the instances in
that module and any of its child modules will be removed from the state.
If you give the address of a resource that has "count" or "for_each" set,
all of the instances of that resource will be removed from the state.
Options:
@ -175,16 +147,14 @@ Options:
doesn't actually remove anything.
-backup=PATH Path where Terraform should write the backup
state. This can't be disabled. If not set, Terraform
will write it to the same path as the statefile with
a backup extension.
state.
-lock=true Lock the state file when locking is supported.
-lock-timeout=0s Duration to retry a state lock.
-state=PATH Path to the source state file. Defaults to the configured
backend, or "terraform.tfstate"
-state=PATH Path to the state file to update. Defaults to the current
workspace state.
`
return strings.TrimSpace(helpText)

View File

@ -72,6 +72,89 @@ func TestStateRm(t *testing.T) {
testStateOutput(t, backups[0], testStateRmOutputOriginal)
}
func TestStateRmNotChildModule(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),
)
// This second instance has the same local address as the first but
// is in a child module. Older versions of Terraform would incorrectly
// remove this one too, since they failed to check the module address.
s.SetResourceInstanceCurrent(
addrs.Resource{
Mode: addrs.ManagedResourceMode,
Type: "test_instance",
Name: "foo",
}.Instance(addrs.NoKey).Absolute(addrs.RootModuleInstance.Child("child", addrs.NoKey)),
&states.ResourceInstanceObjectSrc{
AttrsJSON: []byte(`{"id":"foo","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 := &StateRmCommand{
StateMeta{
Meta: Meta{
testingOverrides: metaOverridesForProvider(p),
Ui: ui,
},
},
}
args := []string{
"-state", statePath,
"test_instance.foo",
}
if code := c.Run(args); code != 0 {
t.Fatalf("bad: %d\n\n%s", code, ui.ErrorWriter.String())
}
// Test it is correct
testStateOutput(t, statePath, `
<no state>
module.child:
test_instance.foo:
ID = foo
provider = provider.test
bar = value
foo = value
`)
// Test we have backups
backups := testStateBackups(t, filepath.Dir(statePath))
if len(backups) != 1 {
t.Fatalf("bad: %#v", backups)
}
testStateOutput(t, backups[0], `
test_instance.foo:
ID = bar
provider = provider.test
bar = value
foo = value
module.child:
test_instance.foo:
ID = foo
provider = provider.test
bar = value
foo = value
`)
}
func TestStateRmNoArgs(t *testing.T) {
state := states.BuildState(func(s *states.SyncState) {
s.SetResourceInstanceCurrent(
@ -173,7 +256,7 @@ func TestStateRmNonExist(t *testing.T) {
t.Fatalf("expected exit status %d, got: %d", 0, code)
}
if msg := ui.OutputWriter.String(); !strings.Contains(msg, "No matching resources found") {
if msg := ui.OutputWriter.String(); !strings.Contains(msg, "No matching resource instances found") {
t.Fatalf("unexpected output:\n%s", msg)
}

View File

@ -1,180 +0,0 @@
package states
import (
"fmt"
"sort"
"github.com/hashicorp/terraform/addrs"
)
// Filter is responsible for filtering and searching a state.
//
// This is a separate struct from State rather than a method on State
// because Filter might create sidecar data structures to optimize
// filtering on the state.
//
// If you change the State, the filter created is invalid and either
// Reset should be called or a new one should be allocated. Filter
// will not watch State for changes and do this for you. If you filter after
// changing the State without calling Reset, the behavior is not defined.
type Filter struct {
State *State
}
// Filter takes the addresses specified by fs and finds all the matches.
// The values of fs are resource addressing syntax that can be parsed by
// ParseResourceAddress.
func (f *Filter) Filter(fs ...string) ([]*FilterResult, error) {
// Parse all the addresses
as := make([]addrs.Targetable, len(fs))
for i, v := range fs {
if addr, diags := addrs.ParseModuleInstanceStr(v); !diags.HasErrors() {
as[i] = addr
continue
}
if addr, diags := addrs.ParseAbsResourceStr(v); !diags.HasErrors() {
as[i] = addr
continue
}
if addr, diags := addrs.ParseAbsResourceInstanceStr(v); !diags.HasErrors() {
as[i] = addr
continue
}
return nil, fmt.Errorf("Error parsing address: %s", v)
}
// If we weren't given any filters, then we list all
if len(fs) == 0 {
as = append(as, addrs.Targetable(nil))
}
// Filter each of the address. We keep track of this in a map to
// strip duplicates.
resultSet := make(map[string]*FilterResult)
for _, addr := range as {
for _, v := range f.filterSingle(addr) {
resultSet[v.String()] = v
}
}
// Make the result list
results := make([]*FilterResult, 0, len(resultSet))
for _, v := range resultSet {
results = append(results, v)
}
// 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
}
func (f *Filter) filterSingle(addr addrs.Targetable) []*FilterResult {
// The slice to keep track of results
var results []*FilterResult
// Check if we received a module instance address that
// should be used as module filter, and if not set the
// filter to the root module instance.
filter, ok := addr.(addrs.ModuleInstance)
if !ok {
filter = addrs.RootModuleInstance
}
// Go through modules first.
modules := make([]*Module, 0, len(f.State.Modules))
for _, m := range f.State.Modules {
if filter.IsRoot() || filter.Equal(m.Addr) || filter.IsAncestor(m.Addr) {
modules = append(modules, m)
// Only add the module to the results if we searched
// for a non-root module and found a (partial) match.
if (addr == nil && !m.Addr.IsRoot()) ||
(!filter.IsRoot() && (filter.Equal(m.Addr) || filter.IsAncestor(m.Addr))) {
results = append(results, &FilterResult{
Address: m.Addr,
Value: m,
})
}
}
}
// With the modules set, go through all the resources within
// the modules to find relevant resources.
for _, m := range modules {
for _, rs := range m.Resources {
if f.relevant(addr, rs.Addr.Absolute(m.Addr), addrs.NoKey) {
results = append(results, &FilterResult{
Address: rs.Addr.Absolute(m.Addr),
Value: rs,
})
}
for key, is := range rs.Instances {
if f.relevant(addr, rs.Addr.Absolute(m.Addr), key) {
results = append(results, &FilterResult{
Address: rs.Addr.Absolute(m.Addr).Instance(key),
Value: is,
})
}
}
}
}
return results
}
func (f *Filter) relevant(filter addrs.Targetable, rs addrs.AbsResource, key addrs.InstanceKey) bool {
switch filter := filter.(type) {
case addrs.AbsResource:
if filter.Module != nil {
return filter.Equal(rs)
}
return filter.Resource.Equal(rs.Resource)
case addrs.AbsResourceInstance:
if filter.Module != nil {
return filter.Equal(rs.Instance(key))
}
return filter.Resource.Equal(rs.Resource.Instance(key))
default:
return true
}
}
// FilterResult is a single result from a filter operation. Filter can
// match multiple things within a state (curently modules and resources).
type FilterResult struct {
// Address is the address that can be used to reference this exact result.
Address addrs.Targetable
// Value is the actual value. This must be type switched on. It can be
// any either a `Module` or `ResourceInstance`.
Value interface{}
}
func (r *FilterResult) String() string {
return fmt.Sprintf("%T: %s", r.Value, r.Address)
}
func (r *FilterResult) SortedType() int {
switch r.Value.(type) {
case *Module:
return 0
case *Resource:
return 1
case *ResourceInstance:
return 2
default:
return 50
}
}

View File

@ -1,516 +0,0 @@
package states
import (
"reflect"
"testing"
"github.com/hashicorp/terraform/addrs"
)
func TestFilterFilter(t *testing.T) {
cases := map[string]struct {
State *State
Filters []string
Expected []string
}{
"all": {
testStateSmall(),
[]string{},
[]string{
"*states.Resource: aws_key_pair.onprem",
"*states.ResourceInstance: aws_key_pair.onprem",
"*states.Module: module.boot",
"*states.Resource: module.boot.aws_route53_record.oasis-consul-boot-a",
"*states.ResourceInstance: module.boot.aws_route53_record.oasis-consul-boot-a",
"*states.Resource: module.boot.aws_route53_record.oasis-consul-boot-ns",
"*states.ResourceInstance: module.boot.aws_route53_record.oasis-consul-boot-ns",
"*states.Resource: module.boot.aws_route53_zone.oasis-consul-boot",
"*states.ResourceInstance: module.boot.aws_route53_zone.oasis-consul-boot",
},
},
"single resource": {
testStateSmall(),
[]string{"aws_key_pair.onprem"},
[]string{
"*states.Resource: aws_key_pair.onprem",
"*states.ResourceInstance: aws_key_pair.onprem",
},
},
"single resource from minimal state": {
testStateSingleMinimal(),
[]string{"aws_instance.web"},
[]string{
"*states.Resource: aws_instance.web",
"*states.ResourceInstance: aws_instance.web",
},
},
"single resource with similar names": {
testStateSmallTestInstance(),
[]string{"test_instance.foo"},
[]string{
"*states.Resource: test_instance.foo",
"*states.ResourceInstance: test_instance.foo",
},
},
"module filter": {
testStateComplete(),
[]string{"module.boot"},
[]string{
"*states.Module: module.boot",
"*states.Resource: module.boot.aws_route53_record.oasis-consul-boot-a",
"*states.ResourceInstance: module.boot.aws_route53_record.oasis-consul-boot-a",
"*states.Resource: module.boot.aws_route53_record.oasis-consul-boot-ns",
"*states.ResourceInstance: module.boot.aws_route53_record.oasis-consul-boot-ns",
"*states.Resource: module.boot.aws_route53_zone.oasis-consul-boot",
"*states.ResourceInstance: module.boot.aws_route53_zone.oasis-consul-boot",
},
},
"resource in module": {
testStateComplete(),
[]string{"module.boot.aws_route53_zone.oasis-consul-boot"},
[]string{
"*states.Resource: module.boot.aws_route53_zone.oasis-consul-boot",
"*states.ResourceInstance: module.boot.aws_route53_zone.oasis-consul-boot",
},
},
"resource in module 2": {
testStateResourceInModule(),
[]string{"module.foo.aws_instance.foo"},
[]string{},
},
"single count index": {
testStateComplete(),
[]string{"module.consul.aws_instance.consul-green[0]"},
[]string{
"*states.ResourceInstance: module.consul.aws_instance.consul-green[0]",
},
},
"no count index": {
testStateComplete(),
[]string{"module.consul.aws_instance.consul-green"},
[]string{
"*states.Resource: module.consul.aws_instance.consul-green",
"*states.ResourceInstance: module.consul.aws_instance.consul-green[0]",
"*states.ResourceInstance: module.consul.aws_instance.consul-green[1]",
"*states.ResourceInstance: module.consul.aws_instance.consul-green[2]",
},
},
"nested modules": {
testStateNestedModules(),
[]string{"module.outer"},
[]string{
"*states.Module: module.outer",
"*states.Module: module.outer.module.child1",
"*states.Resource: module.outer.module.child1.aws_instance.foo",
"*states.ResourceInstance: module.outer.module.child1.aws_instance.foo",
"*states.Module: module.outer.module.child2",
"*states.Resource: module.outer.module.child2.aws_instance.foo",
"*states.ResourceInstance: module.outer.module.child2.aws_instance.foo",
},
},
}
for n, tc := range cases {
// Create the filter
filter := &Filter{State: tc.State}
// Filter!
results, err := filter.Filter(tc.Filters...)
if err != nil {
t.Fatalf("%q: err: %s", n, err)
}
actual := make([]string, len(results))
for i, result := range results {
actual[i] = result.String()
}
if !reflect.DeepEqual(actual, tc.Expected) {
t.Fatalf("%q: expected, then actual\n\n%#v\n\n%#v", n, tc.Expected, actual)
}
}
}
// testStateComplete returns a test State structure.
func testStateComplete() *State {
root := addrs.RootModuleInstance
boot, _ := addrs.ParseModuleInstanceStr("module.boot")
consul, _ := addrs.ParseModuleInstanceStr("module.consul")
vault, _ := addrs.ParseModuleInstanceStr("module.vault")
return BuildState(func(s *SyncState) {
s.SetResourceInstanceCurrent(
addrs.Resource{
Mode: addrs.ManagedResourceMode,
Type: "aws_key_pair",
Name: "onprem",
}.Instance(addrs.NoKey).Absolute(root),
&ResourceInstanceObjectSrc{
AttrsJSON: []byte(`{"id": "1234567890"}`),
Status: ObjectReady,
},
addrs.ProviderConfig{
Type: "aws",
}.Absolute(root),
)
s.SetResourceInstanceCurrent(
addrs.Resource{
Mode: addrs.ManagedResourceMode,
Type: "aws_route53_record",
Name: "oasis-consul-boot-a",
}.Instance(addrs.NoKey).Absolute(boot),
&ResourceInstanceObjectSrc{
AttrsJSON: []byte(`{"id": "1234567890"}`),
Status: ObjectReady,
},
addrs.ProviderConfig{
Type: "aws",
}.Absolute(boot),
)
s.SetResourceInstanceCurrent(
addrs.Resource{
Mode: addrs.ManagedResourceMode,
Type: "aws_route53_record",
Name: "oasis-consul-boot-ns",
}.Instance(addrs.NoKey).Absolute(boot),
&ResourceInstanceObjectSrc{
AttrsJSON: []byte(`{"id": "1234567890"}`),
Status: ObjectReady,
},
addrs.ProviderConfig{
Type: "aws",
}.Absolute(boot),
)
s.SetResourceInstanceCurrent(
addrs.Resource{
Mode: addrs.ManagedResourceMode,
Type: "aws_route53_zone",
Name: "oasis-consul-boot",
}.Instance(addrs.NoKey).Absolute(boot),
&ResourceInstanceObjectSrc{
AttrsJSON: []byte(`{"id": "1234567890"}`),
Status: ObjectReady,
},
addrs.ProviderConfig{
Type: "aws",
}.Absolute(boot),
)
s.SetResourceInstanceCurrent(
addrs.Resource{
Mode: addrs.ManagedResourceMode,
Type: "aws_instance",
Name: "consul-green",
}.Instance(addrs.IntKey(0)).Absolute(consul),
&ResourceInstanceObjectSrc{
AttrsJSON: []byte(`{"id": "1234567890"}`),
Status: ObjectReady,
},
addrs.ProviderConfig{
Type: "aws",
}.Absolute(consul),
)
s.SetResourceInstanceCurrent(
addrs.Resource{
Mode: addrs.ManagedResourceMode,
Type: "aws_instance",
Name: "consul-green",
}.Instance(addrs.IntKey(1)).Absolute(consul),
&ResourceInstanceObjectSrc{
AttrsJSON: []byte(`{"id": "1234567890"}`),
Status: ObjectReady,
},
addrs.ProviderConfig{
Type: "aws",
}.Absolute(consul),
)
s.SetResourceInstanceCurrent(
addrs.Resource{
Mode: addrs.ManagedResourceMode,
Type: "aws_instance",
Name: "consul-green",
}.Instance(addrs.IntKey(2)).Absolute(consul),
&ResourceInstanceObjectSrc{
AttrsJSON: []byte(`{"id": "1234567890"}`),
Status: ObjectReady,
},
addrs.ProviderConfig{
Type: "aws",
}.Absolute(consul),
)
s.SetResourceInstanceCurrent(
addrs.Resource{
Mode: addrs.ManagedResourceMode,
Type: "aws_security_group",
Name: "consul",
}.Instance(addrs.NoKey).Absolute(consul),
&ResourceInstanceObjectSrc{
AttrsJSON: []byte(`{"id": "1234567890"}`),
Status: ObjectReady,
},
addrs.ProviderConfig{
Type: "aws",
}.Absolute(consul),
)
s.SetResourceInstanceCurrent(
addrs.Resource{
Mode: addrs.ManagedResourceMode,
Type: "aws_elb",
Name: "vault",
}.Instance(addrs.NoKey).Absolute(vault),
&ResourceInstanceObjectSrc{
AttrsJSON: []byte(`{"id": "1234567890"}`),
Status: ObjectReady,
},
addrs.ProviderConfig{
Type: "aws",
}.Absolute(vault),
)
s.SetResourceInstanceCurrent(
addrs.Resource{
Mode: addrs.ManagedResourceMode,
Type: "aws_instance",
Name: "vault",
}.Instance(addrs.IntKey(0)).Absolute(vault),
&ResourceInstanceObjectSrc{
AttrsJSON: []byte(`{"id": "1234567890"}`),
Status: ObjectReady,
},
addrs.ProviderConfig{
Type: "aws",
}.Absolute(vault),
)
s.SetResourceInstanceCurrent(
addrs.Resource{
Mode: addrs.ManagedResourceMode,
Type: "aws_instance",
Name: "vault",
}.Instance(addrs.IntKey(1)).Absolute(vault),
&ResourceInstanceObjectSrc{
AttrsJSON: []byte(`{"id": "1234567890"}`),
Status: ObjectReady,
},
addrs.ProviderConfig{
Type: "aws",
}.Absolute(vault),
)
s.SetResourceInstanceCurrent(
addrs.Resource{
Mode: addrs.ManagedResourceMode,
Type: "aws_instance",
Name: "vault",
}.Instance(addrs.IntKey(2)).Absolute(vault),
&ResourceInstanceObjectSrc{
AttrsJSON: []byte(`{"id": "1234567890"}`),
Status: ObjectReady,
},
addrs.ProviderConfig{
Type: "aws",
}.Absolute(vault),
)
s.SetResourceInstanceCurrent(
addrs.Resource{
Mode: addrs.ManagedResourceMode,
Type: "aws_security_group",
Name: "vault",
}.Instance(addrs.NoKey).Absolute(vault),
&ResourceInstanceObjectSrc{
AttrsJSON: []byte(`{"id": "1234567890"}`),
Status: ObjectReady,
},
addrs.ProviderConfig{
Type: "aws",
}.Absolute(vault),
)
})
}
// testStateNestedModules returns a test State structure.
func testStateNestedModules() *State {
outer, _ := addrs.ParseModuleInstanceStr("module.outer")
child1, _ := addrs.ParseModuleInstanceStr("module.outer.module.child1")
child2, _ := addrs.ParseModuleInstanceStr("module.outer.module.child2")
state := BuildState(func(s *SyncState) {
s.SetResourceInstanceCurrent(
addrs.Resource{
Mode: addrs.ManagedResourceMode,
Type: "aws_instance",
Name: "foo",
}.Instance(addrs.NoKey).Absolute(child1),
&ResourceInstanceObjectSrc{
AttrsJSON: []byte(`{"id": "1234567890"}`),
Status: ObjectReady,
},
addrs.ProviderConfig{
Type: "aws",
}.Absolute(child1),
)
s.SetResourceInstanceCurrent(
addrs.Resource{
Mode: addrs.ManagedResourceMode,
Type: "aws_instance",
Name: "foo",
}.Instance(addrs.NoKey).Absolute(child2),
&ResourceInstanceObjectSrc{
AttrsJSON: []byte(`{"id": "1234567890"}`),
Status: ObjectReady,
},
addrs.ProviderConfig{
Type: "aws",
}.Absolute(child2),
)
})
state.Modules[outer.String()] = NewModule(outer)
return state
}
// testStateSingleMinimal returns a test State structure.
func testStateSingleMinimal() *State {
return BuildState(func(s *SyncState) {
s.SetResourceInstanceCurrent(
addrs.Resource{
Mode: addrs.ManagedResourceMode,
Type: "aws_instance",
Name: "web",
}.Instance(addrs.NoKey).Absolute(addrs.RootModuleInstance),
&ResourceInstanceObjectSrc{
AttrsJSON: []byte(`{"id": "1234567890"}`),
Status: ObjectReady,
},
addrs.ProviderConfig{
Type: "aws",
}.Absolute(addrs.RootModuleInstance),
)
})
}
// testStateSmall returns a test State structure.
func testStateSmall() *State {
root := addrs.RootModuleInstance
boot, _ := addrs.ParseModuleInstanceStr("module.boot")
return BuildState(func(s *SyncState) {
s.SetResourceInstanceCurrent(
addrs.Resource{
Mode: addrs.ManagedResourceMode,
Type: "aws_key_pair",
Name: "onprem",
}.Instance(addrs.NoKey).Absolute(root),
&ResourceInstanceObjectSrc{
AttrsJSON: []byte(`{"id": "1234567890"}`),
Status: ObjectReady,
},
addrs.ProviderConfig{
Type: "aws",
}.Absolute(root),
)
s.SetResourceInstanceCurrent(
addrs.Resource{
Mode: addrs.ManagedResourceMode,
Type: "aws_route53_record",
Name: "oasis-consul-boot-a",
}.Instance(addrs.NoKey).Absolute(boot),
&ResourceInstanceObjectSrc{
AttrsJSON: []byte(`{"id": "1234567890"}`),
Status: ObjectReady,
},
addrs.ProviderConfig{
Type: "aws",
}.Absolute(boot),
)
s.SetResourceInstanceCurrent(
addrs.Resource{
Mode: addrs.ManagedResourceMode,
Type: "aws_route53_record",
Name: "oasis-consul-boot-ns",
}.Instance(addrs.NoKey).Absolute(boot),
&ResourceInstanceObjectSrc{
AttrsJSON: []byte(`{"id": "1234567890"}`),
Status: ObjectReady,
},
addrs.ProviderConfig{
Type: "aws",
}.Absolute(boot),
)
s.SetResourceInstanceCurrent(
addrs.Resource{
Mode: addrs.ManagedResourceMode,
Type: "aws_route53_zone",
Name: "oasis-consul-boot",
}.Instance(addrs.NoKey).Absolute(boot),
&ResourceInstanceObjectSrc{
AttrsJSON: []byte(`{"id": "1234567890"}`),
Status: ObjectReady,
},
addrs.ProviderConfig{
Type: "aws",
}.Absolute(boot),
)
})
}
// testStateSmallTestInstance returns a test State structure.
func testStateSmallTestInstance() *State {
return BuildState(func(s *SyncState) {
s.SetResourceInstanceCurrent(
addrs.Resource{
Mode: addrs.ManagedResourceMode,
Type: "test_instance",
Name: "foo",
}.Instance(addrs.NoKey).Absolute(addrs.RootModuleInstance),
&ResourceInstanceObjectSrc{
AttrsJSON: []byte(`{"id": "1234567890"}`),
Status: ObjectReady,
},
addrs.ProviderConfig{
Type: "test",
}.Absolute(addrs.RootModuleInstance),
)
s.SetResourceInstanceCurrent(
addrs.Resource{
Mode: addrs.ManagedResourceMode,
Type: "test_instance",
Name: "bar",
}.Instance(addrs.NoKey).Absolute(addrs.RootModuleInstance),
&ResourceInstanceObjectSrc{
AttrsJSON: []byte(`{"id": "1234567890"}`),
Status: ObjectReady,
},
addrs.ProviderConfig{
Type: "test",
}.Absolute(addrs.RootModuleInstance),
)
})
}
// testStateResourceInModule returns a test State structure.
func testStateResourceInModule() *State {
foo, _ := addrs.ParseModuleInstanceStr("module.foo")
return BuildState(func(s *SyncState) {
s.SetResourceInstanceCurrent(
addrs.Resource{
Mode: addrs.ManagedResourceMode,
Type: "aws_instance",
Name: "bar",
}.Instance(addrs.NoKey).Absolute(foo),
&ResourceInstanceObjectSrc{
AttrsJSON: []byte(`{"id": "1234567890"}`),
Status: ObjectReady,
},
addrs.ProviderConfig{
Type: "aws",
}.Absolute(foo),
)
})
}