core: Add test for V2->V3 state upgrade
This commit is contained in:
parent
706ccb7dfe
commit
9554d54116
|
@ -7,7 +7,6 @@ import (
|
||||||
"strings"
|
"strings"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/davecgh/go-spew/spew"
|
|
||||||
"github.com/hashicorp/terraform/config"
|
"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) {
|
func TestReadWriteState(t *testing.T) {
|
||||||
state := &State{
|
state := &State{
|
||||||
Serial: 9,
|
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"
|
|
||||||
]
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
`
|
|
||||||
|
|
|
@ -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"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
`
|
|
@ -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"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
`
|
Loading…
Reference in New Issue