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