github.com/opentofu/opentofu@v1.7.1/internal/legacy/tofu/upgrade_state_v1_test.go (about) 1 // Copyright (c) The OpenTofu Authors 2 // SPDX-License-Identifier: MPL-2.0 3 // Copyright (c) 2023 HashiCorp, Inc. 4 // SPDX-License-Identifier: MPL-2.0 5 6 package tofu 7 8 import ( 9 "bytes" 10 "reflect" 11 "strings" 12 "testing" 13 14 "github.com/davecgh/go-spew/spew" 15 ) 16 17 // TestReadUpgradeStateV1toV3 tests the state upgrade process from the V1 state 18 // to the current version, and needs editing each time. This means it tests the 19 // entire pipeline of upgrades (which migrate version to version). 20 func TestReadUpgradeStateV1toV3(t *testing.T) { 21 // ReadState should transparently detect the old version but will upgrade 22 // it on Write. 23 actual, err := ReadState(strings.NewReader(testV1State)) 24 if err != nil { 25 t.Fatalf("err: %s", err) 26 } 27 28 buf := new(bytes.Buffer) 29 if err := WriteState(actual, buf); err != nil { 30 t.Fatalf("err: %s", err) 31 } 32 33 if actual.Version != 3 { 34 t.Fatalf("bad: State version not incremented; is %d", actual.Version) 35 } 36 37 roundTripped, err := ReadState(buf) 38 if err != nil { 39 t.Fatalf("err: %s", err) 40 } 41 42 if !reflect.DeepEqual(actual, roundTripped) { 43 t.Logf("actual:\n%#v", actual) 44 t.Fatalf("roundTripped:\n%#v", roundTripped) 45 } 46 } 47 48 func TestReadUpgradeStateV1toV3_outputs(t *testing.T) { 49 // ReadState should transparently detect the old version but will upgrade 50 // it on Write. 51 actual, err := ReadState(strings.NewReader(testV1StateWithOutputs)) 52 if err != nil { 53 t.Fatalf("err: %s", err) 54 } 55 56 buf := new(bytes.Buffer) 57 if err := WriteState(actual, buf); err != nil { 58 t.Fatalf("err: %s", err) 59 } 60 61 if actual.Version != 3 { 62 t.Fatalf("bad: State version not incremented; is %d", actual.Version) 63 } 64 65 roundTripped, err := ReadState(buf) 66 if err != nil { 67 t.Fatalf("err: %s", err) 68 } 69 70 if !reflect.DeepEqual(actual, roundTripped) { 71 spew.Config.DisableMethods = true 72 t.Fatalf("bad:\n%s\n\nround tripped:\n%s\n", spew.Sdump(actual), spew.Sdump(roundTripped)) 73 spew.Config.DisableMethods = false 74 } 75 } 76 77 // Upgrading the state should not lose empty module Outputs and Resources maps 78 // during upgrade. The init for a new module initializes new maps, so we may not 79 // be expecting to check for a nil map. 80 func TestReadUpgradeStateV1toV3_emptyState(t *testing.T) { 81 // ReadState should transparently detect the old version but will upgrade 82 // it on Write. 83 orig, err := ReadStateV1([]byte(testV1EmptyState)) 84 if err != nil { 85 t.Fatalf("err: %s", err) 86 } 87 88 stateV2, err := upgradeStateV1ToV2(orig) 89 if err != nil { 90 t.Fatalf("error attempting upgradeStateV1ToV2: %s", err) 91 } 92 93 for _, m := range stateV2.Modules { 94 if m.Resources == nil { 95 t.Fatal("V1 to V2 upgrade lost module.Resources") 96 } 97 if m.Outputs == nil { 98 t.Fatal("V1 to V2 upgrade lost module.Outputs") 99 } 100 } 101 102 stateV3, err := upgradeStateV2ToV3(stateV2) 103 if err != nil { 104 t.Fatalf("error attempting to upgradeStateV2ToV3: %s", err) 105 } 106 for _, m := range stateV3.Modules { 107 if m.Resources == nil { 108 t.Fatal("V2 to V3 upgrade lost module.Resources") 109 } 110 if m.Outputs == nil { 111 t.Fatal("V2 to V3 upgrade lost module.Outputs") 112 } 113 } 114 115 } 116 117 const testV1EmptyState = `{ 118 "version": 1, 119 "serial": 0, 120 "modules": [ 121 { 122 "path": [ 123 "root" 124 ], 125 "outputs": {}, 126 "resources": {} 127 } 128 ] 129 } 130 ` 131 132 const testV1State = `{ 133 "version": 1, 134 "serial": 9, 135 "remote": { 136 "type": "http", 137 "config": { 138 "url": "http://my-cool-server.com/" 139 } 140 }, 141 "modules": [ 142 { 143 "path": [ 144 "root" 145 ], 146 "outputs": null, 147 "resources": { 148 "foo": { 149 "type": "", 150 "primary": { 151 "id": "bar" 152 } 153 } 154 }, 155 "depends_on": [ 156 "aws_instance.bar" 157 ] 158 } 159 ] 160 } 161 ` 162 163 const testV1StateWithOutputs = `{ 164 "version": 1, 165 "serial": 9, 166 "remote": { 167 "type": "http", 168 "config": { 169 "url": "http://my-cool-server.com/" 170 } 171 }, 172 "modules": [ 173 { 174 "path": [ 175 "root" 176 ], 177 "outputs": { 178 "foo": "bar", 179 "baz": "foo" 180 }, 181 "resources": { 182 "foo": { 183 "type": "", 184 "primary": { 185 "id": "bar" 186 } 187 } 188 }, 189 "depends_on": [ 190 "aws_instance.bar" 191 ] 192 } 193 ] 194 } 195 `