2014-06-05 08:57:06 -05:00
|
|
|
package terraform
|
|
|
|
|
|
|
|
import (
|
2014-09-18 12:51:39 -05:00
|
|
|
"bytes"
|
2014-09-18 15:43:26 -05:00
|
|
|
"encoding/json"
|
2016-09-27 21:52:12 -05:00
|
|
|
"fmt"
|
2014-06-05 08:57:06 -05:00
|
|
|
"reflect"
|
2014-09-18 15:43:26 -05:00
|
|
|
"strings"
|
2014-06-05 08:57:06 -05:00
|
|
|
"testing"
|
2014-06-12 19:51:38 -05:00
|
|
|
|
|
|
|
"github.com/hashicorp/terraform/config"
|
2014-06-05 08:57:06 -05:00
|
|
|
)
|
|
|
|
|
2016-08-24 20:42:13 -05:00
|
|
|
func TestStateValidate(t *testing.T) {
|
|
|
|
cases := map[string]struct {
|
|
|
|
In *State
|
|
|
|
Err bool
|
|
|
|
}{
|
|
|
|
"empty state": {
|
|
|
|
&State{},
|
|
|
|
false,
|
|
|
|
},
|
|
|
|
|
|
|
|
"multiple modules": {
|
|
|
|
&State{
|
|
|
|
Modules: []*ModuleState{
|
|
|
|
&ModuleState{
|
|
|
|
Path: []string{"root", "foo"},
|
|
|
|
},
|
|
|
|
&ModuleState{
|
|
|
|
Path: []string{"root", "foo"},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
true,
|
|
|
|
},
|
|
|
|
}
|
|
|
|
|
|
|
|
for name, tc := range cases {
|
|
|
|
// Init the state
|
|
|
|
tc.In.init()
|
|
|
|
|
|
|
|
err := tc.In.Validate()
|
|
|
|
if (err != nil) != tc.Err {
|
|
|
|
t.Fatalf("%s: err: %s", name, err)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2014-10-11 14:47:06 -05:00
|
|
|
func TestStateAddModule(t *testing.T) {
|
2014-10-11 14:57:06 -05:00
|
|
|
cases := []struct {
|
|
|
|
In [][]string
|
2014-10-11 14:47:06 -05:00
|
|
|
Out [][]string
|
|
|
|
}{
|
|
|
|
{
|
|
|
|
[][]string{
|
|
|
|
[]string{"root"},
|
|
|
|
[]string{"root", "child"},
|
|
|
|
},
|
|
|
|
[][]string{
|
|
|
|
[]string{"root"},
|
|
|
|
[]string{"root", "child"},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
|
|
|
|
{
|
|
|
|
[][]string{
|
|
|
|
[]string{"root", "foo", "bar"},
|
|
|
|
[]string{"root", "foo"},
|
|
|
|
[]string{"root"},
|
|
|
|
[]string{"root", "bar"},
|
|
|
|
},
|
|
|
|
[][]string{
|
|
|
|
[]string{"root"},
|
|
|
|
[]string{"root", "bar"},
|
|
|
|
[]string{"root", "foo"},
|
|
|
|
[]string{"root", "foo", "bar"},
|
|
|
|
},
|
|
|
|
},
|
2015-10-20 14:33:28 -05:00
|
|
|
// Same last element, different middle element
|
|
|
|
{
|
|
|
|
[][]string{
|
|
|
|
[]string{"root", "foo", "bar"}, // This one should sort after...
|
|
|
|
[]string{"root", "foo"},
|
|
|
|
[]string{"root"},
|
|
|
|
[]string{"root", "bar", "bar"}, // ...this one.
|
|
|
|
[]string{"root", "bar"},
|
|
|
|
},
|
|
|
|
[][]string{
|
|
|
|
[]string{"root"},
|
|
|
|
[]string{"root", "bar"},
|
|
|
|
[]string{"root", "foo"},
|
|
|
|
[]string{"root", "bar", "bar"},
|
|
|
|
[]string{"root", "foo", "bar"},
|
|
|
|
},
|
|
|
|
},
|
2014-10-11 14:47:06 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
for _, tc := range cases {
|
|
|
|
s := new(State)
|
|
|
|
for _, p := range tc.In {
|
|
|
|
s.AddModule(p)
|
|
|
|
}
|
|
|
|
|
|
|
|
actual := make([][]string, 0, len(tc.In))
|
|
|
|
for _, m := range s.Modules {
|
|
|
|
actual = append(actual, m.Path)
|
|
|
|
}
|
|
|
|
|
|
|
|
if !reflect.DeepEqual(actual, tc.Out) {
|
|
|
|
t.Fatalf("In: %#v\n\nOut: %#v", tc.In, actual)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2016-03-22 09:22:33 -05:00
|
|
|
func TestStateOutputTypeRoundTrip(t *testing.T) {
|
|
|
|
state := &State{
|
|
|
|
Modules: []*ModuleState{
|
|
|
|
&ModuleState{
|
|
|
|
Path: RootModulePath,
|
2016-05-11 19:05:02 -05:00
|
|
|
Outputs: map[string]*OutputState{
|
|
|
|
"string_output": &OutputState{
|
|
|
|
Value: "String Value",
|
|
|
|
Type: "string",
|
2016-03-22 09:22:33 -05:00
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
}
|
2016-08-10 14:47:25 -05:00
|
|
|
state.init()
|
2016-03-22 09:22:33 -05:00
|
|
|
|
|
|
|
buf := new(bytes.Buffer)
|
|
|
|
if err := WriteState(state, buf); err != nil {
|
|
|
|
t.Fatalf("err: %s", err)
|
|
|
|
}
|
|
|
|
|
|
|
|
roundTripped, err := ReadState(buf)
|
|
|
|
if err != nil {
|
|
|
|
t.Fatalf("err: %s", err)
|
|
|
|
}
|
|
|
|
|
|
|
|
if !reflect.DeepEqual(state, roundTripped) {
|
2016-08-10 14:47:25 -05:00
|
|
|
t.Logf("expected:\n%#v", state)
|
|
|
|
t.Fatalf("got:\n%#v", roundTripped)
|
2016-03-22 09:22:33 -05:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2015-01-27 12:13:50 -06:00
|
|
|
func TestStateModuleOrphans(t *testing.T) {
|
|
|
|
state := &State{
|
|
|
|
Modules: []*ModuleState{
|
|
|
|
&ModuleState{
|
|
|
|
Path: RootModulePath,
|
|
|
|
},
|
|
|
|
&ModuleState{
|
|
|
|
Path: []string{RootModuleName, "foo"},
|
|
|
|
},
|
|
|
|
&ModuleState{
|
|
|
|
Path: []string{RootModuleName, "bar"},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
}
|
|
|
|
|
2016-08-10 14:47:25 -05:00
|
|
|
state.init()
|
|
|
|
|
2015-01-27 12:13:50 -06:00
|
|
|
config := testModule(t, "state-module-orphans").Config()
|
|
|
|
actual := state.ModuleOrphans(RootModulePath, config)
|
|
|
|
expected := [][]string{
|
|
|
|
[]string{RootModuleName, "foo"},
|
|
|
|
}
|
|
|
|
|
|
|
|
if !reflect.DeepEqual(actual, expected) {
|
|
|
|
t.Fatalf("bad: %#v", actual)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2015-07-19 15:41:57 -05:00
|
|
|
func TestStateModuleOrphans_nested(t *testing.T) {
|
|
|
|
state := &State{
|
|
|
|
Modules: []*ModuleState{
|
|
|
|
&ModuleState{
|
|
|
|
Path: RootModulePath,
|
|
|
|
},
|
|
|
|
&ModuleState{
|
|
|
|
Path: []string{RootModuleName, "foo", "bar"},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
}
|
|
|
|
|
2016-08-10 14:47:25 -05:00
|
|
|
state.init()
|
|
|
|
|
2015-07-19 15:41:57 -05:00
|
|
|
actual := state.ModuleOrphans(RootModulePath, nil)
|
|
|
|
expected := [][]string{
|
|
|
|
[]string{RootModuleName, "foo"},
|
|
|
|
}
|
|
|
|
|
|
|
|
if !reflect.DeepEqual(actual, expected) {
|
|
|
|
t.Fatalf("bad: %#v", actual)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2015-02-11 19:47:30 -06:00
|
|
|
func TestStateModuleOrphans_nilConfig(t *testing.T) {
|
|
|
|
state := &State{
|
|
|
|
Modules: []*ModuleState{
|
|
|
|
&ModuleState{
|
|
|
|
Path: RootModulePath,
|
|
|
|
},
|
|
|
|
&ModuleState{
|
|
|
|
Path: []string{RootModuleName, "foo"},
|
|
|
|
},
|
|
|
|
&ModuleState{
|
|
|
|
Path: []string{RootModuleName, "bar"},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
}
|
|
|
|
|
2016-08-10 14:47:25 -05:00
|
|
|
state.init()
|
|
|
|
|
2015-02-11 19:47:30 -06:00
|
|
|
actual := state.ModuleOrphans(RootModulePath, nil)
|
|
|
|
expected := [][]string{
|
|
|
|
[]string{RootModuleName, "foo"},
|
|
|
|
[]string{RootModuleName, "bar"},
|
|
|
|
}
|
|
|
|
|
|
|
|
if !reflect.DeepEqual(actual, expected) {
|
|
|
|
t.Fatalf("bad: %#v", actual)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
core: fix bug detecting deeply nested module orphans
Context:
As part of building up a Plan, Terraform needs to detect "orphaned"
resources--resources which are present in the state but not in the
config. This happens when config for those resources is removed by the
user, making it Terraform's responsibility to destroy them.
Both state and config are organized by Module into a logical tree, so
the process of finding orphans involves checking for orphaned Resources
in the current module and for orphaned Modules, which themselves will
have all their Resources marked as orphans.
Bug:
In #3114 a problem was exposed where, given a module tree that looked
like this:
```
root
|
+-- parent (empty, except for sub-modules)
|
+-- child1 (1 resource)
|
+-- child2 (1 resource)
```
If `parent` was removed, a bunch of error messages would occur during
the plan. The root cause of this was duplicate orphans appearing for the
resources in child1 and child2.
Fix:
This turned out to be a bug in orphaned module detection. When looking
for deeply nested orphaned modules, root.parent was getting added twice
as an orphaned module to the graph.
Here, we add an additional check to prevent a double add, which
addresses this scenario properly.
Fixes #3114 (the Provisioner side of it was fixed in #4877)
2016-02-05 12:19:10 -06:00
|
|
|
func TestStateModuleOrphans_deepNestedNilConfig(t *testing.T) {
|
|
|
|
state := &State{
|
|
|
|
Modules: []*ModuleState{
|
|
|
|
&ModuleState{
|
|
|
|
Path: RootModulePath,
|
|
|
|
},
|
|
|
|
&ModuleState{
|
|
|
|
Path: []string{RootModuleName, "parent", "childfoo"},
|
|
|
|
},
|
|
|
|
&ModuleState{
|
|
|
|
Path: []string{RootModuleName, "parent", "childbar"},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
}
|
|
|
|
|
2016-08-10 14:47:25 -05:00
|
|
|
state.init()
|
|
|
|
|
core: fix bug detecting deeply nested module orphans
Context:
As part of building up a Plan, Terraform needs to detect "orphaned"
resources--resources which are present in the state but not in the
config. This happens when config for those resources is removed by the
user, making it Terraform's responsibility to destroy them.
Both state and config are organized by Module into a logical tree, so
the process of finding orphans involves checking for orphaned Resources
in the current module and for orphaned Modules, which themselves will
have all their Resources marked as orphans.
Bug:
In #3114 a problem was exposed where, given a module tree that looked
like this:
```
root
|
+-- parent (empty, except for sub-modules)
|
+-- child1 (1 resource)
|
+-- child2 (1 resource)
```
If `parent` was removed, a bunch of error messages would occur during
the plan. The root cause of this was duplicate orphans appearing for the
resources in child1 and child2.
Fix:
This turned out to be a bug in orphaned module detection. When looking
for deeply nested orphaned modules, root.parent was getting added twice
as an orphaned module to the graph.
Here, we add an additional check to prevent a double add, which
addresses this scenario properly.
Fixes #3114 (the Provisioner side of it was fixed in #4877)
2016-02-05 12:19:10 -06:00
|
|
|
actual := state.ModuleOrphans(RootModulePath, nil)
|
|
|
|
expected := [][]string{
|
|
|
|
[]string{RootModuleName, "parent"},
|
|
|
|
}
|
|
|
|
|
|
|
|
if !reflect.DeepEqual(actual, expected) {
|
|
|
|
t.Fatalf("bad: %#v", actual)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2016-03-11 13:07:54 -06:00
|
|
|
func TestStateDeepCopy(t *testing.T) {
|
|
|
|
cases := []struct {
|
2016-10-13 10:16:03 -05:00
|
|
|
State *State
|
2016-03-11 13:07:54 -06:00
|
|
|
}{
|
|
|
|
// Version
|
|
|
|
{
|
|
|
|
&State{Version: 5},
|
|
|
|
},
|
|
|
|
// TFVersion
|
|
|
|
{
|
|
|
|
&State{TFVersion: "5"},
|
2016-10-13 10:16:03 -05:00
|
|
|
},
|
|
|
|
// Modules
|
|
|
|
{
|
|
|
|
&State{
|
|
|
|
Version: 6,
|
|
|
|
Modules: []*ModuleState{
|
|
|
|
&ModuleState{
|
|
|
|
Path: rootModulePath,
|
|
|
|
Resources: map[string]*ResourceState{
|
|
|
|
"test_instance.foo": &ResourceState{
|
|
|
|
Primary: &InstanceState{
|
|
|
|
Meta: map[string]string{},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
// Deposed
|
2016-10-13 11:10:57 -05:00
|
|
|
// The nil values shouldn't be there if the State was properly init'ed,
|
|
|
|
// but the Copy should still work anyway.
|
2016-10-13 10:16:03 -05:00
|
|
|
{
|
|
|
|
&State{
|
|
|
|
Version: 6,
|
|
|
|
Modules: []*ModuleState{
|
|
|
|
&ModuleState{
|
|
|
|
Path: rootModulePath,
|
|
|
|
Resources: map[string]*ResourceState{
|
|
|
|
"test_instance.foo": &ResourceState{
|
|
|
|
Primary: &InstanceState{
|
|
|
|
Meta: map[string]string{},
|
|
|
|
},
|
|
|
|
Deposed: []*InstanceState{
|
|
|
|
{ID: "test"},
|
|
|
|
nil,
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
2016-03-11 13:07:54 -06:00
|
|
|
},
|
|
|
|
}
|
|
|
|
|
|
|
|
for i, tc := range cases {
|
2016-09-27 21:52:12 -05:00
|
|
|
t.Run(fmt.Sprintf("copy-%d", i), func(t *testing.T) {
|
2016-10-13 10:16:03 -05:00
|
|
|
actual := tc.State.DeepCopy()
|
|
|
|
expected := tc.State
|
2016-09-27 21:52:12 -05:00
|
|
|
if !reflect.DeepEqual(actual, expected) {
|
2016-10-13 10:16:03 -05:00
|
|
|
t.Fatalf("Expected: %#v\nRecevied: %#v\n", expected, actual)
|
2016-09-27 21:52:12 -05:00
|
|
|
}
|
|
|
|
})
|
2016-03-11 13:07:54 -06:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2015-02-20 15:39:49 -06:00
|
|
|
func TestStateEqual(t *testing.T) {
|
|
|
|
cases := []struct {
|
|
|
|
Result bool
|
|
|
|
One, Two *State
|
|
|
|
}{
|
2015-02-23 23:43:54 -06:00
|
|
|
// Nils
|
|
|
|
{
|
|
|
|
false,
|
|
|
|
nil,
|
|
|
|
&State{Version: 2},
|
|
|
|
},
|
|
|
|
|
|
|
|
{
|
|
|
|
true,
|
|
|
|
nil,
|
|
|
|
nil,
|
|
|
|
},
|
|
|
|
|
2015-02-20 15:39:49 -06:00
|
|
|
// Different versions
|
|
|
|
{
|
|
|
|
false,
|
|
|
|
&State{Version: 5},
|
|
|
|
&State{Version: 2},
|
|
|
|
},
|
|
|
|
|
|
|
|
// Different modules
|
|
|
|
{
|
|
|
|
false,
|
|
|
|
&State{
|
|
|
|
Modules: []*ModuleState{
|
|
|
|
&ModuleState{
|
|
|
|
Path: RootModulePath,
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
&State{},
|
|
|
|
},
|
|
|
|
|
|
|
|
{
|
|
|
|
true,
|
|
|
|
&State{
|
|
|
|
Modules: []*ModuleState{
|
|
|
|
&ModuleState{
|
|
|
|
Path: RootModulePath,
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
&State{
|
|
|
|
Modules: []*ModuleState{
|
|
|
|
&ModuleState{
|
|
|
|
Path: RootModulePath,
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
2015-10-20 12:28:12 -05:00
|
|
|
|
|
|
|
// Meta differs
|
|
|
|
{
|
|
|
|
false,
|
|
|
|
&State{
|
|
|
|
Modules: []*ModuleState{
|
|
|
|
&ModuleState{
|
|
|
|
Path: rootModulePath,
|
|
|
|
Resources: map[string]*ResourceState{
|
|
|
|
"test_instance.foo": &ResourceState{
|
|
|
|
Primary: &InstanceState{
|
|
|
|
Meta: map[string]string{
|
|
|
|
"schema_version": "1",
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
&State{
|
|
|
|
Modules: []*ModuleState{
|
|
|
|
&ModuleState{
|
|
|
|
Path: rootModulePath,
|
|
|
|
Resources: map[string]*ResourceState{
|
|
|
|
"test_instance.foo": &ResourceState{
|
|
|
|
Primary: &InstanceState{
|
|
|
|
Meta: map[string]string{
|
|
|
|
"schema_version": "2",
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
2015-02-20 15:39:49 -06:00
|
|
|
}
|
|
|
|
|
|
|
|
for i, tc := range cases {
|
|
|
|
if tc.One.Equal(tc.Two) != tc.Result {
|
|
|
|
t.Fatalf("Bad: %d\n\n%s\n\n%s", i, tc.One.String(), tc.Two.String())
|
|
|
|
}
|
2015-02-23 23:43:54 -06:00
|
|
|
if tc.Two.Equal(tc.One) != tc.Result {
|
|
|
|
t.Fatalf("Bad: %d\n\n%s\n\n%s", i, tc.One.String(), tc.Two.String())
|
|
|
|
}
|
2015-02-20 15:39:49 -06:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2016-05-08 15:02:49 -05:00
|
|
|
func TestStateCompareAges(t *testing.T) {
|
|
|
|
cases := []struct {
|
|
|
|
Result StateAgeComparison
|
|
|
|
Err bool
|
|
|
|
One, Two *State
|
|
|
|
}{
|
|
|
|
{
|
|
|
|
StateAgeEqual, false,
|
|
|
|
&State{
|
|
|
|
Lineage: "1",
|
|
|
|
Serial: 2,
|
|
|
|
},
|
|
|
|
&State{
|
|
|
|
Lineage: "1",
|
|
|
|
Serial: 2,
|
|
|
|
},
|
|
|
|
},
|
|
|
|
{
|
|
|
|
StateAgeReceiverOlder, false,
|
|
|
|
&State{
|
|
|
|
Lineage: "1",
|
|
|
|
Serial: 2,
|
|
|
|
},
|
|
|
|
&State{
|
|
|
|
Lineage: "1",
|
|
|
|
Serial: 3,
|
|
|
|
},
|
|
|
|
},
|
|
|
|
{
|
|
|
|
StateAgeReceiverNewer, false,
|
|
|
|
&State{
|
|
|
|
Lineage: "1",
|
|
|
|
Serial: 3,
|
|
|
|
},
|
|
|
|
&State{
|
|
|
|
Lineage: "1",
|
|
|
|
Serial: 2,
|
|
|
|
},
|
|
|
|
},
|
|
|
|
{
|
|
|
|
StateAgeEqual, true,
|
|
|
|
&State{
|
|
|
|
Lineage: "1",
|
|
|
|
Serial: 2,
|
|
|
|
},
|
|
|
|
&State{
|
|
|
|
Lineage: "2",
|
|
|
|
Serial: 2,
|
|
|
|
},
|
|
|
|
},
|
|
|
|
{
|
|
|
|
StateAgeEqual, true,
|
|
|
|
&State{
|
|
|
|
Lineage: "1",
|
|
|
|
Serial: 3,
|
|
|
|
},
|
|
|
|
&State{
|
|
|
|
Lineage: "2",
|
|
|
|
Serial: 2,
|
|
|
|
},
|
|
|
|
},
|
|
|
|
}
|
|
|
|
|
|
|
|
for i, tc := range cases {
|
|
|
|
result, err := tc.One.CompareAges(tc.Two)
|
|
|
|
|
|
|
|
if err != nil && !tc.Err {
|
|
|
|
t.Errorf(
|
|
|
|
"%d: got error, but want success\n\n%s\n\n%s",
|
|
|
|
i, tc.One, tc.Two,
|
|
|
|
)
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
|
|
|
|
if err == nil && tc.Err {
|
|
|
|
t.Errorf(
|
|
|
|
"%d: got success, but want error\n\n%s\n\n%s",
|
|
|
|
i, tc.One, tc.Two,
|
|
|
|
)
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
|
|
|
|
if result != tc.Result {
|
|
|
|
t.Errorf(
|
|
|
|
"%d: got result %d, but want %d\n\n%s\n\n%s",
|
|
|
|
i, result, tc.Result, tc.One, tc.Two,
|
|
|
|
)
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func TestStateSameLineage(t *testing.T) {
|
|
|
|
cases := []struct {
|
|
|
|
Result bool
|
|
|
|
One, Two *State
|
|
|
|
}{
|
|
|
|
{
|
|
|
|
true,
|
|
|
|
&State{
|
|
|
|
Lineage: "1",
|
|
|
|
},
|
|
|
|
&State{
|
|
|
|
Lineage: "1",
|
|
|
|
},
|
|
|
|
},
|
|
|
|
{
|
|
|
|
// Empty lineage is compatible with all
|
|
|
|
true,
|
|
|
|
&State{
|
|
|
|
Lineage: "",
|
|
|
|
},
|
|
|
|
&State{
|
|
|
|
Lineage: "1",
|
|
|
|
},
|
|
|
|
},
|
|
|
|
{
|
|
|
|
// Empty lineage is compatible with all
|
|
|
|
true,
|
|
|
|
&State{
|
|
|
|
Lineage: "1",
|
|
|
|
},
|
|
|
|
&State{
|
|
|
|
Lineage: "",
|
|
|
|
},
|
|
|
|
},
|
|
|
|
{
|
|
|
|
false,
|
|
|
|
&State{
|
|
|
|
Lineage: "1",
|
|
|
|
},
|
|
|
|
&State{
|
|
|
|
Lineage: "2",
|
|
|
|
},
|
|
|
|
},
|
|
|
|
}
|
|
|
|
|
|
|
|
for i, tc := range cases {
|
|
|
|
result := tc.One.SameLineage(tc.Two)
|
|
|
|
|
|
|
|
if result != tc.Result {
|
|
|
|
t.Errorf(
|
|
|
|
"%d: got %v, but want %v\n\n%s\n\n%s",
|
|
|
|
i, result, tc.Result, tc.One, tc.Two,
|
|
|
|
)
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2015-03-25 17:38:24 -05:00
|
|
|
func TestStateIncrementSerialMaybe(t *testing.T) {
|
|
|
|
cases := map[string]struct {
|
|
|
|
S1, S2 *State
|
|
|
|
Serial int64
|
|
|
|
}{
|
|
|
|
"S2 is nil": {
|
|
|
|
&State{},
|
|
|
|
nil,
|
|
|
|
0,
|
|
|
|
},
|
|
|
|
"S2 is identical": {
|
|
|
|
&State{},
|
|
|
|
&State{},
|
|
|
|
0,
|
|
|
|
},
|
|
|
|
"S2 is different": {
|
|
|
|
&State{},
|
|
|
|
&State{
|
|
|
|
Modules: []*ModuleState{
|
|
|
|
&ModuleState{Path: rootModulePath},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
1,
|
|
|
|
},
|
2015-10-20 12:28:12 -05:00
|
|
|
"S2 is different, but only via Instance Metadata": {
|
|
|
|
&State{
|
|
|
|
Serial: 3,
|
|
|
|
Modules: []*ModuleState{
|
|
|
|
&ModuleState{
|
|
|
|
Path: rootModulePath,
|
|
|
|
Resources: map[string]*ResourceState{
|
|
|
|
"test_instance.foo": &ResourceState{
|
|
|
|
Primary: &InstanceState{
|
|
|
|
Meta: map[string]string{},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
&State{
|
|
|
|
Serial: 3,
|
|
|
|
Modules: []*ModuleState{
|
|
|
|
&ModuleState{
|
|
|
|
Path: rootModulePath,
|
|
|
|
Resources: map[string]*ResourceState{
|
|
|
|
"test_instance.foo": &ResourceState{
|
|
|
|
Primary: &InstanceState{
|
|
|
|
Meta: map[string]string{
|
|
|
|
"schema_version": "1",
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
4,
|
|
|
|
},
|
2015-03-25 17:39:33 -05:00
|
|
|
"S1 serial is higher": {
|
|
|
|
&State{Serial: 5},
|
|
|
|
&State{
|
|
|
|
Serial: 3,
|
|
|
|
Modules: []*ModuleState{
|
|
|
|
&ModuleState{Path: rootModulePath},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
5,
|
|
|
|
},
|
2016-03-11 13:07:54 -06:00
|
|
|
"S2 has a different TFVersion": {
|
|
|
|
&State{TFVersion: "0.1"},
|
|
|
|
&State{TFVersion: "0.2"},
|
|
|
|
1,
|
|
|
|
},
|
2015-03-25 17:38:24 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
for name, tc := range cases {
|
|
|
|
tc.S1.IncrementSerialMaybe(tc.S2)
|
|
|
|
if tc.S1.Serial != tc.Serial {
|
|
|
|
t.Fatalf("Bad: %s\nGot: %d", name, tc.S1.Serial)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2016-03-30 11:29:20 -05:00
|
|
|
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",
|
|
|
|
},
|
|
|
|
},
|
2016-03-31 11:24:23 -05:00
|
|
|
|
|
|
|
"test_instance.bar": &ResourceState{
|
|
|
|
Type: "test_instance",
|
|
|
|
Primary: &InstanceState{
|
|
|
|
ID: "foo",
|
|
|
|
},
|
|
|
|
},
|
2016-03-30 11:29:20 -05:00
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
&State{
|
|
|
|
Modules: []*ModuleState{
|
|
|
|
&ModuleState{
|
2016-03-31 11:24:23 -05:00
|
|
|
Path: rootModulePath,
|
|
|
|
Resources: map[string]*ResourceState{
|
|
|
|
"test_instance.bar": &ResourceState{
|
|
|
|
Type: "test_instance",
|
|
|
|
Primary: &InstanceState{
|
|
|
|
ID: "foo",
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
2016-03-30 11:29:20 -05:00
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
|
|
|
|
"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())
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2015-02-20 15:39:49 -06:00
|
|
|
func TestResourceStateEqual(t *testing.T) {
|
|
|
|
cases := []struct {
|
|
|
|
Result bool
|
|
|
|
One, Two *ResourceState
|
|
|
|
}{
|
|
|
|
// Different types
|
|
|
|
{
|
|
|
|
false,
|
|
|
|
&ResourceState{Type: "foo"},
|
|
|
|
&ResourceState{Type: "bar"},
|
|
|
|
},
|
|
|
|
|
|
|
|
// Different dependencies
|
|
|
|
{
|
|
|
|
false,
|
|
|
|
&ResourceState{Dependencies: []string{"foo"}},
|
|
|
|
&ResourceState{Dependencies: []string{"bar"}},
|
|
|
|
},
|
|
|
|
|
|
|
|
{
|
|
|
|
false,
|
|
|
|
&ResourceState{Dependencies: []string{"foo", "bar"}},
|
|
|
|
&ResourceState{Dependencies: []string{"foo"}},
|
|
|
|
},
|
|
|
|
|
|
|
|
{
|
|
|
|
true,
|
|
|
|
&ResourceState{Dependencies: []string{"bar", "foo"}},
|
|
|
|
&ResourceState{Dependencies: []string{"foo", "bar"}},
|
|
|
|
},
|
|
|
|
|
|
|
|
// Different primaries
|
|
|
|
{
|
|
|
|
false,
|
|
|
|
&ResourceState{Primary: nil},
|
|
|
|
&ResourceState{Primary: &InstanceState{ID: "foo"}},
|
|
|
|
},
|
|
|
|
|
|
|
|
{
|
|
|
|
true,
|
|
|
|
&ResourceState{Primary: &InstanceState{ID: "foo"}},
|
|
|
|
&ResourceState{Primary: &InstanceState{ID: "foo"}},
|
|
|
|
},
|
|
|
|
|
|
|
|
// Different tainted
|
|
|
|
{
|
|
|
|
false,
|
|
|
|
&ResourceState{
|
2016-04-21 14:59:10 -05:00
|
|
|
Primary: &InstanceState{
|
|
|
|
ID: "foo",
|
2015-02-20 15:39:49 -06:00
|
|
|
},
|
|
|
|
},
|
|
|
|
&ResourceState{
|
2016-04-21 14:59:10 -05:00
|
|
|
Primary: &InstanceState{
|
|
|
|
ID: "foo",
|
|
|
|
Tainted: true,
|
2015-02-20 15:39:49 -06:00
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
|
|
|
|
{
|
|
|
|
true,
|
|
|
|
&ResourceState{
|
2016-04-21 14:59:10 -05:00
|
|
|
Primary: &InstanceState{
|
|
|
|
ID: "foo",
|
|
|
|
Tainted: true,
|
2015-02-20 15:39:49 -06:00
|
|
|
},
|
|
|
|
},
|
|
|
|
&ResourceState{
|
2016-04-21 14:59:10 -05:00
|
|
|
Primary: &InstanceState{
|
|
|
|
ID: "foo",
|
|
|
|
Tainted: true,
|
2015-02-20 15:39:49 -06:00
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
}
|
|
|
|
|
|
|
|
for i, tc := range cases {
|
|
|
|
if tc.One.Equal(tc.Two) != tc.Result {
|
|
|
|
t.Fatalf("Bad: %d\n\n%s\n\n%s", i, tc.One.String(), tc.Two.String())
|
|
|
|
}
|
|
|
|
if tc.Two.Equal(tc.One) != tc.Result {
|
|
|
|
t.Fatalf("Bad: %d\n\n%s\n\n%s", i, tc.One.String(), tc.Two.String())
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2015-02-26 11:58:56 -06:00
|
|
|
func TestResourceStateTaint(t *testing.T) {
|
|
|
|
cases := map[string]struct {
|
|
|
|
Input *ResourceState
|
|
|
|
Output *ResourceState
|
|
|
|
}{
|
|
|
|
"no primary": {
|
|
|
|
&ResourceState{},
|
|
|
|
&ResourceState{},
|
|
|
|
},
|
|
|
|
|
2016-04-21 14:59:10 -05:00
|
|
|
"primary, not tainted": {
|
2015-02-26 11:58:56 -06:00
|
|
|
&ResourceState{
|
|
|
|
Primary: &InstanceState{ID: "foo"},
|
|
|
|
},
|
|
|
|
&ResourceState{
|
2016-04-21 14:59:10 -05:00
|
|
|
Primary: &InstanceState{
|
|
|
|
ID: "foo",
|
|
|
|
Tainted: true,
|
2015-02-26 11:58:56 -06:00
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
|
2016-04-21 14:59:10 -05:00
|
|
|
"primary, tainted": {
|
2015-02-26 11:58:56 -06:00
|
|
|
&ResourceState{
|
2016-04-21 14:59:10 -05:00
|
|
|
Primary: &InstanceState{
|
|
|
|
ID: "foo",
|
|
|
|
Tainted: true,
|
2015-02-26 11:58:56 -06:00
|
|
|
},
|
|
|
|
},
|
|
|
|
&ResourceState{
|
2016-04-21 14:59:10 -05:00
|
|
|
Primary: &InstanceState{
|
|
|
|
ID: "foo",
|
|
|
|
Tainted: true,
|
2015-02-26 11:58:56 -06:00
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
}
|
|
|
|
|
|
|
|
for k, tc := range cases {
|
|
|
|
tc.Input.Taint()
|
|
|
|
if !reflect.DeepEqual(tc.Input, tc.Output) {
|
|
|
|
t.Fatalf(
|
|
|
|
"Failure: %s\n\nExpected: %#v\n\nGot: %#v",
|
|
|
|
k, tc.Output, tc.Input)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2016-03-08 14:37:34 -06:00
|
|
|
func TestResourceStateUntaint(t *testing.T) {
|
|
|
|
cases := map[string]struct {
|
|
|
|
Input *ResourceState
|
|
|
|
ExpectedOutput *ResourceState
|
|
|
|
}{
|
2016-04-21 14:59:10 -05:00
|
|
|
"no primary, err": {
|
2016-03-08 14:37:34 -06:00
|
|
|
Input: &ResourceState{},
|
|
|
|
ExpectedOutput: &ResourceState{},
|
|
|
|
},
|
|
|
|
|
2016-04-21 14:59:10 -05:00
|
|
|
"primary, not tainted": {
|
2016-03-08 14:37:34 -06:00
|
|
|
Input: &ResourceState{
|
|
|
|
Primary: &InstanceState{ID: "foo"},
|
|
|
|
},
|
2016-04-21 14:59:10 -05:00
|
|
|
ExpectedOutput: &ResourceState{
|
2016-03-08 14:37:34 -06:00
|
|
|
Primary: &InstanceState{ID: "foo"},
|
|
|
|
},
|
|
|
|
},
|
2016-04-21 14:59:10 -05:00
|
|
|
"primary, tainted": {
|
2016-03-08 14:37:34 -06:00
|
|
|
Input: &ResourceState{
|
2016-04-21 14:59:10 -05:00
|
|
|
Primary: &InstanceState{
|
|
|
|
ID: "foo",
|
|
|
|
Tainted: true,
|
2016-03-08 14:37:34 -06:00
|
|
|
},
|
|
|
|
},
|
|
|
|
ExpectedOutput: &ResourceState{
|
|
|
|
Primary: &InstanceState{ID: "foo"},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
}
|
|
|
|
|
|
|
|
for k, tc := range cases {
|
2016-04-21 14:59:10 -05:00
|
|
|
tc.Input.Untaint()
|
2016-03-08 14:37:34 -06:00
|
|
|
if !reflect.DeepEqual(tc.Input, tc.ExpectedOutput) {
|
|
|
|
t.Fatalf(
|
|
|
|
"Failure: %s\n\nExpected: %#v\n\nGot: %#v",
|
|
|
|
k, tc.ExpectedOutput, tc.Input)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2015-03-26 12:11:26 -05:00
|
|
|
func TestInstanceStateEmpty(t *testing.T) {
|
|
|
|
cases := map[string]struct {
|
|
|
|
In *InstanceState
|
|
|
|
Result bool
|
|
|
|
}{
|
|
|
|
"nil is empty": {
|
|
|
|
nil,
|
|
|
|
true,
|
|
|
|
},
|
|
|
|
"non-nil but without ID is empty": {
|
|
|
|
&InstanceState{},
|
|
|
|
true,
|
|
|
|
},
|
|
|
|
"with ID is not empty": {
|
|
|
|
&InstanceState{
|
|
|
|
ID: "i-abc123",
|
|
|
|
},
|
|
|
|
false,
|
|
|
|
},
|
|
|
|
}
|
|
|
|
|
|
|
|
for tn, tc := range cases {
|
|
|
|
if tc.In.Empty() != tc.Result {
|
|
|
|
t.Fatalf("%q expected %#v to be empty: %#v", tn, tc.In, tc.Result)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2015-02-20 15:39:49 -06:00
|
|
|
func TestInstanceStateEqual(t *testing.T) {
|
|
|
|
cases := []struct {
|
|
|
|
Result bool
|
|
|
|
One, Two *InstanceState
|
|
|
|
}{
|
|
|
|
// Nils
|
|
|
|
{
|
|
|
|
false,
|
|
|
|
nil,
|
|
|
|
&InstanceState{},
|
|
|
|
},
|
|
|
|
|
|
|
|
{
|
|
|
|
false,
|
|
|
|
&InstanceState{},
|
|
|
|
nil,
|
|
|
|
},
|
|
|
|
|
|
|
|
// Different IDs
|
|
|
|
{
|
|
|
|
false,
|
|
|
|
&InstanceState{ID: "foo"},
|
|
|
|
&InstanceState{ID: "bar"},
|
|
|
|
},
|
|
|
|
|
|
|
|
// Different Attributes
|
|
|
|
{
|
|
|
|
false,
|
|
|
|
&InstanceState{Attributes: map[string]string{"foo": "bar"}},
|
|
|
|
&InstanceState{Attributes: map[string]string{"foo": "baz"}},
|
|
|
|
},
|
|
|
|
|
|
|
|
// Different Attribute keys
|
|
|
|
{
|
|
|
|
false,
|
|
|
|
&InstanceState{Attributes: map[string]string{"foo": "bar"}},
|
|
|
|
&InstanceState{Attributes: map[string]string{"bar": "baz"}},
|
|
|
|
},
|
|
|
|
|
|
|
|
{
|
|
|
|
false,
|
|
|
|
&InstanceState{Attributes: map[string]string{"bar": "baz"}},
|
|
|
|
&InstanceState{Attributes: map[string]string{"foo": "bar"}},
|
|
|
|
},
|
|
|
|
}
|
|
|
|
|
|
|
|
for i, tc := range cases {
|
|
|
|
if tc.One.Equal(tc.Two) != tc.Result {
|
|
|
|
t.Fatalf("Bad: %d\n\n%s\n\n%s", i, tc.One.String(), tc.Two.String())
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2015-02-22 12:35:26 -06:00
|
|
|
func TestStateEmpty(t *testing.T) {
|
|
|
|
cases := []struct {
|
|
|
|
In *State
|
|
|
|
Result bool
|
|
|
|
}{
|
|
|
|
{
|
|
|
|
nil,
|
|
|
|
true,
|
|
|
|
},
|
|
|
|
{
|
|
|
|
&State{},
|
|
|
|
true,
|
|
|
|
},
|
|
|
|
{
|
|
|
|
&State{
|
|
|
|
Remote: &RemoteState{Type: "foo"},
|
|
|
|
},
|
|
|
|
true,
|
|
|
|
},
|
|
|
|
{
|
|
|
|
&State{
|
|
|
|
Modules: []*ModuleState{
|
|
|
|
&ModuleState{},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
false,
|
|
|
|
},
|
|
|
|
}
|
|
|
|
|
|
|
|
for i, tc := range cases {
|
|
|
|
if tc.In.Empty() != tc.Result {
|
|
|
|
t.Fatalf("bad %d %#v:\n\n%#v", i, tc.Result, tc.In)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2016-06-24 14:33:03 -05:00
|
|
|
func TestStateHasResources(t *testing.T) {
|
|
|
|
cases := []struct {
|
|
|
|
In *State
|
|
|
|
Result bool
|
|
|
|
}{
|
|
|
|
{
|
|
|
|
nil,
|
|
|
|
false,
|
|
|
|
},
|
|
|
|
{
|
|
|
|
&State{},
|
|
|
|
false,
|
|
|
|
},
|
|
|
|
{
|
|
|
|
&State{
|
|
|
|
Remote: &RemoteState{Type: "foo"},
|
|
|
|
},
|
|
|
|
false,
|
|
|
|
},
|
|
|
|
{
|
|
|
|
&State{
|
|
|
|
Modules: []*ModuleState{
|
|
|
|
&ModuleState{},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
false,
|
|
|
|
},
|
|
|
|
{
|
|
|
|
&State{
|
|
|
|
Modules: []*ModuleState{
|
|
|
|
&ModuleState{},
|
|
|
|
&ModuleState{},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
false,
|
|
|
|
},
|
|
|
|
{
|
|
|
|
&State{
|
|
|
|
Modules: []*ModuleState{
|
|
|
|
&ModuleState{},
|
|
|
|
&ModuleState{
|
|
|
|
Resources: map[string]*ResourceState{
|
|
|
|
"foo.foo": &ResourceState{},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
true,
|
|
|
|
},
|
|
|
|
}
|
|
|
|
|
|
|
|
for i, tc := range cases {
|
|
|
|
if tc.In.HasResources() != tc.Result {
|
|
|
|
t.Fatalf("bad %d %#v:\n\n%#v", i, tc.Result, tc.In)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2016-03-11 13:07:54 -06:00
|
|
|
func TestStateFromFutureTerraform(t *testing.T) {
|
|
|
|
cases := []struct {
|
|
|
|
In string
|
|
|
|
Result bool
|
|
|
|
}{
|
|
|
|
{
|
|
|
|
"",
|
|
|
|
false,
|
|
|
|
},
|
|
|
|
{
|
|
|
|
"0.1",
|
|
|
|
false,
|
|
|
|
},
|
|
|
|
{
|
|
|
|
"999.15.1",
|
|
|
|
true,
|
|
|
|
},
|
|
|
|
}
|
|
|
|
|
|
|
|
for _, tc := range cases {
|
|
|
|
state := &State{TFVersion: tc.In}
|
|
|
|
actual := state.FromFutureTerraform()
|
|
|
|
if actual != tc.Result {
|
|
|
|
t.Fatalf("%s: bad: %v", tc.In, actual)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2015-02-22 12:29:42 -06:00
|
|
|
func TestStateIsRemote(t *testing.T) {
|
|
|
|
cases := []struct {
|
|
|
|
In *State
|
|
|
|
Result bool
|
|
|
|
}{
|
|
|
|
{
|
|
|
|
nil,
|
|
|
|
false,
|
|
|
|
},
|
|
|
|
{
|
|
|
|
&State{},
|
|
|
|
false,
|
|
|
|
},
|
|
|
|
{
|
|
|
|
&State{
|
|
|
|
Remote: &RemoteState{Type: "foo"},
|
|
|
|
},
|
|
|
|
true,
|
|
|
|
},
|
|
|
|
}
|
|
|
|
|
|
|
|
for i, tc := range cases {
|
|
|
|
if tc.In.IsRemote() != tc.Result {
|
|
|
|
t.Fatalf("bad %d %#v:\n\n%#v", i, tc.Result, tc.In)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2014-09-16 18:20:11 -05:00
|
|
|
func TestInstanceState_MergeDiff(t *testing.T) {
|
|
|
|
is := InstanceState{
|
|
|
|
ID: "foo",
|
|
|
|
Attributes: map[string]string{
|
|
|
|
"foo": "bar",
|
|
|
|
"port": "8000",
|
2014-06-05 08:57:06 -05:00
|
|
|
},
|
|
|
|
}
|
|
|
|
|
2014-09-17 18:33:24 -05:00
|
|
|
diff := &InstanceDiff{
|
2014-06-19 16:08:10 -05:00
|
|
|
Attributes: map[string]*ResourceAttrDiff{
|
|
|
|
"foo": &ResourceAttrDiff{
|
|
|
|
Old: "bar",
|
|
|
|
New: "baz",
|
|
|
|
},
|
|
|
|
"bar": &ResourceAttrDiff{
|
|
|
|
Old: "",
|
|
|
|
New: "foo",
|
|
|
|
},
|
|
|
|
"baz": &ResourceAttrDiff{
|
|
|
|
Old: "",
|
|
|
|
New: "foo",
|
|
|
|
NewComputed: true,
|
|
|
|
},
|
2014-07-09 12:04:14 -05:00
|
|
|
"port": &ResourceAttrDiff{
|
|
|
|
NewRemoved: true,
|
|
|
|
},
|
2014-06-05 09:01:51 -05:00
|
|
|
},
|
2014-06-05 08:57:06 -05:00
|
|
|
}
|
|
|
|
|
2014-09-16 18:20:11 -05:00
|
|
|
is2 := is.MergeDiff(diff)
|
2014-06-05 08:57:06 -05:00
|
|
|
|
|
|
|
expected := map[string]string{
|
|
|
|
"foo": "baz",
|
|
|
|
"bar": "foo",
|
2014-06-12 19:51:38 -05:00
|
|
|
"baz": config.UnknownVariableValue,
|
2014-06-05 08:57:06 -05:00
|
|
|
}
|
|
|
|
|
2014-09-16 18:20:11 -05:00
|
|
|
if !reflect.DeepEqual(expected, is2.Attributes) {
|
|
|
|
t.Fatalf("bad: %#v", is2.Attributes)
|
2014-06-05 08:57:06 -05:00
|
|
|
}
|
|
|
|
}
|
2014-06-05 09:04:44 -05:00
|
|
|
|
2014-09-16 18:20:11 -05:00
|
|
|
func TestInstanceState_MergeDiff_nil(t *testing.T) {
|
2016-04-21 14:59:10 -05:00
|
|
|
var is *InstanceState
|
2014-06-05 09:04:44 -05:00
|
|
|
|
2014-09-17 18:33:24 -05:00
|
|
|
diff := &InstanceDiff{
|
2014-06-19 16:08:10 -05:00
|
|
|
Attributes: map[string]*ResourceAttrDiff{
|
|
|
|
"foo": &ResourceAttrDiff{
|
|
|
|
Old: "",
|
|
|
|
New: "baz",
|
|
|
|
},
|
2014-06-05 09:04:44 -05:00
|
|
|
},
|
|
|
|
}
|
|
|
|
|
2014-09-16 18:20:11 -05:00
|
|
|
is2 := is.MergeDiff(diff)
|
2014-06-05 09:04:44 -05:00
|
|
|
|
|
|
|
expected := map[string]string{
|
|
|
|
"foo": "baz",
|
|
|
|
}
|
|
|
|
|
2014-09-16 18:20:11 -05:00
|
|
|
if !reflect.DeepEqual(expected, is2.Attributes) {
|
|
|
|
t.Fatalf("bad: %#v", is2.Attributes)
|
2014-06-05 09:04:44 -05:00
|
|
|
}
|
|
|
|
}
|
2014-06-18 22:54:22 -05:00
|
|
|
|
2014-09-16 18:20:11 -05:00
|
|
|
func TestInstanceState_MergeDiff_nilDiff(t *testing.T) {
|
|
|
|
is := InstanceState{
|
|
|
|
ID: "foo",
|
|
|
|
Attributes: map[string]string{
|
|
|
|
"foo": "bar",
|
2014-06-23 14:32:04 -05:00
|
|
|
},
|
|
|
|
}
|
|
|
|
|
2014-09-16 18:20:11 -05:00
|
|
|
is2 := is.MergeDiff(nil)
|
2014-06-23 14:32:04 -05:00
|
|
|
|
|
|
|
expected := map[string]string{
|
|
|
|
"foo": "bar",
|
|
|
|
}
|
|
|
|
|
2014-09-16 18:20:11 -05:00
|
|
|
if !reflect.DeepEqual(expected, is2.Attributes) {
|
|
|
|
t.Fatalf("bad: %#v", is2.Attributes)
|
2014-06-23 14:32:04 -05:00
|
|
|
}
|
|
|
|
}
|
2014-09-18 12:51:39 -05:00
|
|
|
|
|
|
|
func TestReadWriteState(t *testing.T) {
|
|
|
|
state := &State{
|
2016-08-10 14:47:25 -05:00
|
|
|
Serial: 9,
|
|
|
|
Lineage: "5d1ad1a1-4027-4665-a908-dbe6adff11d8",
|
2014-09-30 15:29:50 -05:00
|
|
|
Remote: &RemoteState{
|
2014-12-03 20:59:23 -06:00
|
|
|
Type: "http",
|
|
|
|
Config: map[string]string{
|
|
|
|
"url": "http://my-cool-server.com/",
|
|
|
|
},
|
2014-09-30 15:29:50 -05:00
|
|
|
},
|
2014-09-18 12:51:39 -05:00
|
|
|
Modules: []*ModuleState{
|
|
|
|
&ModuleState{
|
|
|
|
Path: rootModulePath,
|
2014-11-21 17:34:23 -06:00
|
|
|
Dependencies: []string{
|
|
|
|
"aws_instance.bar",
|
|
|
|
},
|
2014-09-18 12:51:39 -05:00
|
|
|
Resources: map[string]*ResourceState{
|
|
|
|
"foo": &ResourceState{
|
|
|
|
Primary: &InstanceState{
|
|
|
|
ID: "bar",
|
|
|
|
Ephemeral: EphemeralState{
|
|
|
|
ConnInfo: map[string]string{
|
|
|
|
"type": "ssh",
|
|
|
|
"user": "root",
|
|
|
|
"password": "supersecret",
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
}
|
2016-08-10 14:47:25 -05:00
|
|
|
state.init()
|
2014-09-18 12:51:39 -05:00
|
|
|
|
|
|
|
buf := new(bytes.Buffer)
|
|
|
|
if err := WriteState(state, buf); err != nil {
|
|
|
|
t.Fatalf("err: %s", err)
|
|
|
|
}
|
|
|
|
|
2014-09-18 15:38:36 -05:00
|
|
|
// Verify that the version and serial are set
|
2014-12-03 20:35:58 -06:00
|
|
|
if state.Version != StateVersion {
|
2014-09-18 15:38:36 -05:00
|
|
|
t.Fatalf("bad version number: %d", state.Version)
|
|
|
|
}
|
|
|
|
|
2014-09-18 12:51:39 -05:00
|
|
|
actual, err := ReadState(buf)
|
|
|
|
if err != nil {
|
|
|
|
t.Fatalf("err: %s", err)
|
|
|
|
}
|
|
|
|
|
|
|
|
// ReadState should not restore sensitive information!
|
|
|
|
mod := state.RootModule()
|
|
|
|
mod.Resources["foo"].Primary.Ephemeral = EphemeralState{}
|
2016-08-10 14:47:25 -05:00
|
|
|
mod.Resources["foo"].Primary.Ephemeral.init()
|
2014-09-18 12:51:39 -05:00
|
|
|
|
|
|
|
if !reflect.DeepEqual(actual, state) {
|
2016-08-10 14:47:25 -05:00
|
|
|
t.Logf("expected:\n%#v", state)
|
|
|
|
t.Fatalf("got:\n%#v", actual)
|
2014-09-18 12:51:39 -05:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2014-09-18 15:43:26 -05:00
|
|
|
func TestReadStateNewVersion(t *testing.T) {
|
|
|
|
type out struct {
|
|
|
|
Version int
|
|
|
|
}
|
|
|
|
|
2014-12-03 20:35:58 -06:00
|
|
|
buf, err := json.Marshal(&out{StateVersion + 1})
|
2014-09-18 15:43:26 -05:00
|
|
|
if err != nil {
|
|
|
|
t.Fatalf("err: %v", err)
|
|
|
|
}
|
|
|
|
|
|
|
|
s, err := ReadState(bytes.NewReader(buf))
|
|
|
|
if s != nil {
|
|
|
|
t.Fatalf("unexpected: %#v", s)
|
|
|
|
}
|
2016-06-27 15:42:44 -05:00
|
|
|
if !strings.Contains(err.Error(), "does not support state version") {
|
2014-09-18 15:43:26 -05:00
|
|
|
t.Fatalf("err: %v", err)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2016-03-11 13:07:54 -06:00
|
|
|
func TestReadStateTFVersion(t *testing.T) {
|
|
|
|
type tfVersion struct {
|
2016-05-11 19:05:02 -05:00
|
|
|
Version int `json:"version"`
|
2016-03-11 13:07:54 -06:00
|
|
|
TFVersion string `json:"terraform_version"`
|
|
|
|
}
|
|
|
|
|
|
|
|
cases := []struct {
|
|
|
|
Written string
|
|
|
|
Read string
|
|
|
|
Err bool
|
|
|
|
}{
|
|
|
|
{
|
|
|
|
"0.0.0",
|
|
|
|
"0.0.0",
|
|
|
|
false,
|
|
|
|
},
|
|
|
|
{
|
|
|
|
"",
|
|
|
|
"",
|
|
|
|
false,
|
|
|
|
},
|
|
|
|
{
|
|
|
|
"bad",
|
|
|
|
"",
|
|
|
|
true,
|
|
|
|
},
|
|
|
|
}
|
|
|
|
|
|
|
|
for _, tc := range cases {
|
2016-05-11 19:05:02 -05:00
|
|
|
buf, err := json.Marshal(&tfVersion{
|
|
|
|
Version: 2,
|
|
|
|
TFVersion: tc.Written,
|
|
|
|
})
|
2016-03-11 13:07:54 -06:00
|
|
|
if err != nil {
|
|
|
|
t.Fatalf("err: %v", err)
|
|
|
|
}
|
|
|
|
|
|
|
|
s, err := ReadState(bytes.NewReader(buf))
|
|
|
|
if (err != nil) != tc.Err {
|
|
|
|
t.Fatalf("%s: err: %s", tc.Written, err)
|
|
|
|
}
|
|
|
|
if err != nil {
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
|
|
|
|
if s.TFVersion != tc.Read {
|
|
|
|
t.Fatalf("%s: bad: %s", tc.Written, s.TFVersion)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func TestWriteStateTFVersion(t *testing.T) {
|
|
|
|
cases := []struct {
|
|
|
|
Write string
|
|
|
|
Read string
|
|
|
|
Err bool
|
|
|
|
}{
|
|
|
|
{
|
|
|
|
"0.0.0",
|
|
|
|
"0.0.0",
|
|
|
|
false,
|
|
|
|
},
|
|
|
|
{
|
|
|
|
"",
|
|
|
|
"",
|
|
|
|
false,
|
|
|
|
},
|
|
|
|
{
|
|
|
|
"bad",
|
|
|
|
"",
|
|
|
|
true,
|
|
|
|
},
|
|
|
|
}
|
|
|
|
|
|
|
|
for _, tc := range cases {
|
|
|
|
var buf bytes.Buffer
|
|
|
|
err := WriteState(&State{TFVersion: tc.Write}, &buf)
|
|
|
|
if (err != nil) != tc.Err {
|
|
|
|
t.Fatalf("%s: err: %s", tc.Write, err)
|
|
|
|
}
|
|
|
|
if err != nil {
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
|
|
|
|
s, err := ReadState(&buf)
|
|
|
|
if err != nil {
|
|
|
|
t.Fatalf("%s: err: %s", tc.Write, err)
|
|
|
|
}
|
|
|
|
|
|
|
|
if s.TFVersion != tc.Read {
|
|
|
|
t.Fatalf("%s: bad: %s", tc.Write, s.TFVersion)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2016-01-07 14:43:43 -06:00
|
|
|
func TestParseResourceStateKey(t *testing.T) {
|
|
|
|
cases := []struct {
|
|
|
|
Input string
|
|
|
|
Expected *ResourceStateKey
|
|
|
|
ExpectedErr bool
|
|
|
|
}{
|
|
|
|
{
|
|
|
|
Input: "aws_instance.foo.3",
|
|
|
|
Expected: &ResourceStateKey{
|
2016-05-01 20:51:54 -05:00
|
|
|
Mode: config.ManagedResourceMode,
|
2016-01-07 14:43:43 -06:00
|
|
|
Type: "aws_instance",
|
|
|
|
Name: "foo",
|
|
|
|
Index: 3,
|
|
|
|
},
|
|
|
|
},
|
|
|
|
{
|
|
|
|
Input: "aws_instance.foo.0",
|
|
|
|
Expected: &ResourceStateKey{
|
2016-05-01 20:51:54 -05:00
|
|
|
Mode: config.ManagedResourceMode,
|
2016-01-07 14:43:43 -06:00
|
|
|
Type: "aws_instance",
|
|
|
|
Name: "foo",
|
|
|
|
Index: 0,
|
|
|
|
},
|
|
|
|
},
|
|
|
|
{
|
|
|
|
Input: "aws_instance.foo",
|
|
|
|
Expected: &ResourceStateKey{
|
2016-05-01 20:51:54 -05:00
|
|
|
Mode: config.ManagedResourceMode,
|
2016-01-07 14:43:43 -06:00
|
|
|
Type: "aws_instance",
|
|
|
|
Name: "foo",
|
|
|
|
Index: -1,
|
|
|
|
},
|
|
|
|
},
|
2016-05-01 20:51:54 -05:00
|
|
|
{
|
|
|
|
Input: "data.aws_ami.foo",
|
|
|
|
Expected: &ResourceStateKey{
|
|
|
|
Mode: config.DataResourceMode,
|
|
|
|
Type: "aws_ami",
|
|
|
|
Name: "foo",
|
|
|
|
Index: -1,
|
|
|
|
},
|
|
|
|
},
|
2016-01-07 14:43:43 -06:00
|
|
|
{
|
|
|
|
Input: "aws_instance.foo.malformed",
|
|
|
|
ExpectedErr: true,
|
|
|
|
},
|
|
|
|
{
|
|
|
|
Input: "aws_instance.foo.malformedwithnumber.123",
|
|
|
|
ExpectedErr: true,
|
|
|
|
},
|
|
|
|
{
|
|
|
|
Input: "malformed",
|
|
|
|
ExpectedErr: true,
|
|
|
|
},
|
|
|
|
}
|
|
|
|
for _, tc := range cases {
|
|
|
|
rsk, err := ParseResourceStateKey(tc.Input)
|
|
|
|
if rsk != nil && tc.Expected != nil && !rsk.Equal(tc.Expected) {
|
|
|
|
t.Fatalf("%s: expected %s, got %s", tc.Input, tc.Expected, rsk)
|
|
|
|
}
|
|
|
|
if (err != nil) != tc.ExpectedErr {
|
|
|
|
t.Fatalf("%s: expected err: %t, got %s", tc.Input, tc.ExpectedErr, err)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|