kubeform.dev/terraform-backend-sdk@v0.0.0-20220310143633-45f07fe731c5/plans/planfile/tfplan_test.go (about) 1 package planfile 2 3 import ( 4 "bytes" 5 "testing" 6 7 "github.com/go-test/deep" 8 "github.com/zclconf/go-cty/cty" 9 10 "kubeform.dev/terraform-backend-sdk/addrs" 11 "kubeform.dev/terraform-backend-sdk/lang/marks" 12 "kubeform.dev/terraform-backend-sdk/plans" 13 ) 14 15 func TestTFPlanRoundTrip(t *testing.T) { 16 objTy := cty.Object(map[string]cty.Type{ 17 "id": cty.String, 18 }) 19 20 plan := &plans.Plan{ 21 VariableValues: map[string]plans.DynamicValue{ 22 "foo": mustNewDynamicValueStr("foo value"), 23 }, 24 Changes: &plans.Changes{ 25 Outputs: []*plans.OutputChangeSrc{ 26 { 27 Addr: addrs.OutputValue{Name: "bar"}.Absolute(addrs.RootModuleInstance), 28 ChangeSrc: plans.ChangeSrc{ 29 Action: plans.Create, 30 After: mustDynamicOutputValue("bar value"), 31 }, 32 Sensitive: false, 33 }, 34 { 35 Addr: addrs.OutputValue{Name: "baz"}.Absolute(addrs.RootModuleInstance), 36 ChangeSrc: plans.ChangeSrc{ 37 Action: plans.NoOp, 38 Before: mustDynamicOutputValue("baz value"), 39 After: mustDynamicOutputValue("baz value"), 40 }, 41 Sensitive: false, 42 }, 43 { 44 Addr: addrs.OutputValue{Name: "secret"}.Absolute(addrs.RootModuleInstance), 45 ChangeSrc: plans.ChangeSrc{ 46 Action: plans.Update, 47 Before: mustDynamicOutputValue("old secret value"), 48 After: mustDynamicOutputValue("new secret value"), 49 }, 50 Sensitive: true, 51 }, 52 }, 53 Resources: []*plans.ResourceInstanceChangeSrc{ 54 { 55 Addr: addrs.Resource{ 56 Mode: addrs.ManagedResourceMode, 57 Type: "test_thing", 58 Name: "woot", 59 }.Instance(addrs.IntKey(0)).Absolute(addrs.RootModuleInstance), 60 PrevRunAddr: addrs.Resource{ 61 Mode: addrs.ManagedResourceMode, 62 Type: "test_thing", 63 Name: "woot", 64 }.Instance(addrs.NoKey).Absolute(addrs.RootModuleInstance), 65 ProviderAddr: addrs.AbsProviderConfig{ 66 Provider: addrs.NewDefaultProvider("test"), 67 Module: addrs.RootModule, 68 }, 69 ChangeSrc: plans.ChangeSrc{ 70 Action: plans.DeleteThenCreate, 71 Before: mustNewDynamicValue(cty.ObjectVal(map[string]cty.Value{ 72 "id": cty.StringVal("foo-bar-baz"), 73 "boop": cty.ListVal([]cty.Value{ 74 cty.StringVal("beep"), 75 }), 76 }), objTy), 77 After: mustNewDynamicValue(cty.ObjectVal(map[string]cty.Value{ 78 "id": cty.UnknownVal(cty.String), 79 "boop": cty.ListVal([]cty.Value{ 80 cty.StringVal("beep"), 81 cty.StringVal("honk"), 82 }), 83 }), objTy), 84 AfterValMarks: []cty.PathValueMarks{ 85 { 86 Path: cty.GetAttrPath("boop").IndexInt(1), 87 Marks: cty.NewValueMarks(marks.Sensitive), 88 }, 89 }, 90 }, 91 RequiredReplace: cty.NewPathSet( 92 cty.GetAttrPath("boop"), 93 ), 94 ActionReason: plans.ResourceInstanceReplaceBecauseCannotUpdate, 95 }, 96 { 97 Addr: addrs.Resource{ 98 Mode: addrs.ManagedResourceMode, 99 Type: "test_thing", 100 Name: "woot", 101 }.Instance(addrs.IntKey(1)).Absolute(addrs.RootModuleInstance), 102 PrevRunAddr: addrs.Resource{ 103 Mode: addrs.ManagedResourceMode, 104 Type: "test_thing", 105 Name: "woot", 106 }.Instance(addrs.IntKey(1)).Absolute(addrs.RootModuleInstance), 107 DeposedKey: "foodface", 108 ProviderAddr: addrs.AbsProviderConfig{ 109 Provider: addrs.NewDefaultProvider("test"), 110 Module: addrs.RootModule, 111 }, 112 ChangeSrc: plans.ChangeSrc{ 113 Action: plans.Delete, 114 Before: mustNewDynamicValue(cty.ObjectVal(map[string]cty.Value{ 115 "id": cty.StringVal("bar-baz-foo"), 116 }), objTy), 117 }, 118 }, 119 }, 120 }, 121 DriftedResources: []*plans.ResourceInstanceChangeSrc{ 122 { 123 Addr: addrs.Resource{ 124 Mode: addrs.ManagedResourceMode, 125 Type: "test_thing", 126 Name: "woot", 127 }.Instance(addrs.IntKey(0)).Absolute(addrs.RootModuleInstance), 128 PrevRunAddr: addrs.Resource{ 129 Mode: addrs.ManagedResourceMode, 130 Type: "test_thing", 131 Name: "woot", 132 }.Instance(addrs.NoKey).Absolute(addrs.RootModuleInstance), 133 ProviderAddr: addrs.AbsProviderConfig{ 134 Provider: addrs.NewDefaultProvider("test"), 135 Module: addrs.RootModule, 136 }, 137 ChangeSrc: plans.ChangeSrc{ 138 Action: plans.DeleteThenCreate, 139 Before: mustNewDynamicValue(cty.ObjectVal(map[string]cty.Value{ 140 "id": cty.StringVal("foo-bar-baz"), 141 "boop": cty.ListVal([]cty.Value{ 142 cty.StringVal("beep"), 143 }), 144 }), objTy), 145 After: mustNewDynamicValue(cty.ObjectVal(map[string]cty.Value{ 146 "id": cty.UnknownVal(cty.String), 147 "boop": cty.ListVal([]cty.Value{ 148 cty.StringVal("beep"), 149 cty.StringVal("bonk"), 150 }), 151 }), objTy), 152 AfterValMarks: []cty.PathValueMarks{ 153 { 154 Path: cty.GetAttrPath("boop").IndexInt(1), 155 Marks: cty.NewValueMarks(marks.Sensitive), 156 }, 157 }, 158 }, 159 }, 160 }, 161 TargetAddrs: []addrs.Targetable{ 162 addrs.Resource{ 163 Mode: addrs.ManagedResourceMode, 164 Type: "test_thing", 165 Name: "woot", 166 }.Absolute(addrs.RootModuleInstance), 167 }, 168 ProviderSHA256s: map[string][]byte{ 169 "test": []byte{ 170 0xba, 0x5e, 0x1e, 0x55, 0xb0, 0x1d, 0xfa, 0xce, 171 0xef, 0xfe, 0xc7, 0xed, 0x1a, 0xbe, 0x11, 0xed, 172 0x5c, 0xa1, 0xab, 0x1e, 0xda, 0x7a, 0xba, 0x5e, 173 0x70, 0x7a, 0x11, 0xed, 0xb0, 0x07, 0xab, 0x1e, 174 }, 175 }, 176 Backend: plans.Backend{ 177 Type: "local", 178 Config: mustNewDynamicValue( 179 cty.ObjectVal(map[string]cty.Value{ 180 "foo": cty.StringVal("bar"), 181 }), 182 cty.Object(map[string]cty.Type{ 183 "foo": cty.String, 184 }), 185 ), 186 Workspace: "default", 187 }, 188 } 189 190 var buf bytes.Buffer 191 err := writeTfplan(plan, &buf) 192 if err != nil { 193 t.Fatal(err) 194 } 195 196 newPlan, err := readTfplan(&buf) 197 if err != nil { 198 t.Fatal(err) 199 } 200 201 { 202 oldDepth := deep.MaxDepth 203 oldCompare := deep.CompareUnexportedFields 204 deep.MaxDepth = 20 205 deep.CompareUnexportedFields = true 206 defer func() { 207 deep.MaxDepth = oldDepth 208 deep.CompareUnexportedFields = oldCompare 209 }() 210 } 211 for _, problem := range deep.Equal(newPlan, plan) { 212 t.Error(problem) 213 } 214 } 215 216 func mustDynamicOutputValue(val string) plans.DynamicValue { 217 ret, err := plans.NewDynamicValue(cty.StringVal(val), cty.DynamicPseudoType) 218 if err != nil { 219 panic(err) 220 } 221 return ret 222 } 223 224 func mustNewDynamicValue(val cty.Value, ty cty.Type) plans.DynamicValue { 225 ret, err := plans.NewDynamicValue(val, ty) 226 if err != nil { 227 panic(err) 228 } 229 return ret 230 } 231 232 func mustNewDynamicValueStr(val string) plans.DynamicValue { 233 realVal := cty.StringVal(val) 234 ret, err := plans.NewDynamicValue(realVal, cty.String) 235 if err != nil { 236 panic(err) 237 } 238 return ret 239 } 240 241 // TestTFPlanRoundTripDestroy ensures that encoding and decoding null values for 242 // destroy doesn't leave us with any nil values. 243 func TestTFPlanRoundTripDestroy(t *testing.T) { 244 objTy := cty.Object(map[string]cty.Type{ 245 "id": cty.String, 246 }) 247 248 plan := &plans.Plan{ 249 Changes: &plans.Changes{ 250 Outputs: []*plans.OutputChangeSrc{ 251 { 252 Addr: addrs.OutputValue{Name: "bar"}.Absolute(addrs.RootModuleInstance), 253 ChangeSrc: plans.ChangeSrc{ 254 Action: plans.Delete, 255 Before: mustDynamicOutputValue("output"), 256 After: mustNewDynamicValue(cty.NullVal(cty.String), cty.String), 257 }, 258 }, 259 }, 260 Resources: []*plans.ResourceInstanceChangeSrc{ 261 { 262 Addr: addrs.Resource{ 263 Mode: addrs.ManagedResourceMode, 264 Type: "test_thing", 265 Name: "woot", 266 }.Instance(addrs.IntKey(0)).Absolute(addrs.RootModuleInstance), 267 PrevRunAddr: addrs.Resource{ 268 Mode: addrs.ManagedResourceMode, 269 Type: "test_thing", 270 Name: "woot", 271 }.Instance(addrs.IntKey(0)).Absolute(addrs.RootModuleInstance), 272 ProviderAddr: addrs.AbsProviderConfig{ 273 Provider: addrs.NewDefaultProvider("test"), 274 Module: addrs.RootModule, 275 }, 276 ChangeSrc: plans.ChangeSrc{ 277 Action: plans.Delete, 278 Before: mustNewDynamicValue(cty.ObjectVal(map[string]cty.Value{ 279 "id": cty.StringVal("foo-bar-baz"), 280 }), objTy), 281 After: mustNewDynamicValue(cty.NullVal(objTy), objTy), 282 }, 283 }, 284 }, 285 }, 286 DriftedResources: []*plans.ResourceInstanceChangeSrc{}, 287 TargetAddrs: []addrs.Targetable{ 288 addrs.Resource{ 289 Mode: addrs.ManagedResourceMode, 290 Type: "test_thing", 291 Name: "woot", 292 }.Absolute(addrs.RootModuleInstance), 293 }, 294 Backend: plans.Backend{ 295 Type: "local", 296 Config: mustNewDynamicValue( 297 cty.ObjectVal(map[string]cty.Value{ 298 "foo": cty.StringVal("bar"), 299 }), 300 cty.Object(map[string]cty.Type{ 301 "foo": cty.String, 302 }), 303 ), 304 Workspace: "default", 305 }, 306 } 307 308 var buf bytes.Buffer 309 err := writeTfplan(plan, &buf) 310 if err != nil { 311 t.Fatal(err) 312 } 313 314 newPlan, err := readTfplan(&buf) 315 if err != nil { 316 t.Fatal(err) 317 } 318 319 for _, rics := range newPlan.Changes.Resources { 320 ric, err := rics.Decode(objTy) 321 if err != nil { 322 t.Fatal(err) 323 } 324 325 if ric.After == cty.NilVal { 326 t.Fatalf("unexpected nil After value: %#v\n", ric) 327 } 328 } 329 for _, ocs := range newPlan.Changes.Outputs { 330 oc, err := ocs.Decode() 331 if err != nil { 332 t.Fatal(err) 333 } 334 335 if oc.After == cty.NilVal { 336 t.Fatalf("unexpected nil After value: %#v\n", ocs) 337 } 338 } 339 }