From 9554d54116df671b0f6bf09b988305b5cafad0c8 Mon Sep 17 00:00:00 2001 From: James Nugent Date: Thu, 9 Jun 2016 10:44:17 +0100 Subject: [PATCH] core: Add test for V2->V3 state upgrade --- terraform/state_test.go | 122 ----------------- terraform/upgrade_state_v1_test.go | 134 +++++++++++++++++++ terraform/upgrade_state_v2_test.go | 202 +++++++++++++++++++++++++++++ 3 files changed, 336 insertions(+), 122 deletions(-) create mode 100644 terraform/upgrade_state_v1_test.go create mode 100644 terraform/upgrade_state_v2_test.go diff --git a/terraform/state_test.go b/terraform/state_test.go index afab8daec..2f558460b 100644 --- a/terraform/state_test.go +++ b/terraform/state_test.go @@ -7,7 +7,6 @@ import ( "strings" "testing" - "github.com/davecgh/go-spew/spew" "github.com/hashicorp/terraform/config" ) @@ -1128,62 +1127,6 @@ func TestInstanceState_MergeDiff_nilDiff(t *testing.T) { } } -func TestReadUpgradeStateV1toV3(t *testing.T) { - // ReadState should transparently detect the old version but will upgrade - // it on Write. - actual, err := ReadState(strings.NewReader(testV1State)) - if err != nil { - t.Fatalf("err: %s", err) - } - - buf := new(bytes.Buffer) - if err := WriteState(actual, buf); err != nil { - t.Fatalf("err: %s", err) - } - - if actual.Version != 3 { - t.Fatalf("bad: State version not incremented; is %d", actual.Version) - } - - roundTripped, err := ReadState(buf) - if err != nil { - t.Fatalf("err: %s", err) - } - - if !reflect.DeepEqual(actual, roundTripped) { - t.Fatalf("bad: %#v", actual) - } -} - -func TestReadUpgradeStateV1toV3_outputs(t *testing.T) { - // ReadState should transparently detect the old version but will upgrade - // it on Write. - actual, err := ReadState(strings.NewReader(testV1StateWithOutputs)) - if err != nil { - t.Fatalf("err: %s", err) - } - - buf := new(bytes.Buffer) - if err := WriteState(actual, buf); err != nil { - t.Fatalf("err: %s", err) - } - - if actual.Version != 3 { - t.Fatalf("bad: State version not incremented; is %d", actual.Version) - } - - roundTripped, err := ReadState(buf) - if err != nil { - t.Fatalf("err: %s", err) - } - - if !reflect.DeepEqual(actual, roundTripped) { - spew.Config.DisableMethods = true - t.Fatalf("bad:\n%s\n\nround tripped:\n%s\n", spew.Sdump(actual), spew.Sdump(roundTripped)) - spew.Config.DisableMethods = false - } -} - func TestReadWriteState(t *testing.T) { state := &State{ Serial: 9, @@ -1420,68 +1363,3 @@ func TestParseResourceStateKey(t *testing.T) { } } } - -const testV1State = `{ - "version": 1, - "serial": 9, - "remote": { - "type": "http", - "config": { - "url": "http://my-cool-server.com/" - } - }, - "modules": [ - { - "path": [ - "root" - ], - "outputs": null, - "resources": { - "foo": { - "type": "", - "primary": { - "id": "bar" - } - } - }, - "depends_on": [ - "aws_instance.bar" - ] - } - ] -} -` - -const testV1StateWithOutputs = `{ - "version": 1, - "serial": 9, - "remote": { - "type": "http", - "config": { - "url": "http://my-cool-server.com/" - } - }, - "modules": [ - { - "path": [ - "root" - ], - "outputs": { - "foo": "bar", - "baz": "foo" - }, - "resources": { - "foo": { - "type": "", - "primary": { - "id": "bar" - } - } - }, - "depends_on": [ - "aws_instance.bar" - ] - } - ] -} -` diff --git a/terraform/upgrade_state_v1_test.go b/terraform/upgrade_state_v1_test.go new file mode 100644 index 000000000..74e8bce64 --- /dev/null +++ b/terraform/upgrade_state_v1_test.go @@ -0,0 +1,134 @@ +package terraform + +import ( + "bytes" + "reflect" + "strings" + "testing" + + "github.com/davecgh/go-spew/spew" +) + +// TestReadUpgradeStateV1toV3 tests the state upgrade process from the V1 state +// to the current version, and needs editing each time. This means it tests the +// entire pipeline of upgrades (which migrate version to version). +func TestReadUpgradeStateV1toV3(t *testing.T) { + // ReadState should transparently detect the old version but will upgrade + // it on Write. + actual, err := ReadState(strings.NewReader(testV1State)) + if err != nil { + t.Fatalf("err: %s", err) + } + + buf := new(bytes.Buffer) + if err := WriteState(actual, buf); err != nil { + t.Fatalf("err: %s", err) + } + + if actual.Version != 3 { + t.Fatalf("bad: State version not incremented; is %d", actual.Version) + } + + roundTripped, err := ReadState(buf) + if err != nil { + t.Fatalf("err: %s", err) + } + + if !reflect.DeepEqual(actual, roundTripped) { + t.Fatalf("bad: %#v", actual) + } +} + +func TestReadUpgradeStateV1toV3_outputs(t *testing.T) { + // ReadState should transparently detect the old version but will upgrade + // it on Write. + actual, err := ReadState(strings.NewReader(testV1StateWithOutputs)) + if err != nil { + t.Fatalf("err: %s", err) + } + + buf := new(bytes.Buffer) + if err := WriteState(actual, buf); err != nil { + t.Fatalf("err: %s", err) + } + + if actual.Version != 3 { + t.Fatalf("bad: State version not incremented; is %d", actual.Version) + } + + roundTripped, err := ReadState(buf) + if err != nil { + t.Fatalf("err: %s", err) + } + + if !reflect.DeepEqual(actual, roundTripped) { + spew.Config.DisableMethods = true + t.Fatalf("bad:\n%s\n\nround tripped:\n%s\n", spew.Sdump(actual), spew.Sdump(roundTripped)) + spew.Config.DisableMethods = false + } +} + +const testV1State = `{ + "version": 1, + "serial": 9, + "remote": { + "type": "http", + "config": { + "url": "http://my-cool-server.com/" + } + }, + "modules": [ + { + "path": [ + "root" + ], + "outputs": null, + "resources": { + "foo": { + "type": "", + "primary": { + "id": "bar" + } + } + }, + "depends_on": [ + "aws_instance.bar" + ] + } + ] +} +` + +const testV1StateWithOutputs = `{ + "version": 1, + "serial": 9, + "remote": { + "type": "http", + "config": { + "url": "http://my-cool-server.com/" + } + }, + "modules": [ + { + "path": [ + "root" + ], + "outputs": { + "foo": "bar", + "baz": "foo" + }, + "resources": { + "foo": { + "type": "", + "primary": { + "id": "bar" + } + } + }, + "depends_on": [ + "aws_instance.bar" + ] + } + ] +} +` diff --git a/terraform/upgrade_state_v2_test.go b/terraform/upgrade_state_v2_test.go new file mode 100644 index 000000000..546d74968 --- /dev/null +++ b/terraform/upgrade_state_v2_test.go @@ -0,0 +1,202 @@ +package terraform + +import ( + "bytes" + "strings" + "testing" +) + +// TestReadUpgradeStateV2toV3 tests the state upgrade process from the V2 state +// to the current version, and needs editing each time. This means it tests the +// entire pipeline of upgrades (which migrate version to version). +func TestReadUpgradeStateV2toV3(t *testing.T) { + // ReadState should transparently detect the old version but will upgrade + // it on Write. + upgraded, err := ReadState(strings.NewReader(testV2State)) + if err != nil { + t.Fatalf("err: %s", err) + } + + buf := new(bytes.Buffer) + if err := WriteState(upgraded, buf); err != nil { + t.Fatalf("err: %s", err) + } + + if upgraded.Version != 3 { + t.Fatalf("bad: State version not incremented; is %d", upgraded.Version) + } + + // For this test we cannot assert that we match the round trip because an + // empty map has been removed from state. Instead we make assertions against + // some of the key fields in the _upgraded_ state. + instanceState, ok := upgraded.RootModule().Resources["test_resource.main"] + if !ok { + t.Fatalf("Instance state for test_resource.main was removed from state during upgrade") + } + + primary := instanceState.Primary + if primary == nil { + t.Fatalf("Primary instance was removed from state for test_resource.main") + } + + // Non-empty computed map is moved from .# to .% + if _, ok := primary.Attributes["computed_map.#"]; ok { + t.Fatalf("Count was not upgraded from .# to .%% for computed_map") + } + if count, ok := primary.Attributes["computed_map.%"]; !ok || count != "1" { + t.Fatalf("Count was not in .%% or was not 2 for computed_map") + } + + // list_of_map top level retains .# + if count, ok := primary.Attributes["list_of_map.#"]; !ok || count != "2" { + t.Fatal("Count for list_of_map was migrated incorrectly") + } + + // list_of_map.0 is moved from .# to .% + if _, ok := primary.Attributes["list_of_map.0.#"]; ok { + t.Fatalf("Count was not upgraded from .# to .%% for list_of_map.0") + } + if count, ok := primary.Attributes["list_of_map.0.%"]; !ok || count != "2" { + t.Fatalf("Count was not in .%% or was not 2 for list_of_map.0") + } + + // list_of_map.1 is moved from .# to .% + if _, ok := primary.Attributes["list_of_map.1.#"]; ok { + t.Fatalf("Count was not upgraded from .# to .%% for list_of_map.1") + } + if count, ok := primary.Attributes["list_of_map.1.%"]; !ok || count != "2" { + t.Fatalf("Count was not in .%% or was not 2 for list_of_map.1") + } + + // map is moved from .# to .% + if _, ok := primary.Attributes["map.#"]; ok { + t.Fatalf("Count was not upgraded from .# to .%% for map") + } + if count, ok := primary.Attributes["map.%"]; !ok || count != "2" { + t.Fatalf("Count was not in .%% or was not 2 for map") + } + + // optional_computed_map should be removed from state + if _, ok := primary.Attributes["optional_computed_map"]; ok { + t.Fatal("optional_computed_map was not removed from state") + } + + // required_map is moved from .# to .% + if _, ok := primary.Attributes["required_map.#"]; ok { + t.Fatalf("Count was not upgraded from .# to .%% for required_map") + } + if count, ok := primary.Attributes["required_map.%"]; !ok || count != "3" { + t.Fatalf("Count was not in .%% or was not 3 for map") + } + + // computed_list keeps .# + if count, ok := primary.Attributes["computed_list.#"]; !ok || count != "2" { + t.Fatal("Count was migrated incorrectly for computed_list") + } + + // computed_set keeps .# + if count, ok := primary.Attributes["computed_set.#"]; !ok || count != "2" { + t.Fatal("Count was migrated incorrectly for computed_set") + } + if val, ok := primary.Attributes["computed_set.2337322984"]; !ok || val != "setval1" { + t.Fatal("Set item for computed_set.2337322984 changed or moved") + } + if val, ok := primary.Attributes["computed_set.307881554"]; !ok || val != "setval2" { + t.Fatal("Set item for computed_set.307881554 changed or moved") + } + + // string properties are unaffected + if val, ok := primary.Attributes["id"]; !ok || val != "testId" { + t.Fatal("id was not set correctly after migration") + } +} + +const testV2State = `{ + "version": 2, + "terraform_version": "0.7.0", + "serial": 2, + "modules": [ + { + "path": [ + "root" + ], + "outputs": { + "computed_map": { + "sensitive": false, + "type": "map", + "value": { + "key1": "value1" + } + }, + "computed_set": { + "sensitive": false, + "type": "list", + "value": [ + "setval1", + "setval2" + ] + }, + "map": { + "sensitive": false, + "type": "map", + "value": { + "key": "test", + "test": "test" + } + }, + "set": { + "sensitive": false, + "type": "list", + "value": [ + "test1", + "test2" + ] + } + }, + "resources": { + "test_resource.main": { + "type": "test_resource", + "primary": { + "id": "testId", + "attributes": { + "computed_list.#": "2", + "computed_list.0": "listval1", + "computed_list.1": "listval2", + "computed_map.#": "1", + "computed_map.key1": "value1", + "computed_read_only": "value_from_api", + "computed_read_only_force_new": "value_from_api", + "computed_set.#": "2", + "computed_set.2337322984": "setval1", + "computed_set.307881554": "setval2", + "id": "testId", + "list_of_map.#": "2", + "list_of_map.0.#": "2", + "list_of_map.0.key1": "value1", + "list_of_map.0.key2": "value2", + "list_of_map.1.#": "2", + "list_of_map.1.key3": "value3", + "list_of_map.1.key4": "value4", + "map.#": "2", + "map.key": "test", + "map.test": "test", + "map_that_look_like_set.#": "2", + "map_that_look_like_set.12352223": "hello", + "map_that_look_like_set.36234341": "world", + "optional_computed_map.#": "0", + "required": "Hello World", + "required_map.#": "3", + "required_map.key1": "value1", + "required_map.key2": "value2", + "required_map.key3": "value3", + "set.#": "2", + "set.2326977762": "test1", + "set.331058520": "test2" + } + } + } + } + } + ] +} +`