mirror of
https://github.com/opentofu/opentofu.git
synced 2025-02-16 18:35:03 -06:00
We were only comparing the last element of the module, which meant that deeply nested modules with the same name but different ancestry had an undefined sort order, which could cause inconsistencies in state storage and potentially break remote state MD5 checksumming.
898 lines
16 KiB
Go
898 lines
16 KiB
Go
package terraform
|
|
|
|
import (
|
|
"bytes"
|
|
"encoding/json"
|
|
"reflect"
|
|
"strings"
|
|
"testing"
|
|
|
|
"github.com/hashicorp/terraform/config"
|
|
)
|
|
|
|
func TestStateAddModule(t *testing.T) {
|
|
cases := []struct {
|
|
In [][]string
|
|
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"},
|
|
},
|
|
},
|
|
// 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"},
|
|
},
|
|
},
|
|
}
|
|
|
|
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)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestStateModuleOrphans(t *testing.T) {
|
|
state := &State{
|
|
Modules: []*ModuleState{
|
|
&ModuleState{
|
|
Path: RootModulePath,
|
|
},
|
|
&ModuleState{
|
|
Path: []string{RootModuleName, "foo"},
|
|
},
|
|
&ModuleState{
|
|
Path: []string{RootModuleName, "bar"},
|
|
},
|
|
},
|
|
}
|
|
|
|
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)
|
|
}
|
|
}
|
|
|
|
func TestStateModuleOrphans_nested(t *testing.T) {
|
|
state := &State{
|
|
Modules: []*ModuleState{
|
|
&ModuleState{
|
|
Path: RootModulePath,
|
|
},
|
|
&ModuleState{
|
|
Path: []string{RootModuleName, "foo", "bar"},
|
|
},
|
|
},
|
|
}
|
|
|
|
actual := state.ModuleOrphans(RootModulePath, nil)
|
|
expected := [][]string{
|
|
[]string{RootModuleName, "foo"},
|
|
}
|
|
|
|
if !reflect.DeepEqual(actual, expected) {
|
|
t.Fatalf("bad: %#v", actual)
|
|
}
|
|
}
|
|
|
|
func TestStateModuleOrphans_nilConfig(t *testing.T) {
|
|
state := &State{
|
|
Modules: []*ModuleState{
|
|
&ModuleState{
|
|
Path: RootModulePath,
|
|
},
|
|
&ModuleState{
|
|
Path: []string{RootModuleName, "foo"},
|
|
},
|
|
&ModuleState{
|
|
Path: []string{RootModuleName, "bar"},
|
|
},
|
|
},
|
|
}
|
|
|
|
actual := state.ModuleOrphans(RootModulePath, nil)
|
|
expected := [][]string{
|
|
[]string{RootModuleName, "foo"},
|
|
[]string{RootModuleName, "bar"},
|
|
}
|
|
|
|
if !reflect.DeepEqual(actual, expected) {
|
|
t.Fatalf("bad: %#v", actual)
|
|
}
|
|
}
|
|
|
|
func TestStateEqual(t *testing.T) {
|
|
cases := []struct {
|
|
Result bool
|
|
One, Two *State
|
|
}{
|
|
// Nils
|
|
{
|
|
false,
|
|
nil,
|
|
&State{Version: 2},
|
|
},
|
|
|
|
{
|
|
true,
|
|
nil,
|
|
nil,
|
|
},
|
|
|
|
// 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,
|
|
},
|
|
},
|
|
},
|
|
},
|
|
|
|
// 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",
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
}
|
|
|
|
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())
|
|
}
|
|
}
|
|
}
|
|
|
|
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,
|
|
},
|
|
"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,
|
|
},
|
|
"S1 serial is higher": {
|
|
&State{Serial: 5},
|
|
&State{
|
|
Serial: 3,
|
|
Modules: []*ModuleState{
|
|
&ModuleState{Path: rootModulePath},
|
|
},
|
|
},
|
|
5,
|
|
},
|
|
}
|
|
|
|
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)
|
|
}
|
|
}
|
|
}
|
|
|
|
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{
|
|
Tainted: nil,
|
|
},
|
|
&ResourceState{
|
|
Tainted: []*InstanceState{
|
|
&InstanceState{ID: "foo"},
|
|
},
|
|
},
|
|
},
|
|
|
|
{
|
|
true,
|
|
&ResourceState{
|
|
Tainted: []*InstanceState{
|
|
&InstanceState{ID: "foo"},
|
|
},
|
|
},
|
|
&ResourceState{
|
|
Tainted: []*InstanceState{
|
|
&InstanceState{ID: "foo"},
|
|
},
|
|
},
|
|
},
|
|
|
|
{
|
|
true,
|
|
&ResourceState{
|
|
Tainted: []*InstanceState{
|
|
&InstanceState{ID: "foo"},
|
|
nil,
|
|
},
|
|
},
|
|
&ResourceState{
|
|
Tainted: []*InstanceState{
|
|
&InstanceState{ID: "foo"},
|
|
},
|
|
},
|
|
},
|
|
}
|
|
|
|
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())
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestResourceStateTaint(t *testing.T) {
|
|
cases := map[string]struct {
|
|
Input *ResourceState
|
|
Output *ResourceState
|
|
}{
|
|
"no primary": {
|
|
&ResourceState{},
|
|
&ResourceState{},
|
|
},
|
|
|
|
"primary, no tainted": {
|
|
&ResourceState{
|
|
Primary: &InstanceState{ID: "foo"},
|
|
},
|
|
&ResourceState{
|
|
Tainted: []*InstanceState{
|
|
&InstanceState{ID: "foo"},
|
|
},
|
|
},
|
|
},
|
|
|
|
"primary, with tainted": {
|
|
&ResourceState{
|
|
Primary: &InstanceState{ID: "foo"},
|
|
Tainted: []*InstanceState{
|
|
&InstanceState{ID: "bar"},
|
|
},
|
|
},
|
|
&ResourceState{
|
|
Tainted: []*InstanceState{
|
|
&InstanceState{ID: "bar"},
|
|
&InstanceState{ID: "foo"},
|
|
},
|
|
},
|
|
},
|
|
}
|
|
|
|
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)
|
|
}
|
|
}
|
|
}
|
|
|
|
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)
|
|
}
|
|
}
|
|
}
|
|
|
|
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())
|
|
}
|
|
}
|
|
}
|
|
|
|
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)
|
|
}
|
|
}
|
|
}
|
|
|
|
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)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestInstanceState_MergeDiff(t *testing.T) {
|
|
is := InstanceState{
|
|
ID: "foo",
|
|
Attributes: map[string]string{
|
|
"foo": "bar",
|
|
"port": "8000",
|
|
},
|
|
}
|
|
|
|
diff := &InstanceDiff{
|
|
Attributes: map[string]*ResourceAttrDiff{
|
|
"foo": &ResourceAttrDiff{
|
|
Old: "bar",
|
|
New: "baz",
|
|
},
|
|
"bar": &ResourceAttrDiff{
|
|
Old: "",
|
|
New: "foo",
|
|
},
|
|
"baz": &ResourceAttrDiff{
|
|
Old: "",
|
|
New: "foo",
|
|
NewComputed: true,
|
|
},
|
|
"port": &ResourceAttrDiff{
|
|
NewRemoved: true,
|
|
},
|
|
},
|
|
}
|
|
|
|
is2 := is.MergeDiff(diff)
|
|
|
|
expected := map[string]string{
|
|
"foo": "baz",
|
|
"bar": "foo",
|
|
"baz": config.UnknownVariableValue,
|
|
}
|
|
|
|
if !reflect.DeepEqual(expected, is2.Attributes) {
|
|
t.Fatalf("bad: %#v", is2.Attributes)
|
|
}
|
|
}
|
|
|
|
func TestInstanceState_MergeDiff_nil(t *testing.T) {
|
|
var is *InstanceState = nil
|
|
|
|
diff := &InstanceDiff{
|
|
Attributes: map[string]*ResourceAttrDiff{
|
|
"foo": &ResourceAttrDiff{
|
|
Old: "",
|
|
New: "baz",
|
|
},
|
|
},
|
|
}
|
|
|
|
is2 := is.MergeDiff(diff)
|
|
|
|
expected := map[string]string{
|
|
"foo": "baz",
|
|
}
|
|
|
|
if !reflect.DeepEqual(expected, is2.Attributes) {
|
|
t.Fatalf("bad: %#v", is2.Attributes)
|
|
}
|
|
}
|
|
|
|
func TestInstanceState_MergeDiff_nilDiff(t *testing.T) {
|
|
is := InstanceState{
|
|
ID: "foo",
|
|
Attributes: map[string]string{
|
|
"foo": "bar",
|
|
},
|
|
}
|
|
|
|
is2 := is.MergeDiff(nil)
|
|
|
|
expected := map[string]string{
|
|
"foo": "bar",
|
|
}
|
|
|
|
if !reflect.DeepEqual(expected, is2.Attributes) {
|
|
t.Fatalf("bad: %#v", is2.Attributes)
|
|
}
|
|
}
|
|
|
|
func TestReadUpgradeState(t *testing.T) {
|
|
state := &StateV1{
|
|
Resources: map[string]*ResourceStateV1{
|
|
"foo": &ResourceStateV1{
|
|
ID: "bar",
|
|
},
|
|
},
|
|
}
|
|
buf := new(bytes.Buffer)
|
|
if err := testWriteStateV1(state, buf); err != nil {
|
|
t.Fatalf("err: %s", err)
|
|
}
|
|
|
|
// ReadState should transparently detect the old
|
|
// version and upgrade up so the latest.
|
|
actual, err := ReadState(buf)
|
|
if err != nil {
|
|
t.Fatalf("err: %s", err)
|
|
}
|
|
|
|
upgraded, err := upgradeV1State(state)
|
|
if err != nil {
|
|
t.Fatalf("err: %s", err)
|
|
}
|
|
|
|
if !reflect.DeepEqual(actual, upgraded) {
|
|
t.Fatalf("bad: %#v", actual)
|
|
}
|
|
}
|
|
|
|
func TestReadWriteState(t *testing.T) {
|
|
state := &State{
|
|
Serial: 9,
|
|
Remote: &RemoteState{
|
|
Type: "http",
|
|
Config: map[string]string{
|
|
"url": "http://my-cool-server.com/",
|
|
},
|
|
},
|
|
Modules: []*ModuleState{
|
|
&ModuleState{
|
|
Path: rootModulePath,
|
|
Dependencies: []string{
|
|
"aws_instance.bar",
|
|
},
|
|
Resources: map[string]*ResourceState{
|
|
"foo": &ResourceState{
|
|
Primary: &InstanceState{
|
|
ID: "bar",
|
|
Ephemeral: EphemeralState{
|
|
ConnInfo: map[string]string{
|
|
"type": "ssh",
|
|
"user": "root",
|
|
"password": "supersecret",
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
}
|
|
|
|
// Checksum before the write
|
|
chksum := checksumStruct(t, state)
|
|
|
|
buf := new(bytes.Buffer)
|
|
if err := WriteState(state, buf); err != nil {
|
|
t.Fatalf("err: %s", err)
|
|
}
|
|
|
|
// Verify that the version and serial are set
|
|
if state.Version != StateVersion {
|
|
t.Fatalf("bad version number: %d", state.Version)
|
|
}
|
|
|
|
// Checksum after the write
|
|
chksumAfter := checksumStruct(t, state)
|
|
if chksumAfter != chksum {
|
|
t.Fatalf("structure changed during serialization!")
|
|
}
|
|
|
|
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{}
|
|
|
|
if !reflect.DeepEqual(actual, state) {
|
|
t.Fatalf("bad: %#v", actual)
|
|
}
|
|
}
|
|
|
|
func TestReadStateNewVersion(t *testing.T) {
|
|
type out struct {
|
|
Version int
|
|
}
|
|
|
|
buf, err := json.Marshal(&out{StateVersion + 1})
|
|
if err != nil {
|
|
t.Fatalf("err: %v", err)
|
|
}
|
|
|
|
s, err := ReadState(bytes.NewReader(buf))
|
|
if s != nil {
|
|
t.Fatalf("unexpected: %#v", s)
|
|
}
|
|
if !strings.Contains(err.Error(), "not supported") {
|
|
t.Fatalf("err: %v", err)
|
|
}
|
|
}
|
|
|
|
func TestUpgradeV1State(t *testing.T) {
|
|
old := &StateV1{
|
|
Outputs: map[string]string{
|
|
"ip": "127.0.0.1",
|
|
},
|
|
Resources: map[string]*ResourceStateV1{
|
|
"foo": &ResourceStateV1{
|
|
Type: "test_resource",
|
|
ID: "bar",
|
|
Attributes: map[string]string{
|
|
"key": "val",
|
|
},
|
|
},
|
|
"bar": &ResourceStateV1{
|
|
Type: "test_resource",
|
|
ID: "1234",
|
|
Attributes: map[string]string{
|
|
"a": "b",
|
|
},
|
|
},
|
|
},
|
|
Tainted: map[string]struct{}{
|
|
"bar": struct{}{},
|
|
},
|
|
}
|
|
state, err := upgradeV1State(old)
|
|
if err != nil {
|
|
t.Fatalf("err: %v", err)
|
|
}
|
|
|
|
if len(state.Modules) != 1 {
|
|
t.Fatalf("should only have root module: %#v", state.Modules)
|
|
}
|
|
root := state.RootModule()
|
|
|
|
if len(root.Outputs) != 1 {
|
|
t.Fatalf("bad outputs: %v", root.Outputs)
|
|
}
|
|
if root.Outputs["ip"] != "127.0.0.1" {
|
|
t.Fatalf("bad outputs: %v", root.Outputs)
|
|
}
|
|
|
|
if len(root.Resources) != 2 {
|
|
t.Fatalf("bad resources: %v", root.Resources)
|
|
}
|
|
|
|
foo := root.Resources["foo"]
|
|
if foo.Type != "test_resource" {
|
|
t.Fatalf("bad: %#v", foo)
|
|
}
|
|
if foo.Primary == nil || foo.Primary.ID != "bar" ||
|
|
foo.Primary.Attributes["key"] != "val" {
|
|
t.Fatalf("bad: %#v", foo)
|
|
}
|
|
if len(foo.Tainted) > 0 {
|
|
t.Fatalf("bad: %#v", foo)
|
|
}
|
|
|
|
bar := root.Resources["bar"]
|
|
if bar.Type != "test_resource" {
|
|
t.Fatalf("bad: %#v", bar)
|
|
}
|
|
if bar.Primary != nil {
|
|
t.Fatalf("bad: %#v", bar)
|
|
}
|
|
if len(bar.Tainted) != 1 {
|
|
t.Fatalf("bad: %#v", bar)
|
|
}
|
|
bt := bar.Tainted[0]
|
|
if bt.ID != "1234" || bt.Attributes["a"] != "b" {
|
|
t.Fatalf("bad: %#v", bt)
|
|
}
|
|
}
|