restore (via copypaste) terraform.State.Remove
This commit is contained in:
parent
3e9c51c726
commit
660a854668
|
@ -17,9 +17,9 @@ import (
|
||||||
"sync"
|
"sync"
|
||||||
|
|
||||||
"github.com/hashicorp/errwrap"
|
"github.com/hashicorp/errwrap"
|
||||||
"github.com/hashicorp/go-multierror"
|
multierror "github.com/hashicorp/go-multierror"
|
||||||
"github.com/hashicorp/go-uuid"
|
uuid "github.com/hashicorp/go-uuid"
|
||||||
"github.com/hashicorp/go-version"
|
version "github.com/hashicorp/go-version"
|
||||||
"github.com/hashicorp/hcl2/hcl"
|
"github.com/hashicorp/hcl2/hcl"
|
||||||
"github.com/hashicorp/hcl2/hcl/hclsyntax"
|
"github.com/hashicorp/hcl2/hcl/hclsyntax"
|
||||||
"github.com/mitchellh/copystructure"
|
"github.com/mitchellh/copystructure"
|
||||||
|
@ -328,6 +328,125 @@ func (s *State) Validate() error {
|
||||||
return result
|
return result
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Remove removes the item in the state at the given address, returning
|
||||||
|
// any errors that may have occurred.
|
||||||
|
//
|
||||||
|
// If the address references a module state or resource, it will delete
|
||||||
|
// all children as well. To check what will be deleted, use a StateFilter
|
||||||
|
// first.
|
||||||
|
func (s *State) Remove(addr ...string) error {
|
||||||
|
s.Lock()
|
||||||
|
defer s.Unlock()
|
||||||
|
|
||||||
|
// Filter out what we need to delete
|
||||||
|
filter := &StateFilter{State: s}
|
||||||
|
results, err := filter.Filter(addr...)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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 len(results) == 0 {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Go through each result and grab what we need
|
||||||
|
removed := make(map[interface{}]struct{})
|
||||||
|
for _, r := range results {
|
||||||
|
// Convert the path to our own type
|
||||||
|
path := append([]string{"root"}, r.Path...)
|
||||||
|
|
||||||
|
// If we removed this already, then ignore
|
||||||
|
if _, ok := removed[r.Value]; ok {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
// If we removed the parent already, then ignore
|
||||||
|
if r.Parent != nil {
|
||||||
|
if _, ok := removed[r.Parent.Value]; ok {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add this to the removed list
|
||||||
|
removed[r.Value] = struct{}{}
|
||||||
|
|
||||||
|
switch v := r.Value.(type) {
|
||||||
|
case *ModuleState:
|
||||||
|
s.removeModule(path, v)
|
||||||
|
case *ResourceState:
|
||||||
|
s.removeResource(path, v)
|
||||||
|
case *InstanceState:
|
||||||
|
s.removeInstance(path, r.Parent.Value.(*ResourceState), v)
|
||||||
|
default:
|
||||||
|
return fmt.Errorf("unknown type to delete: %T", r.Value)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Prune since the removal functions often do the bare minimum to
|
||||||
|
// remove a thing and may leave around dangling empty modules, resources,
|
||||||
|
// etc. Prune will clean that all up.
|
||||||
|
s.prune()
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *State) removeModule(path []string, v *ModuleState) {
|
||||||
|
for i, m := range s.Modules {
|
||||||
|
if m == v {
|
||||||
|
s.Modules, s.Modules[len(s.Modules)-1] = append(s.Modules[:i], s.Modules[i+1:]...), nil
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *State) removeResource(path []string, v *ResourceState) {
|
||||||
|
// Get the module this resource lives in. If it doesn't exist, we're done.
|
||||||
|
mod := s.moduleByPath(normalizeModulePath(path))
|
||||||
|
if mod == nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Find this resource. This is a O(N) lookup when if we had the key
|
||||||
|
// it could be O(1) but even with thousands of resources this shouldn't
|
||||||
|
// matter right now. We can easily up performance here when the time comes.
|
||||||
|
for k, r := range mod.Resources {
|
||||||
|
if r == v {
|
||||||
|
// Found it
|
||||||
|
delete(mod.Resources, k)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *State) removeInstance(path []string, r *ResourceState, v *InstanceState) {
|
||||||
|
// Go through the resource and find the instance that matches this
|
||||||
|
// (if any) and remove it.
|
||||||
|
|
||||||
|
// Check primary
|
||||||
|
if r.Primary == v {
|
||||||
|
r.Primary = nil
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check lists
|
||||||
|
lists := [][]*InstanceState{r.Deposed}
|
||||||
|
for _, is := range lists {
|
||||||
|
for i, instance := range is {
|
||||||
|
if instance == v {
|
||||||
|
// Found it, remove it
|
||||||
|
is, is[len(is)-1] = append(is[:i], is[i+1:]...), nil
|
||||||
|
|
||||||
|
// Done
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// RootModule returns the ModuleState for the root module
|
// RootModule returns the ModuleState for the root module
|
||||||
func (s *State) RootModule() *ModuleState {
|
func (s *State) RootModule() *ModuleState {
|
||||||
root := s.ModuleByPath(addrs.RootModuleInstance)
|
root := s.ModuleByPath(addrs.RootModuleInstance)
|
||||||
|
|
|
@ -0,0 +1,267 @@
|
||||||
|
package terraform
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"sort"
|
||||||
|
)
|
||||||
|
|
||||||
|
// StateFilter is responsible for filtering and searching a state.
|
||||||
|
//
|
||||||
|
// This is a separate struct from State rather than a method on State
|
||||||
|
// because StateFilter 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. StateFilter
|
||||||
|
// 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 StateFilter 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 *StateFilter) Filter(fs ...string) ([]*StateFilterResult, error) {
|
||||||
|
// Parse all the addresses
|
||||||
|
as := make([]*ResourceAddress, len(fs))
|
||||||
|
for i, v := range fs {
|
||||||
|
a, err := ParseResourceAddress(v)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("Error parsing address '%s': %s", v, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
as[i] = a
|
||||||
|
}
|
||||||
|
|
||||||
|
// If we weren't given any filters, then we list all
|
||||||
|
if len(fs) == 0 {
|
||||||
|
as = append(as, &ResourceAddress{Index: -1})
|
||||||
|
}
|
||||||
|
|
||||||
|
// Filter each of the address. We keep track of this in a map to
|
||||||
|
// strip duplicates.
|
||||||
|
resultSet := make(map[string]*StateFilterResult)
|
||||||
|
for _, a := range as {
|
||||||
|
for _, r := range f.filterSingle(a) {
|
||||||
|
resultSet[r.String()] = r
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Make the result list
|
||||||
|
results := make([]*StateFilterResult, 0, len(resultSet))
|
||||||
|
for _, v := range resultSet {
|
||||||
|
results = append(results, v)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Sort them and return
|
||||||
|
sort.Sort(StateFilterResultSlice(results))
|
||||||
|
return results, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f *StateFilter) filterSingle(a *ResourceAddress) []*StateFilterResult {
|
||||||
|
// The slice to keep track of results
|
||||||
|
var results []*StateFilterResult
|
||||||
|
|
||||||
|
// Go through modules first.
|
||||||
|
modules := make([]*ModuleState, 0, len(f.State.Modules))
|
||||||
|
for _, m := range f.State.Modules {
|
||||||
|
if f.relevant(a, m) {
|
||||||
|
modules = append(modules, m)
|
||||||
|
|
||||||
|
// Only add the module to the results if we haven't specified a type.
|
||||||
|
// We also ignore the root module.
|
||||||
|
if a.Type == "" && len(m.Path) > 1 {
|
||||||
|
results = append(results, &StateFilterResult{
|
||||||
|
Path: m.Path[1:],
|
||||||
|
Address: (&ResourceAddress{Path: m.Path[1:]}).String(),
|
||||||
|
Value: m,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// With the modules set, go through all the resources within
|
||||||
|
// the modules to find relevant resources.
|
||||||
|
for _, m := range modules {
|
||||||
|
for n, r := range m.Resources {
|
||||||
|
// The name in the state contains valuable information. Parse.
|
||||||
|
key, err := ParseResourceStateKey(n)
|
||||||
|
if err != nil {
|
||||||
|
// If we get an error parsing, then just ignore it
|
||||||
|
// out of the state.
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
// Older states and test fixtures often don't contain the
|
||||||
|
// type directly on the ResourceState. We add this so StateFilter
|
||||||
|
// is a bit more robust.
|
||||||
|
if r.Type == "" {
|
||||||
|
r.Type = key.Type
|
||||||
|
}
|
||||||
|
|
||||||
|
if f.relevant(a, r) {
|
||||||
|
if a.Name != "" && a.Name != key.Name {
|
||||||
|
// Name doesn't match
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
if a.Index >= 0 && key.Index != a.Index {
|
||||||
|
// Index doesn't match
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
if a.Name != "" && a.Name != key.Name {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
// Build the address for this resource
|
||||||
|
addr := &ResourceAddress{
|
||||||
|
Path: m.Path[1:],
|
||||||
|
Name: key.Name,
|
||||||
|
Type: key.Type,
|
||||||
|
Index: key.Index,
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add the resource level result
|
||||||
|
resourceResult := &StateFilterResult{
|
||||||
|
Path: addr.Path,
|
||||||
|
Address: addr.String(),
|
||||||
|
Value: r,
|
||||||
|
}
|
||||||
|
if !a.InstanceTypeSet {
|
||||||
|
results = append(results, resourceResult)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add the instances
|
||||||
|
if r.Primary != nil {
|
||||||
|
addr.InstanceType = TypePrimary
|
||||||
|
addr.InstanceTypeSet = false
|
||||||
|
results = append(results, &StateFilterResult{
|
||||||
|
Path: addr.Path,
|
||||||
|
Address: addr.String(),
|
||||||
|
Parent: resourceResult,
|
||||||
|
Value: r.Primary,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, instance := range r.Deposed {
|
||||||
|
if f.relevant(a, instance) {
|
||||||
|
addr.InstanceType = TypeDeposed
|
||||||
|
addr.InstanceTypeSet = true
|
||||||
|
results = append(results, &StateFilterResult{
|
||||||
|
Path: addr.Path,
|
||||||
|
Address: addr.String(),
|
||||||
|
Parent: resourceResult,
|
||||||
|
Value: instance,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return results
|
||||||
|
}
|
||||||
|
|
||||||
|
// relevant checks for relevance of this address against the given value.
|
||||||
|
func (f *StateFilter) relevant(addr *ResourceAddress, raw interface{}) bool {
|
||||||
|
switch v := raw.(type) {
|
||||||
|
case *ModuleState:
|
||||||
|
path := v.Path[1:]
|
||||||
|
|
||||||
|
if len(addr.Path) > len(path) {
|
||||||
|
// Longer path in address means there is no way we match.
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check for a prefix match
|
||||||
|
for i, p := range addr.Path {
|
||||||
|
if path[i] != p {
|
||||||
|
// Any mismatches don't match.
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return true
|
||||||
|
case *ResourceState:
|
||||||
|
if addr.Type == "" {
|
||||||
|
// If we have no resource type, then we're interested in all!
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
// If the type doesn't match we fail immediately
|
||||||
|
if v.Type != addr.Type {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
return true
|
||||||
|
default:
|
||||||
|
// If we don't know about it, let's just say no
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// StateFilterResult is a single result from a filter operation. Filter
|
||||||
|
// can match multiple things within a state (module, resource, instance, etc.)
|
||||||
|
// and this unifies that.
|
||||||
|
type StateFilterResult struct {
|
||||||
|
// Module path of the result
|
||||||
|
Path []string
|
||||||
|
|
||||||
|
// Address is the address that can be used to reference this exact result.
|
||||||
|
Address string
|
||||||
|
|
||||||
|
// Parent, if non-nil, is a parent of this result. For instances, the
|
||||||
|
// parent would be a resource. For resources, the parent would be
|
||||||
|
// a module. For modules, this is currently nil.
|
||||||
|
Parent *StateFilterResult
|
||||||
|
|
||||||
|
// Value is the actual value. This must be type switched on. It can be
|
||||||
|
// any data structures that `State` can hold: `ModuleState`,
|
||||||
|
// `ResourceState`, `InstanceState`.
|
||||||
|
Value interface{}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *StateFilterResult) String() string {
|
||||||
|
return fmt.Sprintf("%T: %s", r.Value, r.Address)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *StateFilterResult) sortedType() int {
|
||||||
|
switch r.Value.(type) {
|
||||||
|
case *ModuleState:
|
||||||
|
return 0
|
||||||
|
case *ResourceState:
|
||||||
|
return 1
|
||||||
|
case *InstanceState:
|
||||||
|
return 2
|
||||||
|
default:
|
||||||
|
return 50
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// StateFilterResultSlice is a slice of results that implements
|
||||||
|
// sort.Interface. The sorting goal is what is most appealing to
|
||||||
|
// human output.
|
||||||
|
type StateFilterResultSlice []*StateFilterResult
|
||||||
|
|
||||||
|
func (s StateFilterResultSlice) Len() int { return len(s) }
|
||||||
|
func (s StateFilterResultSlice) Swap(i, j int) { s[i], s[j] = s[j], s[i] }
|
||||||
|
func (s StateFilterResultSlice) Less(i, j int) bool {
|
||||||
|
a, b := s[i], s[j]
|
||||||
|
|
||||||
|
// if these address contain an index, we want to sort by index rather than name
|
||||||
|
addrA, errA := ParseResourceAddress(a.Address)
|
||||||
|
addrB, errB := ParseResourceAddress(b.Address)
|
||||||
|
if errA == nil && errB == nil && addrA.Name == addrB.Name && addrA.Index != addrB.Index {
|
||||||
|
return addrA.Index < addrB.Index
|
||||||
|
}
|
||||||
|
|
||||||
|
// If the addresses are different it is just lexographic sorting
|
||||||
|
if a.Address != b.Address {
|
||||||
|
return a.Address < b.Address
|
||||||
|
}
|
||||||
|
|
||||||
|
// Addresses are the same, which means it matters on the type
|
||||||
|
return a.sortedType() < b.sortedType()
|
||||||
|
}
|
|
@ -689,6 +689,291 @@ func TestStateMarshalEqual(t *testing.T) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestStateRemove(t *testing.T) {
|
||||||
|
cases := map[string]struct {
|
||||||
|
Address string
|
||||||
|
One, Two *State
|
||||||
|
}{
|
||||||
|
"simple resource": {
|
||||||
|
"test_instance.foo",
|
||||||
|
&State{
|
||||||
|
Modules: []*ModuleState{
|
||||||
|
&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{
|
||||||
|
Modules: []*ModuleState{
|
||||||
|
&ModuleState{
|
||||||
|
Path: rootModulePath,
|
||||||
|
Resources: map[string]*ResourceState{
|
||||||
|
"test_instance.bar": &ResourceState{
|
||||||
|
Type: "test_instance",
|
||||||
|
Primary: &InstanceState{
|
||||||
|
ID: "foo",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
"single instance": {
|
||||||
|
"test_instance.foo.primary",
|
||||||
|
&State{
|
||||||
|
Modules: []*ModuleState{
|
||||||
|
&ModuleState{
|
||||||
|
Path: rootModulePath,
|
||||||
|
Resources: map[string]*ResourceState{
|
||||||
|
"test_instance.foo": &ResourceState{
|
||||||
|
Type: "test_instance",
|
||||||
|
Primary: &InstanceState{
|
||||||
|
ID: "foo",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
&State{
|
||||||
|
Modules: []*ModuleState{
|
||||||
|
&ModuleState{
|
||||||
|
Path: rootModulePath,
|
||||||
|
Resources: map[string]*ResourceState{},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
"single instance in multi-count": {
|
||||||
|
"test_instance.foo[0]",
|
||||||
|
&State{
|
||||||
|
Modules: []*ModuleState{
|
||||||
|
&ModuleState{
|
||||||
|
Path: rootModulePath,
|
||||||
|
Resources: map[string]*ResourceState{
|
||||||
|
"test_instance.foo.0": &ResourceState{
|
||||||
|
Type: "test_instance",
|
||||||
|
Primary: &InstanceState{
|
||||||
|
ID: "foo",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
"test_instance.foo.1": &ResourceState{
|
||||||
|
Type: "test_instance",
|
||||||
|
Primary: &InstanceState{
|
||||||
|
ID: "foo",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
&State{
|
||||||
|
Modules: []*ModuleState{
|
||||||
|
&ModuleState{
|
||||||
|
Path: rootModulePath,
|
||||||
|
Resources: map[string]*ResourceState{
|
||||||
|
"test_instance.foo.1": &ResourceState{
|
||||||
|
Type: "test_instance",
|
||||||
|
Primary: &InstanceState{
|
||||||
|
ID: "foo",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
"single resource, multi-count": {
|
||||||
|
"test_instance.foo",
|
||||||
|
&State{
|
||||||
|
Modules: []*ModuleState{
|
||||||
|
&ModuleState{
|
||||||
|
Path: rootModulePath,
|
||||||
|
Resources: map[string]*ResourceState{
|
||||||
|
"test_instance.foo.0": &ResourceState{
|
||||||
|
Type: "test_instance",
|
||||||
|
Primary: &InstanceState{
|
||||||
|
ID: "foo",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
"test_instance.foo.1": &ResourceState{
|
||||||
|
Type: "test_instance",
|
||||||
|
Primary: &InstanceState{
|
||||||
|
ID: "foo",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
&State{
|
||||||
|
Modules: []*ModuleState{
|
||||||
|
&ModuleState{
|
||||||
|
Path: rootModulePath,
|
||||||
|
Resources: map[string]*ResourceState{},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
"full module": {
|
||||||
|
"module.foo",
|
||||||
|
&State{
|
||||||
|
Modules: []*ModuleState{
|
||||||
|
&ModuleState{
|
||||||
|
Path: rootModulePath,
|
||||||
|
Resources: map[string]*ResourceState{
|
||||||
|
"test_instance.foo": &ResourceState{
|
||||||
|
Type: "test_instance",
|
||||||
|
Primary: &InstanceState{
|
||||||
|
ID: "foo",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
&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",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
&State{
|
||||||
|
Modules: []*ModuleState{
|
||||||
|
&ModuleState{
|
||||||
|
Path: rootModulePath,
|
||||||
|
Resources: map[string]*ResourceState{
|
||||||
|
"test_instance.foo": &ResourceState{
|
||||||
|
Type: "test_instance",
|
||||||
|
Primary: &InstanceState{
|
||||||
|
ID: "foo",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
"module and children": {
|
||||||
|
"module.foo",
|
||||||
|
&State{
|
||||||
|
Modules: []*ModuleState{
|
||||||
|
&ModuleState{
|
||||||
|
Path: rootModulePath,
|
||||||
|
Resources: map[string]*ResourceState{
|
||||||
|
"test_instance.foo": &ResourceState{
|
||||||
|
Type: "test_instance",
|
||||||
|
Primary: &InstanceState{
|
||||||
|
ID: "foo",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
&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{
|
||||||
|
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",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
&State{
|
||||||
|
Modules: []*ModuleState{
|
||||||
|
&ModuleState{
|
||||||
|
Path: rootModulePath,
|
||||||
|
Resources: map[string]*ResourceState{
|
||||||
|
"test_instance.foo": &ResourceState{
|
||||||
|
Type: "test_instance",
|
||||||
|
Primary: &InstanceState{
|
||||||
|
ID: "foo",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for k, tc := range cases {
|
||||||
|
if err := tc.One.Remove(tc.Address); err != nil {
|
||||||
|
t.Fatalf("bad: %s\n\n%s", k, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if !tc.One.Equal(tc.Two) {
|
||||||
|
t.Fatalf("Bad: %s\n\n%s\n\n%s", k, tc.One.String(), tc.Two.String())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func TestResourceStateEqual(t *testing.T) {
|
func TestResourceStateEqual(t *testing.T) {
|
||||||
cases := []struct {
|
cases := []struct {
|
||||||
Result bool
|
Result bool
|
||||||
|
|
Loading…
Reference in New Issue