github.com/graywolf-at-work-2/terraform-vendor@v1.4.5/internal/legacy/terraform/resource_test.go (about) 1 package terraform 2 3 import ( 4 "fmt" 5 "reflect" 6 "testing" 7 8 "github.com/hashicorp/terraform/internal/configs/configschema" 9 "github.com/zclconf/go-cty/cty" 10 11 "github.com/hashicorp/terraform/internal/configs/hcl2shim" 12 "github.com/mitchellh/reflectwalk" 13 ) 14 15 func TestResourceConfigGet(t *testing.T) { 16 fooStringSchema := &configschema.Block{ 17 Attributes: map[string]*configschema.Attribute{ 18 "foo": {Type: cty.String, Optional: true}, 19 }, 20 } 21 fooListSchema := &configschema.Block{ 22 Attributes: map[string]*configschema.Attribute{ 23 "foo": {Type: cty.List(cty.Number), Optional: true}, 24 }, 25 } 26 27 cases := []struct { 28 Config cty.Value 29 Schema *configschema.Block 30 Key string 31 Value interface{} 32 }{ 33 { 34 Config: cty.ObjectVal(map[string]cty.Value{ 35 "foo": cty.StringVal("bar"), 36 }), 37 Schema: fooStringSchema, 38 Key: "foo", 39 Value: "bar", 40 }, 41 42 { 43 Config: cty.ObjectVal(map[string]cty.Value{ 44 "foo": cty.UnknownVal(cty.String), 45 }), 46 Schema: fooStringSchema, 47 Key: "foo", 48 Value: hcl2shim.UnknownVariableValue, 49 }, 50 51 { 52 Config: cty.ObjectVal(map[string]cty.Value{ 53 "foo": cty.ListVal([]cty.Value{ 54 cty.NumberIntVal(1), 55 cty.NumberIntVal(2), 56 cty.NumberIntVal(5), 57 }), 58 }), 59 Schema: fooListSchema, 60 Key: "foo.0", 61 Value: 1, 62 }, 63 64 { 65 Config: cty.ObjectVal(map[string]cty.Value{ 66 "foo": cty.ListVal([]cty.Value{ 67 cty.NumberIntVal(1), 68 cty.NumberIntVal(2), 69 cty.NumberIntVal(5), 70 }), 71 }), 72 Schema: fooListSchema, 73 Key: "foo.5", 74 Value: nil, 75 }, 76 77 { 78 Config: cty.ObjectVal(map[string]cty.Value{ 79 "foo": cty.ListVal([]cty.Value{ 80 cty.NumberIntVal(1), 81 cty.NumberIntVal(2), 82 cty.NumberIntVal(5), 83 }), 84 }), 85 Schema: fooListSchema, 86 Key: "foo.-1", 87 Value: nil, 88 }, 89 90 // get from map 91 { 92 Config: cty.ObjectVal(map[string]cty.Value{ 93 "mapname": cty.ListVal([]cty.Value{ 94 cty.MapVal(map[string]cty.Value{ 95 "key": cty.NumberIntVal(1), 96 }), 97 }), 98 }), 99 Schema: &configschema.Block{ 100 Attributes: map[string]*configschema.Attribute{ 101 "mapname": {Type: cty.List(cty.Map(cty.Number)), Optional: true}, 102 }, 103 }, 104 Key: "mapname.0.key", 105 Value: 1, 106 }, 107 108 // get from map with dot in key 109 { 110 Config: cty.ObjectVal(map[string]cty.Value{ 111 "mapname": cty.ListVal([]cty.Value{ 112 cty.MapVal(map[string]cty.Value{ 113 "key.name": cty.NumberIntVal(1), 114 }), 115 }), 116 }), 117 Schema: &configschema.Block{ 118 Attributes: map[string]*configschema.Attribute{ 119 "mapname": {Type: cty.List(cty.Map(cty.Number)), Optional: true}, 120 }, 121 }, 122 Key: "mapname.0.key.name", 123 Value: 1, 124 }, 125 126 // get from map with overlapping key names 127 { 128 Config: cty.ObjectVal(map[string]cty.Value{ 129 "mapname": cty.ListVal([]cty.Value{ 130 cty.MapVal(map[string]cty.Value{ 131 "key.name": cty.NumberIntVal(1), 132 "key.name.2": cty.NumberIntVal(2), 133 }), 134 }), 135 }), 136 Schema: &configschema.Block{ 137 Attributes: map[string]*configschema.Attribute{ 138 "mapname": {Type: cty.List(cty.Map(cty.Number)), Optional: true}, 139 }, 140 }, 141 Key: "mapname.0.key.name.2", 142 Value: 2, 143 }, 144 { 145 Config: cty.ObjectVal(map[string]cty.Value{ 146 "mapname": cty.ListVal([]cty.Value{ 147 cty.MapVal(map[string]cty.Value{ 148 "key.name": cty.NumberIntVal(1), 149 "key.name.foo": cty.NumberIntVal(2), 150 }), 151 }), 152 }), 153 Schema: &configschema.Block{ 154 Attributes: map[string]*configschema.Attribute{ 155 "mapname": {Type: cty.List(cty.Map(cty.Number)), Optional: true}, 156 }, 157 }, 158 Key: "mapname.0.key.name", 159 Value: 1, 160 }, 161 { 162 Config: cty.ObjectVal(map[string]cty.Value{ 163 "mapname": cty.ListVal([]cty.Value{ 164 cty.MapVal(map[string]cty.Value{ 165 "listkey": cty.ListVal([]cty.Value{ 166 cty.MapVal(map[string]cty.Value{ 167 "key": cty.NumberIntVal(3), 168 }), 169 }), 170 }), 171 }), 172 }), 173 Schema: &configschema.Block{ 174 Attributes: map[string]*configschema.Attribute{ 175 "mapname": {Type: cty.List(cty.Map(cty.List(cty.Map(cty.Number)))), Optional: true}, 176 }, 177 }, 178 Key: "mapname.0.listkey.0.key", 179 Value: 3, 180 }, 181 } 182 183 for i, tc := range cases { 184 rc := NewResourceConfigShimmed(tc.Config, tc.Schema) 185 186 // Test getting a key 187 t.Run(fmt.Sprintf("get-%d", i), func(t *testing.T) { 188 v, ok := rc.Get(tc.Key) 189 if ok && v == nil { 190 t.Fatal("(nil, true) returned from Get") 191 } 192 193 if !reflect.DeepEqual(v, tc.Value) { 194 t.Fatalf("%d bad: %#v", i, v) 195 } 196 }) 197 198 // Test copying and equality 199 t.Run(fmt.Sprintf("copy-and-equal-%d", i), func(t *testing.T) { 200 copy := rc.DeepCopy() 201 if !reflect.DeepEqual(copy, rc) { 202 t.Fatalf("bad:\n\n%#v\n\n%#v", copy, rc) 203 } 204 205 if !copy.Equal(rc) { 206 t.Fatalf("copy != rc:\n\n%#v\n\n%#v", copy, rc) 207 } 208 if !rc.Equal(copy) { 209 t.Fatalf("rc != copy:\n\n%#v\n\n%#v", copy, rc) 210 } 211 }) 212 } 213 } 214 215 func TestResourceConfigDeepCopy_nil(t *testing.T) { 216 var nilRc *ResourceConfig 217 actual := nilRc.DeepCopy() 218 if actual != nil { 219 t.Fatalf("bad: %#v", actual) 220 } 221 } 222 223 func TestResourceConfigDeepCopy_nilComputed(t *testing.T) { 224 rc := &ResourceConfig{} 225 actual := rc.DeepCopy() 226 if actual.ComputedKeys != nil { 227 t.Fatalf("bad: %#v", actual) 228 } 229 } 230 231 func TestResourceConfigEqual_nil(t *testing.T) { 232 var nilRc *ResourceConfig 233 notNil := NewResourceConfigShimmed(cty.EmptyObjectVal, &configschema.Block{}) 234 235 if nilRc.Equal(notNil) { 236 t.Fatal("should not be equal") 237 } 238 239 if notNil.Equal(nilRc) { 240 t.Fatal("should not be equal") 241 } 242 } 243 244 func TestResourceConfigEqual_computedKeyOrder(t *testing.T) { 245 v := cty.ObjectVal(map[string]cty.Value{ 246 "foo": cty.UnknownVal(cty.String), 247 }) 248 schema := &configschema.Block{ 249 Attributes: map[string]*configschema.Attribute{ 250 "foo": {Type: cty.String, Optional: true}, 251 }, 252 } 253 rc := NewResourceConfigShimmed(v, schema) 254 rc2 := NewResourceConfigShimmed(v, schema) 255 256 // Set the computed keys manually to force ordering to differ 257 rc.ComputedKeys = []string{"foo", "bar"} 258 rc2.ComputedKeys = []string{"bar", "foo"} 259 260 if !rc.Equal(rc2) { 261 t.Fatal("should be equal") 262 } 263 } 264 265 func TestUnknownCheckWalker(t *testing.T) { 266 cases := []struct { 267 Name string 268 Input interface{} 269 Result bool 270 }{ 271 { 272 "primitive", 273 42, 274 false, 275 }, 276 277 { 278 "primitive computed", 279 hcl2shim.UnknownVariableValue, 280 true, 281 }, 282 283 { 284 "list", 285 []interface{}{"foo", hcl2shim.UnknownVariableValue}, 286 true, 287 }, 288 289 { 290 "nested list", 291 []interface{}{ 292 "foo", 293 []interface{}{hcl2shim.UnknownVariableValue}, 294 }, 295 true, 296 }, 297 } 298 299 for i, tc := range cases { 300 t.Run(fmt.Sprintf("%d-%s", i, tc.Name), func(t *testing.T) { 301 var w unknownCheckWalker 302 if err := reflectwalk.Walk(tc.Input, &w); err != nil { 303 t.Fatalf("err: %s", err) 304 } 305 306 if w.Unknown != tc.Result { 307 t.Fatalf("bad: %v", w.Unknown) 308 } 309 }) 310 } 311 } 312 313 func TestNewResourceConfigShimmed(t *testing.T) { 314 for _, tc := range []struct { 315 Name string 316 Val cty.Value 317 Schema *configschema.Block 318 Expected *ResourceConfig 319 }{ 320 { 321 Name: "empty object", 322 Val: cty.NullVal(cty.EmptyObject), 323 Schema: &configschema.Block{ 324 Attributes: map[string]*configschema.Attribute{ 325 "foo": { 326 Type: cty.String, 327 Optional: true, 328 }, 329 }, 330 }, 331 Expected: &ResourceConfig{ 332 Raw: map[string]interface{}{}, 333 Config: map[string]interface{}{}, 334 }, 335 }, 336 { 337 Name: "basic", 338 Val: cty.ObjectVal(map[string]cty.Value{ 339 "foo": cty.StringVal("bar"), 340 }), 341 Schema: &configschema.Block{ 342 Attributes: map[string]*configschema.Attribute{ 343 "foo": { 344 Type: cty.String, 345 Optional: true, 346 }, 347 }, 348 }, 349 Expected: &ResourceConfig{ 350 Raw: map[string]interface{}{ 351 "foo": "bar", 352 }, 353 Config: map[string]interface{}{ 354 "foo": "bar", 355 }, 356 }, 357 }, 358 { 359 Name: "null string", 360 Val: cty.ObjectVal(map[string]cty.Value{ 361 "foo": cty.NullVal(cty.String), 362 }), 363 Schema: &configschema.Block{ 364 Attributes: map[string]*configschema.Attribute{ 365 "foo": { 366 Type: cty.String, 367 Optional: true, 368 }, 369 }, 370 }, 371 Expected: &ResourceConfig{ 372 Raw: map[string]interface{}{}, 373 Config: map[string]interface{}{}, 374 }, 375 }, 376 { 377 Name: "unknown string", 378 Val: cty.ObjectVal(map[string]cty.Value{ 379 "foo": cty.UnknownVal(cty.String), 380 }), 381 Schema: &configschema.Block{ 382 Attributes: map[string]*configschema.Attribute{ 383 "foo": { 384 Type: cty.String, 385 Optional: true, 386 }, 387 }, 388 }, 389 Expected: &ResourceConfig{ 390 ComputedKeys: []string{"foo"}, 391 Raw: map[string]interface{}{ 392 "foo": hcl2shim.UnknownVariableValue, 393 }, 394 Config: map[string]interface{}{ 395 "foo": hcl2shim.UnknownVariableValue, 396 }, 397 }, 398 }, 399 { 400 Name: "unknown collections", 401 Val: cty.ObjectVal(map[string]cty.Value{ 402 "bar": cty.UnknownVal(cty.Map(cty.String)), 403 "baz": cty.UnknownVal(cty.List(cty.String)), 404 }), 405 Schema: &configschema.Block{ 406 Attributes: map[string]*configschema.Attribute{ 407 "bar": { 408 Type: cty.Map(cty.String), 409 Required: true, 410 }, 411 "baz": { 412 Type: cty.List(cty.String), 413 Optional: true, 414 }, 415 }, 416 }, 417 Expected: &ResourceConfig{ 418 ComputedKeys: []string{"bar", "baz"}, 419 Raw: map[string]interface{}{ 420 "bar": hcl2shim.UnknownVariableValue, 421 "baz": hcl2shim.UnknownVariableValue, 422 }, 423 Config: map[string]interface{}{ 424 "bar": hcl2shim.UnknownVariableValue, 425 "baz": hcl2shim.UnknownVariableValue, 426 }, 427 }, 428 }, 429 { 430 Name: "null collections", 431 Val: cty.ObjectVal(map[string]cty.Value{ 432 "bar": cty.NullVal(cty.Map(cty.String)), 433 "baz": cty.NullVal(cty.List(cty.String)), 434 }), 435 Schema: &configschema.Block{ 436 Attributes: map[string]*configschema.Attribute{ 437 "bar": { 438 Type: cty.Map(cty.String), 439 Required: true, 440 }, 441 "baz": { 442 Type: cty.List(cty.String), 443 Optional: true, 444 }, 445 }, 446 }, 447 Expected: &ResourceConfig{ 448 Raw: map[string]interface{}{}, 449 Config: map[string]interface{}{}, 450 }, 451 }, 452 { 453 Name: "unknown blocks", 454 Val: cty.ObjectVal(map[string]cty.Value{ 455 "bar": cty.UnknownVal(cty.Map(cty.String)), 456 "baz": cty.UnknownVal(cty.List(cty.String)), 457 }), 458 Schema: &configschema.Block{ 459 BlockTypes: map[string]*configschema.NestedBlock{ 460 "bar": { 461 Block: configschema.Block{}, 462 Nesting: configschema.NestingList, 463 }, 464 "baz": { 465 Block: configschema.Block{}, 466 Nesting: configschema.NestingSet, 467 }, 468 }, 469 }, 470 Expected: &ResourceConfig{ 471 ComputedKeys: []string{"bar", "baz"}, 472 Raw: map[string]interface{}{ 473 "bar": hcl2shim.UnknownVariableValue, 474 "baz": hcl2shim.UnknownVariableValue, 475 }, 476 Config: map[string]interface{}{ 477 "bar": hcl2shim.UnknownVariableValue, 478 "baz": hcl2shim.UnknownVariableValue, 479 }, 480 }, 481 }, 482 { 483 Name: "unknown in nested blocks", 484 Val: cty.ObjectVal(map[string]cty.Value{ 485 "bar": cty.ListVal([]cty.Value{ 486 cty.ObjectVal(map[string]cty.Value{ 487 "baz": cty.ListVal([]cty.Value{ 488 cty.ObjectVal(map[string]cty.Value{ 489 "list": cty.UnknownVal(cty.List(cty.String)), 490 }), 491 }), 492 }), 493 }), 494 }), 495 Schema: &configschema.Block{ 496 BlockTypes: map[string]*configschema.NestedBlock{ 497 "bar": { 498 Block: configschema.Block{ 499 BlockTypes: map[string]*configschema.NestedBlock{ 500 "baz": { 501 Block: configschema.Block{ 502 Attributes: map[string]*configschema.Attribute{ 503 "list": {Type: cty.List(cty.String), 504 Optional: true, 505 }, 506 }, 507 }, 508 Nesting: configschema.NestingList, 509 }, 510 }, 511 }, 512 Nesting: configschema.NestingList, 513 }, 514 }, 515 }, 516 Expected: &ResourceConfig{ 517 ComputedKeys: []string{"bar.0.baz.0.list"}, 518 Raw: map[string]interface{}{ 519 "bar": []interface{}{map[string]interface{}{ 520 "baz": []interface{}{map[string]interface{}{ 521 "list": "74D93920-ED26-11E3-AC10-0800200C9A66", 522 }}, 523 }}, 524 }, 525 Config: map[string]interface{}{ 526 "bar": []interface{}{map[string]interface{}{ 527 "baz": []interface{}{map[string]interface{}{ 528 "list": "74D93920-ED26-11E3-AC10-0800200C9A66", 529 }}, 530 }}, 531 }, 532 }, 533 }, 534 { 535 Name: "unknown in set", 536 Val: cty.ObjectVal(map[string]cty.Value{ 537 "bar": cty.SetVal([]cty.Value{ 538 cty.ObjectVal(map[string]cty.Value{ 539 "val": cty.UnknownVal(cty.String), 540 }), 541 }), 542 }), 543 Schema: &configschema.Block{ 544 BlockTypes: map[string]*configschema.NestedBlock{ 545 "bar": { 546 Block: configschema.Block{ 547 Attributes: map[string]*configschema.Attribute{ 548 "val": { 549 Type: cty.String, 550 Optional: true, 551 }, 552 }, 553 }, 554 Nesting: configschema.NestingSet, 555 }, 556 }, 557 }, 558 Expected: &ResourceConfig{ 559 ComputedKeys: []string{"bar.0.val"}, 560 Raw: map[string]interface{}{ 561 "bar": []interface{}{map[string]interface{}{ 562 "val": "74D93920-ED26-11E3-AC10-0800200C9A66", 563 }}, 564 }, 565 Config: map[string]interface{}{ 566 "bar": []interface{}{map[string]interface{}{ 567 "val": "74D93920-ED26-11E3-AC10-0800200C9A66", 568 }}, 569 }, 570 }, 571 }, 572 { 573 Name: "unknown in attribute sets", 574 Val: cty.ObjectVal(map[string]cty.Value{ 575 "bar": cty.SetVal([]cty.Value{ 576 cty.ObjectVal(map[string]cty.Value{ 577 "val": cty.UnknownVal(cty.String), 578 }), 579 }), 580 "baz": cty.SetVal([]cty.Value{ 581 cty.ObjectVal(map[string]cty.Value{ 582 "obj": cty.UnknownVal(cty.Object(map[string]cty.Type{ 583 "attr": cty.List(cty.String), 584 })), 585 }), 586 cty.ObjectVal(map[string]cty.Value{ 587 "obj": cty.ObjectVal(map[string]cty.Value{ 588 "attr": cty.UnknownVal(cty.List(cty.String)), 589 }), 590 }), 591 }), 592 }), 593 Schema: &configschema.Block{ 594 Attributes: map[string]*configschema.Attribute{ 595 "bar": &configschema.Attribute{ 596 Type: cty.Set(cty.Object(map[string]cty.Type{ 597 "val": cty.String, 598 })), 599 }, 600 "baz": &configschema.Attribute{ 601 Type: cty.Set(cty.Object(map[string]cty.Type{ 602 "obj": cty.Object(map[string]cty.Type{ 603 "attr": cty.List(cty.String), 604 }), 605 })), 606 }, 607 }, 608 }, 609 Expected: &ResourceConfig{ 610 ComputedKeys: []string{"bar.0.val", "baz.0.obj.attr", "baz.1.obj"}, 611 Raw: map[string]interface{}{ 612 "bar": []interface{}{map[string]interface{}{ 613 "val": "74D93920-ED26-11E3-AC10-0800200C9A66", 614 }}, 615 "baz": []interface{}{ 616 map[string]interface{}{ 617 "obj": map[string]interface{}{ 618 "attr": "74D93920-ED26-11E3-AC10-0800200C9A66", 619 }, 620 }, 621 map[string]interface{}{ 622 "obj": "74D93920-ED26-11E3-AC10-0800200C9A66", 623 }, 624 }, 625 }, 626 Config: map[string]interface{}{ 627 "bar": []interface{}{map[string]interface{}{ 628 "val": "74D93920-ED26-11E3-AC10-0800200C9A66", 629 }}, 630 "baz": []interface{}{ 631 map[string]interface{}{ 632 "obj": map[string]interface{}{ 633 "attr": "74D93920-ED26-11E3-AC10-0800200C9A66", 634 }, 635 }, 636 map[string]interface{}{ 637 "obj": "74D93920-ED26-11E3-AC10-0800200C9A66", 638 }, 639 }, 640 }, 641 }, 642 }, 643 { 644 Name: "null blocks", 645 Val: cty.ObjectVal(map[string]cty.Value{ 646 "bar": cty.NullVal(cty.Map(cty.String)), 647 "baz": cty.NullVal(cty.List(cty.String)), 648 }), 649 Schema: &configschema.Block{ 650 BlockTypes: map[string]*configschema.NestedBlock{ 651 "bar": { 652 Block: configschema.Block{}, 653 Nesting: configschema.NestingMap, 654 }, 655 "baz": { 656 Block: configschema.Block{}, 657 Nesting: configschema.NestingSingle, 658 }, 659 }, 660 }, 661 Expected: &ResourceConfig{ 662 Raw: map[string]interface{}{}, 663 Config: map[string]interface{}{}, 664 }, 665 }, 666 } { 667 t.Run(tc.Name, func(*testing.T) { 668 cfg := NewResourceConfigShimmed(tc.Val, tc.Schema) 669 if !tc.Expected.Equal(cfg) { 670 t.Fatalf("expected:\n%#v\ngot:\n%#v", tc.Expected, cfg) 671 } 672 }) 673 } 674 }