github.com/cycloidio/terraform@v1.1.10-0.20220513142504-76d5c768dc63/terraform/context_plan_test.go (about) 1 package terraform 2 3 import ( 4 "bytes" 5 "errors" 6 "fmt" 7 "os" 8 "reflect" 9 "sort" 10 "strings" 11 "sync" 12 "testing" 13 14 "github.com/davecgh/go-spew/spew" 15 "github.com/google/go-cmp/cmp" 16 "github.com/zclconf/go-cty/cty" 17 18 "github.com/cycloidio/terraform/addrs" 19 "github.com/cycloidio/terraform/configs/configschema" 20 "github.com/cycloidio/terraform/configs/hcl2shim" 21 "github.com/cycloidio/terraform/lang/marks" 22 "github.com/cycloidio/terraform/plans" 23 "github.com/cycloidio/terraform/providers" 24 "github.com/cycloidio/terraform/provisioners" 25 "github.com/cycloidio/terraform/states" 26 "github.com/cycloidio/terraform/tfdiags" 27 ) 28 29 func TestContext2Plan_basic(t *testing.T) { 30 m := testModule(t, "plan-good") 31 p := testProvider("aws") 32 ctx := testContext2(t, &ContextOpts{ 33 Providers: map[addrs.Provider]providers.Factory{ 34 addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p), 35 }, 36 }) 37 38 plan, diags := ctx.Plan(m, states.NewState(), DefaultPlanOpts) 39 if diags.HasErrors() { 40 t.Fatalf("unexpected errors: %s", diags.Err()) 41 } 42 43 if l := len(plan.Changes.Resources); l < 2 { 44 t.Fatalf("wrong number of resources %d; want fewer than two\n%s", l, spew.Sdump(plan.Changes.Resources)) 45 } 46 47 schema := p.GetProviderSchemaResponse.ResourceTypes["aws_instance"].Block 48 ty := schema.ImpliedType() 49 for _, r := range plan.Changes.Resources { 50 ric, err := r.Decode(ty) 51 if err != nil { 52 t.Fatal(err) 53 } 54 55 switch i := ric.Addr.String(); i { 56 case "aws_instance.bar": 57 foo := ric.After.GetAttr("foo").AsString() 58 if foo != "2" { 59 t.Fatalf("incorrect plan for 'bar': %#v", ric.After) 60 } 61 case "aws_instance.foo": 62 num, _ := ric.After.GetAttr("num").AsBigFloat().Int64() 63 if num != 2 { 64 t.Fatalf("incorrect plan for 'foo': %#v", ric.After) 65 } 66 default: 67 t.Fatal("unknown instance:", i) 68 } 69 } 70 71 if !p.ValidateProviderConfigCalled { 72 t.Fatal("provider config was not checked before Configure") 73 } 74 75 } 76 77 func TestContext2Plan_createBefore_deposed(t *testing.T) { 78 m := testModule(t, "plan-cbd") 79 p := testProvider("aws") 80 p.PlanResourceChangeFn = testDiffFn 81 82 state := states.NewState() 83 root := state.EnsureModule(addrs.RootModuleInstance) 84 root.SetResourceInstanceCurrent( 85 mustResourceInstanceAddr("aws_instance.foo").Resource, 86 &states.ResourceInstanceObjectSrc{ 87 Status: states.ObjectReady, 88 AttrsJSON: []byte(`{"id":"baz","type":"aws_instance"}`), 89 }, 90 mustProviderConfig(`provider["registry.terraform.io/hashicorp/aws"]`), 91 ) 92 root.SetResourceInstanceDeposed( 93 mustResourceInstanceAddr("aws_instance.foo").Resource, 94 states.DeposedKey("00000001"), 95 &states.ResourceInstanceObjectSrc{ 96 Status: states.ObjectReady, 97 AttrsJSON: []byte(`{"id":"foo"}`), 98 }, 99 mustProviderConfig(`provider["registry.terraform.io/hashicorp/aws"]`), 100 ) 101 102 ctx := testContext2(t, &ContextOpts{ 103 Providers: map[addrs.Provider]providers.Factory{ 104 addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p), 105 }, 106 }) 107 108 plan, diags := ctx.Plan(m, state, DefaultPlanOpts) 109 if diags.HasErrors() { 110 t.Fatalf("unexpected errors: %s", diags.Err()) 111 } 112 113 // the state should still show one deposed 114 expectedState := strings.TrimSpace(` 115 aws_instance.foo: (1 deposed) 116 ID = baz 117 provider = provider["registry.terraform.io/hashicorp/aws"] 118 type = aws_instance 119 Deposed ID 1 = foo`) 120 121 if plan.PriorState.String() != expectedState { 122 t.Fatalf("\nexpected: %q\ngot: %q\n", expectedState, plan.PriorState.String()) 123 } 124 125 schema := p.GetProviderSchemaResponse.ResourceTypes["aws_instance"].Block 126 ty := schema.ImpliedType() 127 128 type InstanceGen struct { 129 Addr string 130 DeposedKey states.DeposedKey 131 } 132 want := map[InstanceGen]bool{ 133 { 134 Addr: "aws_instance.foo", 135 }: true, 136 { 137 Addr: "aws_instance.foo", 138 DeposedKey: states.DeposedKey("00000001"), 139 }: true, 140 } 141 got := make(map[InstanceGen]bool) 142 changes := make(map[InstanceGen]*plans.ResourceInstanceChangeSrc) 143 144 for _, change := range plan.Changes.Resources { 145 k := InstanceGen{ 146 Addr: change.Addr.String(), 147 DeposedKey: change.DeposedKey, 148 } 149 got[k] = true 150 changes[k] = change 151 } 152 if !reflect.DeepEqual(got, want) { 153 t.Fatalf("wrong resource instance object changes in plan\ngot: %s\nwant: %s", spew.Sdump(got), spew.Sdump(want)) 154 } 155 156 { 157 ric, err := changes[InstanceGen{Addr: "aws_instance.foo"}].Decode(ty) 158 if err != nil { 159 t.Fatal(err) 160 } 161 162 if got, want := ric.Action, plans.NoOp; got != want { 163 t.Errorf("current object change action is %s; want %s", got, want) 164 } 165 166 // the existing instance should only have an unchanged id 167 expected, err := schema.CoerceValue(cty.ObjectVal(map[string]cty.Value{ 168 "id": cty.StringVal("baz"), 169 "type": cty.StringVal("aws_instance"), 170 })) 171 if err != nil { 172 t.Fatal(err) 173 } 174 175 checkVals(t, expected, ric.After) 176 } 177 178 { 179 ric, err := changes[InstanceGen{Addr: "aws_instance.foo", DeposedKey: states.DeposedKey("00000001")}].Decode(ty) 180 if err != nil { 181 t.Fatal(err) 182 } 183 184 if got, want := ric.Action, plans.Delete; got != want { 185 t.Errorf("deposed object change action is %s; want %s", got, want) 186 } 187 } 188 } 189 190 func TestContext2Plan_createBefore_maintainRoot(t *testing.T) { 191 m := testModule(t, "plan-cbd-maintain-root") 192 p := testProvider("aws") 193 ctx := testContext2(t, &ContextOpts{ 194 Providers: map[addrs.Provider]providers.Factory{ 195 addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p), 196 }, 197 }) 198 199 plan, diags := ctx.Plan(m, states.NewState(), DefaultPlanOpts) 200 if diags.HasErrors() { 201 t.Fatalf("unexpected errors: %s", diags.Err()) 202 } 203 204 if !plan.PriorState.Empty() { 205 t.Fatal("expected empty prior state, got:", plan.PriorState) 206 } 207 208 if len(plan.Changes.Resources) != 4 { 209 t.Error("expected 4 resource in plan, got", len(plan.Changes.Resources)) 210 } 211 212 for _, res := range plan.Changes.Resources { 213 // these should all be creates 214 if res.Action != plans.Create { 215 t.Fatalf("unexpected action %s for %s", res.Action, res.Addr.String()) 216 } 217 } 218 } 219 220 func TestContext2Plan_emptyDiff(t *testing.T) { 221 m := testModule(t, "plan-empty") 222 p := testProvider("aws") 223 p.PlanResourceChangeFn = func(req providers.PlanResourceChangeRequest) (resp providers.PlanResourceChangeResponse) { 224 resp.PlannedState = req.ProposedNewState 225 return resp 226 } 227 228 ctx := testContext2(t, &ContextOpts{ 229 Providers: map[addrs.Provider]providers.Factory{ 230 addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p), 231 }, 232 }) 233 234 plan, diags := ctx.Plan(m, states.NewState(), DefaultPlanOpts) 235 if diags.HasErrors() { 236 t.Fatalf("unexpected errors: %s", diags.Err()) 237 } 238 239 if !plan.PriorState.Empty() { 240 t.Fatal("expected empty state, got:", plan.PriorState) 241 } 242 243 if len(plan.Changes.Resources) != 2 { 244 t.Error("expected 2 resource in plan, got", len(plan.Changes.Resources)) 245 } 246 247 actions := map[string]plans.Action{} 248 249 for _, res := range plan.Changes.Resources { 250 actions[res.Addr.String()] = res.Action 251 } 252 253 expected := map[string]plans.Action{ 254 "aws_instance.foo": plans.Create, 255 "aws_instance.bar": plans.Create, 256 } 257 if !cmp.Equal(expected, actions) { 258 t.Fatal(cmp.Diff(expected, actions)) 259 } 260 } 261 262 func TestContext2Plan_escapedVar(t *testing.T) { 263 m := testModule(t, "plan-escaped-var") 264 p := testProvider("aws") 265 p.PlanResourceChangeFn = testDiffFn 266 ctx := testContext2(t, &ContextOpts{ 267 Providers: map[addrs.Provider]providers.Factory{ 268 addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p), 269 }, 270 }) 271 272 plan, diags := ctx.Plan(m, states.NewState(), DefaultPlanOpts) 273 if diags.HasErrors() { 274 t.Fatalf("unexpected errors: %s", diags.Err()) 275 } 276 277 if len(plan.Changes.Resources) != 1 { 278 t.Error("expected 1 resource in plan, got", len(plan.Changes.Resources)) 279 } 280 281 res := plan.Changes.Resources[0] 282 if res.Action != plans.Create { 283 t.Fatalf("expected resource creation, got %s", res.Action) 284 } 285 286 schema := p.GetProviderSchemaResponse.ResourceTypes["aws_instance"].Block 287 ty := schema.ImpliedType() 288 289 ric, err := res.Decode(ty) 290 if err != nil { 291 t.Fatal(err) 292 } 293 294 expected := objectVal(t, schema, map[string]cty.Value{ 295 "id": cty.UnknownVal(cty.String), 296 "foo": cty.StringVal("bar-${baz}"), 297 "type": cty.UnknownVal(cty.String), 298 }) 299 300 checkVals(t, expected, ric.After) 301 } 302 303 func TestContext2Plan_minimal(t *testing.T) { 304 m := testModule(t, "plan-empty") 305 p := testProvider("aws") 306 ctx := testContext2(t, &ContextOpts{ 307 Providers: map[addrs.Provider]providers.Factory{ 308 addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p), 309 }, 310 }) 311 312 plan, diags := ctx.Plan(m, states.NewState(), DefaultPlanOpts) 313 if diags.HasErrors() { 314 t.Fatalf("unexpected errors: %s", diags.Err()) 315 } 316 317 if !plan.PriorState.Empty() { 318 t.Fatal("expected empty state, got:", plan.PriorState) 319 } 320 321 if len(plan.Changes.Resources) != 2 { 322 t.Error("expected 2 resource in plan, got", len(plan.Changes.Resources)) 323 } 324 325 actions := map[string]plans.Action{} 326 327 for _, res := range plan.Changes.Resources { 328 actions[res.Addr.String()] = res.Action 329 } 330 331 expected := map[string]plans.Action{ 332 "aws_instance.foo": plans.Create, 333 "aws_instance.bar": plans.Create, 334 } 335 if !cmp.Equal(expected, actions) { 336 t.Fatal(cmp.Diff(expected, actions)) 337 } 338 } 339 340 func TestContext2Plan_modules(t *testing.T) { 341 m := testModule(t, "plan-modules") 342 p := testProvider("aws") 343 p.PlanResourceChangeFn = testDiffFn 344 ctx := testContext2(t, &ContextOpts{ 345 Providers: map[addrs.Provider]providers.Factory{ 346 addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p), 347 }, 348 }) 349 350 plan, diags := ctx.Plan(m, states.NewState(), DefaultPlanOpts) 351 if diags.HasErrors() { 352 t.Fatalf("unexpected errors: %s", diags.Err()) 353 } 354 355 if len(plan.Changes.Resources) != 3 { 356 t.Error("expected 3 resource in plan, got", len(plan.Changes.Resources)) 357 } 358 359 schema := p.GetProviderSchemaResponse.ResourceTypes["aws_instance"].Block 360 ty := schema.ImpliedType() 361 362 expectFoo := objectVal(t, schema, map[string]cty.Value{ 363 "id": cty.UnknownVal(cty.String), 364 "foo": cty.StringVal("2"), 365 "type": cty.UnknownVal(cty.String), 366 }) 367 368 expectNum := objectVal(t, schema, map[string]cty.Value{ 369 "id": cty.UnknownVal(cty.String), 370 "num": cty.NumberIntVal(2), 371 "type": cty.UnknownVal(cty.String), 372 }) 373 374 for _, res := range plan.Changes.Resources { 375 if res.Action != plans.Create { 376 t.Fatalf("expected resource creation, got %s", res.Action) 377 } 378 ric, err := res.Decode(ty) 379 if err != nil { 380 t.Fatal(err) 381 } 382 383 var expected cty.Value 384 switch i := ric.Addr.String(); i { 385 case "aws_instance.bar": 386 expected = expectFoo 387 case "aws_instance.foo": 388 expected = expectNum 389 case "module.child.aws_instance.foo": 390 expected = expectNum 391 default: 392 t.Fatal("unknown instance:", i) 393 } 394 395 checkVals(t, expected, ric.After) 396 } 397 } 398 func TestContext2Plan_moduleExpand(t *testing.T) { 399 // Test a smattering of plan expansion behavior 400 m := testModule(t, "plan-modules-expand") 401 p := testProvider("aws") 402 ctx := testContext2(t, &ContextOpts{ 403 Providers: map[addrs.Provider]providers.Factory{ 404 addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p), 405 }, 406 }) 407 408 plan, diags := ctx.Plan(m, states.NewState(), DefaultPlanOpts) 409 if diags.HasErrors() { 410 t.Fatalf("unexpected errors: %s", diags.Err()) 411 } 412 413 schema := p.GetProviderSchemaResponse.ResourceTypes["aws_instance"].Block 414 ty := schema.ImpliedType() 415 416 expected := map[string]struct{}{ 417 `aws_instance.foo["a"]`: {}, 418 `module.count_child[1].aws_instance.foo[0]`: {}, 419 `module.count_child[1].aws_instance.foo[1]`: {}, 420 `module.count_child[0].aws_instance.foo[0]`: {}, 421 `module.count_child[0].aws_instance.foo[1]`: {}, 422 `module.for_each_child["a"].aws_instance.foo[1]`: {}, 423 `module.for_each_child["a"].aws_instance.foo[0]`: {}, 424 } 425 426 for _, res := range plan.Changes.Resources { 427 if res.Action != plans.Create { 428 t.Fatalf("expected resource creation, got %s", res.Action) 429 } 430 ric, err := res.Decode(ty) 431 if err != nil { 432 t.Fatal(err) 433 } 434 435 _, ok := expected[ric.Addr.String()] 436 if !ok { 437 t.Fatal("unexpected resource:", ric.Addr.String()) 438 } 439 delete(expected, ric.Addr.String()) 440 } 441 for addr := range expected { 442 t.Error("missing resource", addr) 443 } 444 } 445 446 // GH-1475 447 func TestContext2Plan_moduleCycle(t *testing.T) { 448 m := testModule(t, "plan-module-cycle") 449 p := testProvider("aws") 450 p.GetProviderSchemaResponse = getProviderSchemaResponseFromProviderSchema(&ProviderSchema{ 451 ResourceTypes: map[string]*configschema.Block{ 452 "aws_instance": { 453 Attributes: map[string]*configschema.Attribute{ 454 "id": {Type: cty.String, Computed: true}, 455 "some_input": {Type: cty.String, Optional: true}, 456 "type": {Type: cty.String, Computed: true}, 457 }, 458 }, 459 }, 460 }) 461 462 ctx := testContext2(t, &ContextOpts{ 463 Providers: map[addrs.Provider]providers.Factory{ 464 addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p), 465 }, 466 }) 467 468 plan, diags := ctx.Plan(m, states.NewState(), DefaultPlanOpts) 469 if diags.HasErrors() { 470 t.Fatalf("unexpected errors: %s", diags.Err()) 471 } 472 473 schema := p.GetProviderSchemaResponse.ResourceTypes["aws_instance"].Block 474 ty := schema.ImpliedType() 475 476 if len(plan.Changes.Resources) != 2 { 477 t.Fatal("expected 2 changes, got", len(plan.Changes.Resources)) 478 } 479 480 for _, res := range plan.Changes.Resources { 481 if res.Action != plans.Create { 482 t.Fatalf("expected resource creation, got %s", res.Action) 483 } 484 ric, err := res.Decode(ty) 485 if err != nil { 486 t.Fatal(err) 487 } 488 489 var expected cty.Value 490 switch i := ric.Addr.String(); i { 491 case "aws_instance.b": 492 expected = objectVal(t, schema, map[string]cty.Value{ 493 "id": cty.UnknownVal(cty.String), 494 "type": cty.UnknownVal(cty.String), 495 }) 496 case "aws_instance.c": 497 expected = objectVal(t, schema, map[string]cty.Value{ 498 "id": cty.UnknownVal(cty.String), 499 "some_input": cty.UnknownVal(cty.String), 500 "type": cty.UnknownVal(cty.String), 501 }) 502 default: 503 t.Fatal("unknown instance:", i) 504 } 505 506 checkVals(t, expected, ric.After) 507 } 508 } 509 510 func TestContext2Plan_moduleDeadlock(t *testing.T) { 511 testCheckDeadlock(t, func() { 512 m := testModule(t, "plan-module-deadlock") 513 p := testProvider("aws") 514 p.PlanResourceChangeFn = testDiffFn 515 516 ctx := testContext2(t, &ContextOpts{ 517 Providers: map[addrs.Provider]providers.Factory{ 518 addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p), 519 }, 520 }) 521 522 plan, err := ctx.Plan(m, states.NewState(), DefaultPlanOpts) 523 if err != nil { 524 t.Fatalf("err: %s", err) 525 } 526 527 schema := p.GetProviderSchemaResponse.ResourceTypes["aws_instance"].Block 528 ty := schema.ImpliedType() 529 530 for _, res := range plan.Changes.Resources { 531 if res.Action != plans.Create { 532 t.Fatalf("expected resource creation, got %s", res.Action) 533 } 534 ric, err := res.Decode(ty) 535 if err != nil { 536 t.Fatal(err) 537 } 538 539 expected := objectVal(t, schema, map[string]cty.Value{ 540 "id": cty.UnknownVal(cty.String), 541 "type": cty.UnknownVal(cty.String), 542 }) 543 switch i := ric.Addr.String(); i { 544 case "module.child.aws_instance.foo[0]": 545 case "module.child.aws_instance.foo[1]": 546 case "module.child.aws_instance.foo[2]": 547 default: 548 t.Fatal("unknown instance:", i) 549 } 550 551 checkVals(t, expected, ric.After) 552 } 553 }) 554 } 555 556 func TestContext2Plan_moduleInput(t *testing.T) { 557 m := testModule(t, "plan-module-input") 558 p := testProvider("aws") 559 p.PlanResourceChangeFn = testDiffFn 560 ctx := testContext2(t, &ContextOpts{ 561 Providers: map[addrs.Provider]providers.Factory{ 562 addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p), 563 }, 564 }) 565 566 plan, diags := ctx.Plan(m, states.NewState(), DefaultPlanOpts) 567 if diags.HasErrors() { 568 t.Fatalf("unexpected errors: %s", diags.Err()) 569 } 570 571 schema := p.GetProviderSchemaResponse.ResourceTypes["aws_instance"].Block 572 ty := schema.ImpliedType() 573 574 if len(plan.Changes.Resources) != 2 { 575 t.Fatal("expected 2 changes, got", len(plan.Changes.Resources)) 576 } 577 578 for _, res := range plan.Changes.Resources { 579 if res.Action != plans.Create { 580 t.Fatalf("expected resource creation, got %s", res.Action) 581 } 582 ric, err := res.Decode(ty) 583 if err != nil { 584 t.Fatal(err) 585 } 586 587 var expected cty.Value 588 589 switch i := ric.Addr.String(); i { 590 case "aws_instance.bar": 591 expected = objectVal(t, schema, map[string]cty.Value{ 592 "id": cty.UnknownVal(cty.String), 593 "foo": cty.StringVal("2"), 594 "type": cty.UnknownVal(cty.String), 595 }) 596 case "module.child.aws_instance.foo": 597 expected = objectVal(t, schema, map[string]cty.Value{ 598 "id": cty.UnknownVal(cty.String), 599 "foo": cty.StringVal("42"), 600 "type": cty.UnknownVal(cty.String), 601 }) 602 default: 603 t.Fatal("unknown instance:", i) 604 } 605 606 checkVals(t, expected, ric.After) 607 } 608 } 609 610 func TestContext2Plan_moduleInputComputed(t *testing.T) { 611 m := testModule(t, "plan-module-input-computed") 612 p := testProvider("aws") 613 p.PlanResourceChangeFn = testDiffFn 614 ctx := testContext2(t, &ContextOpts{ 615 Providers: map[addrs.Provider]providers.Factory{ 616 addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p), 617 }, 618 }) 619 620 plan, diags := ctx.Plan(m, states.NewState(), DefaultPlanOpts) 621 if diags.HasErrors() { 622 t.Fatalf("unexpected errors: %s", diags.Err()) 623 } 624 625 schema := p.GetProviderSchemaResponse.ResourceTypes["aws_instance"].Block 626 ty := schema.ImpliedType() 627 628 if len(plan.Changes.Resources) != 2 { 629 t.Fatal("expected 2 changes, got", len(plan.Changes.Resources)) 630 } 631 632 for _, res := range plan.Changes.Resources { 633 if res.Action != plans.Create { 634 t.Fatalf("expected resource creation, got %s", res.Action) 635 } 636 ric, err := res.Decode(ty) 637 if err != nil { 638 t.Fatal(err) 639 } 640 641 switch i := ric.Addr.String(); i { 642 case "aws_instance.bar": 643 checkVals(t, objectVal(t, schema, map[string]cty.Value{ 644 "id": cty.UnknownVal(cty.String), 645 "foo": cty.UnknownVal(cty.String), 646 "type": cty.UnknownVal(cty.String), 647 "compute": cty.StringVal("foo"), 648 }), ric.After) 649 case "module.child.aws_instance.foo": 650 checkVals(t, objectVal(t, schema, map[string]cty.Value{ 651 "id": cty.UnknownVal(cty.String), 652 "foo": cty.UnknownVal(cty.String), 653 "type": cty.UnknownVal(cty.String), 654 }), ric.After) 655 default: 656 t.Fatal("unknown instance:", i) 657 } 658 } 659 } 660 661 func TestContext2Plan_moduleInputFromVar(t *testing.T) { 662 m := testModule(t, "plan-module-input-var") 663 p := testProvider("aws") 664 p.PlanResourceChangeFn = testDiffFn 665 ctx := testContext2(t, &ContextOpts{ 666 Providers: map[addrs.Provider]providers.Factory{ 667 addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p), 668 }, 669 }) 670 671 plan, diags := ctx.Plan(m, states.NewState(), &PlanOpts{ 672 Mode: plans.NormalMode, 673 SetVariables: InputValues{ 674 "foo": &InputValue{ 675 Value: cty.StringVal("52"), 676 SourceType: ValueFromCaller, 677 }, 678 }, 679 }) 680 if diags.HasErrors() { 681 t.Fatalf("unexpected errors: %s", diags.Err()) 682 } 683 684 schema := p.GetProviderSchemaResponse.ResourceTypes["aws_instance"].Block 685 ty := schema.ImpliedType() 686 687 if len(plan.Changes.Resources) != 2 { 688 t.Fatal("expected 2 changes, got", len(plan.Changes.Resources)) 689 } 690 691 for _, res := range plan.Changes.Resources { 692 if res.Action != plans.Create { 693 t.Fatalf("expected resource creation, got %s", res.Action) 694 } 695 ric, err := res.Decode(ty) 696 if err != nil { 697 t.Fatal(err) 698 } 699 700 switch i := ric.Addr.String(); i { 701 case "aws_instance.bar": 702 checkVals(t, objectVal(t, schema, map[string]cty.Value{ 703 "id": cty.UnknownVal(cty.String), 704 "foo": cty.StringVal("2"), 705 "type": cty.UnknownVal(cty.String), 706 }), ric.After) 707 case "module.child.aws_instance.foo": 708 checkVals(t, objectVal(t, schema, map[string]cty.Value{ 709 "id": cty.UnknownVal(cty.String), 710 "foo": cty.StringVal("52"), 711 "type": cty.UnknownVal(cty.String), 712 }), ric.After) 713 default: 714 t.Fatal("unknown instance:", i) 715 } 716 } 717 } 718 719 func TestContext2Plan_moduleMultiVar(t *testing.T) { 720 m := testModule(t, "plan-module-multi-var") 721 p := testProvider("aws") 722 p.GetProviderSchemaResponse = getProviderSchemaResponseFromProviderSchema(&ProviderSchema{ 723 ResourceTypes: map[string]*configschema.Block{ 724 "aws_instance": { 725 Attributes: map[string]*configschema.Attribute{ 726 "id": {Type: cty.String, Computed: true}, 727 "foo": {Type: cty.String, Optional: true}, 728 "baz": {Type: cty.String, Optional: true}, 729 }, 730 }, 731 }, 732 }) 733 734 ctx := testContext2(t, &ContextOpts{ 735 Providers: map[addrs.Provider]providers.Factory{ 736 addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p), 737 }, 738 }) 739 740 plan, diags := ctx.Plan(m, states.NewState(), DefaultPlanOpts) 741 if diags.HasErrors() { 742 t.Fatalf("unexpected errors: %s", diags.Err()) 743 } 744 745 schema := p.GetProviderSchemaResponse.ResourceTypes["aws_instance"].Block 746 ty := schema.ImpliedType() 747 748 if len(plan.Changes.Resources) != 5 { 749 t.Fatal("expected 5 changes, got", len(plan.Changes.Resources)) 750 } 751 752 for _, res := range plan.Changes.Resources { 753 if res.Action != plans.Create { 754 t.Fatalf("expected resource creation, got %s", res.Action) 755 } 756 757 ric, err := res.Decode(ty) 758 if err != nil { 759 t.Fatal(err) 760 } 761 762 switch i := ric.Addr.String(); i { 763 case "aws_instance.parent[0]": 764 checkVals(t, objectVal(t, schema, map[string]cty.Value{ 765 "id": cty.UnknownVal(cty.String), 766 }), ric.After) 767 case "aws_instance.parent[1]": 768 checkVals(t, objectVal(t, schema, map[string]cty.Value{ 769 "id": cty.UnknownVal(cty.String), 770 }), ric.After) 771 case "module.child.aws_instance.bar[0]": 772 checkVals(t, objectVal(t, schema, map[string]cty.Value{ 773 "id": cty.UnknownVal(cty.String), 774 "baz": cty.StringVal("baz"), 775 }), ric.After) 776 case "module.child.aws_instance.bar[1]": 777 checkVals(t, objectVal(t, schema, map[string]cty.Value{ 778 "id": cty.UnknownVal(cty.String), 779 "baz": cty.StringVal("baz"), 780 }), ric.After) 781 case "module.child.aws_instance.foo": 782 checkVals(t, objectVal(t, schema, map[string]cty.Value{ 783 "id": cty.UnknownVal(cty.String), 784 "foo": cty.StringVal("baz,baz"), 785 }), ric.After) 786 default: 787 t.Fatal("unknown instance:", i) 788 } 789 } 790 } 791 792 func TestContext2Plan_moduleOrphans(t *testing.T) { 793 m := testModule(t, "plan-modules-remove") 794 p := testProvider("aws") 795 p.PlanResourceChangeFn = testDiffFn 796 797 state := states.NewState() 798 child := state.EnsureModule(addrs.RootModuleInstance.Child("child", addrs.NoKey)) 799 child.SetResourceInstanceCurrent( 800 mustResourceInstanceAddr("aws_instance.foo").Resource, 801 &states.ResourceInstanceObjectSrc{ 802 Status: states.ObjectReady, 803 AttrsJSON: []byte(`{"id":"baz"}`), 804 }, 805 mustProviderConfig(`provider["registry.terraform.io/hashicorp/aws"]`), 806 ) 807 808 ctx := testContext2(t, &ContextOpts{ 809 Providers: map[addrs.Provider]providers.Factory{ 810 addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p), 811 }, 812 }) 813 814 plan, diags := ctx.Plan(m, state, DefaultPlanOpts) 815 if diags.HasErrors() { 816 t.Fatalf("unexpected errors: %s", diags.Err()) 817 } 818 schema := p.GetProviderSchemaResponse.ResourceTypes["aws_instance"].Block 819 ty := schema.ImpliedType() 820 821 if len(plan.Changes.Resources) != 2 { 822 t.Fatal("expected 2 changes, got", len(plan.Changes.Resources)) 823 } 824 825 for _, res := range plan.Changes.Resources { 826 827 ric, err := res.Decode(ty) 828 if err != nil { 829 t.Fatal(err) 830 } 831 832 switch i := ric.Addr.String(); i { 833 case "aws_instance.foo": 834 if res.Action != plans.Create { 835 t.Fatalf("expected resource creation, got %s", res.Action) 836 } 837 checkVals(t, objectVal(t, schema, map[string]cty.Value{ 838 "id": cty.UnknownVal(cty.String), 839 "num": cty.NumberIntVal(2), 840 "type": cty.UnknownVal(cty.String), 841 }), ric.After) 842 case "module.child.aws_instance.foo": 843 if res.Action != plans.Delete { 844 t.Fatalf("expected resource delete, got %s", res.Action) 845 } 846 default: 847 t.Fatal("unknown instance:", i) 848 } 849 } 850 851 expectedState := `<no state> 852 module.child: 853 aws_instance.foo: 854 ID = baz 855 provider = provider["registry.terraform.io/hashicorp/aws"]` 856 857 if plan.PriorState.String() != expectedState { 858 t.Fatalf("\nexpected state: %q\n\ngot: %q", expectedState, plan.PriorState.String()) 859 } 860 } 861 862 // https://github.com/cycloidio/terraform/issues/3114 863 func TestContext2Plan_moduleOrphansWithProvisioner(t *testing.T) { 864 m := testModule(t, "plan-modules-remove-provisioners") 865 p := testProvider("aws") 866 p.PlanResourceChangeFn = testDiffFn 867 pr := testProvisioner() 868 869 state := states.NewState() 870 root := state.EnsureModule(addrs.RootModuleInstance) 871 root.SetResourceInstanceCurrent( 872 mustResourceInstanceAddr("aws_instance.top").Resource, 873 &states.ResourceInstanceObjectSrc{ 874 Status: states.ObjectReady, 875 AttrsJSON: []byte(`{"id":"top","type":"aws_instance"}`), 876 }, 877 mustProviderConfig(`provider["registry.terraform.io/hashicorp/aws"]`), 878 ) 879 child1 := state.EnsureModule(addrs.RootModuleInstance.Child("parent", addrs.NoKey).Child("child1", addrs.NoKey)) 880 child1.SetResourceInstanceCurrent( 881 mustResourceInstanceAddr("aws_instance.foo").Resource, 882 &states.ResourceInstanceObjectSrc{ 883 Status: states.ObjectReady, 884 AttrsJSON: []byte(`{"id":"baz","type":"aws_instance"}`), 885 }, 886 mustProviderConfig(`provider["registry.terraform.io/hashicorp/aws"]`), 887 ) 888 child2 := state.EnsureModule(addrs.RootModuleInstance.Child("parent", addrs.NoKey).Child("child2", addrs.NoKey)) 889 child2.SetResourceInstanceCurrent( 890 mustResourceInstanceAddr("aws_instance.foo").Resource, 891 &states.ResourceInstanceObjectSrc{ 892 Status: states.ObjectReady, 893 AttrsJSON: []byte(`{"id":"baz","type":"aws_instance"}`), 894 }, 895 mustProviderConfig(`provider["registry.terraform.io/hashicorp/aws"]`), 896 ) 897 898 ctx := testContext2(t, &ContextOpts{ 899 Providers: map[addrs.Provider]providers.Factory{ 900 addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p), 901 }, 902 Provisioners: map[string]provisioners.Factory{ 903 "shell": testProvisionerFuncFixed(pr), 904 }, 905 }) 906 907 plan, diags := ctx.Plan(m, state, DefaultPlanOpts) 908 if diags.HasErrors() { 909 t.Fatalf("unexpected errors: %s", diags.Err()) 910 } 911 912 schema := p.GetProviderSchemaResponse.ResourceTypes["aws_instance"].Block 913 ty := schema.ImpliedType() 914 915 if len(plan.Changes.Resources) != 3 { 916 t.Error("expected 3 planned resources, got", len(plan.Changes.Resources)) 917 } 918 919 for _, res := range plan.Changes.Resources { 920 921 ric, err := res.Decode(ty) 922 if err != nil { 923 t.Fatal(err) 924 } 925 926 switch i := ric.Addr.String(); i { 927 case "module.parent.module.child1.aws_instance.foo": 928 if res.Action != plans.Delete { 929 t.Fatalf("expected resource Delete, got %s", res.Action) 930 } 931 case "module.parent.module.child2.aws_instance.foo": 932 if res.Action != plans.Delete { 933 t.Fatalf("expected resource Delete, got %s", res.Action) 934 } 935 case "aws_instance.top": 936 if res.Action != plans.NoOp { 937 t.Fatalf("expected no changes, got %s", res.Action) 938 } 939 default: 940 t.Fatalf("unknown instance: %s\nafter: %#v", i, hcl2shim.ConfigValueFromHCL2(ric.After)) 941 } 942 } 943 944 expectedState := `aws_instance.top: 945 ID = top 946 provider = provider["registry.terraform.io/hashicorp/aws"] 947 type = aws_instance 948 949 module.parent.child1: 950 aws_instance.foo: 951 ID = baz 952 provider = provider["registry.terraform.io/hashicorp/aws"] 953 type = aws_instance 954 module.parent.child2: 955 aws_instance.foo: 956 ID = baz 957 provider = provider["registry.terraform.io/hashicorp/aws"] 958 type = aws_instance` 959 960 if expectedState != plan.PriorState.String() { 961 t.Fatalf("\nexpect state:\n%s\n\ngot state:\n%s\n", expectedState, plan.PriorState.String()) 962 } 963 } 964 965 func TestContext2Plan_moduleProviderInherit(t *testing.T) { 966 var l sync.Mutex 967 var calls []string 968 969 m := testModule(t, "plan-module-provider-inherit") 970 ctx := testContext2(t, &ContextOpts{ 971 Providers: map[addrs.Provider]providers.Factory{ 972 addrs.NewDefaultProvider("aws"): func() (providers.Interface, error) { 973 l.Lock() 974 defer l.Unlock() 975 976 p := testProvider("aws") 977 p.GetProviderSchemaResponse = getProviderSchemaResponseFromProviderSchema(&ProviderSchema{ 978 Provider: &configschema.Block{ 979 Attributes: map[string]*configschema.Attribute{ 980 "from": {Type: cty.String, Optional: true}, 981 }, 982 }, 983 ResourceTypes: map[string]*configschema.Block{ 984 "aws_instance": { 985 Attributes: map[string]*configschema.Attribute{ 986 "from": {Type: cty.String, Optional: true}, 987 }, 988 }, 989 }, 990 }) 991 p.ConfigureProviderFn = func(req providers.ConfigureProviderRequest) (resp providers.ConfigureProviderResponse) { 992 from := req.Config.GetAttr("from") 993 if from.IsNull() || from.AsString() != "root" { 994 resp.Diagnostics = resp.Diagnostics.Append(fmt.Errorf("not root")) 995 } 996 997 return 998 } 999 p.PlanResourceChangeFn = func(req providers.PlanResourceChangeRequest) (resp providers.PlanResourceChangeResponse) { 1000 from := req.Config.GetAttr("from").AsString() 1001 1002 l.Lock() 1003 defer l.Unlock() 1004 calls = append(calls, from) 1005 return testDiffFn(req) 1006 } 1007 return p, nil 1008 }, 1009 }, 1010 }) 1011 1012 _, err := ctx.Plan(m, states.NewState(), DefaultPlanOpts) 1013 if err != nil { 1014 t.Fatalf("err: %s", err) 1015 } 1016 1017 actual := calls 1018 sort.Strings(actual) 1019 expected := []string{"child", "root"} 1020 if !reflect.DeepEqual(actual, expected) { 1021 t.Fatalf("bad: %#v", actual) 1022 } 1023 } 1024 1025 // This tests (for GH-11282) that deeply nested modules properly inherit 1026 // configuration. 1027 func TestContext2Plan_moduleProviderInheritDeep(t *testing.T) { 1028 var l sync.Mutex 1029 1030 m := testModule(t, "plan-module-provider-inherit-deep") 1031 ctx := testContext2(t, &ContextOpts{ 1032 Providers: map[addrs.Provider]providers.Factory{ 1033 addrs.NewDefaultProvider("aws"): func() (providers.Interface, error) { 1034 l.Lock() 1035 defer l.Unlock() 1036 1037 var from string 1038 p := testProvider("aws") 1039 1040 p.GetProviderSchemaResponse = getProviderSchemaResponseFromProviderSchema(&ProviderSchema{ 1041 Provider: &configschema.Block{ 1042 Attributes: map[string]*configschema.Attribute{ 1043 "from": {Type: cty.String, Optional: true}, 1044 }, 1045 }, 1046 ResourceTypes: map[string]*configschema.Block{ 1047 "aws_instance": { 1048 Attributes: map[string]*configschema.Attribute{}, 1049 }, 1050 }, 1051 }) 1052 1053 p.ConfigureProviderFn = func(req providers.ConfigureProviderRequest) (resp providers.ConfigureProviderResponse) { 1054 v := req.Config.GetAttr("from") 1055 if v.IsNull() || v.AsString() != "root" { 1056 resp.Diagnostics = resp.Diagnostics.Append(fmt.Errorf("not root")) 1057 } 1058 from = v.AsString() 1059 1060 return 1061 } 1062 1063 p.PlanResourceChangeFn = func(req providers.PlanResourceChangeRequest) (resp providers.PlanResourceChangeResponse) { 1064 if from != "root" { 1065 resp.Diagnostics = resp.Diagnostics.Append(fmt.Errorf("bad resource")) 1066 return 1067 } 1068 1069 return testDiffFn(req) 1070 } 1071 return p, nil 1072 }, 1073 }, 1074 }) 1075 1076 _, err := ctx.Plan(m, states.NewState(), DefaultPlanOpts) 1077 if err != nil { 1078 t.Fatalf("err: %s", err) 1079 } 1080 } 1081 1082 func TestContext2Plan_moduleProviderDefaultsVar(t *testing.T) { 1083 var l sync.Mutex 1084 var calls []string 1085 1086 m := testModule(t, "plan-module-provider-defaults-var") 1087 ctx := testContext2(t, &ContextOpts{ 1088 Providers: map[addrs.Provider]providers.Factory{ 1089 addrs.NewDefaultProvider("aws"): func() (providers.Interface, error) { 1090 l.Lock() 1091 defer l.Unlock() 1092 1093 p := testProvider("aws") 1094 p.GetProviderSchemaResponse = getProviderSchemaResponseFromProviderSchema(&ProviderSchema{ 1095 Provider: &configschema.Block{ 1096 Attributes: map[string]*configschema.Attribute{ 1097 "to": {Type: cty.String, Optional: true}, 1098 "from": {Type: cty.String, Optional: true}, 1099 }, 1100 }, 1101 ResourceTypes: map[string]*configschema.Block{ 1102 "aws_instance": { 1103 Attributes: map[string]*configschema.Attribute{ 1104 "from": {Type: cty.String, Optional: true}, 1105 }, 1106 }, 1107 }, 1108 }) 1109 p.ConfigureProviderFn = func(req providers.ConfigureProviderRequest) (resp providers.ConfigureProviderResponse) { 1110 var buf bytes.Buffer 1111 from := req.Config.GetAttr("from") 1112 if !from.IsNull() { 1113 buf.WriteString(from.AsString() + "\n") 1114 } 1115 to := req.Config.GetAttr("to") 1116 if !to.IsNull() { 1117 buf.WriteString(to.AsString() + "\n") 1118 } 1119 1120 l.Lock() 1121 defer l.Unlock() 1122 calls = append(calls, buf.String()) 1123 return 1124 } 1125 1126 return p, nil 1127 }, 1128 }, 1129 }) 1130 1131 _, err := ctx.Plan(m, states.NewState(), &PlanOpts{ 1132 Mode: plans.NormalMode, 1133 SetVariables: InputValues{ 1134 "foo": &InputValue{ 1135 Value: cty.StringVal("root"), 1136 SourceType: ValueFromCaller, 1137 }, 1138 }, 1139 }) 1140 if err != nil { 1141 t.Fatalf("err: %s", err) 1142 } 1143 1144 expected := []string{ 1145 "child\nchild\n", 1146 "root\n", 1147 } 1148 sort.Strings(calls) 1149 if !reflect.DeepEqual(calls, expected) { 1150 t.Fatalf("expected:\n%#v\ngot:\n%#v\n", expected, calls) 1151 } 1152 } 1153 1154 func TestContext2Plan_moduleProviderVar(t *testing.T) { 1155 m := testModule(t, "plan-module-provider-var") 1156 p := testProvider("aws") 1157 p.GetProviderSchemaResponse = getProviderSchemaResponseFromProviderSchema(&ProviderSchema{ 1158 Provider: &configschema.Block{ 1159 Attributes: map[string]*configschema.Attribute{ 1160 "value": {Type: cty.String, Optional: true}, 1161 }, 1162 }, 1163 ResourceTypes: map[string]*configschema.Block{ 1164 "aws_instance": { 1165 Attributes: map[string]*configschema.Attribute{ 1166 "value": {Type: cty.String, Optional: true}, 1167 }, 1168 }, 1169 }, 1170 }) 1171 1172 ctx := testContext2(t, &ContextOpts{ 1173 Providers: map[addrs.Provider]providers.Factory{ 1174 addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p), 1175 }, 1176 }) 1177 1178 plan, diags := ctx.Plan(m, states.NewState(), DefaultPlanOpts) 1179 if diags.HasErrors() { 1180 t.Fatalf("unexpected errors: %s", diags.Err()) 1181 } 1182 1183 schema := p.GetProviderSchemaResponse.ResourceTypes["aws_instance"].Block 1184 ty := schema.ImpliedType() 1185 1186 if len(plan.Changes.Resources) != 1 { 1187 t.Fatal("expected 1 changes, got", len(plan.Changes.Resources)) 1188 } 1189 1190 for _, res := range plan.Changes.Resources { 1191 if res.Action != plans.Create { 1192 t.Fatalf("expected resource creation, got %s", res.Action) 1193 } 1194 ric, err := res.Decode(ty) 1195 if err != nil { 1196 t.Fatal(err) 1197 } 1198 1199 switch i := ric.Addr.String(); i { 1200 case "module.child.aws_instance.test": 1201 checkVals(t, objectVal(t, schema, map[string]cty.Value{ 1202 "value": cty.StringVal("hello"), 1203 }), ric.After) 1204 default: 1205 t.Fatal("unknown instance:", i) 1206 } 1207 } 1208 } 1209 1210 func TestContext2Plan_moduleVar(t *testing.T) { 1211 m := testModule(t, "plan-module-var") 1212 p := testProvider("aws") 1213 p.PlanResourceChangeFn = testDiffFn 1214 ctx := testContext2(t, &ContextOpts{ 1215 Providers: map[addrs.Provider]providers.Factory{ 1216 addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p), 1217 }, 1218 }) 1219 1220 plan, diags := ctx.Plan(m, states.NewState(), DefaultPlanOpts) 1221 if diags.HasErrors() { 1222 t.Fatalf("unexpected errors: %s", diags.Err()) 1223 } 1224 1225 schema := p.GetProviderSchemaResponse.ResourceTypes["aws_instance"].Block 1226 ty := schema.ImpliedType() 1227 1228 if len(plan.Changes.Resources) != 2 { 1229 t.Fatal("expected 2 changes, got", len(plan.Changes.Resources)) 1230 } 1231 1232 for _, res := range plan.Changes.Resources { 1233 if res.Action != plans.Create { 1234 t.Fatalf("expected resource creation, got %s", res.Action) 1235 } 1236 ric, err := res.Decode(ty) 1237 if err != nil { 1238 t.Fatal(err) 1239 } 1240 1241 var expected cty.Value 1242 1243 switch i := ric.Addr.String(); i { 1244 case "aws_instance.bar": 1245 expected = objectVal(t, schema, map[string]cty.Value{ 1246 "id": cty.UnknownVal(cty.String), 1247 "foo": cty.StringVal("2"), 1248 "type": cty.UnknownVal(cty.String), 1249 }) 1250 case "module.child.aws_instance.foo": 1251 expected = objectVal(t, schema, map[string]cty.Value{ 1252 "id": cty.UnknownVal(cty.String), 1253 "num": cty.NumberIntVal(2), 1254 "type": cty.UnknownVal(cty.String), 1255 }) 1256 default: 1257 t.Fatal("unknown instance:", i) 1258 } 1259 1260 checkVals(t, expected, ric.After) 1261 } 1262 } 1263 1264 func TestContext2Plan_moduleVarWrongTypeBasic(t *testing.T) { 1265 m := testModule(t, "plan-module-wrong-var-type") 1266 p := testProvider("aws") 1267 ctx := testContext2(t, &ContextOpts{ 1268 Providers: map[addrs.Provider]providers.Factory{ 1269 addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p), 1270 }, 1271 }) 1272 1273 _, diags := ctx.Plan(m, states.NewState(), DefaultPlanOpts) 1274 if !diags.HasErrors() { 1275 t.Fatalf("succeeded; want errors") 1276 } 1277 } 1278 1279 func TestContext2Plan_moduleVarWrongTypeNested(t *testing.T) { 1280 m := testModule(t, "plan-module-wrong-var-type-nested") 1281 p := testProvider("null") 1282 ctx := testContext2(t, &ContextOpts{ 1283 Providers: map[addrs.Provider]providers.Factory{ 1284 addrs.NewDefaultProvider("null"): testProviderFuncFixed(p), 1285 }, 1286 }) 1287 1288 _, diags := ctx.Plan(m, states.NewState(), DefaultPlanOpts) 1289 if !diags.HasErrors() { 1290 t.Fatalf("succeeded; want errors") 1291 } 1292 } 1293 1294 func TestContext2Plan_moduleVarWithDefaultValue(t *testing.T) { 1295 m := testModule(t, "plan-module-var-with-default-value") 1296 p := testProvider("null") 1297 ctx := testContext2(t, &ContextOpts{ 1298 Providers: map[addrs.Provider]providers.Factory{ 1299 addrs.NewDefaultProvider("null"): testProviderFuncFixed(p), 1300 }, 1301 }) 1302 1303 _, diags := ctx.Plan(m, states.NewState(), DefaultPlanOpts) 1304 if diags.HasErrors() { 1305 t.Fatalf("unexpected errors: %s", diags.Err()) 1306 } 1307 } 1308 1309 func TestContext2Plan_moduleVarComputed(t *testing.T) { 1310 m := testModule(t, "plan-module-var-computed") 1311 p := testProvider("aws") 1312 p.PlanResourceChangeFn = testDiffFn 1313 ctx := testContext2(t, &ContextOpts{ 1314 Providers: map[addrs.Provider]providers.Factory{ 1315 addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p), 1316 }, 1317 }) 1318 1319 plan, diags := ctx.Plan(m, states.NewState(), DefaultPlanOpts) 1320 if diags.HasErrors() { 1321 t.Fatalf("unexpected errors: %s", diags.Err()) 1322 } 1323 schema := p.GetProviderSchemaResponse.ResourceTypes["aws_instance"].Block 1324 ty := schema.ImpliedType() 1325 1326 if len(plan.Changes.Resources) != 2 { 1327 t.Fatal("expected 2 changes, got", len(plan.Changes.Resources)) 1328 } 1329 1330 for _, res := range plan.Changes.Resources { 1331 if res.Action != plans.Create { 1332 t.Fatalf("expected resource creation, got %s", res.Action) 1333 } 1334 ric, err := res.Decode(ty) 1335 if err != nil { 1336 t.Fatal(err) 1337 } 1338 1339 switch i := ric.Addr.String(); i { 1340 case "aws_instance.bar": 1341 checkVals(t, objectVal(t, schema, map[string]cty.Value{ 1342 "id": cty.UnknownVal(cty.String), 1343 "foo": cty.UnknownVal(cty.String), 1344 "type": cty.UnknownVal(cty.String), 1345 }), ric.After) 1346 case "module.child.aws_instance.foo": 1347 checkVals(t, objectVal(t, schema, map[string]cty.Value{ 1348 "id": cty.UnknownVal(cty.String), 1349 "foo": cty.UnknownVal(cty.String), 1350 "type": cty.UnknownVal(cty.String), 1351 "compute": cty.StringVal("foo"), 1352 }), ric.After) 1353 default: 1354 t.Fatal("unknown instance:", i) 1355 } 1356 } 1357 } 1358 1359 func TestContext2Plan_preventDestroy_bad(t *testing.T) { 1360 m := testModule(t, "plan-prevent-destroy-bad") 1361 p := testProvider("aws") 1362 p.PlanResourceChangeFn = testDiffFn 1363 state := states.NewState() 1364 root := state.EnsureModule(addrs.RootModuleInstance) 1365 root.SetResourceInstanceCurrent( 1366 mustResourceInstanceAddr("aws_instance.foo").Resource, 1367 &states.ResourceInstanceObjectSrc{ 1368 Status: states.ObjectReady, 1369 AttrsJSON: []byte(`{"id":"i-abc123"}`), 1370 }, 1371 mustProviderConfig(`provider["registry.terraform.io/hashicorp/aws"]`), 1372 ) 1373 1374 ctx := testContext2(t, &ContextOpts{ 1375 Providers: map[addrs.Provider]providers.Factory{ 1376 addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p), 1377 }, 1378 }) 1379 1380 plan, err := ctx.Plan(m, state, DefaultPlanOpts) 1381 1382 expectedErr := "aws_instance.foo has lifecycle.prevent_destroy" 1383 if !strings.Contains(fmt.Sprintf("%s", err), expectedErr) { 1384 if plan != nil { 1385 t.Logf(legacyDiffComparisonString(plan.Changes)) 1386 } 1387 t.Fatalf("expected err would contain %q\nerr: %s", expectedErr, err) 1388 } 1389 } 1390 1391 func TestContext2Plan_preventDestroy_good(t *testing.T) { 1392 m := testModule(t, "plan-prevent-destroy-good") 1393 p := testProvider("aws") 1394 p.PlanResourceChangeFn = testDiffFn 1395 1396 state := states.NewState() 1397 root := state.EnsureModule(addrs.RootModuleInstance) 1398 root.SetResourceInstanceCurrent( 1399 mustResourceInstanceAddr("aws_instance.foo").Resource, 1400 &states.ResourceInstanceObjectSrc{ 1401 Status: states.ObjectReady, 1402 AttrsJSON: []byte(`{"id":"i-abc123","type":"aws_instance"}`), 1403 }, 1404 mustProviderConfig(`provider["registry.terraform.io/hashicorp/aws"]`), 1405 ) 1406 1407 ctx := testContext2(t, &ContextOpts{ 1408 Providers: map[addrs.Provider]providers.Factory{ 1409 addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p), 1410 }, 1411 }) 1412 1413 plan, diags := ctx.Plan(m, state, DefaultPlanOpts) 1414 if diags.HasErrors() { 1415 t.Fatalf("unexpected errors: %s", diags.Err()) 1416 } 1417 1418 if !plan.Changes.Empty() { 1419 t.Fatalf("expected no changes, got %#v\n", plan.Changes) 1420 } 1421 } 1422 1423 func TestContext2Plan_preventDestroy_countBad(t *testing.T) { 1424 m := testModule(t, "plan-prevent-destroy-count-bad") 1425 p := testProvider("aws") 1426 1427 state := states.NewState() 1428 root := state.EnsureModule(addrs.RootModuleInstance) 1429 root.SetResourceInstanceCurrent( 1430 mustResourceInstanceAddr("aws_instance.foo[0]").Resource, 1431 &states.ResourceInstanceObjectSrc{ 1432 Status: states.ObjectReady, 1433 AttrsJSON: []byte(`{"id":"i-abc123"}`), 1434 }, 1435 mustProviderConfig(`provider["registry.terraform.io/hashicorp/aws"]`), 1436 ) 1437 root.SetResourceInstanceCurrent( 1438 mustResourceInstanceAddr("aws_instance.foo[1]").Resource, 1439 &states.ResourceInstanceObjectSrc{ 1440 Status: states.ObjectReady, 1441 AttrsJSON: []byte(`{"id":"i-abc345"}`), 1442 }, 1443 mustProviderConfig(`provider["registry.terraform.io/hashicorp/aws"]`), 1444 ) 1445 1446 ctx := testContext2(t, &ContextOpts{ 1447 Providers: map[addrs.Provider]providers.Factory{ 1448 addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p), 1449 }, 1450 }) 1451 1452 plan, err := ctx.Plan(m, state, DefaultPlanOpts) 1453 1454 expectedErr := "aws_instance.foo[1] has lifecycle.prevent_destroy" 1455 if !strings.Contains(fmt.Sprintf("%s", err), expectedErr) { 1456 if plan != nil { 1457 t.Logf(legacyDiffComparisonString(plan.Changes)) 1458 } 1459 t.Fatalf("expected err would contain %q\nerr: %s", expectedErr, err) 1460 } 1461 } 1462 1463 func TestContext2Plan_preventDestroy_countGood(t *testing.T) { 1464 m := testModule(t, "plan-prevent-destroy-count-good") 1465 p := testProvider("aws") 1466 p.GetProviderSchemaResponse = getProviderSchemaResponseFromProviderSchema(&ProviderSchema{ 1467 ResourceTypes: map[string]*configschema.Block{ 1468 "aws_instance": { 1469 Attributes: map[string]*configschema.Attribute{ 1470 "current": {Type: cty.String, Optional: true}, 1471 "id": {Type: cty.String, Computed: true}, 1472 }, 1473 }, 1474 }, 1475 }) 1476 1477 state := states.NewState() 1478 root := state.EnsureModule(addrs.RootModuleInstance) 1479 root.SetResourceInstanceCurrent( 1480 mustResourceInstanceAddr("aws_instance.foo[0]").Resource, 1481 &states.ResourceInstanceObjectSrc{ 1482 Status: states.ObjectReady, 1483 AttrsJSON: []byte(`{"id":"i-abc123"}`), 1484 }, 1485 mustProviderConfig(`provider["registry.terraform.io/hashicorp/aws"]`), 1486 ) 1487 root.SetResourceInstanceCurrent( 1488 mustResourceInstanceAddr("aws_instance.foo[1]").Resource, 1489 &states.ResourceInstanceObjectSrc{ 1490 Status: states.ObjectReady, 1491 AttrsJSON: []byte(`{"id":"i-abc345"}`), 1492 }, 1493 mustProviderConfig(`provider["registry.terraform.io/hashicorp/aws"]`), 1494 ) 1495 1496 ctx := testContext2(t, &ContextOpts{ 1497 Providers: map[addrs.Provider]providers.Factory{ 1498 addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p), 1499 }, 1500 }) 1501 1502 plan, diags := ctx.Plan(m, state, DefaultPlanOpts) 1503 if diags.HasErrors() { 1504 t.Fatalf("unexpected errors: %s", diags.Err()) 1505 } 1506 1507 if plan.Changes.Empty() { 1508 t.Fatalf("Expected non-empty plan, got %s", legacyDiffComparisonString(plan.Changes)) 1509 } 1510 } 1511 1512 func TestContext2Plan_preventDestroy_countGoodNoChange(t *testing.T) { 1513 m := testModule(t, "plan-prevent-destroy-count-good") 1514 p := testProvider("aws") 1515 p.PlanResourceChangeFn = testDiffFn 1516 p.GetProviderSchemaResponse = getProviderSchemaResponseFromProviderSchema(&ProviderSchema{ 1517 ResourceTypes: map[string]*configschema.Block{ 1518 "aws_instance": { 1519 Attributes: map[string]*configschema.Attribute{ 1520 "current": {Type: cty.String, Optional: true}, 1521 "type": {Type: cty.String, Optional: true, Computed: true}, 1522 "id": {Type: cty.String, Computed: true}, 1523 }, 1524 }, 1525 }, 1526 }) 1527 1528 state := states.NewState() 1529 root := state.EnsureModule(addrs.RootModuleInstance) 1530 root.SetResourceInstanceCurrent( 1531 mustResourceInstanceAddr("aws_instance.foo[0]").Resource, 1532 &states.ResourceInstanceObjectSrc{ 1533 Status: states.ObjectReady, 1534 AttrsJSON: []byte(`{"id":"i-abc123","current":"0","type":"aws_instance"}`), 1535 }, 1536 mustProviderConfig(`provider["registry.terraform.io/hashicorp/aws"]`), 1537 ) 1538 1539 ctx := testContext2(t, &ContextOpts{ 1540 Providers: map[addrs.Provider]providers.Factory{ 1541 addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p), 1542 }, 1543 }) 1544 1545 plan, diags := ctx.Plan(m, state, DefaultPlanOpts) 1546 if diags.HasErrors() { 1547 t.Fatalf("unexpected errors: %s", diags.Err()) 1548 } 1549 1550 if !plan.Changes.Empty() { 1551 t.Fatalf("Expected empty plan, got %s", legacyDiffComparisonString(plan.Changes)) 1552 } 1553 } 1554 1555 func TestContext2Plan_preventDestroy_destroyPlan(t *testing.T) { 1556 m := testModule(t, "plan-prevent-destroy-good") 1557 p := testProvider("aws") 1558 1559 state := states.NewState() 1560 root := state.EnsureModule(addrs.RootModuleInstance) 1561 root.SetResourceInstanceCurrent( 1562 mustResourceInstanceAddr("aws_instance.foo").Resource, 1563 &states.ResourceInstanceObjectSrc{ 1564 Status: states.ObjectReady, 1565 AttrsJSON: []byte(`{"id":"i-abc123"}`), 1566 }, 1567 mustProviderConfig(`provider["registry.terraform.io/hashicorp/aws"]`), 1568 ) 1569 1570 ctx := testContext2(t, &ContextOpts{ 1571 Providers: map[addrs.Provider]providers.Factory{ 1572 addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p), 1573 }, 1574 }) 1575 1576 plan, diags := ctx.Plan(m, state, &PlanOpts{ 1577 Mode: plans.DestroyMode, 1578 }) 1579 1580 expectedErr := "aws_instance.foo has lifecycle.prevent_destroy" 1581 if !strings.Contains(fmt.Sprintf("%s", diags.Err()), expectedErr) { 1582 if plan != nil { 1583 t.Logf(legacyDiffComparisonString(plan.Changes)) 1584 } 1585 t.Fatalf("expected diagnostics would contain %q\nactual diags: %s", expectedErr, diags.Err()) 1586 } 1587 } 1588 1589 func TestContext2Plan_provisionerCycle(t *testing.T) { 1590 m := testModule(t, "plan-provisioner-cycle") 1591 p := testProvider("aws") 1592 pr := testProvisioner() 1593 ctx := testContext2(t, &ContextOpts{ 1594 Providers: map[addrs.Provider]providers.Factory{ 1595 addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p), 1596 }, 1597 Provisioners: map[string]provisioners.Factory{ 1598 "local-exec": testProvisionerFuncFixed(pr), 1599 }, 1600 }) 1601 1602 _, diags := ctx.Plan(m, states.NewState(), DefaultPlanOpts) 1603 if !diags.HasErrors() { 1604 t.Fatalf("succeeded; want errors") 1605 } 1606 } 1607 1608 func TestContext2Plan_computed(t *testing.T) { 1609 m := testModule(t, "plan-computed") 1610 p := testProvider("aws") 1611 p.PlanResourceChangeFn = testDiffFn 1612 ctx := testContext2(t, &ContextOpts{ 1613 Providers: map[addrs.Provider]providers.Factory{ 1614 addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p), 1615 }, 1616 }) 1617 1618 plan, diags := ctx.Plan(m, states.NewState(), DefaultPlanOpts) 1619 if diags.HasErrors() { 1620 t.Fatalf("unexpected errors: %s", diags.Err()) 1621 } 1622 1623 schema := p.GetProviderSchemaResponse.ResourceTypes["aws_instance"].Block 1624 ty := schema.ImpliedType() 1625 1626 if len(plan.Changes.Resources) != 2 { 1627 t.Fatal("expected 2 changes, got", len(plan.Changes.Resources)) 1628 } 1629 1630 for _, res := range plan.Changes.Resources { 1631 if res.Action != plans.Create { 1632 t.Fatalf("expected resource creation, got %s", res.Action) 1633 } 1634 ric, err := res.Decode(ty) 1635 if err != nil { 1636 t.Fatal(err) 1637 } 1638 1639 switch i := ric.Addr.String(); i { 1640 case "aws_instance.bar": 1641 checkVals(t, objectVal(t, schema, map[string]cty.Value{ 1642 "id": cty.UnknownVal(cty.String), 1643 "foo": cty.UnknownVal(cty.String), 1644 "type": cty.UnknownVal(cty.String), 1645 }), ric.After) 1646 case "aws_instance.foo": 1647 checkVals(t, objectVal(t, schema, map[string]cty.Value{ 1648 "id": cty.UnknownVal(cty.String), 1649 "foo": cty.UnknownVal(cty.String), 1650 "num": cty.NumberIntVal(2), 1651 "type": cty.UnknownVal(cty.String), 1652 "compute": cty.StringVal("foo"), 1653 }), ric.After) 1654 default: 1655 t.Fatal("unknown instance:", i) 1656 } 1657 } 1658 } 1659 1660 func TestContext2Plan_blockNestingGroup(t *testing.T) { 1661 m := testModule(t, "plan-block-nesting-group") 1662 p := testProvider("test") 1663 p.GetProviderSchemaResponse = getProviderSchemaResponseFromProviderSchema(&ProviderSchema{ 1664 ResourceTypes: map[string]*configschema.Block{ 1665 "test": { 1666 BlockTypes: map[string]*configschema.NestedBlock{ 1667 "blah": { 1668 Nesting: configschema.NestingGroup, 1669 Block: configschema.Block{ 1670 Attributes: map[string]*configschema.Attribute{ 1671 "baz": {Type: cty.String, Required: true}, 1672 }, 1673 }, 1674 }, 1675 }, 1676 }, 1677 }, 1678 }) 1679 p.PlanResourceChangeFn = func(req providers.PlanResourceChangeRequest) providers.PlanResourceChangeResponse { 1680 return providers.PlanResourceChangeResponse{ 1681 PlannedState: req.ProposedNewState, 1682 } 1683 } 1684 ctx := testContext2(t, &ContextOpts{ 1685 Providers: map[addrs.Provider]providers.Factory{ 1686 addrs.NewDefaultProvider("test"): testProviderFuncFixed(p), 1687 }, 1688 }) 1689 1690 plan, diags := ctx.Plan(m, states.NewState(), DefaultPlanOpts) 1691 if diags.HasErrors() { 1692 t.Fatalf("unexpected errors: %s", diags.Err()) 1693 } 1694 1695 if got, want := 1, len(plan.Changes.Resources); got != want { 1696 t.Fatalf("wrong number of planned resource changes %d; want %d\n%s", got, want, spew.Sdump(plan.Changes.Resources)) 1697 } 1698 1699 if !p.PlanResourceChangeCalled { 1700 t.Fatalf("PlanResourceChange was not called at all") 1701 } 1702 1703 got := p.PlanResourceChangeRequest 1704 want := providers.PlanResourceChangeRequest{ 1705 TypeName: "test", 1706 1707 // Because block type "blah" is defined as NestingGroup, we get a non-null 1708 // value for it with null nested attributes, rather than the "blah" object 1709 // itself being null, when there's no "blah" block in the config at all. 1710 // 1711 // This represents the situation where the remote service _always_ creates 1712 // a single "blah", regardless of whether the block is present, but when 1713 // the block _is_ present the user can override some aspects of it. The 1714 // absense of the block means "use the defaults", in that case. 1715 Config: cty.ObjectVal(map[string]cty.Value{ 1716 "blah": cty.ObjectVal(map[string]cty.Value{ 1717 "baz": cty.NullVal(cty.String), 1718 }), 1719 }), 1720 ProposedNewState: cty.ObjectVal(map[string]cty.Value{ 1721 "blah": cty.ObjectVal(map[string]cty.Value{ 1722 "baz": cty.NullVal(cty.String), 1723 }), 1724 }), 1725 } 1726 if !cmp.Equal(got, want, valueTrans) { 1727 t.Errorf("wrong PlanResourceChange request\n%s", cmp.Diff(got, want, valueTrans)) 1728 } 1729 } 1730 1731 func TestContext2Plan_computedDataResource(t *testing.T) { 1732 m := testModule(t, "plan-computed-data-resource") 1733 p := testProvider("aws") 1734 p.GetProviderSchemaResponse = getProviderSchemaResponseFromProviderSchema(&ProviderSchema{ 1735 ResourceTypes: map[string]*configschema.Block{ 1736 "aws_instance": { 1737 Attributes: map[string]*configschema.Attribute{ 1738 "num": {Type: cty.String, Optional: true}, 1739 "compute": {Type: cty.String, Optional: true}, 1740 "foo": {Type: cty.String, Computed: true}, 1741 }, 1742 }, 1743 }, 1744 DataSources: map[string]*configschema.Block{ 1745 "aws_vpc": { 1746 Attributes: map[string]*configschema.Attribute{ 1747 "foo": {Type: cty.String, Optional: true}, 1748 }, 1749 }, 1750 }, 1751 }) 1752 1753 ctx := testContext2(t, &ContextOpts{ 1754 Providers: map[addrs.Provider]providers.Factory{ 1755 addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p), 1756 }, 1757 }) 1758 1759 plan, diags := ctx.Plan(m, states.NewState(), DefaultPlanOpts) 1760 if diags.HasErrors() { 1761 t.Fatalf("unexpected errors: %s", diags.Err()) 1762 } 1763 schema := p.GetProviderSchemaResponse.DataSources["aws_vpc"].Block 1764 ty := schema.ImpliedType() 1765 1766 if rc := plan.Changes.ResourceInstance(addrs.Resource{Mode: addrs.ManagedResourceMode, Type: "aws_instance", Name: "foo"}.Instance(addrs.NoKey).Absolute(addrs.RootModuleInstance)); rc == nil { 1767 t.Fatalf("missing diff for aws_instance.foo") 1768 } 1769 rcs := plan.Changes.ResourceInstance(addrs.Resource{ 1770 Mode: addrs.DataResourceMode, 1771 Type: "aws_vpc", 1772 Name: "bar", 1773 }.Instance(addrs.NoKey).Absolute(addrs.RootModuleInstance)) 1774 if rcs == nil { 1775 t.Fatalf("missing diff for data.aws_vpc.bar") 1776 } 1777 1778 rc, err := rcs.Decode(ty) 1779 if err != nil { 1780 t.Fatal(err) 1781 } 1782 1783 checkVals(t, 1784 cty.ObjectVal(map[string]cty.Value{ 1785 "foo": cty.UnknownVal(cty.String), 1786 }), 1787 rc.After, 1788 ) 1789 } 1790 1791 func TestContext2Plan_computedInFunction(t *testing.T) { 1792 m := testModule(t, "plan-computed-in-function") 1793 p := testProvider("aws") 1794 p.GetProviderSchemaResponse = getProviderSchemaResponseFromProviderSchema(&ProviderSchema{ 1795 ResourceTypes: map[string]*configschema.Block{ 1796 "aws_instance": { 1797 Attributes: map[string]*configschema.Attribute{ 1798 "attr": {Type: cty.Number, Optional: true}, 1799 }, 1800 }, 1801 }, 1802 DataSources: map[string]*configschema.Block{ 1803 "aws_data_source": { 1804 Attributes: map[string]*configschema.Attribute{ 1805 "computed": {Type: cty.List(cty.String), Computed: true}, 1806 }, 1807 }, 1808 }, 1809 }) 1810 p.ReadDataSourceResponse = &providers.ReadDataSourceResponse{ 1811 State: cty.ObjectVal(map[string]cty.Value{ 1812 "computed": cty.ListVal([]cty.Value{ 1813 cty.StringVal("foo"), 1814 }), 1815 }), 1816 } 1817 1818 ctx := testContext2(t, &ContextOpts{ 1819 Providers: map[addrs.Provider]providers.Factory{ 1820 addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p), 1821 }, 1822 }) 1823 1824 diags := ctx.Validate(m) 1825 assertNoErrors(t, diags) 1826 1827 _, diags = ctx.Plan(m, states.NewState(), DefaultPlanOpts) 1828 assertNoErrors(t, diags) 1829 1830 if !p.ReadDataSourceCalled { 1831 t.Fatalf("ReadDataSource was not called on provider during plan; should've been called") 1832 } 1833 } 1834 1835 func TestContext2Plan_computedDataCountResource(t *testing.T) { 1836 m := testModule(t, "plan-computed-data-count") 1837 p := testProvider("aws") 1838 p.GetProviderSchemaResponse = getProviderSchemaResponseFromProviderSchema(&ProviderSchema{ 1839 ResourceTypes: map[string]*configschema.Block{ 1840 "aws_instance": { 1841 Attributes: map[string]*configschema.Attribute{ 1842 "num": {Type: cty.String, Optional: true}, 1843 "compute": {Type: cty.String, Optional: true}, 1844 "foo": {Type: cty.String, Computed: true}, 1845 }, 1846 }, 1847 }, 1848 DataSources: map[string]*configschema.Block{ 1849 "aws_vpc": { 1850 Attributes: map[string]*configschema.Attribute{ 1851 "foo": {Type: cty.String, Optional: true}, 1852 }, 1853 }, 1854 }, 1855 }) 1856 1857 ctx := testContext2(t, &ContextOpts{ 1858 Providers: map[addrs.Provider]providers.Factory{ 1859 addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p), 1860 }, 1861 }) 1862 1863 plan, diags := ctx.Plan(m, states.NewState(), DefaultPlanOpts) 1864 if diags.HasErrors() { 1865 t.Fatalf("unexpected errors: %s", diags.Err()) 1866 } 1867 1868 // make sure we created 3 "bar"s 1869 for i := 0; i < 3; i++ { 1870 addr := addrs.Resource{ 1871 Mode: addrs.DataResourceMode, 1872 Type: "aws_vpc", 1873 Name: "bar", 1874 }.Instance(addrs.IntKey(i)).Absolute(addrs.RootModuleInstance) 1875 1876 if rcs := plan.Changes.ResourceInstance(addr); rcs == nil { 1877 t.Fatalf("missing changes for %s", addr) 1878 } 1879 } 1880 } 1881 1882 func TestContext2Plan_localValueCount(t *testing.T) { 1883 m := testModule(t, "plan-local-value-count") 1884 p := testProvider("test") 1885 ctx := testContext2(t, &ContextOpts{ 1886 Providers: map[addrs.Provider]providers.Factory{ 1887 addrs.NewDefaultProvider("test"): testProviderFuncFixed(p), 1888 }, 1889 }) 1890 1891 plan, diags := ctx.Plan(m, states.NewState(), DefaultPlanOpts) 1892 if diags.HasErrors() { 1893 t.Fatalf("unexpected errors: %s", diags.Err()) 1894 } 1895 1896 // make sure we created 3 "foo"s 1897 for i := 0; i < 3; i++ { 1898 addr := addrs.Resource{ 1899 Mode: addrs.ManagedResourceMode, 1900 Type: "test_resource", 1901 Name: "foo", 1902 }.Instance(addrs.IntKey(i)).Absolute(addrs.RootModuleInstance) 1903 1904 if rcs := plan.Changes.ResourceInstance(addr); rcs == nil { 1905 t.Fatalf("missing changes for %s", addr) 1906 } 1907 } 1908 } 1909 1910 func TestContext2Plan_dataResourceBecomesComputed(t *testing.T) { 1911 m := testModule(t, "plan-data-resource-becomes-computed") 1912 p := testProvider("aws") 1913 1914 p.GetProviderSchemaResponse = getProviderSchemaResponseFromProviderSchema(&ProviderSchema{ 1915 ResourceTypes: map[string]*configschema.Block{ 1916 "aws_instance": { 1917 Attributes: map[string]*configschema.Attribute{ 1918 "foo": {Type: cty.String, Optional: true}, 1919 "computed": {Type: cty.String, Computed: true}, 1920 }, 1921 }, 1922 }, 1923 DataSources: map[string]*configschema.Block{ 1924 "aws_data_source": { 1925 Attributes: map[string]*configschema.Attribute{ 1926 "id": {Type: cty.String, Computed: true}, 1927 "foo": {Type: cty.String, Optional: true}, 1928 }, 1929 }, 1930 }, 1931 }) 1932 1933 p.PlanResourceChangeFn = func(req providers.PlanResourceChangeRequest) providers.PlanResourceChangeResponse { 1934 fooVal := req.ProposedNewState.GetAttr("foo") 1935 return providers.PlanResourceChangeResponse{ 1936 PlannedState: cty.ObjectVal(map[string]cty.Value{ 1937 "foo": fooVal, 1938 "computed": cty.UnknownVal(cty.String), 1939 }), 1940 PlannedPrivate: req.PriorPrivate, 1941 } 1942 } 1943 1944 schema := p.GetProviderSchemaResponse.DataSources["aws_data_source"].Block 1945 ty := schema.ImpliedType() 1946 1947 p.ReadDataSourceResponse = &providers.ReadDataSourceResponse{ 1948 // This should not be called, because the configuration for the 1949 // data resource contains an unknown value for "foo". 1950 Diagnostics: tfdiags.Diagnostics(nil).Append(fmt.Errorf("ReadDataSource called, but should not have been")), 1951 } 1952 1953 state := states.NewState() 1954 root := state.EnsureModule(addrs.RootModuleInstance) 1955 root.SetResourceInstanceCurrent( 1956 mustResourceInstanceAddr("data.aws_data_source.foo").Resource, 1957 &states.ResourceInstanceObjectSrc{ 1958 Status: states.ObjectReady, 1959 AttrsJSON: []byte(`{"id":"i-abc123","foo":"baz"}`), 1960 }, 1961 mustProviderConfig(`provider["registry.terraform.io/hashicorp/aws"]`), 1962 ) 1963 1964 ctx := testContext2(t, &ContextOpts{ 1965 Providers: map[addrs.Provider]providers.Factory{ 1966 addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p), 1967 }, 1968 }) 1969 1970 plan, diags := ctx.Plan(m, state, DefaultPlanOpts) 1971 if diags.HasErrors() { 1972 t.Fatalf("unexpected errors during plan: %s", diags.Err()) 1973 } 1974 1975 rcs := plan.Changes.ResourceInstance(addrs.Resource{ 1976 Mode: addrs.DataResourceMode, 1977 Type: "aws_data_source", 1978 Name: "foo", 1979 }.Instance(addrs.NoKey).Absolute(addrs.RootModuleInstance)) 1980 if rcs == nil { 1981 t.Logf("full changeset: %s", spew.Sdump(plan.Changes)) 1982 t.Fatalf("missing diff for data.aws_data_resource.foo") 1983 } 1984 1985 rc, err := rcs.Decode(ty) 1986 if err != nil { 1987 t.Fatal(err) 1988 } 1989 1990 // foo should now be unknown 1991 foo := rc.After.GetAttr("foo") 1992 if foo.IsKnown() { 1993 t.Fatalf("foo should be unknown, got %#v", foo) 1994 } 1995 } 1996 1997 func TestContext2Plan_computedList(t *testing.T) { 1998 m := testModule(t, "plan-computed-list") 1999 p := testProvider("aws") 2000 p.PlanResourceChangeFn = testDiffFn 2001 p.GetProviderSchemaResponse = getProviderSchemaResponseFromProviderSchema(&ProviderSchema{ 2002 ResourceTypes: map[string]*configschema.Block{ 2003 "aws_instance": { 2004 Attributes: map[string]*configschema.Attribute{ 2005 "compute": {Type: cty.String, Optional: true}, 2006 "foo": {Type: cty.String, Optional: true}, 2007 "num": {Type: cty.String, Optional: true}, 2008 "list": {Type: cty.List(cty.String), Computed: true}, 2009 }, 2010 }, 2011 }, 2012 }) 2013 2014 ctx := testContext2(t, &ContextOpts{ 2015 Providers: map[addrs.Provider]providers.Factory{ 2016 addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p), 2017 }, 2018 }) 2019 2020 plan, diags := ctx.Plan(m, states.NewState(), DefaultPlanOpts) 2021 if diags.HasErrors() { 2022 t.Fatalf("unexpected errors: %s", diags.Err()) 2023 } 2024 2025 schema := p.GetProviderSchemaResponse.ResourceTypes["aws_instance"].Block 2026 ty := schema.ImpliedType() 2027 2028 if len(plan.Changes.Resources) != 2 { 2029 t.Fatal("expected 2 changes, got", len(plan.Changes.Resources)) 2030 } 2031 2032 for _, res := range plan.Changes.Resources { 2033 if res.Action != plans.Create { 2034 t.Fatalf("expected resource creation, got %s", res.Action) 2035 } 2036 ric, err := res.Decode(ty) 2037 if err != nil { 2038 t.Fatal(err) 2039 } 2040 2041 switch i := ric.Addr.String(); i { 2042 case "aws_instance.bar": 2043 checkVals(t, objectVal(t, schema, map[string]cty.Value{ 2044 "foo": cty.UnknownVal(cty.String), 2045 }), ric.After) 2046 case "aws_instance.foo": 2047 checkVals(t, objectVal(t, schema, map[string]cty.Value{ 2048 "list": cty.UnknownVal(cty.List(cty.String)), 2049 "num": cty.NumberIntVal(2), 2050 "compute": cty.StringVal("list.#"), 2051 }), ric.After) 2052 default: 2053 t.Fatal("unknown instance:", i) 2054 } 2055 } 2056 } 2057 2058 // GH-8695. This tests that you can index into a computed list on a 2059 // splatted resource. 2060 func TestContext2Plan_computedMultiIndex(t *testing.T) { 2061 m := testModule(t, "plan-computed-multi-index") 2062 p := testProvider("aws") 2063 p.PlanResourceChangeFn = testDiffFn 2064 2065 p.GetProviderSchemaResponse = getProviderSchemaResponseFromProviderSchema(&ProviderSchema{ 2066 ResourceTypes: map[string]*configschema.Block{ 2067 "aws_instance": { 2068 Attributes: map[string]*configschema.Attribute{ 2069 "compute": {Type: cty.String, Optional: true}, 2070 "foo": {Type: cty.List(cty.String), Optional: true}, 2071 "ip": {Type: cty.List(cty.String), Computed: true}, 2072 }, 2073 }, 2074 }, 2075 }) 2076 2077 ctx := testContext2(t, &ContextOpts{ 2078 Providers: map[addrs.Provider]providers.Factory{ 2079 addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p), 2080 }, 2081 }) 2082 2083 plan, diags := ctx.Plan(m, states.NewState(), DefaultPlanOpts) 2084 if diags.HasErrors() { 2085 t.Fatalf("unexpected errors: %s", diags.Err()) 2086 } 2087 2088 schema := p.GetProviderSchemaResponse.ResourceTypes["aws_instance"].Block 2089 ty := schema.ImpliedType() 2090 2091 if len(plan.Changes.Resources) != 3 { 2092 t.Fatal("expected 3 changes, got", len(plan.Changes.Resources)) 2093 } 2094 2095 for _, res := range plan.Changes.Resources { 2096 if res.Action != plans.Create { 2097 t.Fatalf("expected resource creation, got %s", res.Action) 2098 } 2099 ric, err := res.Decode(ty) 2100 if err != nil { 2101 t.Fatal(err) 2102 } 2103 2104 switch i := ric.Addr.String(); i { 2105 case "aws_instance.foo[0]": 2106 checkVals(t, objectVal(t, schema, map[string]cty.Value{ 2107 "ip": cty.UnknownVal(cty.List(cty.String)), 2108 "foo": cty.NullVal(cty.List(cty.String)), 2109 "compute": cty.StringVal("ip.#"), 2110 }), ric.After) 2111 case "aws_instance.foo[1]": 2112 checkVals(t, objectVal(t, schema, map[string]cty.Value{ 2113 "ip": cty.UnknownVal(cty.List(cty.String)), 2114 "foo": cty.NullVal(cty.List(cty.String)), 2115 "compute": cty.StringVal("ip.#"), 2116 }), ric.After) 2117 case "aws_instance.bar[0]": 2118 checkVals(t, objectVal(t, schema, map[string]cty.Value{ 2119 "foo": cty.UnknownVal(cty.List(cty.String)), 2120 }), ric.After) 2121 default: 2122 t.Fatal("unknown instance:", i) 2123 } 2124 } 2125 } 2126 2127 func TestContext2Plan_count(t *testing.T) { 2128 m := testModule(t, "plan-count") 2129 p := testProvider("aws") 2130 p.PlanResourceChangeFn = testDiffFn 2131 ctx := testContext2(t, &ContextOpts{ 2132 Providers: map[addrs.Provider]providers.Factory{ 2133 addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p), 2134 }, 2135 }) 2136 2137 plan, diags := ctx.Plan(m, states.NewState(), DefaultPlanOpts) 2138 if diags.HasErrors() { 2139 t.Fatalf("unexpected errors: %s", diags.Err()) 2140 } 2141 2142 schema := p.GetProviderSchemaResponse.ResourceTypes["aws_instance"].Block 2143 ty := schema.ImpliedType() 2144 2145 if len(plan.Changes.Resources) != 6 { 2146 t.Fatal("expected 6 changes, got", len(plan.Changes.Resources)) 2147 } 2148 2149 for _, res := range plan.Changes.Resources { 2150 if res.Action != plans.Create { 2151 t.Fatalf("expected resource creation, got %s", res.Action) 2152 } 2153 ric, err := res.Decode(ty) 2154 if err != nil { 2155 t.Fatal(err) 2156 } 2157 2158 switch i := ric.Addr.String(); i { 2159 case "aws_instance.bar": 2160 checkVals(t, objectVal(t, schema, map[string]cty.Value{ 2161 "id": cty.UnknownVal(cty.String), 2162 "foo": cty.StringVal("foo,foo,foo,foo,foo"), 2163 "type": cty.UnknownVal(cty.String), 2164 }), ric.After) 2165 case "aws_instance.foo[0]": 2166 checkVals(t, objectVal(t, schema, map[string]cty.Value{ 2167 "id": cty.UnknownVal(cty.String), 2168 "foo": cty.StringVal("foo"), 2169 "type": cty.UnknownVal(cty.String), 2170 }), ric.After) 2171 case "aws_instance.foo[1]": 2172 checkVals(t, objectVal(t, schema, map[string]cty.Value{ 2173 "id": cty.UnknownVal(cty.String), 2174 "foo": cty.StringVal("foo"), 2175 "type": cty.UnknownVal(cty.String), 2176 }), ric.After) 2177 case "aws_instance.foo[2]": 2178 checkVals(t, objectVal(t, schema, map[string]cty.Value{ 2179 "id": cty.UnknownVal(cty.String), 2180 "foo": cty.StringVal("foo"), 2181 "type": cty.UnknownVal(cty.String), 2182 }), ric.After) 2183 case "aws_instance.foo[3]": 2184 checkVals(t, objectVal(t, schema, map[string]cty.Value{ 2185 "id": cty.UnknownVal(cty.String), 2186 "foo": cty.StringVal("foo"), 2187 "type": cty.UnknownVal(cty.String), 2188 }), ric.After) 2189 case "aws_instance.foo[4]": 2190 checkVals(t, objectVal(t, schema, map[string]cty.Value{ 2191 "id": cty.UnknownVal(cty.String), 2192 "foo": cty.StringVal("foo"), 2193 "type": cty.UnknownVal(cty.String), 2194 }), ric.After) 2195 default: 2196 t.Fatal("unknown instance:", i) 2197 } 2198 } 2199 } 2200 2201 func TestContext2Plan_countComputed(t *testing.T) { 2202 m := testModule(t, "plan-count-computed") 2203 p := testProvider("aws") 2204 ctx := testContext2(t, &ContextOpts{ 2205 Providers: map[addrs.Provider]providers.Factory{ 2206 addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p), 2207 }, 2208 }) 2209 2210 _, err := ctx.Plan(m, states.NewState(), DefaultPlanOpts) 2211 if err == nil { 2212 t.Fatal("should error") 2213 } 2214 } 2215 2216 func TestContext2Plan_countComputedModule(t *testing.T) { 2217 m := testModule(t, "plan-count-computed-module") 2218 p := testProvider("aws") 2219 p.PlanResourceChangeFn = testDiffFn 2220 ctx := testContext2(t, &ContextOpts{ 2221 Providers: map[addrs.Provider]providers.Factory{ 2222 addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p), 2223 }, 2224 }) 2225 2226 _, err := ctx.Plan(m, states.NewState(), DefaultPlanOpts) 2227 2228 expectedErr := `The "count" value depends on resource attributes` 2229 if !strings.Contains(fmt.Sprintf("%s", err), expectedErr) { 2230 t.Fatalf("expected err would contain %q\nerr: %s\n", 2231 expectedErr, err) 2232 } 2233 } 2234 2235 func TestContext2Plan_countModuleStatic(t *testing.T) { 2236 m := testModule(t, "plan-count-module-static") 2237 p := testProvider("aws") 2238 p.PlanResourceChangeFn = testDiffFn 2239 ctx := testContext2(t, &ContextOpts{ 2240 Providers: map[addrs.Provider]providers.Factory{ 2241 addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p), 2242 }, 2243 }) 2244 2245 plan, diags := ctx.Plan(m, states.NewState(), DefaultPlanOpts) 2246 if diags.HasErrors() { 2247 t.Fatalf("unexpected errors: %s", diags.Err()) 2248 } 2249 2250 schema := p.GetProviderSchemaResponse.ResourceTypes["aws_instance"].Block 2251 ty := schema.ImpliedType() 2252 2253 if len(plan.Changes.Resources) != 3 { 2254 t.Fatal("expected 3 changes, got", len(plan.Changes.Resources)) 2255 } 2256 2257 for _, res := range plan.Changes.Resources { 2258 if res.Action != plans.Create { 2259 t.Fatalf("expected resource creation, got %s", res.Action) 2260 } 2261 ric, err := res.Decode(ty) 2262 if err != nil { 2263 t.Fatal(err) 2264 } 2265 2266 switch i := ric.Addr.String(); i { 2267 case "module.child.aws_instance.foo[0]": 2268 checkVals(t, objectVal(t, schema, map[string]cty.Value{ 2269 "id": cty.UnknownVal(cty.String), 2270 "type": cty.UnknownVal(cty.String), 2271 }), ric.After) 2272 case "module.child.aws_instance.foo[1]": 2273 checkVals(t, objectVal(t, schema, map[string]cty.Value{ 2274 "id": cty.UnknownVal(cty.String), 2275 "type": cty.UnknownVal(cty.String), 2276 }), ric.After) 2277 case "module.child.aws_instance.foo[2]": 2278 checkVals(t, objectVal(t, schema, map[string]cty.Value{ 2279 "id": cty.UnknownVal(cty.String), 2280 "type": cty.UnknownVal(cty.String), 2281 }), ric.After) 2282 default: 2283 t.Fatal("unknown instance:", i) 2284 } 2285 } 2286 } 2287 2288 func TestContext2Plan_countModuleStaticGrandchild(t *testing.T) { 2289 m := testModule(t, "plan-count-module-static-grandchild") 2290 p := testProvider("aws") 2291 p.PlanResourceChangeFn = testDiffFn 2292 ctx := testContext2(t, &ContextOpts{ 2293 Providers: map[addrs.Provider]providers.Factory{ 2294 addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p), 2295 }, 2296 }) 2297 2298 plan, diags := ctx.Plan(m, states.NewState(), DefaultPlanOpts) 2299 if diags.HasErrors() { 2300 t.Fatalf("unexpected errors: %s", diags.Err()) 2301 } 2302 2303 schema := p.GetProviderSchemaResponse.ResourceTypes["aws_instance"].Block 2304 ty := schema.ImpliedType() 2305 2306 if len(plan.Changes.Resources) != 3 { 2307 t.Fatal("expected 3 changes, got", len(plan.Changes.Resources)) 2308 } 2309 2310 for _, res := range plan.Changes.Resources { 2311 if res.Action != plans.Create { 2312 t.Fatalf("expected resource creation, got %s", res.Action) 2313 } 2314 ric, err := res.Decode(ty) 2315 if err != nil { 2316 t.Fatal(err) 2317 } 2318 2319 switch i := ric.Addr.String(); i { 2320 case "module.child.module.child.aws_instance.foo[0]": 2321 checkVals(t, objectVal(t, schema, map[string]cty.Value{ 2322 "id": cty.UnknownVal(cty.String), 2323 "type": cty.UnknownVal(cty.String), 2324 }), ric.After) 2325 case "module.child.module.child.aws_instance.foo[1]": 2326 checkVals(t, objectVal(t, schema, map[string]cty.Value{ 2327 "id": cty.UnknownVal(cty.String), 2328 "type": cty.UnknownVal(cty.String), 2329 }), ric.After) 2330 case "module.child.module.child.aws_instance.foo[2]": 2331 checkVals(t, objectVal(t, schema, map[string]cty.Value{ 2332 "id": cty.UnknownVal(cty.String), 2333 "type": cty.UnknownVal(cty.String), 2334 }), ric.After) 2335 default: 2336 t.Fatal("unknown instance:", i) 2337 } 2338 } 2339 } 2340 2341 func TestContext2Plan_countIndex(t *testing.T) { 2342 m := testModule(t, "plan-count-index") 2343 p := testProvider("aws") 2344 p.PlanResourceChangeFn = testDiffFn 2345 ctx := testContext2(t, &ContextOpts{ 2346 Providers: map[addrs.Provider]providers.Factory{ 2347 addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p), 2348 }, 2349 }) 2350 2351 plan, diags := ctx.Plan(m, states.NewState(), DefaultPlanOpts) 2352 if diags.HasErrors() { 2353 t.Fatalf("unexpected errors: %s", diags.Err()) 2354 } 2355 2356 schema := p.GetProviderSchemaResponse.ResourceTypes["aws_instance"].Block 2357 ty := schema.ImpliedType() 2358 2359 if len(plan.Changes.Resources) != 2 { 2360 t.Fatal("expected 2 changes, got", len(plan.Changes.Resources)) 2361 } 2362 2363 for _, res := range plan.Changes.Resources { 2364 if res.Action != plans.Create { 2365 t.Fatalf("expected resource creation, got %s", res.Action) 2366 } 2367 ric, err := res.Decode(ty) 2368 if err != nil { 2369 t.Fatal(err) 2370 } 2371 2372 switch i := ric.Addr.String(); i { 2373 case "aws_instance.foo[0]": 2374 checkVals(t, objectVal(t, schema, map[string]cty.Value{ 2375 "id": cty.UnknownVal(cty.String), 2376 "foo": cty.StringVal("0"), 2377 "type": cty.UnknownVal(cty.String), 2378 }), ric.After) 2379 case "aws_instance.foo[1]": 2380 checkVals(t, objectVal(t, schema, map[string]cty.Value{ 2381 "id": cty.UnknownVal(cty.String), 2382 "foo": cty.StringVal("1"), 2383 "type": cty.UnknownVal(cty.String), 2384 }), ric.After) 2385 default: 2386 t.Fatal("unknown instance:", i) 2387 } 2388 } 2389 } 2390 2391 func TestContext2Plan_countVar(t *testing.T) { 2392 m := testModule(t, "plan-count-var") 2393 p := testProvider("aws") 2394 p.PlanResourceChangeFn = testDiffFn 2395 ctx := testContext2(t, &ContextOpts{ 2396 Providers: map[addrs.Provider]providers.Factory{ 2397 addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p), 2398 }, 2399 }) 2400 2401 plan, diags := ctx.Plan(m, states.NewState(), &PlanOpts{ 2402 Mode: plans.NormalMode, 2403 SetVariables: InputValues{ 2404 "instance_count": &InputValue{ 2405 Value: cty.StringVal("3"), 2406 SourceType: ValueFromCaller, 2407 }, 2408 }, 2409 }) 2410 if diags.HasErrors() { 2411 t.Fatalf("unexpected errors: %s", diags.Err()) 2412 } 2413 schema := p.GetProviderSchemaResponse.ResourceTypes["aws_instance"].Block 2414 ty := schema.ImpliedType() 2415 2416 if len(plan.Changes.Resources) != 4 { 2417 t.Fatal("expected 4 changes, got", len(plan.Changes.Resources)) 2418 } 2419 2420 for _, res := range plan.Changes.Resources { 2421 if res.Action != plans.Create { 2422 t.Fatalf("expected resource creation, got %s", res.Action) 2423 } 2424 ric, err := res.Decode(ty) 2425 if err != nil { 2426 t.Fatal(err) 2427 } 2428 2429 switch i := ric.Addr.String(); i { 2430 case "aws_instance.bar": 2431 checkVals(t, objectVal(t, schema, map[string]cty.Value{ 2432 "id": cty.UnknownVal(cty.String), 2433 "foo": cty.StringVal("foo,foo,foo"), 2434 "type": cty.UnknownVal(cty.String), 2435 }), ric.After) 2436 case "aws_instance.foo[0]": 2437 checkVals(t, objectVal(t, schema, map[string]cty.Value{ 2438 "id": cty.UnknownVal(cty.String), 2439 "foo": cty.StringVal("foo"), 2440 "type": cty.UnknownVal(cty.String), 2441 }), ric.After) 2442 case "aws_instance.foo[1]": 2443 checkVals(t, objectVal(t, schema, map[string]cty.Value{ 2444 "id": cty.UnknownVal(cty.String), 2445 "foo": cty.StringVal("foo"), 2446 "type": cty.UnknownVal(cty.String), 2447 }), ric.After) 2448 case "aws_instance.foo[2]": 2449 checkVals(t, objectVal(t, schema, map[string]cty.Value{ 2450 "id": cty.UnknownVal(cty.String), 2451 "foo": cty.StringVal("foo"), 2452 "type": cty.UnknownVal(cty.String), 2453 }), ric.After) 2454 default: 2455 t.Fatal("unknown instance:", i) 2456 } 2457 } 2458 } 2459 2460 func TestContext2Plan_countZero(t *testing.T) { 2461 m := testModule(t, "plan-count-zero") 2462 p := testProvider("aws") 2463 p.GetProviderSchemaResponse = getProviderSchemaResponseFromProviderSchema(&ProviderSchema{ 2464 ResourceTypes: map[string]*configschema.Block{ 2465 "aws_instance": { 2466 Attributes: map[string]*configschema.Attribute{ 2467 "foo": {Type: cty.DynamicPseudoType, Optional: true}, 2468 }, 2469 }, 2470 }, 2471 }) 2472 2473 // This schema contains a DynamicPseudoType, and therefore can't go through any shim functions 2474 p.PlanResourceChangeFn = func(req providers.PlanResourceChangeRequest) (resp providers.PlanResourceChangeResponse) { 2475 resp.PlannedState = req.ProposedNewState 2476 resp.PlannedPrivate = req.PriorPrivate 2477 return resp 2478 } 2479 2480 ctx := testContext2(t, &ContextOpts{ 2481 Providers: map[addrs.Provider]providers.Factory{ 2482 addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p), 2483 }, 2484 }) 2485 2486 plan, diags := ctx.Plan(m, states.NewState(), DefaultPlanOpts) 2487 if diags.HasErrors() { 2488 t.Fatalf("unexpected errors: %s", diags.Err()) 2489 } 2490 schema := p.GetProviderSchemaResponse.ResourceTypes["aws_instance"].Block 2491 ty := schema.ImpliedType() 2492 2493 if len(plan.Changes.Resources) != 1 { 2494 t.Fatal("expected 1 changes, got", len(plan.Changes.Resources)) 2495 } 2496 2497 res := plan.Changes.Resources[0] 2498 2499 if res.Action != plans.Create { 2500 t.Fatalf("expected resource creation, got %s", res.Action) 2501 } 2502 ric, err := res.Decode(ty) 2503 if err != nil { 2504 t.Fatal(err) 2505 } 2506 2507 expected := cty.TupleVal(nil) 2508 2509 foo := ric.After.GetAttr("foo") 2510 2511 if !cmp.Equal(expected, foo, valueComparer) { 2512 t.Fatal(cmp.Diff(expected, foo, valueComparer)) 2513 } 2514 } 2515 2516 func TestContext2Plan_countOneIndex(t *testing.T) { 2517 m := testModule(t, "plan-count-one-index") 2518 p := testProvider("aws") 2519 p.PlanResourceChangeFn = testDiffFn 2520 ctx := testContext2(t, &ContextOpts{ 2521 Providers: map[addrs.Provider]providers.Factory{ 2522 addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p), 2523 }, 2524 }) 2525 2526 plan, diags := ctx.Plan(m, states.NewState(), DefaultPlanOpts) 2527 if diags.HasErrors() { 2528 t.Fatalf("unexpected errors: %s", diags.Err()) 2529 } 2530 2531 schema := p.GetProviderSchemaResponse.ResourceTypes["aws_instance"].Block 2532 ty := schema.ImpliedType() 2533 2534 if len(plan.Changes.Resources) != 2 { 2535 t.Fatal("expected 2 changes, got", len(plan.Changes.Resources)) 2536 } 2537 2538 for _, res := range plan.Changes.Resources { 2539 if res.Action != plans.Create { 2540 t.Fatalf("expected resource creation, got %s", res.Action) 2541 } 2542 ric, err := res.Decode(ty) 2543 if err != nil { 2544 t.Fatal(err) 2545 } 2546 2547 switch i := ric.Addr.String(); i { 2548 case "aws_instance.bar": 2549 checkVals(t, objectVal(t, schema, map[string]cty.Value{ 2550 "id": cty.UnknownVal(cty.String), 2551 "foo": cty.StringVal("foo"), 2552 "type": cty.UnknownVal(cty.String), 2553 }), ric.After) 2554 case "aws_instance.foo[0]": 2555 checkVals(t, objectVal(t, schema, map[string]cty.Value{ 2556 "id": cty.UnknownVal(cty.String), 2557 "foo": cty.StringVal("foo"), 2558 "type": cty.UnknownVal(cty.String), 2559 }), ric.After) 2560 default: 2561 t.Fatal("unknown instance:", i) 2562 } 2563 } 2564 } 2565 2566 func TestContext2Plan_countDecreaseToOne(t *testing.T) { 2567 m := testModule(t, "plan-count-dec") 2568 p := testProvider("aws") 2569 p.PlanResourceChangeFn = testDiffFn 2570 2571 state := states.NewState() 2572 root := state.EnsureModule(addrs.RootModuleInstance) 2573 root.SetResourceInstanceCurrent( 2574 mustResourceInstanceAddr("aws_instance.foo[0]").Resource, 2575 &states.ResourceInstanceObjectSrc{ 2576 Status: states.ObjectReady, 2577 AttrsJSON: []byte(`{"id":"bar","foo":"foo","type":"aws_instance"}`), 2578 }, 2579 mustProviderConfig(`provider["registry.terraform.io/hashicorp/aws"]`), 2580 ) 2581 root.SetResourceInstanceCurrent( 2582 mustResourceInstanceAddr("aws_instance.foo[1]").Resource, 2583 &states.ResourceInstanceObjectSrc{ 2584 Status: states.ObjectReady, 2585 AttrsJSON: []byte(`{"id":"bar"}`), 2586 }, 2587 mustProviderConfig(`provider["registry.terraform.io/hashicorp/aws"]`), 2588 ) 2589 root.SetResourceInstanceCurrent( 2590 mustResourceInstanceAddr("aws_instance.foo[2]").Resource, 2591 &states.ResourceInstanceObjectSrc{ 2592 Status: states.ObjectReady, 2593 AttrsJSON: []byte(`{"id":"bar"}`), 2594 }, 2595 mustProviderConfig(`provider["registry.terraform.io/hashicorp/aws"]`), 2596 ) 2597 2598 ctx := testContext2(t, &ContextOpts{ 2599 Providers: map[addrs.Provider]providers.Factory{ 2600 addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p), 2601 }, 2602 }) 2603 2604 plan, diags := ctx.Plan(m, state, DefaultPlanOpts) 2605 if diags.HasErrors() { 2606 t.Fatalf("unexpected errors: %s", diags.Err()) 2607 } 2608 schema := p.GetProviderSchemaResponse.ResourceTypes["aws_instance"].Block 2609 ty := schema.ImpliedType() 2610 2611 if len(plan.Changes.Resources) != 4 { 2612 t.Fatal("expected 4 changes, got", len(plan.Changes.Resources)) 2613 } 2614 2615 for _, res := range plan.Changes.Resources { 2616 2617 ric, err := res.Decode(ty) 2618 if err != nil { 2619 t.Fatal(err) 2620 } 2621 2622 switch i := ric.Addr.String(); i { 2623 case "aws_instance.bar": 2624 if res.Action != plans.Create { 2625 t.Fatalf("expected resource create, got %s", res.Action) 2626 } 2627 checkVals(t, objectVal(t, schema, map[string]cty.Value{ 2628 "id": cty.UnknownVal(cty.String), 2629 "foo": cty.StringVal("bar"), 2630 "type": cty.UnknownVal(cty.String), 2631 }), ric.After) 2632 case "aws_instance.foo": 2633 if res.Action != plans.NoOp { 2634 t.Fatalf("resource %s should be unchanged", i) 2635 } 2636 case "aws_instance.foo[1]": 2637 if res.Action != plans.Delete { 2638 t.Fatalf("expected resource delete, got %s", res.Action) 2639 } 2640 case "aws_instance.foo[2]": 2641 if res.Action != plans.Delete { 2642 t.Fatalf("expected resource delete, got %s", res.Action) 2643 } 2644 default: 2645 t.Fatal("unknown instance:", i) 2646 } 2647 } 2648 2649 expectedState := `aws_instance.foo: 2650 ID = bar 2651 provider = provider["registry.terraform.io/hashicorp/aws"] 2652 foo = foo 2653 type = aws_instance 2654 aws_instance.foo.1: 2655 ID = bar 2656 provider = provider["registry.terraform.io/hashicorp/aws"] 2657 aws_instance.foo.2: 2658 ID = bar 2659 provider = provider["registry.terraform.io/hashicorp/aws"]` 2660 2661 if plan.PriorState.String() != expectedState { 2662 t.Fatalf("epected state:\n%q\n\ngot state:\n%q\n", expectedState, plan.PriorState.String()) 2663 } 2664 } 2665 2666 func TestContext2Plan_countIncreaseFromNotSet(t *testing.T) { 2667 m := testModule(t, "plan-count-inc") 2668 p := testProvider("aws") 2669 p.PlanResourceChangeFn = testDiffFn 2670 2671 state := states.NewState() 2672 root := state.EnsureModule(addrs.RootModuleInstance) 2673 root.SetResourceInstanceCurrent( 2674 mustResourceInstanceAddr("aws_instance.foo").Resource, 2675 &states.ResourceInstanceObjectSrc{ 2676 Status: states.ObjectReady, 2677 AttrsJSON: []byte(`{"id":"bar","type":"aws_instance","foo":"foo"}`), 2678 }, 2679 mustProviderConfig(`provider["registry.terraform.io/hashicorp/aws"]`), 2680 ) 2681 2682 ctx := testContext2(t, &ContextOpts{ 2683 Providers: map[addrs.Provider]providers.Factory{ 2684 addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p), 2685 }, 2686 }) 2687 2688 plan, diags := ctx.Plan(m, state, DefaultPlanOpts) 2689 if diags.HasErrors() { 2690 t.Fatalf("unexpected errors: %s", diags.Err()) 2691 } 2692 schema := p.GetProviderSchemaResponse.ResourceTypes["aws_instance"].Block 2693 ty := schema.ImpliedType() 2694 2695 if len(plan.Changes.Resources) != 4 { 2696 t.Fatal("expected 4 changes, got", len(plan.Changes.Resources)) 2697 } 2698 2699 for _, res := range plan.Changes.Resources { 2700 2701 ric, err := res.Decode(ty) 2702 if err != nil { 2703 t.Fatal(err) 2704 } 2705 2706 switch i := ric.Addr.String(); i { 2707 case "aws_instance.bar": 2708 if res.Action != plans.Create { 2709 t.Fatalf("expected resource create, got %s", res.Action) 2710 } 2711 checkVals(t, objectVal(t, schema, map[string]cty.Value{ 2712 "id": cty.UnknownVal(cty.String), 2713 "foo": cty.StringVal("bar"), 2714 "type": cty.UnknownVal(cty.String), 2715 }), ric.After) 2716 case "aws_instance.foo[0]": 2717 if res.Action != plans.NoOp { 2718 t.Fatalf("resource %s should be unchanged", i) 2719 } 2720 case "aws_instance.foo[1]": 2721 if res.Action != plans.Create { 2722 t.Fatalf("expected resource create, got %s", res.Action) 2723 } 2724 checkVals(t, objectVal(t, schema, map[string]cty.Value{ 2725 "id": cty.UnknownVal(cty.String), 2726 "foo": cty.StringVal("foo"), 2727 "type": cty.UnknownVal(cty.String), 2728 }), ric.After) 2729 case "aws_instance.foo[2]": 2730 if res.Action != plans.Create { 2731 t.Fatalf("expected resource create, got %s", res.Action) 2732 } 2733 checkVals(t, objectVal(t, schema, map[string]cty.Value{ 2734 "id": cty.UnknownVal(cty.String), 2735 "foo": cty.StringVal("foo"), 2736 "type": cty.UnknownVal(cty.String), 2737 }), ric.After) 2738 default: 2739 t.Fatal("unknown instance:", i) 2740 } 2741 } 2742 } 2743 2744 func TestContext2Plan_countIncreaseFromOne(t *testing.T) { 2745 m := testModule(t, "plan-count-inc") 2746 p := testProvider("aws") 2747 p.PlanResourceChangeFn = testDiffFn 2748 state := states.NewState() 2749 root := state.EnsureModule(addrs.RootModuleInstance) 2750 root.SetResourceInstanceCurrent( 2751 mustResourceInstanceAddr("aws_instance.foo[0]").Resource, 2752 &states.ResourceInstanceObjectSrc{ 2753 Status: states.ObjectReady, 2754 AttrsJSON: []byte(`{"id":"bar","foo":"foo","type":"aws_instance"}`), 2755 }, 2756 mustProviderConfig(`provider["registry.terraform.io/hashicorp/aws"]`), 2757 ) 2758 2759 ctx := testContext2(t, &ContextOpts{ 2760 Providers: map[addrs.Provider]providers.Factory{ 2761 addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p), 2762 }, 2763 }) 2764 2765 plan, diags := ctx.Plan(m, state, DefaultPlanOpts) 2766 if diags.HasErrors() { 2767 t.Fatalf("unexpected errors: %s", diags.Err()) 2768 } 2769 schema := p.GetProviderSchemaResponse.ResourceTypes["aws_instance"].Block 2770 ty := schema.ImpliedType() 2771 2772 if len(plan.Changes.Resources) != 4 { 2773 t.Fatal("expected 4 changes, got", len(plan.Changes.Resources)) 2774 } 2775 2776 for _, res := range plan.Changes.Resources { 2777 2778 ric, err := res.Decode(ty) 2779 if err != nil { 2780 t.Fatal(err) 2781 } 2782 2783 switch i := ric.Addr.String(); i { 2784 case "aws_instance.bar": 2785 if res.Action != plans.Create { 2786 t.Fatalf("expected resource create, got %s", res.Action) 2787 } 2788 checkVals(t, objectVal(t, schema, map[string]cty.Value{ 2789 "id": cty.UnknownVal(cty.String), 2790 "foo": cty.StringVal("bar"), 2791 "type": cty.UnknownVal(cty.String), 2792 }), ric.After) 2793 case "aws_instance.foo[0]": 2794 if res.Action != plans.NoOp { 2795 t.Fatalf("resource %s should be unchanged", i) 2796 } 2797 case "aws_instance.foo[1]": 2798 if res.Action != plans.Create { 2799 t.Fatalf("expected resource create, got %s", res.Action) 2800 } 2801 checkVals(t, objectVal(t, schema, map[string]cty.Value{ 2802 "id": cty.UnknownVal(cty.String), 2803 "foo": cty.StringVal("foo"), 2804 "type": cty.UnknownVal(cty.String), 2805 }), ric.After) 2806 case "aws_instance.foo[2]": 2807 if res.Action != plans.Create { 2808 t.Fatalf("expected resource create, got %s", res.Action) 2809 } 2810 checkVals(t, objectVal(t, schema, map[string]cty.Value{ 2811 "id": cty.UnknownVal(cty.String), 2812 "foo": cty.StringVal("foo"), 2813 "type": cty.UnknownVal(cty.String), 2814 }), ric.After) 2815 default: 2816 t.Fatal("unknown instance:", i) 2817 } 2818 } 2819 } 2820 2821 // https://github.com/PeoplePerHour/terraform/pull/11 2822 // 2823 // This tests a case where both a "resource" and "resource.0" are in 2824 // the state file, which apparently is a reasonable backwards compatibility 2825 // concern found in the above 3rd party repo. 2826 func TestContext2Plan_countIncreaseFromOneCorrupted(t *testing.T) { 2827 m := testModule(t, "plan-count-inc") 2828 p := testProvider("aws") 2829 p.PlanResourceChangeFn = testDiffFn 2830 2831 state := states.NewState() 2832 root := state.EnsureModule(addrs.RootModuleInstance) 2833 root.SetResourceInstanceCurrent( 2834 mustResourceInstanceAddr("aws_instance.foo").Resource, 2835 &states.ResourceInstanceObjectSrc{ 2836 Status: states.ObjectReady, 2837 AttrsJSON: []byte(`{"id":"bar","foo":"foo","type":"aws_instance"}`), 2838 }, 2839 mustProviderConfig(`provider["registry.terraform.io/hashicorp/aws"]`), 2840 ) 2841 root.SetResourceInstanceCurrent( 2842 mustResourceInstanceAddr("aws_instance.foo[0]").Resource, 2843 &states.ResourceInstanceObjectSrc{ 2844 Status: states.ObjectReady, 2845 AttrsJSON: []byte(`{"id":"bar","foo":"foo","type":"aws_instance"}`), 2846 }, 2847 mustProviderConfig(`provider["registry.terraform.io/hashicorp/aws"]`), 2848 ) 2849 2850 ctx := testContext2(t, &ContextOpts{ 2851 Providers: map[addrs.Provider]providers.Factory{ 2852 addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p), 2853 }, 2854 }) 2855 2856 plan, diags := ctx.Plan(m, state, DefaultPlanOpts) 2857 if diags.HasErrors() { 2858 t.Fatalf("unexpected errors: %s", diags.Err()) 2859 } 2860 schema := p.GetProviderSchemaResponse.ResourceTypes["aws_instance"].Block 2861 ty := schema.ImpliedType() 2862 2863 if len(plan.Changes.Resources) != 5 { 2864 t.Fatal("expected 5 changes, got", len(plan.Changes.Resources)) 2865 } 2866 2867 for _, res := range plan.Changes.Resources { 2868 2869 ric, err := res.Decode(ty) 2870 if err != nil { 2871 t.Fatal(err) 2872 } 2873 2874 switch i := ric.Addr.String(); i { 2875 case "aws_instance.bar": 2876 if res.Action != plans.Create { 2877 t.Fatalf("expected resource create, got %s", res.Action) 2878 } 2879 checkVals(t, objectVal(t, schema, map[string]cty.Value{ 2880 "id": cty.UnknownVal(cty.String), 2881 "foo": cty.StringVal("bar"), 2882 "type": cty.UnknownVal(cty.String), 2883 }), ric.After) 2884 case "aws_instance.foo": 2885 if res.Action != plans.Delete { 2886 t.Fatalf("resource %s should be removed", i) 2887 } 2888 case "aws_instance.foo[0]": 2889 if res.Action != plans.NoOp { 2890 t.Fatalf("resource %s should be unchanged", i) 2891 } 2892 case "aws_instance.foo[1]": 2893 if res.Action != plans.Create { 2894 t.Fatalf("expected resource create, got %s", res.Action) 2895 } 2896 checkVals(t, objectVal(t, schema, map[string]cty.Value{ 2897 "id": cty.UnknownVal(cty.String), 2898 "foo": cty.StringVal("foo"), 2899 "type": cty.UnknownVal(cty.String), 2900 }), ric.After) 2901 case "aws_instance.foo[2]": 2902 if res.Action != plans.Create { 2903 t.Fatalf("expected resource create, got %s", res.Action) 2904 } 2905 checkVals(t, objectVal(t, schema, map[string]cty.Value{ 2906 "id": cty.UnknownVal(cty.String), 2907 "foo": cty.StringVal("foo"), 2908 "type": cty.UnknownVal(cty.String), 2909 }), ric.After) 2910 default: 2911 t.Fatal("unknown instance:", i) 2912 } 2913 } 2914 } 2915 2916 // A common pattern in TF configs is to have a set of resources with the same 2917 // count and to use count.index to create correspondences between them: 2918 // 2919 // foo_id = "${foo.bar.*.id[count.index]}" 2920 // 2921 // This test is for the situation where some instances already exist and the 2922 // count is increased. In that case, we should see only the create diffs 2923 // for the new instances and not any update diffs for the existing ones. 2924 func TestContext2Plan_countIncreaseWithSplatReference(t *testing.T) { 2925 m := testModule(t, "plan-count-splat-reference") 2926 p := testProvider("aws") 2927 p.GetProviderSchemaResponse = getProviderSchemaResponseFromProviderSchema(&ProviderSchema{ 2928 ResourceTypes: map[string]*configschema.Block{ 2929 "aws_instance": { 2930 Attributes: map[string]*configschema.Attribute{ 2931 "name": {Type: cty.String, Optional: true}, 2932 "foo_name": {Type: cty.String, Optional: true}, 2933 "id": {Type: cty.String, Computed: true}, 2934 }, 2935 }, 2936 }, 2937 }) 2938 2939 state := states.NewState() 2940 root := state.EnsureModule(addrs.RootModuleInstance) 2941 root.SetResourceInstanceCurrent( 2942 mustResourceInstanceAddr("aws_instance.foo[0]").Resource, 2943 &states.ResourceInstanceObjectSrc{ 2944 Status: states.ObjectReady, 2945 AttrsJSON: []byte(`{"id":"bar","name":"foo 0"}`), 2946 }, 2947 mustProviderConfig(`provider["registry.terraform.io/hashicorp/aws"]`), 2948 ) 2949 root.SetResourceInstanceCurrent( 2950 mustResourceInstanceAddr("aws_instance.foo[1]").Resource, 2951 &states.ResourceInstanceObjectSrc{ 2952 Status: states.ObjectReady, 2953 AttrsJSON: []byte(`{"id":"bar","name":"foo 1"}`), 2954 }, 2955 mustProviderConfig(`provider["registry.terraform.io/hashicorp/aws"]`), 2956 ) 2957 root.SetResourceInstanceCurrent( 2958 mustResourceInstanceAddr("aws_instance.bar[0]").Resource, 2959 &states.ResourceInstanceObjectSrc{ 2960 Status: states.ObjectReady, 2961 AttrsJSON: []byte(`{"id":"bar","foo_name":"foo 0"}`), 2962 }, 2963 mustProviderConfig(`provider["registry.terraform.io/hashicorp/aws"]`), 2964 ) 2965 root.SetResourceInstanceCurrent( 2966 mustResourceInstanceAddr("aws_instance.bar[1]").Resource, 2967 &states.ResourceInstanceObjectSrc{ 2968 Status: states.ObjectReady, 2969 AttrsJSON: []byte(`{"id":"bar","foo_name":"foo 1"}`), 2970 }, 2971 mustProviderConfig(`provider["registry.terraform.io/hashicorp/aws"]`), 2972 ) 2973 2974 ctx := testContext2(t, &ContextOpts{ 2975 Providers: map[addrs.Provider]providers.Factory{ 2976 addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p), 2977 }, 2978 }) 2979 2980 plan, diags := ctx.Plan(m, state, DefaultPlanOpts) 2981 if diags.HasErrors() { 2982 t.Fatalf("unexpected errors: %s", diags.Err()) 2983 } 2984 2985 schema := p.GetProviderSchemaResponse.ResourceTypes["aws_instance"].Block 2986 ty := schema.ImpliedType() 2987 2988 if len(plan.Changes.Resources) != 6 { 2989 t.Fatal("expected 6 changes, got", len(plan.Changes.Resources)) 2990 } 2991 2992 for _, res := range plan.Changes.Resources { 2993 ric, err := res.Decode(ty) 2994 if err != nil { 2995 t.Fatal(err) 2996 } 2997 2998 switch i := ric.Addr.String(); i { 2999 case "aws_instance.bar[0]", "aws_instance.bar[1]", "aws_instance.foo[0]", "aws_instance.foo[1]": 3000 if res.Action != plans.NoOp { 3001 t.Fatalf("resource %s should be unchanged", i) 3002 } 3003 case "aws_instance.bar[2]": 3004 if res.Action != plans.Create { 3005 t.Fatalf("expected resource create, got %s", res.Action) 3006 } 3007 // The instance ID changed, so just check that the name updated 3008 if ric.After.GetAttr("foo_name") != cty.StringVal("foo 2") { 3009 t.Fatalf("resource %s attr \"foo_name\" should be changed", i) 3010 } 3011 case "aws_instance.foo[2]": 3012 if res.Action != plans.Create { 3013 t.Fatalf("expected resource create, got %s", res.Action) 3014 } 3015 // The instance ID changed, so just check that the name updated 3016 if ric.After.GetAttr("name") != cty.StringVal("foo 2") { 3017 t.Fatalf("resource %s attr \"name\" should be changed", i) 3018 } 3019 default: 3020 t.Fatal("unknown instance:", i) 3021 } 3022 } 3023 } 3024 3025 func TestContext2Plan_forEach(t *testing.T) { 3026 m := testModule(t, "plan-for-each") 3027 p := testProvider("aws") 3028 ctx := testContext2(t, &ContextOpts{ 3029 Providers: map[addrs.Provider]providers.Factory{ 3030 addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p), 3031 }, 3032 }) 3033 3034 plan, diags := ctx.Plan(m, states.NewState(), DefaultPlanOpts) 3035 if diags.HasErrors() { 3036 t.Fatalf("unexpected errors: %s", diags.Err()) 3037 } 3038 3039 schema := p.GetProviderSchemaResponse.ResourceTypes["aws_instance"].Block 3040 ty := schema.ImpliedType() 3041 3042 if len(plan.Changes.Resources) != 8 { 3043 t.Fatal("expected 8 changes, got", len(plan.Changes.Resources)) 3044 } 3045 3046 for _, res := range plan.Changes.Resources { 3047 if res.Action != plans.Create { 3048 t.Fatalf("expected resource creation, got %s", res.Action) 3049 } 3050 _, err := res.Decode(ty) 3051 if err != nil { 3052 t.Fatal(err) 3053 } 3054 } 3055 } 3056 3057 func TestContext2Plan_forEachUnknownValue(t *testing.T) { 3058 // This module has a variable defined, but it's value is unknown. We 3059 // expect this to produce an error, but not to panic. 3060 m := testModule(t, "plan-for-each-unknown-value") 3061 p := testProvider("aws") 3062 ctx := testContext2(t, &ContextOpts{ 3063 Providers: map[addrs.Provider]providers.Factory{ 3064 addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p), 3065 }, 3066 }) 3067 3068 _, diags := ctx.Plan(m, states.NewState(), &PlanOpts{ 3069 Mode: plans.NormalMode, 3070 SetVariables: InputValues{ 3071 "foo": { 3072 Value: cty.UnknownVal(cty.String), 3073 SourceType: ValueFromCLIArg, 3074 }, 3075 }, 3076 }) 3077 if !diags.HasErrors() { 3078 // Should get this error: 3079 // Invalid for_each argument: The "for_each" value depends on resource attributes that cannot be determined until apply... 3080 t.Fatal("succeeded; want errors") 3081 } 3082 3083 gotErrStr := diags.Err().Error() 3084 wantErrStr := "Invalid for_each argument" 3085 if !strings.Contains(gotErrStr, wantErrStr) { 3086 t.Fatalf("missing expected error\ngot: %s\n\nwant: error containing %q", gotErrStr, wantErrStr) 3087 } 3088 } 3089 3090 func TestContext2Plan_destroy(t *testing.T) { 3091 m := testModule(t, "plan-destroy") 3092 p := testProvider("aws") 3093 3094 state := states.NewState() 3095 root := state.EnsureModule(addrs.RootModuleInstance) 3096 root.SetResourceInstanceCurrent( 3097 mustResourceInstanceAddr("aws_instance.one").Resource, 3098 &states.ResourceInstanceObjectSrc{ 3099 Status: states.ObjectReady, 3100 AttrsJSON: []byte(`{"id":"bar"}`), 3101 }, 3102 mustProviderConfig(`provider["registry.terraform.io/hashicorp/aws"]`), 3103 ) 3104 root.SetResourceInstanceCurrent( 3105 mustResourceInstanceAddr("aws_instance.two").Resource, 3106 &states.ResourceInstanceObjectSrc{ 3107 Status: states.ObjectReady, 3108 AttrsJSON: []byte(`{"id":"baz"}`), 3109 }, 3110 mustProviderConfig(`provider["registry.terraform.io/hashicorp/aws"]`), 3111 ) 3112 3113 ctx := testContext2(t, &ContextOpts{ 3114 Providers: map[addrs.Provider]providers.Factory{ 3115 addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p), 3116 }, 3117 }) 3118 3119 plan, diags := ctx.Plan(m, state, &PlanOpts{ 3120 Mode: plans.DestroyMode, 3121 }) 3122 if diags.HasErrors() { 3123 t.Fatalf("unexpected errors: %s", diags.Err()) 3124 } 3125 3126 schema := p.GetProviderSchemaResponse.ResourceTypes["aws_instance"].Block 3127 ty := schema.ImpliedType() 3128 3129 if len(plan.Changes.Resources) != 2 { 3130 t.Fatal("expected 2 changes, got", len(plan.Changes.Resources)) 3131 } 3132 3133 for _, res := range plan.Changes.Resources { 3134 ric, err := res.Decode(ty) 3135 if err != nil { 3136 t.Fatal(err) 3137 } 3138 3139 switch i := ric.Addr.String(); i { 3140 case "aws_instance.one", "aws_instance.two": 3141 if res.Action != plans.Delete { 3142 t.Fatalf("resource %s should be removed", i) 3143 } 3144 3145 default: 3146 t.Fatal("unknown instance:", i) 3147 } 3148 } 3149 } 3150 3151 func TestContext2Plan_moduleDestroy(t *testing.T) { 3152 m := testModule(t, "plan-module-destroy") 3153 p := testProvider("aws") 3154 3155 state := states.NewState() 3156 root := state.EnsureModule(addrs.RootModuleInstance) 3157 root.SetResourceInstanceCurrent( 3158 mustResourceInstanceAddr("aws_instance.foo").Resource, 3159 &states.ResourceInstanceObjectSrc{ 3160 Status: states.ObjectReady, 3161 AttrsJSON: []byte(`{"id":"bar"}`), 3162 }, 3163 mustProviderConfig(`provider["registry.terraform.io/hashicorp/aws"]`), 3164 ) 3165 child := state.EnsureModule(addrs.RootModuleInstance.Child("child", addrs.NoKey)) 3166 child.SetResourceInstanceCurrent( 3167 mustResourceInstanceAddr("aws_instance.foo").Resource, 3168 &states.ResourceInstanceObjectSrc{ 3169 Status: states.ObjectReady, 3170 AttrsJSON: []byte(`{"id":"bar"}`), 3171 }, 3172 mustProviderConfig(`provider["registry.terraform.io/hashicorp/aws"]`), 3173 ) 3174 3175 ctx := testContext2(t, &ContextOpts{ 3176 Providers: map[addrs.Provider]providers.Factory{ 3177 addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p), 3178 }, 3179 }) 3180 3181 plan, diags := ctx.Plan(m, state, &PlanOpts{ 3182 Mode: plans.DestroyMode, 3183 }) 3184 if diags.HasErrors() { 3185 t.Fatalf("unexpected errors: %s", diags.Err()) 3186 } 3187 schema := p.GetProviderSchemaResponse.ResourceTypes["aws_instance"].Block 3188 ty := schema.ImpliedType() 3189 3190 if len(plan.Changes.Resources) != 2 { 3191 t.Fatal("expected 2 changes, got", len(plan.Changes.Resources)) 3192 } 3193 3194 for _, res := range plan.Changes.Resources { 3195 ric, err := res.Decode(ty) 3196 if err != nil { 3197 t.Fatal(err) 3198 } 3199 3200 switch i := ric.Addr.String(); i { 3201 case "aws_instance.foo", "module.child.aws_instance.foo": 3202 if res.Action != plans.Delete { 3203 t.Fatalf("resource %s should be removed", i) 3204 } 3205 3206 default: 3207 t.Fatal("unknown instance:", i) 3208 } 3209 } 3210 } 3211 3212 // GH-1835 3213 func TestContext2Plan_moduleDestroyCycle(t *testing.T) { 3214 m := testModule(t, "plan-module-destroy-gh-1835") 3215 p := testProvider("aws") 3216 3217 state := states.NewState() 3218 aModule := state.EnsureModule(addrs.RootModuleInstance.Child("a_module", addrs.NoKey)) 3219 aModule.SetResourceInstanceCurrent( 3220 mustResourceInstanceAddr("aws_instance.a").Resource, 3221 &states.ResourceInstanceObjectSrc{ 3222 Status: states.ObjectReady, 3223 AttrsJSON: []byte(`{"id":"a"}`), 3224 }, 3225 mustProviderConfig(`provider["registry.terraform.io/hashicorp/aws"]`), 3226 ) 3227 bModule := state.EnsureModule(addrs.RootModuleInstance.Child("b_module", addrs.NoKey)) 3228 bModule.SetResourceInstanceCurrent( 3229 mustResourceInstanceAddr("aws_instance.b").Resource, 3230 &states.ResourceInstanceObjectSrc{ 3231 Status: states.ObjectReady, 3232 AttrsJSON: []byte(`{"id":"b"}`), 3233 }, 3234 mustProviderConfig(`provider["registry.terraform.io/hashicorp/aws"]`), 3235 ) 3236 3237 ctx := testContext2(t, &ContextOpts{ 3238 Providers: map[addrs.Provider]providers.Factory{ 3239 addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p), 3240 }, 3241 }) 3242 3243 plan, diags := ctx.Plan(m, state, &PlanOpts{ 3244 Mode: plans.DestroyMode, 3245 }) 3246 if diags.HasErrors() { 3247 t.Fatalf("unexpected errors: %s", diags.Err()) 3248 } 3249 3250 schema := p.GetProviderSchemaResponse.ResourceTypes["aws_instance"].Block 3251 ty := schema.ImpliedType() 3252 3253 if len(plan.Changes.Resources) != 2 { 3254 t.Fatal("expected 2 changes, got", len(plan.Changes.Resources)) 3255 } 3256 3257 for _, res := range plan.Changes.Resources { 3258 ric, err := res.Decode(ty) 3259 if err != nil { 3260 t.Fatal(err) 3261 } 3262 3263 switch i := ric.Addr.String(); i { 3264 case "module.a_module.aws_instance.a", "module.b_module.aws_instance.b": 3265 if res.Action != plans.Delete { 3266 t.Fatalf("resource %s should be removed", i) 3267 } 3268 3269 default: 3270 t.Fatal("unknown instance:", i) 3271 } 3272 } 3273 } 3274 3275 func TestContext2Plan_moduleDestroyMultivar(t *testing.T) { 3276 m := testModule(t, "plan-module-destroy-multivar") 3277 p := testProvider("aws") 3278 3279 state := states.NewState() 3280 child := state.EnsureModule(addrs.RootModuleInstance.Child("child", addrs.NoKey)) 3281 child.SetResourceInstanceCurrent( 3282 mustResourceInstanceAddr("aws_instance.foo[0]").Resource, 3283 &states.ResourceInstanceObjectSrc{ 3284 Status: states.ObjectReady, 3285 AttrsJSON: []byte(`{"id":"bar0"}`), 3286 }, 3287 mustProviderConfig(`provider["registry.terraform.io/hashicorp/aws"]`), 3288 ) 3289 child.SetResourceInstanceCurrent( 3290 mustResourceInstanceAddr("aws_instance.foo[1]").Resource, 3291 &states.ResourceInstanceObjectSrc{ 3292 Status: states.ObjectReady, 3293 AttrsJSON: []byte(`{"id":"bar1"}`), 3294 }, 3295 mustProviderConfig(`provider["registry.terraform.io/hashicorp/aws"]`), 3296 ) 3297 3298 ctx := testContext2(t, &ContextOpts{ 3299 Providers: map[addrs.Provider]providers.Factory{ 3300 addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p), 3301 }, 3302 }) 3303 3304 plan, diags := ctx.Plan(m, state, &PlanOpts{ 3305 Mode: plans.DestroyMode, 3306 }) 3307 if diags.HasErrors() { 3308 t.Fatalf("unexpected errors: %s", diags.Err()) 3309 } 3310 3311 schema := p.GetProviderSchemaResponse.ResourceTypes["aws_instance"].Block 3312 ty := schema.ImpliedType() 3313 3314 if len(plan.Changes.Resources) != 2 { 3315 t.Fatal("expected 2 changes, got", len(plan.Changes.Resources)) 3316 } 3317 3318 for _, res := range plan.Changes.Resources { 3319 ric, err := res.Decode(ty) 3320 if err != nil { 3321 t.Fatal(err) 3322 } 3323 3324 switch i := ric.Addr.String(); i { 3325 case "module.child.aws_instance.foo[0]", "module.child.aws_instance.foo[1]": 3326 if res.Action != plans.Delete { 3327 t.Fatalf("resource %s should be removed", i) 3328 } 3329 3330 default: 3331 t.Fatal("unknown instance:", i) 3332 } 3333 } 3334 } 3335 3336 func TestContext2Plan_pathVar(t *testing.T) { 3337 cwd, err := os.Getwd() 3338 if err != nil { 3339 t.Fatalf("err: %s", err) 3340 } 3341 3342 m := testModule(t, "plan-path-var") 3343 p := testProvider("aws") 3344 p.GetProviderSchemaResponse = getProviderSchemaResponseFromProviderSchema(&ProviderSchema{ 3345 ResourceTypes: map[string]*configschema.Block{ 3346 "aws_instance": { 3347 Attributes: map[string]*configschema.Attribute{ 3348 "cwd": {Type: cty.String, Optional: true}, 3349 "module": {Type: cty.String, Optional: true}, 3350 "root": {Type: cty.String, Optional: true}, 3351 }, 3352 }, 3353 }, 3354 }) 3355 3356 ctx := testContext2(t, &ContextOpts{ 3357 Providers: map[addrs.Provider]providers.Factory{ 3358 addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p), 3359 }, 3360 }) 3361 3362 plan, diags := ctx.Plan(m, states.NewState(), DefaultPlanOpts) 3363 if diags.HasErrors() { 3364 t.Fatalf("err: %s", diags.Err()) 3365 } 3366 3367 schema := p.GetProviderSchemaResponse.ResourceTypes["aws_instance"].Block 3368 ty := schema.ImpliedType() 3369 3370 if len(plan.Changes.Resources) != 1 { 3371 t.Fatal("expected 1 changes, got", len(plan.Changes.Resources)) 3372 } 3373 3374 for _, res := range plan.Changes.Resources { 3375 ric, err := res.Decode(ty) 3376 if err != nil { 3377 t.Fatal(err) 3378 } 3379 3380 switch i := ric.Addr.String(); i { 3381 case "aws_instance.foo": 3382 if res.Action != plans.Create { 3383 t.Fatalf("resource %s should be created", i) 3384 } 3385 checkVals(t, objectVal(t, schema, map[string]cty.Value{ 3386 "cwd": cty.StringVal(cwd + "/barpath"), 3387 "module": cty.StringVal(m.Module.SourceDir + "/foopath"), 3388 "root": cty.StringVal(m.Module.SourceDir + "/barpath"), 3389 }), ric.After) 3390 default: 3391 t.Fatal("unknown instance:", i) 3392 } 3393 } 3394 } 3395 3396 func TestContext2Plan_diffVar(t *testing.T) { 3397 m := testModule(t, "plan-diffvar") 3398 p := testProvider("aws") 3399 p.PlanResourceChangeFn = testDiffFn 3400 state := states.NewState() 3401 root := state.EnsureModule(addrs.RootModuleInstance) 3402 root.SetResourceInstanceCurrent( 3403 mustResourceInstanceAddr("aws_instance.foo").Resource, 3404 &states.ResourceInstanceObjectSrc{ 3405 Status: states.ObjectReady, 3406 AttrsJSON: []byte(`{"id":"bar","num":"2","type":"aws_instance"}`), 3407 }, 3408 mustProviderConfig(`provider["registry.terraform.io/hashicorp/aws"]`), 3409 ) 3410 3411 ctx := testContext2(t, &ContextOpts{ 3412 Providers: map[addrs.Provider]providers.Factory{ 3413 addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p), 3414 }, 3415 }) 3416 3417 plan, diags := ctx.Plan(m, state, DefaultPlanOpts) 3418 if diags.HasErrors() { 3419 t.Fatalf("unexpected errors: %s", diags.Err()) 3420 } 3421 3422 schema := p.GetProviderSchemaResponse.ResourceTypes["aws_instance"].Block 3423 ty := schema.ImpliedType() 3424 3425 if len(plan.Changes.Resources) != 2 { 3426 t.Fatal("expected 2 changes, got", len(plan.Changes.Resources)) 3427 } 3428 3429 for _, res := range plan.Changes.Resources { 3430 ric, err := res.Decode(ty) 3431 if err != nil { 3432 t.Fatal(err) 3433 } 3434 3435 switch i := ric.Addr.String(); i { 3436 case "aws_instance.bar": 3437 if res.Action != plans.Create { 3438 t.Fatalf("resource %s should be created", i) 3439 } 3440 checkVals(t, objectVal(t, schema, map[string]cty.Value{ 3441 "id": cty.UnknownVal(cty.String), 3442 "num": cty.NumberIntVal(3), 3443 "type": cty.UnknownVal(cty.String), 3444 }), ric.After) 3445 case "aws_instance.foo": 3446 if res.Action != plans.Update { 3447 t.Fatalf("resource %s should be updated", i) 3448 } 3449 checkVals(t, objectVal(t, schema, map[string]cty.Value{ 3450 "id": cty.StringVal("bar"), 3451 "num": cty.NumberIntVal(2), 3452 "type": cty.StringVal("aws_instance"), 3453 }), ric.Before) 3454 checkVals(t, objectVal(t, schema, map[string]cty.Value{ 3455 "id": cty.StringVal("bar"), 3456 "num": cty.NumberIntVal(3), 3457 "type": cty.StringVal("aws_instance"), 3458 }), ric.After) 3459 default: 3460 t.Fatal("unknown instance:", i) 3461 } 3462 } 3463 } 3464 3465 func TestContext2Plan_hook(t *testing.T) { 3466 m := testModule(t, "plan-good") 3467 h := new(MockHook) 3468 p := testProvider("aws") 3469 ctx := testContext2(t, &ContextOpts{ 3470 Hooks: []Hook{h}, 3471 Providers: map[addrs.Provider]providers.Factory{ 3472 addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p), 3473 }, 3474 }) 3475 3476 _, diags := ctx.Plan(m, states.NewState(), DefaultPlanOpts) 3477 if diags.HasErrors() { 3478 t.Fatalf("unexpected errors: %s", diags.Err()) 3479 } 3480 3481 if !h.PreDiffCalled { 3482 t.Fatal("should be called") 3483 } 3484 if !h.PostDiffCalled { 3485 t.Fatal("should be called") 3486 } 3487 } 3488 3489 func TestContext2Plan_closeProvider(t *testing.T) { 3490 // this fixture only has an aliased provider located in the module, to make 3491 // sure that the provier name contains a path more complex than 3492 // "provider.aws". 3493 m := testModule(t, "plan-close-module-provider") 3494 p := testProvider("aws") 3495 ctx := testContext2(t, &ContextOpts{ 3496 Providers: map[addrs.Provider]providers.Factory{ 3497 addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p), 3498 }, 3499 }) 3500 3501 _, diags := ctx.Plan(m, states.NewState(), DefaultPlanOpts) 3502 if diags.HasErrors() { 3503 t.Fatalf("unexpected errors: %s", diags.Err()) 3504 } 3505 3506 if !p.CloseCalled { 3507 t.Fatal("provider not closed") 3508 } 3509 } 3510 3511 func TestContext2Plan_orphan(t *testing.T) { 3512 m := testModule(t, "plan-orphan") 3513 p := testProvider("aws") 3514 p.PlanResourceChangeFn = testDiffFn 3515 state := states.NewState() 3516 root := state.EnsureModule(addrs.RootModuleInstance) 3517 root.SetResourceInstanceCurrent( 3518 mustResourceInstanceAddr("aws_instance.baz").Resource, 3519 &states.ResourceInstanceObjectSrc{ 3520 Status: states.ObjectReady, 3521 AttrsJSON: []byte(`{"id":"bar"}`), 3522 }, 3523 mustProviderConfig(`provider["registry.terraform.io/hashicorp/aws"]`), 3524 ) 3525 3526 ctx := testContext2(t, &ContextOpts{ 3527 Providers: map[addrs.Provider]providers.Factory{ 3528 addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p), 3529 }, 3530 }) 3531 3532 plan, diags := ctx.Plan(m, state, DefaultPlanOpts) 3533 if diags.HasErrors() { 3534 t.Fatalf("unexpected errors: %s", diags.Err()) 3535 } 3536 3537 schema := p.GetProviderSchemaResponse.ResourceTypes["aws_instance"].Block 3538 ty := schema.ImpliedType() 3539 3540 if len(plan.Changes.Resources) != 2 { 3541 t.Fatal("expected 2 changes, got", len(plan.Changes.Resources)) 3542 } 3543 3544 for _, res := range plan.Changes.Resources { 3545 ric, err := res.Decode(ty) 3546 if err != nil { 3547 t.Fatal(err) 3548 } 3549 3550 switch i := ric.Addr.String(); i { 3551 case "aws_instance.baz": 3552 if res.Action != plans.Delete { 3553 t.Fatalf("resource %s should be removed", i) 3554 } 3555 if got, want := ric.ActionReason, plans.ResourceInstanceDeleteBecauseNoResourceConfig; got != want { 3556 t.Errorf("wrong action reason\ngot: %s\nwant: %s", got, want) 3557 } 3558 case "aws_instance.foo": 3559 if res.Action != plans.Create { 3560 t.Fatalf("resource %s should be created", i) 3561 } 3562 if got, want := ric.ActionReason, plans.ResourceInstanceChangeNoReason; got != want { 3563 t.Errorf("wrong action reason\ngot: %s\nwant: %s", got, want) 3564 } 3565 checkVals(t, objectVal(t, schema, map[string]cty.Value{ 3566 "id": cty.UnknownVal(cty.String), 3567 "num": cty.NumberIntVal(2), 3568 "type": cty.UnknownVal(cty.String), 3569 }), ric.After) 3570 default: 3571 t.Fatal("unknown instance:", i) 3572 } 3573 } 3574 } 3575 3576 // This tests that configurations with UUIDs don't produce errors. 3577 // For shadows, this would produce errors since a UUID changes every time. 3578 func TestContext2Plan_shadowUuid(t *testing.T) { 3579 m := testModule(t, "plan-shadow-uuid") 3580 p := testProvider("aws") 3581 ctx := testContext2(t, &ContextOpts{ 3582 Providers: map[addrs.Provider]providers.Factory{ 3583 addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p), 3584 }, 3585 }) 3586 3587 _, diags := ctx.Plan(m, states.NewState(), DefaultPlanOpts) 3588 if diags.HasErrors() { 3589 t.Fatalf("unexpected errors: %s", diags.Err()) 3590 } 3591 } 3592 3593 func TestContext2Plan_state(t *testing.T) { 3594 m := testModule(t, "plan-good") 3595 p := testProvider("aws") 3596 p.PlanResourceChangeFn = testDiffFn 3597 state := states.NewState() 3598 root := state.EnsureModule(addrs.RootModuleInstance) 3599 root.SetResourceInstanceCurrent( 3600 mustResourceInstanceAddr("aws_instance.foo").Resource, 3601 &states.ResourceInstanceObjectSrc{ 3602 Status: states.ObjectReady, 3603 AttrsJSON: []byte(`{"id":"bar"}`), 3604 }, 3605 mustProviderConfig(`provider["registry.terraform.io/hashicorp/aws"]`), 3606 ) 3607 ctx := testContext2(t, &ContextOpts{ 3608 Providers: map[addrs.Provider]providers.Factory{ 3609 addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p), 3610 }, 3611 }) 3612 3613 plan, diags := ctx.Plan(m, state, DefaultPlanOpts) 3614 if diags.HasErrors() { 3615 t.Fatalf("unexpected errors: %s", diags.Err()) 3616 } 3617 3618 if len(plan.Changes.Resources) < 2 { 3619 t.Fatalf("bad: %#v", plan.Changes.Resources) 3620 } 3621 schema := p.GetProviderSchemaResponse.ResourceTypes["aws_instance"].Block 3622 ty := schema.ImpliedType() 3623 3624 if len(plan.Changes.Resources) != 2 { 3625 t.Fatal("expected 2 changes, got", len(plan.Changes.Resources)) 3626 } 3627 3628 for _, res := range plan.Changes.Resources { 3629 ric, err := res.Decode(ty) 3630 if err != nil { 3631 t.Fatal(err) 3632 } 3633 3634 switch i := ric.Addr.String(); i { 3635 case "aws_instance.bar": 3636 if res.Action != plans.Create { 3637 t.Fatalf("resource %s should be created", i) 3638 } 3639 if got, want := ric.ActionReason, plans.ResourceInstanceChangeNoReason; got != want { 3640 t.Errorf("wrong action reason\ngot: %s\nwant: %s", got, want) 3641 } 3642 checkVals(t, objectVal(t, schema, map[string]cty.Value{ 3643 "id": cty.UnknownVal(cty.String), 3644 "foo": cty.StringVal("2"), 3645 "type": cty.UnknownVal(cty.String), 3646 }), ric.After) 3647 case "aws_instance.foo": 3648 if res.Action != plans.Update { 3649 t.Fatalf("resource %s should be updated", i) 3650 } 3651 if got, want := ric.ActionReason, plans.ResourceInstanceChangeNoReason; got != want { 3652 t.Errorf("wrong action reason\ngot: %s\nwant: %s", got, want) 3653 } 3654 checkVals(t, objectVal(t, schema, map[string]cty.Value{ 3655 "id": cty.StringVal("bar"), 3656 "num": cty.NullVal(cty.Number), 3657 "type": cty.NullVal(cty.String), 3658 }), ric.Before) 3659 checkVals(t, objectVal(t, schema, map[string]cty.Value{ 3660 "id": cty.StringVal("bar"), 3661 "num": cty.NumberIntVal(2), 3662 "type": cty.UnknownVal(cty.String), 3663 }), ric.After) 3664 default: 3665 t.Fatal("unknown instance:", i) 3666 } 3667 } 3668 } 3669 3670 func TestContext2Plan_requiresReplace(t *testing.T) { 3671 m := testModule(t, "plan-requires-replace") 3672 p := testProvider("test") 3673 p.GetProviderSchemaResponse = &providers.GetProviderSchemaResponse{ 3674 Provider: providers.Schema{ 3675 Block: &configschema.Block{}, 3676 }, 3677 ResourceTypes: map[string]providers.Schema{ 3678 "test_thing": { 3679 Block: &configschema.Block{ 3680 Attributes: map[string]*configschema.Attribute{ 3681 "v": { 3682 Type: cty.String, 3683 Required: true, 3684 }, 3685 }, 3686 }, 3687 }, 3688 }, 3689 } 3690 p.PlanResourceChangeFn = func(req providers.PlanResourceChangeRequest) providers.PlanResourceChangeResponse { 3691 return providers.PlanResourceChangeResponse{ 3692 PlannedState: req.ProposedNewState, 3693 RequiresReplace: []cty.Path{ 3694 cty.GetAttrPath("v"), 3695 }, 3696 } 3697 } 3698 3699 state := states.NewState() 3700 root := state.EnsureModule(addrs.RootModuleInstance) 3701 root.SetResourceInstanceCurrent( 3702 mustResourceInstanceAddr("test_thing.foo").Resource, 3703 &states.ResourceInstanceObjectSrc{ 3704 Status: states.ObjectReady, 3705 AttrsJSON: []byte(`{"v":"hello"}`), 3706 }, 3707 mustProviderConfig(`provider["registry.terraform.io/hashicorp/test"]`), 3708 ) 3709 3710 ctx := testContext2(t, &ContextOpts{ 3711 Providers: map[addrs.Provider]providers.Factory{ 3712 addrs.NewDefaultProvider("test"): testProviderFuncFixed(p), 3713 }, 3714 }) 3715 3716 plan, diags := ctx.Plan(m, state, DefaultPlanOpts) 3717 if diags.HasErrors() { 3718 t.Fatalf("unexpected errors: %s", diags.Err()) 3719 } 3720 3721 schema := p.GetProviderSchemaResponse.ResourceTypes["test_thing"].Block 3722 ty := schema.ImpliedType() 3723 3724 if got, want := len(plan.Changes.Resources), 1; got != want { 3725 t.Fatalf("got %d changes; want %d", got, want) 3726 } 3727 3728 for _, res := range plan.Changes.Resources { 3729 t.Run(res.Addr.String(), func(t *testing.T) { 3730 ric, err := res.Decode(ty) 3731 if err != nil { 3732 t.Fatal(err) 3733 } 3734 3735 switch i := ric.Addr.String(); i { 3736 case "test_thing.foo": 3737 if got, want := ric.Action, plans.DeleteThenCreate; got != want { 3738 t.Errorf("wrong action\ngot: %s\nwant: %s", got, want) 3739 } 3740 if got, want := ric.ActionReason, plans.ResourceInstanceReplaceBecauseCannotUpdate; got != want { 3741 t.Errorf("wrong action reason\ngot: %s\nwant: %s", got, want) 3742 } 3743 checkVals(t, objectVal(t, schema, map[string]cty.Value{ 3744 "v": cty.StringVal("goodbye"), 3745 }), ric.After) 3746 default: 3747 t.Fatalf("unexpected resource instance %s", i) 3748 } 3749 }) 3750 } 3751 } 3752 3753 func TestContext2Plan_taint(t *testing.T) { 3754 m := testModule(t, "plan-taint") 3755 p := testProvider("aws") 3756 p.PlanResourceChangeFn = testDiffFn 3757 state := states.NewState() 3758 root := state.EnsureModule(addrs.RootModuleInstance) 3759 root.SetResourceInstanceCurrent( 3760 mustResourceInstanceAddr("aws_instance.foo").Resource, 3761 &states.ResourceInstanceObjectSrc{ 3762 Status: states.ObjectReady, 3763 AttrsJSON: []byte(`{"id":"bar","num":"2","type":"aws_instance"}`), 3764 }, 3765 mustProviderConfig(`provider["registry.terraform.io/hashicorp/aws"]`), 3766 ) 3767 root.SetResourceInstanceCurrent( 3768 mustResourceInstanceAddr("aws_instance.bar").Resource, 3769 &states.ResourceInstanceObjectSrc{ 3770 Status: states.ObjectTainted, 3771 AttrsJSON: []byte(`{"id":"baz"}`), 3772 }, 3773 mustProviderConfig(`provider["registry.terraform.io/hashicorp/aws"]`), 3774 ) 3775 3776 ctx := testContext2(t, &ContextOpts{ 3777 Providers: map[addrs.Provider]providers.Factory{ 3778 addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p), 3779 }, 3780 }) 3781 3782 plan, diags := ctx.Plan(m, state, DefaultPlanOpts) 3783 if diags.HasErrors() { 3784 t.Fatalf("unexpected errors: %s", diags.Err()) 3785 } 3786 3787 schema := p.GetProviderSchemaResponse.ResourceTypes["aws_instance"].Block 3788 ty := schema.ImpliedType() 3789 3790 if len(plan.Changes.Resources) != 2 { 3791 t.Fatal("expected 2 changes, got", len(plan.Changes.Resources)) 3792 } 3793 3794 for _, res := range plan.Changes.Resources { 3795 t.Run(res.Addr.String(), func(t *testing.T) { 3796 ric, err := res.Decode(ty) 3797 if err != nil { 3798 t.Fatal(err) 3799 } 3800 3801 switch i := ric.Addr.String(); i { 3802 case "aws_instance.bar": 3803 if got, want := res.Action, plans.DeleteThenCreate; got != want { 3804 t.Errorf("wrong action\ngot: %s\nwant: %s", got, want) 3805 } 3806 if got, want := res.ActionReason, plans.ResourceInstanceReplaceBecauseTainted; got != want { 3807 t.Errorf("wrong action reason\ngot: %s\nwant: %s", got, want) 3808 } 3809 checkVals(t, objectVal(t, schema, map[string]cty.Value{ 3810 "id": cty.UnknownVal(cty.String), 3811 "foo": cty.StringVal("2"), 3812 "type": cty.UnknownVal(cty.String), 3813 }), ric.After) 3814 case "aws_instance.foo": 3815 if got, want := res.Action, plans.NoOp; got != want { 3816 t.Errorf("wrong action\ngot: %s\nwant: %s", got, want) 3817 } 3818 if got, want := res.ActionReason, plans.ResourceInstanceChangeNoReason; got != want { 3819 t.Errorf("wrong action reason\ngot: %s\nwant: %s", got, want) 3820 } 3821 default: 3822 t.Fatal("unknown instance:", i) 3823 } 3824 }) 3825 } 3826 } 3827 3828 func TestContext2Plan_taintIgnoreChanges(t *testing.T) { 3829 m := testModule(t, "plan-taint-ignore-changes") 3830 p := testProvider("aws") 3831 p.GetProviderSchemaResponse = getProviderSchemaResponseFromProviderSchema(&ProviderSchema{ 3832 ResourceTypes: map[string]*configschema.Block{ 3833 "aws_instance": { 3834 Attributes: map[string]*configschema.Attribute{ 3835 "id": {Type: cty.String, Computed: true}, 3836 "vars": {Type: cty.String, Optional: true}, 3837 "type": {Type: cty.String, Computed: true}, 3838 }, 3839 }, 3840 }, 3841 }) 3842 3843 state := states.NewState() 3844 root := state.EnsureModule(addrs.RootModuleInstance) 3845 root.SetResourceInstanceCurrent( 3846 mustResourceInstanceAddr("aws_instance.foo").Resource, 3847 &states.ResourceInstanceObjectSrc{ 3848 Status: states.ObjectTainted, 3849 AttrsJSON: []byte(`{"id":"foo","vars":"foo","type":"aws_instance"}`), 3850 }, 3851 mustProviderConfig(`provider["registry.terraform.io/hashicorp/aws"]`), 3852 ) 3853 3854 ctx := testContext2(t, &ContextOpts{ 3855 Providers: map[addrs.Provider]providers.Factory{ 3856 addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p), 3857 }, 3858 }) 3859 3860 plan, diags := ctx.Plan(m, state, DefaultPlanOpts) 3861 if diags.HasErrors() { 3862 t.Fatalf("unexpected errors: %s", diags.Err()) 3863 } 3864 3865 schema := p.GetProviderSchemaResponse.ResourceTypes["aws_instance"].Block 3866 ty := schema.ImpliedType() 3867 3868 if len(plan.Changes.Resources) != 1 { 3869 t.Fatal("expected 1 changes, got", len(plan.Changes.Resources)) 3870 } 3871 3872 for _, res := range plan.Changes.Resources { 3873 ric, err := res.Decode(ty) 3874 if err != nil { 3875 t.Fatal(err) 3876 } 3877 3878 switch i := ric.Addr.String(); i { 3879 case "aws_instance.foo": 3880 if got, want := res.Action, plans.DeleteThenCreate; got != want { 3881 t.Errorf("wrong action\ngot: %s\nwant: %s", got, want) 3882 } 3883 if got, want := res.ActionReason, plans.ResourceInstanceReplaceBecauseTainted; got != want { 3884 t.Errorf("wrong action reason\ngot: %s\nwant: %s", got, want) 3885 } 3886 checkVals(t, objectVal(t, schema, map[string]cty.Value{ 3887 "id": cty.StringVal("foo"), 3888 "vars": cty.StringVal("foo"), 3889 "type": cty.StringVal("aws_instance"), 3890 }), ric.Before) 3891 checkVals(t, objectVal(t, schema, map[string]cty.Value{ 3892 "id": cty.UnknownVal(cty.String), 3893 "vars": cty.StringVal("foo"), 3894 "type": cty.UnknownVal(cty.String), 3895 }), ric.After) 3896 default: 3897 t.Fatal("unknown instance:", i) 3898 } 3899 } 3900 } 3901 3902 // Fails about 50% of the time before the fix for GH-4982, covers the fix. 3903 func TestContext2Plan_taintDestroyInterpolatedCountRace(t *testing.T) { 3904 m := testModule(t, "plan-taint-interpolated-count") 3905 p := testProvider("aws") 3906 p.PlanResourceChangeFn = testDiffFn 3907 state := states.NewState() 3908 root := state.EnsureModule(addrs.RootModuleInstance) 3909 root.SetResourceInstanceCurrent( 3910 mustResourceInstanceAddr("aws_instance.foo[0]").Resource, 3911 &states.ResourceInstanceObjectSrc{ 3912 Status: states.ObjectTainted, 3913 AttrsJSON: []byte(`{"id":"bar","type":"aws_instance"}`), 3914 }, 3915 mustProviderConfig(`provider["registry.terraform.io/hashicorp/aws"]`), 3916 ) 3917 root.SetResourceInstanceCurrent( 3918 mustResourceInstanceAddr("aws_instance.foo[1]").Resource, 3919 &states.ResourceInstanceObjectSrc{ 3920 Status: states.ObjectReady, 3921 AttrsJSON: []byte(`{"id":"bar","type":"aws_instance"}`), 3922 }, 3923 mustProviderConfig(`provider["registry.terraform.io/hashicorp/aws"]`), 3924 ) 3925 root.SetResourceInstanceCurrent( 3926 mustResourceInstanceAddr("aws_instance.foo[2]").Resource, 3927 &states.ResourceInstanceObjectSrc{ 3928 Status: states.ObjectReady, 3929 AttrsJSON: []byte(`{"id":"bar","type":"aws_instance"}`), 3930 }, 3931 mustProviderConfig(`provider["registry.terraform.io/hashicorp/aws"]`), 3932 ) 3933 3934 for i := 0; i < 100; i++ { 3935 ctx := testContext2(t, &ContextOpts{ 3936 Providers: map[addrs.Provider]providers.Factory{ 3937 addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p), 3938 }, 3939 }) 3940 3941 plan, diags := ctx.Plan(m, state.DeepCopy(), DefaultPlanOpts) 3942 if diags.HasErrors() { 3943 t.Fatalf("unexpected errors: %s", diags.Err()) 3944 } 3945 3946 schema := p.GetProviderSchemaResponse.ResourceTypes["aws_instance"].Block 3947 ty := schema.ImpliedType() 3948 3949 if len(plan.Changes.Resources) != 3 { 3950 t.Fatal("expected 3 changes, got", len(plan.Changes.Resources)) 3951 } 3952 3953 for _, res := range plan.Changes.Resources { 3954 ric, err := res.Decode(ty) 3955 if err != nil { 3956 t.Fatal(err) 3957 } 3958 3959 switch i := ric.Addr.String(); i { 3960 case "aws_instance.foo[0]": 3961 if got, want := ric.Action, plans.DeleteThenCreate; got != want { 3962 t.Errorf("wrong action\ngot: %s\nwant: %s", got, want) 3963 } 3964 if got, want := ric.ActionReason, plans.ResourceInstanceReplaceBecauseTainted; got != want { 3965 t.Errorf("wrong action reason\ngot: %s\nwant: %s", got, want) 3966 } 3967 checkVals(t, objectVal(t, schema, map[string]cty.Value{ 3968 "id": cty.StringVal("bar"), 3969 "type": cty.StringVal("aws_instance"), 3970 }), ric.Before) 3971 checkVals(t, objectVal(t, schema, map[string]cty.Value{ 3972 "id": cty.UnknownVal(cty.String), 3973 "type": cty.UnknownVal(cty.String), 3974 }), ric.After) 3975 case "aws_instance.foo[1]", "aws_instance.foo[2]": 3976 if res.Action != plans.NoOp { 3977 t.Fatalf("resource %s should not be changed", i) 3978 } 3979 default: 3980 t.Fatal("unknown instance:", i) 3981 } 3982 } 3983 } 3984 } 3985 3986 func TestContext2Plan_targeted(t *testing.T) { 3987 m := testModule(t, "plan-targeted") 3988 p := testProvider("aws") 3989 p.PlanResourceChangeFn = testDiffFn 3990 ctx := testContext2(t, &ContextOpts{ 3991 Providers: map[addrs.Provider]providers.Factory{ 3992 addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p), 3993 }, 3994 }) 3995 3996 plan, diags := ctx.Plan(m, states.NewState(), &PlanOpts{ 3997 Mode: plans.NormalMode, 3998 Targets: []addrs.Targetable{ 3999 addrs.RootModuleInstance.Resource( 4000 addrs.ManagedResourceMode, "aws_instance", "foo", 4001 ), 4002 }, 4003 }) 4004 if diags.HasErrors() { 4005 t.Fatalf("unexpected errors: %s", diags.Err()) 4006 } 4007 schema := p.GetProviderSchemaResponse.ResourceTypes["aws_instance"].Block 4008 ty := schema.ImpliedType() 4009 4010 if len(plan.Changes.Resources) != 1 { 4011 t.Fatal("expected 1 changes, got", len(plan.Changes.Resources)) 4012 } 4013 4014 for _, res := range plan.Changes.Resources { 4015 ric, err := res.Decode(ty) 4016 if err != nil { 4017 t.Fatal(err) 4018 } 4019 4020 switch i := ric.Addr.String(); i { 4021 case "aws_instance.foo": 4022 if res.Action != plans.Create { 4023 t.Fatalf("resource %s should be created", i) 4024 } 4025 checkVals(t, objectVal(t, schema, map[string]cty.Value{ 4026 "id": cty.UnknownVal(cty.String), 4027 "num": cty.NumberIntVal(2), 4028 "type": cty.UnknownVal(cty.String), 4029 }), ric.After) 4030 default: 4031 t.Fatal("unknown instance:", i) 4032 } 4033 } 4034 } 4035 4036 // Test that targeting a module properly plans any inputs that depend 4037 // on another module. 4038 func TestContext2Plan_targetedCrossModule(t *testing.T) { 4039 m := testModule(t, "plan-targeted-cross-module") 4040 p := testProvider("aws") 4041 p.PlanResourceChangeFn = testDiffFn 4042 ctx := testContext2(t, &ContextOpts{ 4043 Providers: map[addrs.Provider]providers.Factory{ 4044 addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p), 4045 }, 4046 }) 4047 4048 plan, diags := ctx.Plan(m, states.NewState(), &PlanOpts{ 4049 Mode: plans.NormalMode, 4050 Targets: []addrs.Targetable{ 4051 addrs.RootModuleInstance.Child("B", addrs.NoKey), 4052 }, 4053 }) 4054 if diags.HasErrors() { 4055 t.Fatalf("unexpected errors: %s", diags.Err()) 4056 } 4057 schema := p.GetProviderSchemaResponse.ResourceTypes["aws_instance"].Block 4058 ty := schema.ImpliedType() 4059 4060 if len(plan.Changes.Resources) != 2 { 4061 t.Fatal("expected 2 changes, got", len(plan.Changes.Resources)) 4062 } 4063 4064 for _, res := range plan.Changes.Resources { 4065 ric, err := res.Decode(ty) 4066 if err != nil { 4067 t.Fatal(err) 4068 } 4069 if res.Action != plans.Create { 4070 t.Fatalf("resource %s should be created", ric.Addr) 4071 } 4072 switch i := ric.Addr.String(); i { 4073 case "module.A.aws_instance.foo": 4074 checkVals(t, objectVal(t, schema, map[string]cty.Value{ 4075 "id": cty.UnknownVal(cty.String), 4076 "foo": cty.StringVal("bar"), 4077 "type": cty.UnknownVal(cty.String), 4078 }), ric.After) 4079 case "module.B.aws_instance.bar": 4080 checkVals(t, objectVal(t, schema, map[string]cty.Value{ 4081 "id": cty.UnknownVal(cty.String), 4082 "foo": cty.UnknownVal(cty.String), 4083 "type": cty.UnknownVal(cty.String), 4084 }), ric.After) 4085 default: 4086 t.Fatal("unknown instance:", i) 4087 } 4088 } 4089 } 4090 4091 func TestContext2Plan_targetedModuleWithProvider(t *testing.T) { 4092 m := testModule(t, "plan-targeted-module-with-provider") 4093 p := testProvider("null") 4094 p.GetProviderSchemaResponse = getProviderSchemaResponseFromProviderSchema(&ProviderSchema{ 4095 Provider: &configschema.Block{ 4096 Attributes: map[string]*configschema.Attribute{ 4097 "key": {Type: cty.String, Optional: true}, 4098 }, 4099 }, 4100 ResourceTypes: map[string]*configschema.Block{ 4101 "null_resource": { 4102 Attributes: map[string]*configschema.Attribute{}, 4103 }, 4104 }, 4105 }) 4106 4107 ctx := testContext2(t, &ContextOpts{ 4108 Providers: map[addrs.Provider]providers.Factory{ 4109 addrs.NewDefaultProvider("null"): testProviderFuncFixed(p), 4110 }, 4111 }) 4112 4113 plan, diags := ctx.Plan(m, states.NewState(), &PlanOpts{ 4114 Mode: plans.NormalMode, 4115 Targets: []addrs.Targetable{ 4116 addrs.RootModuleInstance.Child("child2", addrs.NoKey), 4117 }, 4118 }) 4119 if diags.HasErrors() { 4120 t.Fatalf("unexpected errors: %s", diags.Err()) 4121 } 4122 4123 schema := p.GetProviderSchemaResponse.ResourceTypes["null_resource"].Block 4124 ty := schema.ImpliedType() 4125 4126 if len(plan.Changes.Resources) != 1 { 4127 t.Fatal("expected 1 changes, got", len(plan.Changes.Resources)) 4128 } 4129 4130 res := plan.Changes.Resources[0] 4131 ric, err := res.Decode(ty) 4132 if err != nil { 4133 t.Fatal(err) 4134 } 4135 4136 if ric.Addr.String() != "module.child2.null_resource.foo" { 4137 t.Fatalf("unexpcetd resource: %s", ric.Addr) 4138 } 4139 } 4140 4141 func TestContext2Plan_targetedOrphan(t *testing.T) { 4142 m := testModule(t, "plan-targeted-orphan") 4143 p := testProvider("aws") 4144 4145 state := states.NewState() 4146 root := state.EnsureModule(addrs.RootModuleInstance) 4147 root.SetResourceInstanceCurrent( 4148 mustResourceInstanceAddr("aws_instance.orphan").Resource, 4149 &states.ResourceInstanceObjectSrc{ 4150 Status: states.ObjectReady, 4151 AttrsJSON: []byte(`{"id":"i-789xyz"}`), 4152 }, 4153 mustProviderConfig(`provider["registry.terraform.io/hashicorp/aws"]`), 4154 ) 4155 root.SetResourceInstanceCurrent( 4156 mustResourceInstanceAddr("aws_instance.nottargeted").Resource, 4157 &states.ResourceInstanceObjectSrc{ 4158 Status: states.ObjectReady, 4159 AttrsJSON: []byte(`{"id":"i-abc123"}`), 4160 }, 4161 mustProviderConfig(`provider["registry.terraform.io/hashicorp/aws"]`), 4162 ) 4163 4164 ctx := testContext2(t, &ContextOpts{ 4165 Providers: map[addrs.Provider]providers.Factory{ 4166 addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p), 4167 }, 4168 }) 4169 4170 plan, diags := ctx.Plan(m, state, &PlanOpts{ 4171 Mode: plans.DestroyMode, 4172 Targets: []addrs.Targetable{ 4173 addrs.RootModuleInstance.Resource( 4174 addrs.ManagedResourceMode, "aws_instance", "orphan", 4175 ), 4176 }, 4177 }) 4178 if diags.HasErrors() { 4179 t.Fatalf("unexpected errors: %s", diags.Err()) 4180 } 4181 4182 schema := p.GetProviderSchemaResponse.ResourceTypes["aws_instance"].Block 4183 ty := schema.ImpliedType() 4184 4185 if len(plan.Changes.Resources) != 1 { 4186 t.Fatal("expected 1 changes, got", len(plan.Changes.Resources)) 4187 } 4188 4189 for _, res := range plan.Changes.Resources { 4190 ric, err := res.Decode(ty) 4191 if err != nil { 4192 t.Fatal(err) 4193 } 4194 4195 switch i := ric.Addr.String(); i { 4196 case "aws_instance.orphan": 4197 if res.Action != plans.Delete { 4198 t.Fatalf("resource %s should be destroyed", ric.Addr) 4199 } 4200 default: 4201 t.Fatal("unknown instance:", i) 4202 } 4203 } 4204 } 4205 4206 // https://github.com/cycloidio/terraform/issues/2538 4207 func TestContext2Plan_targetedModuleOrphan(t *testing.T) { 4208 m := testModule(t, "plan-targeted-module-orphan") 4209 p := testProvider("aws") 4210 4211 state := states.NewState() 4212 child := state.EnsureModule(addrs.RootModuleInstance.Child("child", addrs.NoKey)) 4213 child.SetResourceInstanceCurrent( 4214 mustResourceInstanceAddr("aws_instance.orphan").Resource, 4215 &states.ResourceInstanceObjectSrc{ 4216 Status: states.ObjectReady, 4217 AttrsJSON: []byte(`{"id":"i-789xyz"}`), 4218 }, 4219 mustProviderConfig(`provider["registry.terraform.io/hashicorp/aws"]`), 4220 ) 4221 child.SetResourceInstanceCurrent( 4222 mustResourceInstanceAddr("aws_instance.nottargeted").Resource, 4223 &states.ResourceInstanceObjectSrc{ 4224 Status: states.ObjectReady, 4225 AttrsJSON: []byte(`{"id":"i-abc123"}`), 4226 }, 4227 mustProviderConfig(`provider["registry.terraform.io/hashicorp/aws"]`), 4228 ) 4229 4230 ctx := testContext2(t, &ContextOpts{ 4231 Providers: map[addrs.Provider]providers.Factory{ 4232 addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p), 4233 }, 4234 }) 4235 4236 plan, diags := ctx.Plan(m, state, &PlanOpts{ 4237 Mode: plans.DestroyMode, 4238 Targets: []addrs.Targetable{ 4239 addrs.RootModuleInstance.Child("child", addrs.NoKey).Resource( 4240 addrs.ManagedResourceMode, "aws_instance", "orphan", 4241 ), 4242 }, 4243 }) 4244 if diags.HasErrors() { 4245 t.Fatalf("unexpected errors: %s", diags.Err()) 4246 } 4247 4248 schema := p.GetProviderSchemaResponse.ResourceTypes["aws_instance"].Block 4249 ty := schema.ImpliedType() 4250 4251 if len(plan.Changes.Resources) != 1 { 4252 t.Fatal("expected 1 changes, got", len(plan.Changes.Resources)) 4253 } 4254 4255 res := plan.Changes.Resources[0] 4256 ric, err := res.Decode(ty) 4257 if err != nil { 4258 t.Fatal(err) 4259 } 4260 4261 if ric.Addr.String() != "module.child.aws_instance.orphan" { 4262 t.Fatalf("unexpected resource :%s", ric.Addr) 4263 } 4264 if res.Action != plans.Delete { 4265 t.Fatalf("resource %s should be deleted", ric.Addr) 4266 } 4267 } 4268 4269 func TestContext2Plan_targetedModuleUntargetedVariable(t *testing.T) { 4270 m := testModule(t, "plan-targeted-module-untargeted-variable") 4271 p := testProvider("aws") 4272 p.PlanResourceChangeFn = testDiffFn 4273 ctx := testContext2(t, &ContextOpts{ 4274 Providers: map[addrs.Provider]providers.Factory{ 4275 addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p), 4276 }, 4277 }) 4278 4279 plan, diags := ctx.Plan(m, states.NewState(), &PlanOpts{ 4280 Targets: []addrs.Targetable{ 4281 addrs.RootModuleInstance.Resource( 4282 addrs.ManagedResourceMode, "aws_instance", "blue", 4283 ), 4284 addrs.RootModuleInstance.Child("blue_mod", addrs.NoKey), 4285 }, 4286 }) 4287 if diags.HasErrors() { 4288 t.Fatalf("unexpected errors: %s", diags.Err()) 4289 } 4290 4291 schema := p.GetProviderSchemaResponse.ResourceTypes["aws_instance"].Block 4292 ty := schema.ImpliedType() 4293 4294 if len(plan.Changes.Resources) != 2 { 4295 t.Fatal("expected 2 changes, got", len(plan.Changes.Resources)) 4296 } 4297 4298 for _, res := range plan.Changes.Resources { 4299 ric, err := res.Decode(ty) 4300 if err != nil { 4301 t.Fatal(err) 4302 } 4303 if res.Action != plans.Create { 4304 t.Fatalf("resource %s should be created", ric.Addr) 4305 } 4306 switch i := ric.Addr.String(); i { 4307 case "aws_instance.blue": 4308 checkVals(t, objectVal(t, schema, map[string]cty.Value{ 4309 "id": cty.UnknownVal(cty.String), 4310 "type": cty.UnknownVal(cty.String), 4311 }), ric.After) 4312 case "module.blue_mod.aws_instance.mod": 4313 checkVals(t, objectVal(t, schema, map[string]cty.Value{ 4314 "id": cty.UnknownVal(cty.String), 4315 "value": cty.UnknownVal(cty.String), 4316 "type": cty.UnknownVal(cty.String), 4317 }), ric.After) 4318 default: 4319 t.Fatal("unknown instance:", i) 4320 } 4321 } 4322 } 4323 4324 // ensure that outputs missing references due to targetting are removed from 4325 // the graph. 4326 func TestContext2Plan_outputContainsTargetedResource(t *testing.T) { 4327 m := testModule(t, "plan-untargeted-resource-output") 4328 p := testProvider("aws") 4329 ctx := testContext2(t, &ContextOpts{ 4330 Providers: map[addrs.Provider]providers.Factory{ 4331 addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p), 4332 }, 4333 }) 4334 4335 _, diags := ctx.Plan(m, states.NewState(), &PlanOpts{ 4336 Targets: []addrs.Targetable{ 4337 addrs.RootModuleInstance.Child("mod", addrs.NoKey).Resource( 4338 addrs.ManagedResourceMode, "aws_instance", "a", 4339 ), 4340 }, 4341 }) 4342 if diags.HasErrors() { 4343 t.Fatalf("err: %s", diags) 4344 } 4345 if len(diags) != 1 { 4346 t.Fatalf("got %d diagnostics; want 1", diags) 4347 } 4348 if got, want := diags[0].Severity(), tfdiags.Warning; got != want { 4349 t.Errorf("wrong diagnostic severity %#v; want %#v", got, want) 4350 } 4351 if got, want := diags[0].Description().Summary, "Resource targeting is in effect"; got != want { 4352 t.Errorf("wrong diagnostic summary %#v; want %#v", got, want) 4353 } 4354 } 4355 4356 // https://github.com/cycloidio/terraform/issues/4515 4357 func TestContext2Plan_targetedOverTen(t *testing.T) { 4358 m := testModule(t, "plan-targeted-over-ten") 4359 p := testProvider("aws") 4360 p.PlanResourceChangeFn = testDiffFn 4361 4362 state := states.NewState() 4363 root := state.EnsureModule(addrs.RootModuleInstance) 4364 for i := 0; i < 13; i++ { 4365 key := fmt.Sprintf("aws_instance.foo[%d]", i) 4366 id := fmt.Sprintf("i-abc%d", i) 4367 attrs := fmt.Sprintf(`{"id":"%s","type":"aws_instance"}`, id) 4368 4369 root.SetResourceInstanceCurrent( 4370 mustResourceInstanceAddr(key).Resource, 4371 &states.ResourceInstanceObjectSrc{ 4372 Status: states.ObjectReady, 4373 AttrsJSON: []byte(attrs), 4374 }, 4375 mustProviderConfig(`provider["registry.terraform.io/hashicorp/aws"]`), 4376 ) 4377 } 4378 4379 ctx := testContext2(t, &ContextOpts{ 4380 Providers: map[addrs.Provider]providers.Factory{ 4381 addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p), 4382 }, 4383 }) 4384 4385 plan, diags := ctx.Plan(m, state, &PlanOpts{ 4386 Targets: []addrs.Targetable{ 4387 addrs.RootModuleInstance.ResourceInstance( 4388 addrs.ManagedResourceMode, "aws_instance", "foo", addrs.IntKey(1), 4389 ), 4390 }, 4391 }) 4392 if diags.HasErrors() { 4393 t.Fatalf("unexpected errors: %s", diags.Err()) 4394 } 4395 4396 schema := p.GetProviderSchemaResponse.ResourceTypes["aws_instance"].Block 4397 ty := schema.ImpliedType() 4398 4399 for _, res := range plan.Changes.Resources { 4400 ric, err := res.Decode(ty) 4401 if err != nil { 4402 t.Fatal(err) 4403 } 4404 if res.Action != plans.NoOp { 4405 t.Fatalf("unexpected action %s for %s", res.Action, ric.Addr) 4406 } 4407 } 4408 } 4409 4410 func TestContext2Plan_provider(t *testing.T) { 4411 m := testModule(t, "plan-provider") 4412 p := testProvider("aws") 4413 4414 var value interface{} 4415 p.ConfigureProviderFn = func(req providers.ConfigureProviderRequest) (resp providers.ConfigureProviderResponse) { 4416 value = req.Config.GetAttr("foo").AsString() 4417 return 4418 } 4419 4420 ctx := testContext2(t, &ContextOpts{ 4421 Providers: map[addrs.Provider]providers.Factory{ 4422 addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p), 4423 }, 4424 }) 4425 opts := &PlanOpts{ 4426 Mode: plans.NormalMode, 4427 SetVariables: InputValues{ 4428 "foo": &InputValue{ 4429 Value: cty.StringVal("bar"), 4430 SourceType: ValueFromCaller, 4431 }, 4432 }, 4433 } 4434 4435 if _, err := ctx.Plan(m, states.NewState(), opts); err != nil { 4436 t.Fatalf("err: %s", err) 4437 } 4438 4439 if value != "bar" { 4440 t.Fatalf("bad: %#v", value) 4441 } 4442 } 4443 4444 func TestContext2Plan_varListErr(t *testing.T) { 4445 m := testModule(t, "plan-var-list-err") 4446 p := testProvider("aws") 4447 ctx := testContext2(t, &ContextOpts{ 4448 Providers: map[addrs.Provider]providers.Factory{ 4449 addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p), 4450 }, 4451 }) 4452 4453 _, err := ctx.Plan(m, states.NewState(), DefaultPlanOpts) 4454 4455 if err == nil { 4456 t.Fatal("should error") 4457 } 4458 } 4459 4460 func TestContext2Plan_ignoreChanges(t *testing.T) { 4461 m := testModule(t, "plan-ignore-changes") 4462 p := testProvider("aws") 4463 p.PlanResourceChangeFn = testDiffFn 4464 4465 state := states.NewState() 4466 root := state.EnsureModule(addrs.RootModuleInstance) 4467 root.SetResourceInstanceCurrent( 4468 mustResourceInstanceAddr("aws_instance.foo").Resource, 4469 &states.ResourceInstanceObjectSrc{ 4470 Status: states.ObjectReady, 4471 AttrsJSON: []byte(`{"id":"bar","ami":"ami-abcd1234","type":"aws_instance"}`), 4472 }, 4473 mustProviderConfig(`provider["registry.terraform.io/hashicorp/aws"]`), 4474 ) 4475 4476 ctx := testContext2(t, &ContextOpts{ 4477 Providers: map[addrs.Provider]providers.Factory{ 4478 addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p), 4479 }, 4480 }) 4481 4482 plan, diags := ctx.Plan(m, state, &PlanOpts{ 4483 Mode: plans.NormalMode, 4484 SetVariables: InputValues{ 4485 "foo": &InputValue{ 4486 Value: cty.StringVal("ami-1234abcd"), 4487 SourceType: ValueFromCaller, 4488 }, 4489 }, 4490 }) 4491 if diags.HasErrors() { 4492 t.Fatalf("unexpected errors: %s", diags.Err()) 4493 } 4494 4495 schema := p.GetProviderSchemaResponse.ResourceTypes["aws_instance"].Block 4496 ty := schema.ImpliedType() 4497 4498 if len(plan.Changes.Resources) != 1 { 4499 t.Fatal("expected 1 changes, got", len(plan.Changes.Resources)) 4500 } 4501 4502 res := plan.Changes.Resources[0] 4503 ric, err := res.Decode(ty) 4504 if err != nil { 4505 t.Fatal(err) 4506 } 4507 4508 if ric.Addr.String() != "aws_instance.foo" { 4509 t.Fatalf("unexpected resource: %s", ric.Addr) 4510 } 4511 4512 checkVals(t, objectVal(t, schema, map[string]cty.Value{ 4513 "id": cty.StringVal("bar"), 4514 "ami": cty.StringVal("ami-abcd1234"), 4515 "type": cty.StringVal("aws_instance"), 4516 }), ric.After) 4517 } 4518 4519 func TestContext2Plan_ignoreChangesWildcard(t *testing.T) { 4520 m := testModule(t, "plan-ignore-changes-wildcard") 4521 p := testProvider("aws") 4522 p.PlanResourceChangeFn = testDiffFn 4523 4524 state := states.NewState() 4525 root := state.EnsureModule(addrs.RootModuleInstance) 4526 root.SetResourceInstanceCurrent( 4527 mustResourceInstanceAddr("aws_instance.foo").Resource, 4528 &states.ResourceInstanceObjectSrc{ 4529 Status: states.ObjectReady, 4530 AttrsJSON: []byte(`{"id":"bar","ami":"ami-abcd1234","instance":"t2.micro","type":"aws_instance"}`), 4531 }, 4532 mustProviderConfig(`provider["registry.terraform.io/hashicorp/aws"]`), 4533 ) 4534 4535 ctx := testContext2(t, &ContextOpts{ 4536 Providers: map[addrs.Provider]providers.Factory{ 4537 addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p), 4538 }, 4539 }) 4540 4541 plan, diags := ctx.Plan(m, state, &PlanOpts{ 4542 Mode: plans.NormalMode, 4543 SetVariables: InputValues{ 4544 "foo": &InputValue{ 4545 Value: cty.StringVal("ami-1234abcd"), 4546 SourceType: ValueFromCaller, 4547 }, 4548 "bar": &InputValue{ 4549 Value: cty.StringVal("t2.small"), 4550 SourceType: ValueFromCaller, 4551 }, 4552 }, 4553 }) 4554 if diags.HasErrors() { 4555 t.Fatalf("unexpected errors: %s", diags.Err()) 4556 } 4557 4558 for _, res := range plan.Changes.Resources { 4559 if res.Action != plans.NoOp { 4560 t.Fatalf("unexpected resource diffs in root module: %s", spew.Sdump(plan.Changes.Resources)) 4561 } 4562 } 4563 } 4564 4565 func TestContext2Plan_ignoreChangesInMap(t *testing.T) { 4566 p := testProvider("test") 4567 4568 p.GetProviderSchemaResponse = getProviderSchemaResponseFromProviderSchema(&ProviderSchema{ 4569 ResourceTypes: map[string]*configschema.Block{ 4570 "test_ignore_changes_map": { 4571 Attributes: map[string]*configschema.Attribute{ 4572 "tags": {Type: cty.Map(cty.String), Optional: true}, 4573 }, 4574 }, 4575 }, 4576 }) 4577 p.PlanResourceChangeFn = func(req providers.PlanResourceChangeRequest) providers.PlanResourceChangeResponse { 4578 return providers.PlanResourceChangeResponse{ 4579 PlannedState: req.ProposedNewState, 4580 } 4581 } 4582 4583 s := states.BuildState(func(ss *states.SyncState) { 4584 ss.SetResourceInstanceCurrent( 4585 addrs.Resource{ 4586 Mode: addrs.ManagedResourceMode, 4587 Type: "test_ignore_changes_map", 4588 Name: "foo", 4589 }.Instance(addrs.NoKey).Absolute(addrs.RootModuleInstance), 4590 &states.ResourceInstanceObjectSrc{ 4591 Status: states.ObjectReady, 4592 AttrsJSON: []byte(`{"id":"foo","tags":{"ignored":"from state","other":"from state"},"type":"aws_instance"}`), 4593 }, 4594 addrs.AbsProviderConfig{ 4595 Provider: addrs.NewDefaultProvider("test"), 4596 Module: addrs.RootModule, 4597 }, 4598 ) 4599 }) 4600 m := testModule(t, "plan-ignore-changes-in-map") 4601 4602 ctx := testContext2(t, &ContextOpts{ 4603 Providers: map[addrs.Provider]providers.Factory{ 4604 addrs.NewDefaultProvider("test"): testProviderFuncFixed(p), 4605 }, 4606 }) 4607 4608 plan, diags := ctx.Plan(m, s, DefaultPlanOpts) 4609 if diags.HasErrors() { 4610 t.Fatalf("unexpected errors: %s", diags.Err()) 4611 } 4612 4613 schema := p.GetProviderSchemaResponse.ResourceTypes["test_ignore_changes_map"].Block 4614 ty := schema.ImpliedType() 4615 4616 if got, want := len(plan.Changes.Resources), 1; got != want { 4617 t.Fatalf("wrong number of changes %d; want %d", got, want) 4618 } 4619 4620 res := plan.Changes.Resources[0] 4621 ric, err := res.Decode(ty) 4622 if err != nil { 4623 t.Fatal(err) 4624 } 4625 if res.Action != plans.Update { 4626 t.Fatalf("resource %s should be updated, got %s", ric.Addr, res.Action) 4627 } 4628 4629 if got, want := ric.Addr.String(), "test_ignore_changes_map.foo"; got != want { 4630 t.Fatalf("unexpected resource address %s; want %s", got, want) 4631 } 4632 4633 checkVals(t, objectVal(t, schema, map[string]cty.Value{ 4634 "tags": cty.MapVal(map[string]cty.Value{ 4635 "ignored": cty.StringVal("from state"), 4636 "other": cty.StringVal("from config"), 4637 }), 4638 }), ric.After) 4639 } 4640 4641 func TestContext2Plan_ignoreChangesSensitive(t *testing.T) { 4642 m := testModule(t, "plan-ignore-changes-sensitive") 4643 p := testProvider("aws") 4644 p.PlanResourceChangeFn = testDiffFn 4645 4646 state := states.NewState() 4647 root := state.EnsureModule(addrs.RootModuleInstance) 4648 root.SetResourceInstanceCurrent( 4649 mustResourceInstanceAddr("aws_instance.foo").Resource, 4650 &states.ResourceInstanceObjectSrc{ 4651 Status: states.ObjectReady, 4652 AttrsJSON: []byte(`{"id":"bar","ami":"ami-abcd1234","type":"aws_instance"}`), 4653 }, 4654 mustProviderConfig(`provider["registry.terraform.io/hashicorp/aws"]`), 4655 ) 4656 4657 ctx := testContext2(t, &ContextOpts{ 4658 Providers: map[addrs.Provider]providers.Factory{ 4659 addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p), 4660 }, 4661 }) 4662 4663 plan, diags := ctx.Plan(m, state, &PlanOpts{ 4664 Mode: plans.NormalMode, 4665 SetVariables: InputValues{ 4666 "foo": &InputValue{ 4667 Value: cty.StringVal("ami-1234abcd"), 4668 SourceType: ValueFromCaller, 4669 }, 4670 }, 4671 }) 4672 if diags.HasErrors() { 4673 t.Fatalf("unexpected errors: %s", diags.Err()) 4674 } 4675 4676 schema := p.GetProviderSchemaResponse.ResourceTypes["aws_instance"].Block 4677 ty := schema.ImpliedType() 4678 4679 if len(plan.Changes.Resources) != 1 { 4680 t.Fatal("expected 1 changes, got", len(plan.Changes.Resources)) 4681 } 4682 4683 res := plan.Changes.Resources[0] 4684 ric, err := res.Decode(ty) 4685 if err != nil { 4686 t.Fatal(err) 4687 } 4688 4689 if ric.Addr.String() != "aws_instance.foo" { 4690 t.Fatalf("unexpected resource: %s", ric.Addr) 4691 } 4692 4693 checkVals(t, objectVal(t, schema, map[string]cty.Value{ 4694 "id": cty.StringVal("bar"), 4695 "ami": cty.StringVal("ami-abcd1234"), 4696 "type": cty.StringVal("aws_instance"), 4697 }), ric.After) 4698 } 4699 4700 func TestContext2Plan_moduleMapLiteral(t *testing.T) { 4701 m := testModule(t, "plan-module-map-literal") 4702 p := testProvider("aws") 4703 p.GetProviderSchemaResponse = getProviderSchemaResponseFromProviderSchema(&ProviderSchema{ 4704 ResourceTypes: map[string]*configschema.Block{ 4705 "aws_instance": { 4706 Attributes: map[string]*configschema.Attribute{ 4707 "meta": {Type: cty.Map(cty.String), Optional: true}, 4708 "tags": {Type: cty.Map(cty.String), Optional: true}, 4709 }, 4710 }, 4711 }, 4712 }) 4713 p.PlanResourceChangeFn = func(req providers.PlanResourceChangeRequest) (resp providers.PlanResourceChangeResponse) { 4714 s := req.ProposedNewState.AsValueMap() 4715 m := s["tags"].AsValueMap() 4716 4717 if m["foo"].AsString() != "bar" { 4718 t.Fatalf("Bad value in tags attr: %#v", m) 4719 } 4720 4721 meta := s["meta"].AsValueMap() 4722 if len(meta) != 0 { 4723 t.Fatalf("Meta attr not empty: %#v", meta) 4724 } 4725 return testDiffFn(req) 4726 } 4727 ctx := testContext2(t, &ContextOpts{ 4728 Providers: map[addrs.Provider]providers.Factory{ 4729 addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p), 4730 }, 4731 }) 4732 4733 _, diags := ctx.Plan(m, states.NewState(), DefaultPlanOpts) 4734 if diags.HasErrors() { 4735 t.Fatalf("unexpected errors: %s", diags.Err()) 4736 } 4737 } 4738 4739 func TestContext2Plan_computedValueInMap(t *testing.T) { 4740 m := testModule(t, "plan-computed-value-in-map") 4741 p := testProvider("aws") 4742 p.GetProviderSchemaResponse = getProviderSchemaResponseFromProviderSchema(&ProviderSchema{ 4743 ResourceTypes: map[string]*configschema.Block{ 4744 "aws_instance": { 4745 Attributes: map[string]*configschema.Attribute{ 4746 "looked_up": {Type: cty.String, Optional: true}, 4747 }, 4748 }, 4749 "aws_computed_source": { 4750 Attributes: map[string]*configschema.Attribute{ 4751 "computed_read_only": {Type: cty.String, Computed: true}, 4752 }, 4753 }, 4754 }, 4755 }) 4756 p.PlanResourceChangeFn = func(req providers.PlanResourceChangeRequest) (resp providers.PlanResourceChangeResponse) { 4757 resp = testDiffFn(req) 4758 4759 if req.TypeName != "aws_computed_source" { 4760 return 4761 } 4762 4763 planned := resp.PlannedState.AsValueMap() 4764 planned["computed_read_only"] = cty.UnknownVal(cty.String) 4765 resp.PlannedState = cty.ObjectVal(planned) 4766 return resp 4767 } 4768 4769 ctx := testContext2(t, &ContextOpts{ 4770 Providers: map[addrs.Provider]providers.Factory{ 4771 addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p), 4772 }, 4773 }) 4774 4775 plan, diags := ctx.Plan(m, states.NewState(), DefaultPlanOpts) 4776 if diags.HasErrors() { 4777 t.Fatalf("unexpected errors: %s", diags.Err()) 4778 } 4779 4780 if len(plan.Changes.Resources) != 2 { 4781 t.Fatal("expected 2 changes, got", len(plan.Changes.Resources)) 4782 } 4783 4784 for _, res := range plan.Changes.Resources { 4785 schema := p.GetProviderSchemaResponse.ResourceTypes[res.Addr.Resource.Resource.Type].Block 4786 4787 ric, err := res.Decode(schema.ImpliedType()) 4788 if err != nil { 4789 t.Fatal(err) 4790 } 4791 4792 if res.Action != plans.Create { 4793 t.Fatalf("resource %s should be created", ric.Addr) 4794 } 4795 4796 switch i := ric.Addr.String(); i { 4797 case "aws_computed_source.intermediates": 4798 checkVals(t, objectVal(t, schema, map[string]cty.Value{ 4799 "computed_read_only": cty.UnknownVal(cty.String), 4800 }), ric.After) 4801 case "module.test_mod.aws_instance.inner2": 4802 checkVals(t, objectVal(t, schema, map[string]cty.Value{ 4803 "looked_up": cty.UnknownVal(cty.String), 4804 }), ric.After) 4805 default: 4806 t.Fatal("unknown instance:", i) 4807 } 4808 } 4809 } 4810 4811 func TestContext2Plan_moduleVariableFromSplat(t *testing.T) { 4812 m := testModule(t, "plan-module-variable-from-splat") 4813 p := testProvider("aws") 4814 p.GetProviderSchemaResponse = getProviderSchemaResponseFromProviderSchema(&ProviderSchema{ 4815 ResourceTypes: map[string]*configschema.Block{ 4816 "aws_instance": { 4817 Attributes: map[string]*configschema.Attribute{ 4818 "thing": {Type: cty.String, Optional: true}, 4819 }, 4820 }, 4821 }, 4822 }) 4823 4824 ctx := testContext2(t, &ContextOpts{ 4825 Providers: map[addrs.Provider]providers.Factory{ 4826 addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p), 4827 }, 4828 }) 4829 4830 plan, diags := ctx.Plan(m, states.NewState(), DefaultPlanOpts) 4831 if diags.HasErrors() { 4832 t.Fatalf("unexpected errors: %s", diags.Err()) 4833 } 4834 4835 if len(plan.Changes.Resources) != 4 { 4836 t.Fatal("expected 4 changes, got", len(plan.Changes.Resources)) 4837 } 4838 4839 for _, res := range plan.Changes.Resources { 4840 schema := p.GetProviderSchemaResponse.ResourceTypes[res.Addr.Resource.Resource.Type].Block 4841 4842 ric, err := res.Decode(schema.ImpliedType()) 4843 if err != nil { 4844 t.Fatal(err) 4845 } 4846 4847 if res.Action != plans.Create { 4848 t.Fatalf("resource %s should be created", ric.Addr) 4849 } 4850 4851 switch i := ric.Addr.String(); i { 4852 case "module.mod1.aws_instance.test[0]", 4853 "module.mod1.aws_instance.test[1]", 4854 "module.mod2.aws_instance.test[0]", 4855 "module.mod2.aws_instance.test[1]": 4856 checkVals(t, objectVal(t, schema, map[string]cty.Value{ 4857 "thing": cty.StringVal("doesnt"), 4858 }), ric.After) 4859 default: 4860 t.Fatal("unknown instance:", i) 4861 } 4862 } 4863 } 4864 4865 func TestContext2Plan_createBeforeDestroy_depends_datasource(t *testing.T) { 4866 m := testModule(t, "plan-cbd-depends-datasource") 4867 p := testProvider("aws") 4868 p.GetProviderSchemaResponse = getProviderSchemaResponseFromProviderSchema(&ProviderSchema{ 4869 ResourceTypes: map[string]*configschema.Block{ 4870 "aws_instance": { 4871 Attributes: map[string]*configschema.Attribute{ 4872 "num": {Type: cty.String, Optional: true}, 4873 "computed": {Type: cty.String, Optional: true, Computed: true}, 4874 }, 4875 }, 4876 }, 4877 DataSources: map[string]*configschema.Block{ 4878 "aws_vpc": { 4879 Attributes: map[string]*configschema.Attribute{ 4880 "id": {Type: cty.String, Computed: true}, 4881 "foo": {Type: cty.Number, Optional: true}, 4882 }, 4883 }, 4884 }, 4885 }) 4886 p.PlanResourceChangeFn = func(req providers.PlanResourceChangeRequest) providers.PlanResourceChangeResponse { 4887 computedVal := req.ProposedNewState.GetAttr("computed") 4888 if computedVal.IsNull() { 4889 computedVal = cty.UnknownVal(cty.String) 4890 } 4891 return providers.PlanResourceChangeResponse{ 4892 PlannedState: cty.ObjectVal(map[string]cty.Value{ 4893 "num": req.ProposedNewState.GetAttr("num"), 4894 "computed": computedVal, 4895 }), 4896 } 4897 } 4898 p.ReadDataSourceFn = func(req providers.ReadDataSourceRequest) providers.ReadDataSourceResponse { 4899 cfg := req.Config.AsValueMap() 4900 cfg["id"] = cty.StringVal("data_id") 4901 return providers.ReadDataSourceResponse{ 4902 State: cty.ObjectVal(cfg), 4903 } 4904 } 4905 4906 ctx := testContext2(t, &ContextOpts{ 4907 Providers: map[addrs.Provider]providers.Factory{ 4908 addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p), 4909 }, 4910 }) 4911 4912 plan, diags := ctx.Plan(m, states.NewState(), DefaultPlanOpts) 4913 if diags.HasErrors() { 4914 t.Fatalf("unexpected errors: %s", diags.Err()) 4915 } 4916 4917 seenAddrs := make(map[string]struct{}) 4918 for _, res := range plan.Changes.Resources { 4919 var schema *configschema.Block 4920 switch res.Addr.Resource.Resource.Mode { 4921 case addrs.DataResourceMode: 4922 schema = p.GetProviderSchemaResponse.DataSources[res.Addr.Resource.Resource.Type].Block 4923 case addrs.ManagedResourceMode: 4924 schema = p.GetProviderSchemaResponse.ResourceTypes[res.Addr.Resource.Resource.Type].Block 4925 } 4926 4927 ric, err := res.Decode(schema.ImpliedType()) 4928 if err != nil { 4929 t.Fatal(err) 4930 } 4931 4932 seenAddrs[ric.Addr.String()] = struct{}{} 4933 4934 t.Run(ric.Addr.String(), func(t *testing.T) { 4935 switch i := ric.Addr.String(); i { 4936 case "aws_instance.foo[0]": 4937 if res.Action != plans.Create { 4938 t.Fatalf("resource %s should be created, got %s", ric.Addr, ric.Action) 4939 } 4940 checkVals(t, objectVal(t, schema, map[string]cty.Value{ 4941 "num": cty.StringVal("2"), 4942 "computed": cty.StringVal("data_id"), 4943 }), ric.After) 4944 case "aws_instance.foo[1]": 4945 if res.Action != plans.Create { 4946 t.Fatalf("resource %s should be created, got %s", ric.Addr, ric.Action) 4947 } 4948 checkVals(t, objectVal(t, schema, map[string]cty.Value{ 4949 "num": cty.StringVal("2"), 4950 "computed": cty.StringVal("data_id"), 4951 }), ric.After) 4952 default: 4953 t.Fatal("unknown instance:", i) 4954 } 4955 }) 4956 } 4957 4958 wantAddrs := map[string]struct{}{ 4959 "aws_instance.foo[0]": {}, 4960 "aws_instance.foo[1]": {}, 4961 } 4962 if !cmp.Equal(seenAddrs, wantAddrs) { 4963 t.Errorf("incorrect addresses in changeset:\n%s", cmp.Diff(wantAddrs, seenAddrs)) 4964 } 4965 } 4966 4967 // interpolated lists need to be stored in the original order. 4968 func TestContext2Plan_listOrder(t *testing.T) { 4969 m := testModule(t, "plan-list-order") 4970 p := testProvider("aws") 4971 p.GetProviderSchemaResponse = getProviderSchemaResponseFromProviderSchema(&ProviderSchema{ 4972 ResourceTypes: map[string]*configschema.Block{ 4973 "aws_instance": { 4974 Attributes: map[string]*configschema.Attribute{ 4975 "foo": {Type: cty.List(cty.String), Optional: true}, 4976 }, 4977 }, 4978 }, 4979 }) 4980 ctx := testContext2(t, &ContextOpts{ 4981 Providers: map[addrs.Provider]providers.Factory{ 4982 addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p), 4983 }, 4984 }) 4985 4986 plan, diags := ctx.Plan(m, states.NewState(), DefaultPlanOpts) 4987 if diags.HasErrors() { 4988 t.Fatalf("unexpected errors: %s", diags.Err()) 4989 } 4990 4991 changes := plan.Changes 4992 rDiffA := changes.ResourceInstance(addrs.Resource{ 4993 Mode: addrs.ManagedResourceMode, 4994 Type: "aws_instance", 4995 Name: "a", 4996 }.Instance(addrs.NoKey).Absolute(addrs.RootModuleInstance)) 4997 rDiffB := changes.ResourceInstance(addrs.Resource{ 4998 Mode: addrs.ManagedResourceMode, 4999 Type: "aws_instance", 5000 Name: "b", 5001 }.Instance(addrs.NoKey).Absolute(addrs.RootModuleInstance)) 5002 5003 if !cmp.Equal(rDiffA.After, rDiffB.After, valueComparer) { 5004 t.Fatal(cmp.Diff(rDiffA.After, rDiffB.After, valueComparer)) 5005 } 5006 } 5007 5008 // Make sure ignore-changes doesn't interfere with set/list/map diffs. 5009 // If a resource was being replaced by a RequiresNew attribute that gets 5010 // ignored, we need to filter the diff properly to properly update rather than 5011 // replace. 5012 func TestContext2Plan_ignoreChangesWithFlatmaps(t *testing.T) { 5013 m := testModule(t, "plan-ignore-changes-with-flatmaps") 5014 p := testProvider("aws") 5015 p.GetProviderSchemaResponse = getProviderSchemaResponseFromProviderSchema(&ProviderSchema{ 5016 ResourceTypes: map[string]*configschema.Block{ 5017 "aws_instance": { 5018 Attributes: map[string]*configschema.Attribute{ 5019 "user_data": {Type: cty.String, Optional: true}, 5020 "require_new": {Type: cty.String, Optional: true}, 5021 5022 // This test predates the 0.12 work to integrate cty and 5023 // HCL, and so it was ported as-is where its expected 5024 // test output was clearly expecting a list of maps here 5025 // even though it is named "set". 5026 "set": {Type: cty.List(cty.Map(cty.String)), Optional: true}, 5027 "lst": {Type: cty.List(cty.String), Optional: true}, 5028 }, 5029 }, 5030 }, 5031 }) 5032 5033 state := states.NewState() 5034 root := state.EnsureModule(addrs.RootModuleInstance) 5035 root.SetResourceInstanceCurrent( 5036 mustResourceInstanceAddr("aws_instance.foo").Resource, 5037 &states.ResourceInstanceObjectSrc{ 5038 Status: states.ObjectReady, 5039 AttrsJSON: []byte(`{ 5040 "user_data":"x","require_new":"", 5041 "set":[{"a":"1"}], 5042 "lst":["j"] 5043 }`), 5044 }, 5045 mustProviderConfig(`provider["registry.terraform.io/hashicorp/aws"]`), 5046 ) 5047 5048 ctx := testContext2(t, &ContextOpts{ 5049 Providers: map[addrs.Provider]providers.Factory{ 5050 addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p), 5051 }, 5052 }) 5053 5054 plan, diags := ctx.Plan(m, state, DefaultPlanOpts) 5055 if diags.HasErrors() { 5056 t.Fatalf("unexpected errors: %s", diags.Err()) 5057 } 5058 5059 if len(plan.Changes.Resources) != 1 { 5060 t.Fatal("expected 1 changes, got", len(plan.Changes.Resources)) 5061 } 5062 5063 res := plan.Changes.Resources[0] 5064 schema := p.GetProviderSchemaResponse.ResourceTypes[res.Addr.Resource.Resource.Type].Block 5065 5066 ric, err := res.Decode(schema.ImpliedType()) 5067 if err != nil { 5068 t.Fatal(err) 5069 } 5070 5071 if res.Action != plans.Update { 5072 t.Fatalf("resource %s should be updated, got %s", ric.Addr, ric.Action) 5073 } 5074 5075 if ric.Addr.String() != "aws_instance.foo" { 5076 t.Fatalf("unknown resource: %s", ric.Addr) 5077 } 5078 5079 checkVals(t, objectVal(t, schema, map[string]cty.Value{ 5080 "lst": cty.ListVal([]cty.Value{ 5081 cty.StringVal("j"), 5082 cty.StringVal("k"), 5083 }), 5084 "require_new": cty.StringVal(""), 5085 "user_data": cty.StringVal("x"), 5086 "set": cty.ListVal([]cty.Value{cty.MapVal(map[string]cty.Value{ 5087 "a": cty.StringVal("1"), 5088 "b": cty.StringVal("2"), 5089 })}), 5090 }), ric.After) 5091 } 5092 5093 // TestContext2Plan_resourceNestedCount ensures resource sets that depend on 5094 // the count of another resource set (ie: count of a data source that depends 5095 // on another data source's instance count - data.x.foo.*.id) get properly 5096 // normalized to the indexes they should be. This case comes up when there is 5097 // an existing state (after an initial apply). 5098 func TestContext2Plan_resourceNestedCount(t *testing.T) { 5099 m := testModule(t, "nested-resource-count-plan") 5100 p := testProvider("aws") 5101 p.PlanResourceChangeFn = testDiffFn 5102 p.ReadResourceFn = func(req providers.ReadResourceRequest) providers.ReadResourceResponse { 5103 return providers.ReadResourceResponse{ 5104 NewState: req.PriorState, 5105 } 5106 } 5107 5108 state := states.NewState() 5109 root := state.EnsureModule(addrs.RootModuleInstance) 5110 root.SetResourceInstanceCurrent( 5111 mustResourceInstanceAddr("aws_instance.foo[0]").Resource, 5112 &states.ResourceInstanceObjectSrc{ 5113 Status: states.ObjectReady, 5114 AttrsJSON: []byte(`{"id":"foo0","type":"aws_instance"}`), 5115 }, 5116 mustProviderConfig(`provider["registry.terraform.io/hashicorp/aws"]`), 5117 ) 5118 root.SetResourceInstanceCurrent( 5119 mustResourceInstanceAddr("aws_instance.foo[1]").Resource, 5120 &states.ResourceInstanceObjectSrc{ 5121 Status: states.ObjectReady, 5122 AttrsJSON: []byte(`{"id":"foo1","type":"aws_instance"}`), 5123 }, 5124 mustProviderConfig(`provider["registry.terraform.io/hashicorp/aws"]`), 5125 ) 5126 root.SetResourceInstanceCurrent( 5127 mustResourceInstanceAddr("aws_instance.bar[0]").Resource, 5128 &states.ResourceInstanceObjectSrc{ 5129 Status: states.ObjectReady, 5130 AttrsJSON: []byte(`{"id":"bar0","type":"aws_instance"}`), 5131 Dependencies: []addrs.ConfigResource{mustConfigResourceAddr("aws_instance.foo")}, 5132 }, 5133 mustProviderConfig(`provider["registry.terraform.io/hashicorp/aws"]`), 5134 ) 5135 root.SetResourceInstanceCurrent( 5136 mustResourceInstanceAddr("aws_instance.bar[1]").Resource, 5137 &states.ResourceInstanceObjectSrc{ 5138 Status: states.ObjectReady, 5139 AttrsJSON: []byte(`{"id":"bar1","type":"aws_instance"}`), 5140 Dependencies: []addrs.ConfigResource{mustConfigResourceAddr("aws_instance.foo")}, 5141 }, 5142 mustProviderConfig(`provider["registry.terraform.io/hashicorp/aws"]`), 5143 ) 5144 root.SetResourceInstanceCurrent( 5145 mustResourceInstanceAddr("aws_instance.baz[0]").Resource, 5146 &states.ResourceInstanceObjectSrc{ 5147 Status: states.ObjectReady, 5148 AttrsJSON: []byte(`{"id":"baz0","type":"aws_instance"}`), 5149 Dependencies: []addrs.ConfigResource{mustConfigResourceAddr("aws_instance.bar")}, 5150 }, 5151 mustProviderConfig(`provider["registry.terraform.io/hashicorp/aws"]`), 5152 ) 5153 root.SetResourceInstanceCurrent( 5154 mustResourceInstanceAddr("aws_instance.baz[1]").Resource, 5155 &states.ResourceInstanceObjectSrc{ 5156 Status: states.ObjectReady, 5157 AttrsJSON: []byte(`{"id":"baz1","type":"aws_instance"}`), 5158 Dependencies: []addrs.ConfigResource{mustConfigResourceAddr("aws_instance.bar")}, 5159 }, 5160 mustProviderConfig(`provider["registry.terraform.io/hashicorp/aws"]`), 5161 ) 5162 ctx := testContext2(t, &ContextOpts{ 5163 Providers: map[addrs.Provider]providers.Factory{ 5164 addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p), 5165 }, 5166 }) 5167 5168 diags := ctx.Validate(m) 5169 if diags.HasErrors() { 5170 t.Fatalf("validate errors: %s", diags.Err()) 5171 } 5172 5173 plan, diags := ctx.Plan(m, state, DefaultPlanOpts) 5174 if diags.HasErrors() { 5175 t.Fatalf("plan errors: %s", diags.Err()) 5176 } 5177 5178 for _, res := range plan.Changes.Resources { 5179 if res.Action != plans.NoOp { 5180 t.Fatalf("resource %s should not change, plan returned %s", res.Addr, res.Action) 5181 } 5182 } 5183 } 5184 5185 // Higher level test at TestResource_dataSourceListApplyPanic 5186 func TestContext2Plan_computedAttrRefTypeMismatch(t *testing.T) { 5187 m := testModule(t, "plan-computed-attr-ref-type-mismatch") 5188 p := testProvider("aws") 5189 p.ValidateResourceConfigFn = func(req providers.ValidateResourceConfigRequest) providers.ValidateResourceConfigResponse { 5190 var diags tfdiags.Diagnostics 5191 if req.TypeName == "aws_instance" { 5192 amiVal := req.Config.GetAttr("ami") 5193 if amiVal.Type() != cty.String { 5194 diags = diags.Append(fmt.Errorf("Expected ami to be cty.String, got %#v", amiVal)) 5195 } 5196 } 5197 return providers.ValidateResourceConfigResponse{ 5198 Diagnostics: diags, 5199 } 5200 } 5201 p.ApplyResourceChangeFn = func(req providers.ApplyResourceChangeRequest) (resp providers.ApplyResourceChangeResponse) { 5202 if req.TypeName != "aws_ami_list" { 5203 t.Fatalf("Reached apply for unexpected resource type! %s", req.TypeName) 5204 } 5205 // Pretend like we make a thing and the computed list "ids" is populated 5206 s := req.PlannedState.AsValueMap() 5207 s["id"] = cty.StringVal("someid") 5208 s["ids"] = cty.ListVal([]cty.Value{ 5209 cty.StringVal("ami-abc123"), 5210 cty.StringVal("ami-bcd345"), 5211 }) 5212 5213 resp.NewState = cty.ObjectVal(s) 5214 return 5215 } 5216 ctx := testContext2(t, &ContextOpts{ 5217 Providers: map[addrs.Provider]providers.Factory{ 5218 addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p), 5219 }, 5220 }) 5221 5222 _, diags := ctx.Plan(m, states.NewState(), DefaultPlanOpts) 5223 if !diags.HasErrors() { 5224 t.Fatalf("Succeeded; want type mismatch error for 'ami' argument") 5225 } 5226 5227 expected := `Inappropriate value for attribute "ami"` 5228 if errStr := diags.Err().Error(); !strings.Contains(errStr, expected) { 5229 t.Fatalf("expected:\n\n%s\n\nto contain:\n\n%s", errStr, expected) 5230 } 5231 } 5232 5233 func TestContext2Plan_selfRef(t *testing.T) { 5234 p := testProvider("aws") 5235 p.GetProviderSchemaResponse = getProviderSchemaResponseFromProviderSchema(&ProviderSchema{ 5236 ResourceTypes: map[string]*configschema.Block{ 5237 "aws_instance": { 5238 Attributes: map[string]*configschema.Attribute{ 5239 "foo": {Type: cty.String, Optional: true}, 5240 }, 5241 }, 5242 }, 5243 }) 5244 5245 m := testModule(t, "plan-self-ref") 5246 c := testContext2(t, &ContextOpts{ 5247 Providers: map[addrs.Provider]providers.Factory{ 5248 addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p), 5249 }, 5250 }) 5251 5252 diags := c.Validate(m) 5253 if diags.HasErrors() { 5254 t.Fatalf("unexpected validation failure: %s", diags.Err()) 5255 } 5256 5257 _, diags = c.Plan(m, states.NewState(), DefaultPlanOpts) 5258 if !diags.HasErrors() { 5259 t.Fatalf("plan succeeded; want error") 5260 } 5261 5262 gotErrStr := diags.Err().Error() 5263 wantErrStr := "Self-referential block" 5264 if !strings.Contains(gotErrStr, wantErrStr) { 5265 t.Fatalf("missing expected error\ngot: %s\n\nwant: error containing %q", gotErrStr, wantErrStr) 5266 } 5267 } 5268 5269 func TestContext2Plan_selfRefMulti(t *testing.T) { 5270 p := testProvider("aws") 5271 p.GetProviderSchemaResponse = getProviderSchemaResponseFromProviderSchema(&ProviderSchema{ 5272 ResourceTypes: map[string]*configschema.Block{ 5273 "aws_instance": { 5274 Attributes: map[string]*configschema.Attribute{ 5275 "foo": {Type: cty.String, Optional: true}, 5276 }, 5277 }, 5278 }, 5279 }) 5280 5281 m := testModule(t, "plan-self-ref-multi") 5282 c := testContext2(t, &ContextOpts{ 5283 Providers: map[addrs.Provider]providers.Factory{ 5284 addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p), 5285 }, 5286 }) 5287 5288 diags := c.Validate(m) 5289 if diags.HasErrors() { 5290 t.Fatalf("unexpected validation failure: %s", diags.Err()) 5291 } 5292 5293 _, diags = c.Plan(m, states.NewState(), DefaultPlanOpts) 5294 if !diags.HasErrors() { 5295 t.Fatalf("plan succeeded; want error") 5296 } 5297 5298 gotErrStr := diags.Err().Error() 5299 wantErrStr := "Self-referential block" 5300 if !strings.Contains(gotErrStr, wantErrStr) { 5301 t.Fatalf("missing expected error\ngot: %s\n\nwant: error containing %q", gotErrStr, wantErrStr) 5302 } 5303 } 5304 5305 func TestContext2Plan_selfRefMultiAll(t *testing.T) { 5306 p := testProvider("aws") 5307 p.GetProviderSchemaResponse = getProviderSchemaResponseFromProviderSchema(&ProviderSchema{ 5308 ResourceTypes: map[string]*configschema.Block{ 5309 "aws_instance": { 5310 Attributes: map[string]*configschema.Attribute{ 5311 "foo": {Type: cty.List(cty.String), Optional: true}, 5312 }, 5313 }, 5314 }, 5315 }) 5316 5317 m := testModule(t, "plan-self-ref-multi-all") 5318 c := testContext2(t, &ContextOpts{ 5319 Providers: map[addrs.Provider]providers.Factory{ 5320 addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p), 5321 }, 5322 }) 5323 5324 diags := c.Validate(m) 5325 if diags.HasErrors() { 5326 t.Fatalf("unexpected validation failure: %s", diags.Err()) 5327 } 5328 5329 _, diags = c.Plan(m, states.NewState(), DefaultPlanOpts) 5330 if !diags.HasErrors() { 5331 t.Fatalf("plan succeeded; want error") 5332 } 5333 5334 gotErrStr := diags.Err().Error() 5335 5336 // The graph is checked for cycles before we can walk it, so we don't 5337 // encounter the self-reference check. 5338 //wantErrStr := "Self-referential block" 5339 wantErrStr := "Cycle" 5340 if !strings.Contains(gotErrStr, wantErrStr) { 5341 t.Fatalf("missing expected error\ngot: %s\n\nwant: error containing %q", gotErrStr, wantErrStr) 5342 } 5343 } 5344 5345 func TestContext2Plan_invalidOutput(t *testing.T) { 5346 m := testModuleInline(t, map[string]string{ 5347 "main.tf": ` 5348 data "aws_data_source" "name" {} 5349 5350 output "out" { 5351 value = data.aws_data_source.name.missing 5352 }`, 5353 }) 5354 5355 p := testProvider("aws") 5356 p.ReadDataSourceResponse = &providers.ReadDataSourceResponse{ 5357 State: cty.ObjectVal(map[string]cty.Value{ 5358 "id": cty.StringVal("data_id"), 5359 "foo": cty.StringVal("foo"), 5360 }), 5361 } 5362 5363 ctx := testContext2(t, &ContextOpts{ 5364 Providers: map[addrs.Provider]providers.Factory{ 5365 addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p), 5366 }, 5367 }) 5368 5369 _, diags := ctx.Plan(m, states.NewState(), DefaultPlanOpts) 5370 if !diags.HasErrors() { 5371 // Should get this error: 5372 // Unsupported attribute: This object does not have an attribute named "missing" 5373 t.Fatal("succeeded; want errors") 5374 } 5375 5376 gotErrStr := diags.Err().Error() 5377 wantErrStr := "Unsupported attribute" 5378 if !strings.Contains(gotErrStr, wantErrStr) { 5379 t.Fatalf("missing expected error\ngot: %s\n\nwant: error containing %q", gotErrStr, wantErrStr) 5380 } 5381 } 5382 5383 func TestContext2Plan_invalidModuleOutput(t *testing.T) { 5384 m := testModuleInline(t, map[string]string{ 5385 "child/main.tf": ` 5386 data "aws_data_source" "name" {} 5387 5388 output "out" { 5389 value = "${data.aws_data_source.name.missing}" 5390 }`, 5391 "main.tf": ` 5392 module "child" { 5393 source = "./child" 5394 } 5395 5396 resource "aws_instance" "foo" { 5397 foo = "${module.child.out}" 5398 }`, 5399 }) 5400 5401 p := testProvider("aws") 5402 p.ReadDataSourceResponse = &providers.ReadDataSourceResponse{ 5403 State: cty.ObjectVal(map[string]cty.Value{ 5404 "id": cty.StringVal("data_id"), 5405 "foo": cty.StringVal("foo"), 5406 }), 5407 } 5408 5409 ctx := testContext2(t, &ContextOpts{ 5410 Providers: map[addrs.Provider]providers.Factory{ 5411 addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p), 5412 }, 5413 }) 5414 5415 _, diags := ctx.Plan(m, states.NewState(), DefaultPlanOpts) 5416 if !diags.HasErrors() { 5417 // Should get this error: 5418 // Unsupported attribute: This object does not have an attribute named "missing" 5419 t.Fatal("succeeded; want errors") 5420 } 5421 5422 gotErrStr := diags.Err().Error() 5423 wantErrStr := "Unsupported attribute" 5424 if !strings.Contains(gotErrStr, wantErrStr) { 5425 t.Fatalf("missing expected error\ngot: %s\n\nwant: error containing %q", gotErrStr, wantErrStr) 5426 } 5427 } 5428 5429 func TestContext2Plan_variableValidation(t *testing.T) { 5430 m := testModuleInline(t, map[string]string{ 5431 "main.tf": ` 5432 variable "x" { 5433 default = "bar" 5434 } 5435 5436 resource "aws_instance" "foo" { 5437 foo = var.x 5438 }`, 5439 }) 5440 5441 p := testProvider("aws") 5442 p.ValidateResourceConfigFn = func(req providers.ValidateResourceConfigRequest) (resp providers.ValidateResourceConfigResponse) { 5443 foo := req.Config.GetAttr("foo").AsString() 5444 if foo == "bar" { 5445 resp.Diagnostics = resp.Diagnostics.Append(errors.New("foo cannot be bar")) 5446 } 5447 return 5448 } 5449 5450 p.PlanResourceChangeFn = func(req providers.PlanResourceChangeRequest) (resp providers.PlanResourceChangeResponse) { 5451 resp.PlannedState = req.ProposedNewState 5452 return 5453 } 5454 5455 ctx := testContext2(t, &ContextOpts{ 5456 Providers: map[addrs.Provider]providers.Factory{ 5457 addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p), 5458 }, 5459 }) 5460 5461 _, diags := ctx.Plan(m, states.NewState(), DefaultPlanOpts) 5462 if !diags.HasErrors() { 5463 // Should get this error: 5464 // Unsupported attribute: This object does not have an attribute named "missing" 5465 t.Fatal("succeeded; want errors") 5466 } 5467 } 5468 5469 func TestContext2Plan_variableSensitivity(t *testing.T) { 5470 m := testModule(t, "plan-variable-sensitivity") 5471 5472 p := testProvider("aws") 5473 p.PlanResourceChangeFn = func(req providers.PlanResourceChangeRequest) (resp providers.PlanResourceChangeResponse) { 5474 resp.PlannedState = req.ProposedNewState 5475 return 5476 } 5477 5478 ctx := testContext2(t, &ContextOpts{ 5479 Providers: map[addrs.Provider]providers.Factory{ 5480 addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p), 5481 }, 5482 }) 5483 5484 plan, diags := ctx.Plan(m, states.NewState(), DefaultPlanOpts) 5485 if diags.HasErrors() { 5486 t.Fatalf("unexpected errors: %s", diags.Err()) 5487 } 5488 schema := p.GetProviderSchemaResponse.ResourceTypes["aws_instance"].Block 5489 ty := schema.ImpliedType() 5490 5491 if len(plan.Changes.Resources) != 1 { 5492 t.Fatal("expected 1 changes, got", len(plan.Changes.Resources)) 5493 } 5494 5495 for _, res := range plan.Changes.Resources { 5496 if res.Action != plans.Create { 5497 t.Fatalf("expected resource creation, got %s", res.Action) 5498 } 5499 ric, err := res.Decode(ty) 5500 if err != nil { 5501 t.Fatal(err) 5502 } 5503 5504 switch i := ric.Addr.String(); i { 5505 case "aws_instance.foo": 5506 checkVals(t, objectVal(t, schema, map[string]cty.Value{ 5507 "foo": cty.StringVal("foo").Mark(marks.Sensitive), 5508 }), ric.After) 5509 if len(res.ChangeSrc.BeforeValMarks) != 0 { 5510 t.Errorf("unexpected BeforeValMarks: %#v", res.ChangeSrc.BeforeValMarks) 5511 } 5512 if len(res.ChangeSrc.AfterValMarks) != 1 { 5513 t.Errorf("unexpected AfterValMarks: %#v", res.ChangeSrc.AfterValMarks) 5514 continue 5515 } 5516 pvm := res.ChangeSrc.AfterValMarks[0] 5517 if got, want := pvm.Path, cty.GetAttrPath("foo"); !got.Equals(want) { 5518 t.Errorf("unexpected path for mark\n got: %#v\nwant: %#v", got, want) 5519 } 5520 if got, want := pvm.Marks, cty.NewValueMarks(marks.Sensitive); !got.Equal(want) { 5521 t.Errorf("unexpected value for mark\n got: %#v\nwant: %#v", got, want) 5522 } 5523 default: 5524 t.Fatal("unknown instance:", i) 5525 } 5526 } 5527 } 5528 5529 func TestContext2Plan_variableSensitivityModule(t *testing.T) { 5530 m := testModule(t, "plan-variable-sensitivity-module") 5531 5532 p := testProvider("aws") 5533 p.PlanResourceChangeFn = func(req providers.PlanResourceChangeRequest) (resp providers.PlanResourceChangeResponse) { 5534 resp.PlannedState = req.ProposedNewState 5535 return 5536 } 5537 5538 ctx := testContext2(t, &ContextOpts{ 5539 Providers: map[addrs.Provider]providers.Factory{ 5540 addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p), 5541 }, 5542 }) 5543 5544 plan, diags := ctx.Plan(m, states.NewState(), &PlanOpts{ 5545 Mode: plans.NormalMode, 5546 SetVariables: InputValues{ 5547 "another_var": &InputValue{ 5548 Value: cty.StringVal("boop"), 5549 SourceType: ValueFromCaller, 5550 }, 5551 }, 5552 }) 5553 if diags.HasErrors() { 5554 t.Fatalf("unexpected errors: %s", diags.Err()) 5555 } 5556 schema := p.GetProviderSchemaResponse.ResourceTypes["aws_instance"].Block 5557 ty := schema.ImpliedType() 5558 5559 if len(plan.Changes.Resources) != 1 { 5560 t.Fatal("expected 1 changes, got", len(plan.Changes.Resources)) 5561 } 5562 5563 for _, res := range plan.Changes.Resources { 5564 if res.Action != plans.Create { 5565 t.Fatalf("expected resource creation, got %s", res.Action) 5566 } 5567 ric, err := res.Decode(ty) 5568 if err != nil { 5569 t.Fatal(err) 5570 } 5571 5572 switch i := ric.Addr.String(); i { 5573 case "module.child.aws_instance.foo": 5574 checkVals(t, objectVal(t, schema, map[string]cty.Value{ 5575 "foo": cty.StringVal("foo").Mark(marks.Sensitive), 5576 "value": cty.StringVal("boop").Mark(marks.Sensitive), 5577 }), ric.After) 5578 if len(res.ChangeSrc.BeforeValMarks) != 0 { 5579 t.Errorf("unexpected BeforeValMarks: %#v", res.ChangeSrc.BeforeValMarks) 5580 } 5581 if len(res.ChangeSrc.AfterValMarks) != 2 { 5582 t.Errorf("expected AfterValMarks to contain two elements: %#v", res.ChangeSrc.AfterValMarks) 5583 continue 5584 } 5585 // validate that the after marks have "foo" and "value" 5586 contains := func(pvmSlice []cty.PathValueMarks, stepName string) bool { 5587 for _, pvm := range pvmSlice { 5588 if pvm.Path.Equals(cty.GetAttrPath(stepName)) { 5589 if pvm.Marks.Equal(cty.NewValueMarks(marks.Sensitive)) { 5590 return true 5591 } 5592 } 5593 } 5594 return false 5595 } 5596 if !contains(res.ChangeSrc.AfterValMarks, "foo") { 5597 t.Error("unexpected AfterValMarks to contain \"foo\" with sensitive mark") 5598 } 5599 if !contains(res.ChangeSrc.AfterValMarks, "value") { 5600 t.Error("unexpected AfterValMarks to contain \"value\" with sensitive mark") 5601 } 5602 default: 5603 t.Fatal("unknown instance:", i) 5604 } 5605 } 5606 } 5607 5608 func checkVals(t *testing.T, expected, got cty.Value) { 5609 t.Helper() 5610 // The GoStringer format seems to result in the closest thing to a useful 5611 // diff for values with marks. 5612 // TODO: if we want to continue using cmp.Diff on cty.Values, we should 5613 // make a transformer that creates a more comparable structure. 5614 valueTrans := cmp.Transformer("gostring", func(v cty.Value) string { 5615 return fmt.Sprintf("%#v\n", v) 5616 }) 5617 if !cmp.Equal(expected, got, valueComparer, typeComparer, equateEmpty) { 5618 t.Fatal(cmp.Diff(expected, got, valueTrans, equateEmpty)) 5619 } 5620 } 5621 5622 func objectVal(t *testing.T, schema *configschema.Block, m map[string]cty.Value) cty.Value { 5623 t.Helper() 5624 v, err := schema.CoerceValue( 5625 cty.ObjectVal(m), 5626 ) 5627 if err != nil { 5628 t.Fatal(err) 5629 } 5630 return v 5631 } 5632 5633 func TestContext2Plan_requiredModuleOutput(t *testing.T) { 5634 m := testModule(t, "plan-required-output") 5635 p := testProvider("test") 5636 p.GetProviderSchemaResponse = getProviderSchemaResponseFromProviderSchema(&ProviderSchema{ 5637 ResourceTypes: map[string]*configschema.Block{ 5638 "test_resource": { 5639 Attributes: map[string]*configschema.Attribute{ 5640 "id": {Type: cty.String, Computed: true}, 5641 "required": {Type: cty.String, Required: true}, 5642 }, 5643 }, 5644 }, 5645 }) 5646 5647 ctx := testContext2(t, &ContextOpts{ 5648 Providers: map[addrs.Provider]providers.Factory{ 5649 addrs.NewDefaultProvider("test"): testProviderFuncFixed(p), 5650 }, 5651 }) 5652 5653 plan, diags := ctx.Plan(m, states.NewState(), DefaultPlanOpts) 5654 if diags.HasErrors() { 5655 t.Fatalf("unexpected errors: %s", diags.Err()) 5656 } 5657 5658 schema := p.GetProviderSchemaResponse.ResourceTypes["test_resource"].Block 5659 ty := schema.ImpliedType() 5660 5661 if len(plan.Changes.Resources) != 2 { 5662 t.Fatal("expected 2 changes, got", len(plan.Changes.Resources)) 5663 } 5664 5665 for _, res := range plan.Changes.Resources { 5666 t.Run(fmt.Sprintf("%s %s", res.Action, res.Addr), func(t *testing.T) { 5667 if res.Action != plans.Create { 5668 t.Fatalf("expected resource creation, got %s", res.Action) 5669 } 5670 ric, err := res.Decode(ty) 5671 if err != nil { 5672 t.Fatal(err) 5673 } 5674 5675 var expected cty.Value 5676 switch i := ric.Addr.String(); i { 5677 case "test_resource.root": 5678 expected = objectVal(t, schema, map[string]cty.Value{ 5679 "id": cty.UnknownVal(cty.String), 5680 "required": cty.UnknownVal(cty.String), 5681 }) 5682 case "module.mod.test_resource.for_output": 5683 expected = objectVal(t, schema, map[string]cty.Value{ 5684 "id": cty.UnknownVal(cty.String), 5685 "required": cty.StringVal("val"), 5686 }) 5687 default: 5688 t.Fatal("unknown instance:", i) 5689 } 5690 5691 checkVals(t, expected, ric.After) 5692 }) 5693 } 5694 } 5695 5696 func TestContext2Plan_requiredModuleObject(t *testing.T) { 5697 m := testModule(t, "plan-required-whole-mod") 5698 p := testProvider("test") 5699 p.GetProviderSchemaResponse = getProviderSchemaResponseFromProviderSchema(&ProviderSchema{ 5700 ResourceTypes: map[string]*configschema.Block{ 5701 "test_resource": { 5702 Attributes: map[string]*configschema.Attribute{ 5703 "id": {Type: cty.String, Computed: true}, 5704 "required": {Type: cty.String, Required: true}, 5705 }, 5706 }, 5707 }, 5708 }) 5709 5710 ctx := testContext2(t, &ContextOpts{ 5711 Providers: map[addrs.Provider]providers.Factory{ 5712 addrs.NewDefaultProvider("test"): testProviderFuncFixed(p), 5713 }, 5714 }) 5715 5716 plan, diags := ctx.Plan(m, states.NewState(), DefaultPlanOpts) 5717 if diags.HasErrors() { 5718 t.Fatalf("unexpected errors: %s", diags.Err()) 5719 } 5720 5721 schema := p.GetProviderSchemaResponse.ResourceTypes["test_resource"].Block 5722 ty := schema.ImpliedType() 5723 5724 if len(plan.Changes.Resources) != 2 { 5725 t.Fatal("expected 2 changes, got", len(plan.Changes.Resources)) 5726 } 5727 5728 for _, res := range plan.Changes.Resources { 5729 t.Run(fmt.Sprintf("%s %s", res.Action, res.Addr), func(t *testing.T) { 5730 if res.Action != plans.Create { 5731 t.Fatalf("expected resource creation, got %s", res.Action) 5732 } 5733 ric, err := res.Decode(ty) 5734 if err != nil { 5735 t.Fatal(err) 5736 } 5737 5738 var expected cty.Value 5739 switch i := ric.Addr.String(); i { 5740 case "test_resource.root": 5741 expected = objectVal(t, schema, map[string]cty.Value{ 5742 "id": cty.UnknownVal(cty.String), 5743 "required": cty.UnknownVal(cty.String), 5744 }) 5745 case "module.mod.test_resource.for_output": 5746 expected = objectVal(t, schema, map[string]cty.Value{ 5747 "id": cty.UnknownVal(cty.String), 5748 "required": cty.StringVal("val"), 5749 }) 5750 default: 5751 t.Fatal("unknown instance:", i) 5752 } 5753 5754 checkVals(t, expected, ric.After) 5755 }) 5756 } 5757 } 5758 5759 func TestContext2Plan_expandOrphan(t *testing.T) { 5760 m := testModuleInline(t, map[string]string{ 5761 "main.tf": ` 5762 module "mod" { 5763 count = 1 5764 source = "./mod" 5765 } 5766 `, 5767 "mod/main.tf": ` 5768 resource "aws_instance" "foo" { 5769 } 5770 `, 5771 }) 5772 5773 state := states.NewState() 5774 state.EnsureModule(addrs.RootModuleInstance.Child("mod", addrs.IntKey(0))).SetResourceInstanceCurrent( 5775 mustResourceInstanceAddr("aws_instance.foo").Resource, 5776 &states.ResourceInstanceObjectSrc{ 5777 Status: states.ObjectReady, 5778 AttrsJSON: []byte(`{"id":"child","type":"aws_instance"}`), 5779 }, 5780 mustProviderConfig(`provider["registry.terraform.io/hashicorp/aws"]`), 5781 ) 5782 state.EnsureModule(addrs.RootModuleInstance.Child("mod", addrs.IntKey(1))).SetResourceInstanceCurrent( 5783 mustResourceInstanceAddr("aws_instance.foo").Resource, 5784 &states.ResourceInstanceObjectSrc{ 5785 Status: states.ObjectReady, 5786 AttrsJSON: []byte(`{"id":"child","type":"aws_instance"}`), 5787 }, 5788 mustProviderConfig(`provider["registry.terraform.io/hashicorp/aws"]`), 5789 ) 5790 5791 p := testProvider("aws") 5792 p.PlanResourceChangeFn = testDiffFn 5793 ctx := testContext2(t, &ContextOpts{ 5794 Providers: map[addrs.Provider]providers.Factory{ 5795 addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p), 5796 }, 5797 }) 5798 5799 plan, diags := ctx.Plan(m, state, DefaultPlanOpts) 5800 if diags.HasErrors() { 5801 t.Fatal(diags.ErrWithWarnings()) 5802 } 5803 5804 expected := map[string]plans.Action{ 5805 `module.mod[1].aws_instance.foo`: plans.Delete, 5806 `module.mod[0].aws_instance.foo`: plans.NoOp, 5807 } 5808 5809 for _, res := range plan.Changes.Resources { 5810 want := expected[res.Addr.String()] 5811 if res.Action != want { 5812 t.Fatalf("expected %s action, got: %q %s", want, res.Addr, res.Action) 5813 } 5814 delete(expected, res.Addr.String()) 5815 } 5816 5817 for res, action := range expected { 5818 t.Errorf("missing %s change for %s", action, res) 5819 } 5820 } 5821 5822 func TestContext2Plan_indexInVar(t *testing.T) { 5823 m := testModuleInline(t, map[string]string{ 5824 "main.tf": ` 5825 module "a" { 5826 count = 1 5827 source = "./mod" 5828 in = "test" 5829 } 5830 5831 module "b" { 5832 count = 1 5833 source = "./mod" 5834 in = length(module.a) 5835 } 5836 `, 5837 "mod/main.tf": ` 5838 resource "aws_instance" "foo" { 5839 foo = var.in 5840 } 5841 5842 variable "in" { 5843 } 5844 5845 output"out" { 5846 value = aws_instance.foo.id 5847 } 5848 `, 5849 }) 5850 5851 p := testProvider("aws") 5852 ctx := testContext2(t, &ContextOpts{ 5853 Providers: map[addrs.Provider]providers.Factory{ 5854 addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p), 5855 }, 5856 }) 5857 5858 _, diags := ctx.Plan(m, states.NewState(), DefaultPlanOpts) 5859 if diags.HasErrors() { 5860 t.Fatal(diags.ErrWithWarnings()) 5861 } 5862 } 5863 5864 func TestContext2Plan_targetExpandedAddress(t *testing.T) { 5865 m := testModuleInline(t, map[string]string{ 5866 "main.tf": ` 5867 module "mod" { 5868 count = 3 5869 source = "./mod" 5870 } 5871 `, 5872 "mod/main.tf": ` 5873 resource "aws_instance" "foo" { 5874 count = 2 5875 } 5876 `, 5877 }) 5878 5879 p := testProvider("aws") 5880 5881 targets := []addrs.Targetable{} 5882 target, diags := addrs.ParseTargetStr("module.mod[1].aws_instance.foo[0]") 5883 if diags.HasErrors() { 5884 t.Fatal(diags.ErrWithWarnings()) 5885 } 5886 targets = append(targets, target.Subject) 5887 5888 target, diags = addrs.ParseTargetStr("module.mod[2]") 5889 if diags.HasErrors() { 5890 t.Fatal(diags.ErrWithWarnings()) 5891 } 5892 targets = append(targets, target.Subject) 5893 5894 ctx := testContext2(t, &ContextOpts{ 5895 Providers: map[addrs.Provider]providers.Factory{ 5896 addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p), 5897 }, 5898 }) 5899 5900 plan, diags := ctx.Plan(m, states.NewState(), &PlanOpts{ 5901 Mode: plans.NormalMode, 5902 Targets: targets, 5903 }) 5904 if diags.HasErrors() { 5905 t.Fatal(diags.ErrWithWarnings()) 5906 } 5907 5908 expected := map[string]plans.Action{ 5909 // the single targeted mod[1] instances 5910 `module.mod[1].aws_instance.foo[0]`: plans.Create, 5911 // the whole mode[2] 5912 `module.mod[2].aws_instance.foo[0]`: plans.Create, 5913 `module.mod[2].aws_instance.foo[1]`: plans.Create, 5914 } 5915 5916 for _, res := range plan.Changes.Resources { 5917 want := expected[res.Addr.String()] 5918 if res.Action != want { 5919 t.Fatalf("expected %s action, got: %q %s", want, res.Addr, res.Action) 5920 } 5921 delete(expected, res.Addr.String()) 5922 } 5923 5924 for res, action := range expected { 5925 t.Errorf("missing %s change for %s", action, res) 5926 } 5927 } 5928 5929 func TestContext2Plan_targetResourceInModuleInstance(t *testing.T) { 5930 m := testModuleInline(t, map[string]string{ 5931 "main.tf": ` 5932 module "mod" { 5933 count = 3 5934 source = "./mod" 5935 } 5936 `, 5937 "mod/main.tf": ` 5938 resource "aws_instance" "foo" { 5939 } 5940 `, 5941 }) 5942 5943 p := testProvider("aws") 5944 5945 target, diags := addrs.ParseTargetStr("module.mod[1].aws_instance.foo") 5946 if diags.HasErrors() { 5947 t.Fatal(diags.ErrWithWarnings()) 5948 } 5949 5950 targets := []addrs.Targetable{target.Subject} 5951 5952 ctx := testContext2(t, &ContextOpts{ 5953 Providers: map[addrs.Provider]providers.Factory{ 5954 addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p), 5955 }, 5956 }) 5957 5958 plan, diags := ctx.Plan(m, states.NewState(), &PlanOpts{ 5959 Mode: plans.NormalMode, 5960 Targets: targets, 5961 }) 5962 if diags.HasErrors() { 5963 t.Fatal(diags.ErrWithWarnings()) 5964 } 5965 5966 expected := map[string]plans.Action{ 5967 // the single targeted mod[1] instance 5968 `module.mod[1].aws_instance.foo`: plans.Create, 5969 } 5970 5971 for _, res := range plan.Changes.Resources { 5972 want := expected[res.Addr.String()] 5973 if res.Action != want { 5974 t.Fatalf("expected %s action, got: %q %s", want, res.Addr, res.Action) 5975 } 5976 delete(expected, res.Addr.String()) 5977 } 5978 5979 for res, action := range expected { 5980 t.Errorf("missing %s change for %s", action, res) 5981 } 5982 } 5983 5984 func TestContext2Plan_moduleRefIndex(t *testing.T) { 5985 m := testModuleInline(t, map[string]string{ 5986 "main.tf": ` 5987 module "mod" { 5988 for_each = { 5989 a = "thing" 5990 } 5991 in = null 5992 source = "./mod" 5993 } 5994 5995 module "single" { 5996 source = "./mod" 5997 in = module.mod["a"] 5998 } 5999 `, 6000 "mod/main.tf": ` 6001 variable "in" { 6002 } 6003 6004 output "out" { 6005 value = "foo" 6006 } 6007 6008 resource "aws_instance" "foo" { 6009 } 6010 `, 6011 }) 6012 6013 p := testProvider("aws") 6014 6015 ctx := testContext2(t, &ContextOpts{ 6016 Providers: map[addrs.Provider]providers.Factory{ 6017 addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p), 6018 }, 6019 }) 6020 6021 _, diags := ctx.Plan(m, states.NewState(), DefaultPlanOpts) 6022 if diags.HasErrors() { 6023 t.Fatal(diags.ErrWithWarnings()) 6024 } 6025 } 6026 6027 func TestContext2Plan_noChangeDataPlan(t *testing.T) { 6028 m := testModuleInline(t, map[string]string{ 6029 "main.tf": ` 6030 data "test_data_source" "foo" {} 6031 `, 6032 }) 6033 6034 p := new(MockProvider) 6035 p.GetProviderSchemaResponse = getProviderSchemaResponseFromProviderSchema(&ProviderSchema{ 6036 DataSources: map[string]*configschema.Block{ 6037 "test_data_source": { 6038 Attributes: map[string]*configschema.Attribute{ 6039 "id": { 6040 Type: cty.String, 6041 Computed: true, 6042 }, 6043 "foo": { 6044 Type: cty.String, 6045 Optional: true, 6046 }, 6047 }, 6048 }, 6049 }, 6050 }) 6051 6052 p.ReadDataSourceResponse = &providers.ReadDataSourceResponse{ 6053 State: cty.ObjectVal(map[string]cty.Value{ 6054 "id": cty.StringVal("data_id"), 6055 "foo": cty.StringVal("foo"), 6056 }), 6057 } 6058 6059 state := states.NewState() 6060 root := state.EnsureModule(addrs.RootModuleInstance) 6061 root.SetResourceInstanceCurrent( 6062 mustResourceInstanceAddr("data.test_data_source.foo").Resource, 6063 &states.ResourceInstanceObjectSrc{ 6064 Status: states.ObjectReady, 6065 AttrsJSON: []byte(`{"id":"data_id", "foo":"foo"}`), 6066 }, 6067 mustProviderConfig(`provider["registry.terraform.io/hashicorp/test"]`), 6068 ) 6069 6070 ctx := testContext2(t, &ContextOpts{ 6071 Providers: map[addrs.Provider]providers.Factory{ 6072 addrs.NewDefaultProvider("test"): testProviderFuncFixed(p), 6073 }, 6074 }) 6075 6076 plan, diags := ctx.Plan(m, state, DefaultPlanOpts) 6077 if diags.HasErrors() { 6078 t.Fatal(diags.ErrWithWarnings()) 6079 } 6080 6081 for _, res := range plan.Changes.Resources { 6082 if res.Action != plans.NoOp { 6083 t.Fatalf("expected NoOp, got: %q %s", res.Addr, res.Action) 6084 } 6085 } 6086 } 6087 6088 // for_each can reference a resource with 0 instances 6089 func TestContext2Plan_scaleInForEach(t *testing.T) { 6090 p := testProvider("test") 6091 6092 m := testModuleInline(t, map[string]string{ 6093 "main.tf": ` 6094 locals { 6095 m = {} 6096 } 6097 6098 resource "test_instance" "a" { 6099 for_each = local.m 6100 } 6101 6102 resource "test_instance" "b" { 6103 for_each = test_instance.a 6104 } 6105 `}) 6106 6107 state := states.NewState() 6108 root := state.EnsureModule(addrs.RootModuleInstance) 6109 root.SetResourceInstanceCurrent( 6110 mustResourceInstanceAddr("test_instance.a[0]").Resource, 6111 &states.ResourceInstanceObjectSrc{ 6112 Status: states.ObjectReady, 6113 AttrsJSON: []byte(`{"id":"a0"}`), 6114 Dependencies: []addrs.ConfigResource{}, 6115 }, 6116 mustProviderConfig(`provider["registry.terraform.io/hashicorp/test"]`), 6117 ) 6118 root.SetResourceInstanceCurrent( 6119 mustResourceInstanceAddr("test_instance.b").Resource, 6120 &states.ResourceInstanceObjectSrc{ 6121 Status: states.ObjectReady, 6122 AttrsJSON: []byte(`{"id":"b"}`), 6123 Dependencies: []addrs.ConfigResource{mustConfigResourceAddr("test_instance.a")}, 6124 }, 6125 mustProviderConfig(`provider["registry.terraform.io/hashicorp/test"]`), 6126 ) 6127 6128 ctx := testContext2(t, &ContextOpts{ 6129 Providers: map[addrs.Provider]providers.Factory{ 6130 addrs.NewDefaultProvider("test"): testProviderFuncFixed(p), 6131 }, 6132 }) 6133 6134 plan, diags := ctx.Plan(m, state, DefaultPlanOpts) 6135 assertNoErrors(t, diags) 6136 6137 t.Run("test_instance.a[0]", func(t *testing.T) { 6138 instAddr := mustResourceInstanceAddr("test_instance.a[0]") 6139 change := plan.Changes.ResourceInstance(instAddr) 6140 if change == nil { 6141 t.Fatalf("no planned change for %s", instAddr) 6142 } 6143 if got, want := change.PrevRunAddr, instAddr; !want.Equal(got) { 6144 t.Errorf("wrong previous run address for %s %s; want %s", instAddr, got, want) 6145 } 6146 if got, want := change.Action, plans.Delete; got != want { 6147 t.Errorf("wrong action for %s %s; want %s", instAddr, got, want) 6148 } 6149 if got, want := change.ActionReason, plans.ResourceInstanceDeleteBecauseWrongRepetition; got != want { 6150 t.Errorf("wrong action reason for %s %s; want %s", instAddr, got, want) 6151 } 6152 }) 6153 t.Run("test_instance.b", func(t *testing.T) { 6154 instAddr := mustResourceInstanceAddr("test_instance.b") 6155 change := plan.Changes.ResourceInstance(instAddr) 6156 if change == nil { 6157 t.Fatalf("no planned change for %s", instAddr) 6158 } 6159 if got, want := change.PrevRunAddr, instAddr; !want.Equal(got) { 6160 t.Errorf("wrong previous run address for %s %s; want %s", instAddr, got, want) 6161 } 6162 if got, want := change.Action, plans.Delete; got != want { 6163 t.Errorf("wrong action for %s %s; want %s", instAddr, got, want) 6164 } 6165 if got, want := change.ActionReason, plans.ResourceInstanceDeleteBecauseWrongRepetition; got != want { 6166 t.Errorf("wrong action reason for %s %s; want %s", instAddr, got, want) 6167 } 6168 }) 6169 } 6170 6171 func TestContext2Plan_targetedModuleInstance(t *testing.T) { 6172 m := testModule(t, "plan-targeted") 6173 p := testProvider("aws") 6174 p.PlanResourceChangeFn = testDiffFn 6175 ctx := testContext2(t, &ContextOpts{ 6176 Providers: map[addrs.Provider]providers.Factory{ 6177 addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p), 6178 }, 6179 }) 6180 6181 plan, diags := ctx.Plan(m, states.NewState(), &PlanOpts{ 6182 Mode: plans.NormalMode, 6183 Targets: []addrs.Targetable{ 6184 addrs.RootModuleInstance.Child("mod", addrs.IntKey(0)), 6185 }, 6186 }) 6187 if diags.HasErrors() { 6188 t.Fatalf("unexpected errors: %s", diags.Err()) 6189 } 6190 schema := p.GetProviderSchemaResponse.ResourceTypes["aws_instance"].Block 6191 ty := schema.ImpliedType() 6192 6193 if len(plan.Changes.Resources) != 1 { 6194 t.Fatal("expected 1 changes, got", len(plan.Changes.Resources)) 6195 } 6196 6197 for _, res := range plan.Changes.Resources { 6198 ric, err := res.Decode(ty) 6199 if err != nil { 6200 t.Fatal(err) 6201 } 6202 6203 switch i := ric.Addr.String(); i { 6204 case "module.mod[0].aws_instance.foo": 6205 if res.Action != plans.Create { 6206 t.Fatalf("resource %s should be created", i) 6207 } 6208 checkVals(t, objectVal(t, schema, map[string]cty.Value{ 6209 "id": cty.UnknownVal(cty.String), 6210 "num": cty.NumberIntVal(2), 6211 "type": cty.UnknownVal(cty.String), 6212 }), ric.After) 6213 default: 6214 t.Fatal("unknown instance:", i) 6215 } 6216 } 6217 } 6218 6219 func TestContext2Plan_dataRefreshedInPlan(t *testing.T) { 6220 m := testModuleInline(t, map[string]string{ 6221 "main.tf": ` 6222 data "test_data_source" "d" { 6223 } 6224 `}) 6225 6226 p := testProvider("test") 6227 p.ReadDataSourceResponse = &providers.ReadDataSourceResponse{ 6228 State: cty.ObjectVal(map[string]cty.Value{ 6229 "id": cty.StringVal("this"), 6230 "foo": cty.NullVal(cty.String), 6231 }), 6232 } 6233 6234 ctx := testContext2(t, &ContextOpts{ 6235 Providers: map[addrs.Provider]providers.Factory{ 6236 addrs.NewDefaultProvider("test"): testProviderFuncFixed(p), 6237 }, 6238 }) 6239 6240 plan, diags := ctx.Plan(m, states.NewState(), DefaultPlanOpts) 6241 if diags.HasErrors() { 6242 t.Fatal(diags.ErrWithWarnings()) 6243 } 6244 6245 d := plan.PriorState.ResourceInstance(mustResourceInstanceAddr("data.test_data_source.d")) 6246 if d == nil || d.Current == nil { 6247 t.Fatal("data.test_data_source.d not found in state:", plan.PriorState) 6248 } 6249 6250 if d.Current.Status != states.ObjectReady { 6251 t.Fatal("expected data.test_data_source.d to be fully read in refreshed state, got status", d.Current.Status) 6252 } 6253 } 6254 6255 func TestContext2Plan_dataReferencesResource(t *testing.T) { 6256 p := testProvider("test") 6257 6258 p.ReadDataSourceFn = func(req providers.ReadDataSourceRequest) (resp providers.ReadDataSourceResponse) { 6259 resp.Diagnostics = resp.Diagnostics.Append(fmt.Errorf("data source should not be read")) 6260 return resp 6261 } 6262 6263 m := testModuleInline(t, map[string]string{ 6264 "main.tf": ` 6265 locals { 6266 x = "value" 6267 } 6268 6269 resource "test_resource" "a" { 6270 value = local.x 6271 } 6272 6273 // test_resource.a.value can be resolved during plan, but the reference implies 6274 // that the data source should wait until the resource is created. 6275 data "test_data_source" "d" { 6276 foo = test_resource.a.value 6277 } 6278 6279 // ensure referencing an indexed instance that has not yet created will also 6280 // delay reading the data source 6281 resource "test_resource" "b" { 6282 count = 2 6283 value = local.x 6284 } 6285 6286 data "test_data_source" "e" { 6287 foo = test_resource.b[0].value 6288 } 6289 `}) 6290 6291 ctx := testContext2(t, &ContextOpts{ 6292 Providers: map[addrs.Provider]providers.Factory{ 6293 addrs.NewDefaultProvider("test"): testProviderFuncFixed(p), 6294 }, 6295 }) 6296 6297 _, diags := ctx.Plan(m, states.NewState(), DefaultPlanOpts) 6298 assertNoErrors(t, diags) 6299 } 6300 6301 func TestContext2Plan_skipRefresh(t *testing.T) { 6302 p := testProvider("test") 6303 p.PlanResourceChangeFn = testDiffFn 6304 6305 m := testModuleInline(t, map[string]string{ 6306 "main.tf": ` 6307 resource "test_instance" "a" { 6308 } 6309 `}) 6310 6311 state := states.NewState() 6312 root := state.EnsureModule(addrs.RootModuleInstance) 6313 root.SetResourceInstanceCurrent( 6314 mustResourceInstanceAddr("test_instance.a").Resource, 6315 &states.ResourceInstanceObjectSrc{ 6316 Status: states.ObjectReady, 6317 AttrsJSON: []byte(`{"id":"a","type":"test_instance"}`), 6318 Dependencies: []addrs.ConfigResource{}, 6319 }, 6320 mustProviderConfig(`provider["registry.terraform.io/hashicorp/test"]`), 6321 ) 6322 6323 ctx := testContext2(t, &ContextOpts{ 6324 Providers: map[addrs.Provider]providers.Factory{ 6325 addrs.NewDefaultProvider("test"): testProviderFuncFixed(p), 6326 }, 6327 }) 6328 6329 plan, diags := ctx.Plan(m, state, &PlanOpts{ 6330 Mode: plans.NormalMode, 6331 SkipRefresh: true, 6332 }) 6333 assertNoErrors(t, diags) 6334 6335 if p.ReadResourceCalled { 6336 t.Fatal("Resource should not have been refreshed") 6337 } 6338 6339 for _, c := range plan.Changes.Resources { 6340 if c.Action != plans.NoOp { 6341 t.Fatalf("expected no changes, got %s for %q", c.Action, c.Addr) 6342 } 6343 } 6344 } 6345 6346 func TestContext2Plan_dataInModuleDependsOn(t *testing.T) { 6347 p := testProvider("test") 6348 6349 readDataSourceB := false 6350 p.ReadDataSourceFn = func(req providers.ReadDataSourceRequest) (resp providers.ReadDataSourceResponse) { 6351 cfg := req.Config.AsValueMap() 6352 foo := cfg["foo"].AsString() 6353 6354 cfg["id"] = cty.StringVal("ID") 6355 cfg["foo"] = cty.StringVal("new") 6356 6357 if foo == "b" { 6358 readDataSourceB = true 6359 } 6360 6361 resp.State = cty.ObjectVal(cfg) 6362 return resp 6363 } 6364 6365 m := testModuleInline(t, map[string]string{ 6366 "main.tf": ` 6367 module "a" { 6368 source = "./mod_a" 6369 } 6370 6371 module "b" { 6372 source = "./mod_b" 6373 depends_on = [module.a] 6374 }`, 6375 "mod_a/main.tf": ` 6376 data "test_data_source" "a" { 6377 foo = "a" 6378 }`, 6379 "mod_b/main.tf": ` 6380 data "test_data_source" "b" { 6381 foo = "b" 6382 }`, 6383 }) 6384 6385 ctx := testContext2(t, &ContextOpts{ 6386 Providers: map[addrs.Provider]providers.Factory{ 6387 addrs.NewDefaultProvider("test"): testProviderFuncFixed(p), 6388 }, 6389 }) 6390 6391 _, diags := ctx.Plan(m, states.NewState(), DefaultPlanOpts) 6392 assertNoErrors(t, diags) 6393 6394 // The change to data source a should not prevent data source b from being 6395 // read. 6396 if !readDataSourceB { 6397 t.Fatal("data source b was not read during plan") 6398 } 6399 } 6400 6401 func TestContext2Plan_rpcDiagnostics(t *testing.T) { 6402 m := testModuleInline(t, map[string]string{ 6403 "main.tf": ` 6404 resource "test_instance" "a" { 6405 } 6406 `, 6407 }) 6408 6409 p := testProvider("test") 6410 p.PlanResourceChangeFn = func(req providers.PlanResourceChangeRequest) providers.PlanResourceChangeResponse { 6411 resp := testDiffFn(req) 6412 resp.Diagnostics = resp.Diagnostics.Append(tfdiags.SimpleWarning("don't frobble")) 6413 return resp 6414 } 6415 6416 p.GetProviderSchemaResponse = getProviderSchemaResponseFromProviderSchema(&ProviderSchema{ 6417 ResourceTypes: map[string]*configschema.Block{ 6418 "test_instance": { 6419 Attributes: map[string]*configschema.Attribute{ 6420 "id": {Type: cty.String, Computed: true}, 6421 }, 6422 }, 6423 }, 6424 }) 6425 6426 ctx := testContext2(t, &ContextOpts{ 6427 Providers: map[addrs.Provider]providers.Factory{ 6428 addrs.NewDefaultProvider("test"): testProviderFuncFixed(p), 6429 }, 6430 }) 6431 _, diags := ctx.Plan(m, states.NewState(), DefaultPlanOpts) 6432 if diags.HasErrors() { 6433 t.Fatal(diags.Err()) 6434 } 6435 6436 if len(diags) == 0 { 6437 t.Fatal("expected warnings") 6438 } 6439 6440 for _, d := range diags { 6441 des := d.Description().Summary 6442 if !strings.Contains(des, "frobble") { 6443 t.Fatalf(`expected frobble, got %q`, des) 6444 } 6445 } 6446 } 6447 6448 // ignore_changes needs to be re-applied to the planned value for provider 6449 // using the LegacyTypeSystem 6450 func TestContext2Plan_legacyProviderIgnoreChanges(t *testing.T) { 6451 m := testModuleInline(t, map[string]string{ 6452 "main.tf": ` 6453 resource "test_instance" "a" { 6454 lifecycle { 6455 ignore_changes = [data] 6456 } 6457 } 6458 `, 6459 }) 6460 6461 p := testProvider("test") 6462 p.PlanResourceChangeFn = func(req providers.PlanResourceChangeRequest) (resp providers.PlanResourceChangeResponse) { 6463 m := req.ProposedNewState.AsValueMap() 6464 // this provider "hashes" the data attribute as bar 6465 m["data"] = cty.StringVal("bar") 6466 6467 resp.PlannedState = cty.ObjectVal(m) 6468 resp.LegacyTypeSystem = true 6469 return resp 6470 } 6471 6472 p.GetProviderSchemaResponse = getProviderSchemaResponseFromProviderSchema(&ProviderSchema{ 6473 ResourceTypes: map[string]*configschema.Block{ 6474 "test_instance": { 6475 Attributes: map[string]*configschema.Attribute{ 6476 "id": {Type: cty.String, Computed: true}, 6477 "data": {Type: cty.String, Optional: true}, 6478 }, 6479 }, 6480 }, 6481 }) 6482 6483 state := states.NewState() 6484 root := state.EnsureModule(addrs.RootModuleInstance) 6485 root.SetResourceInstanceCurrent( 6486 mustResourceInstanceAddr("test_instance.a").Resource, 6487 &states.ResourceInstanceObjectSrc{ 6488 Status: states.ObjectReady, 6489 AttrsJSON: []byte(`{"id":"a","data":"foo"}`), 6490 Dependencies: []addrs.ConfigResource{}, 6491 }, 6492 mustProviderConfig(`provider["registry.terraform.io/hashicorp/test"]`), 6493 ) 6494 6495 ctx := testContext2(t, &ContextOpts{ 6496 Providers: map[addrs.Provider]providers.Factory{ 6497 addrs.NewDefaultProvider("test"): testProviderFuncFixed(p), 6498 }, 6499 }) 6500 plan, diags := ctx.Plan(m, state, DefaultPlanOpts) 6501 if diags.HasErrors() { 6502 t.Fatal(diags.Err()) 6503 } 6504 6505 for _, c := range plan.Changes.Resources { 6506 if c.Action != plans.NoOp { 6507 t.Fatalf("expected no changes, got %s for %q", c.Action, c.Addr) 6508 } 6509 } 6510 } 6511 6512 func TestContext2Plan_validateIgnoreAll(t *testing.T) { 6513 m := testModuleInline(t, map[string]string{ 6514 "main.tf": ` 6515 resource "test_instance" "a" { 6516 lifecycle { 6517 ignore_changes = all 6518 } 6519 } 6520 `, 6521 }) 6522 6523 p := testProvider("test") 6524 p.GetProviderSchemaResponse = getProviderSchemaResponseFromProviderSchema(&ProviderSchema{ 6525 ResourceTypes: map[string]*configschema.Block{ 6526 "test_instance": { 6527 Attributes: map[string]*configschema.Attribute{ 6528 "id": {Type: cty.String, Computed: true}, 6529 "data": {Type: cty.String, Optional: true}, 6530 }, 6531 }, 6532 }, 6533 }) 6534 p.ValidateResourceConfigFn = func(req providers.ValidateResourceConfigRequest) providers.ValidateResourceConfigResponse { 6535 var diags tfdiags.Diagnostics 6536 if req.TypeName == "test_instance" { 6537 if !req.Config.GetAttr("id").IsNull() { 6538 diags = diags.Append(errors.New("id cannot be set in config")) 6539 } 6540 } 6541 return providers.ValidateResourceConfigResponse{ 6542 Diagnostics: diags, 6543 } 6544 } 6545 6546 state := states.NewState() 6547 root := state.EnsureModule(addrs.RootModuleInstance) 6548 root.SetResourceInstanceCurrent( 6549 mustResourceInstanceAddr("test_instance.a").Resource, 6550 &states.ResourceInstanceObjectSrc{ 6551 Status: states.ObjectReady, 6552 AttrsJSON: []byte(`{"id":"a","data":"foo"}`), 6553 Dependencies: []addrs.ConfigResource{}, 6554 }, 6555 mustProviderConfig(`provider["registry.terraform.io/hashicorp/test"]`), 6556 ) 6557 6558 ctx := testContext2(t, &ContextOpts{ 6559 Providers: map[addrs.Provider]providers.Factory{ 6560 addrs.NewDefaultProvider("test"): testProviderFuncFixed(p), 6561 }, 6562 }) 6563 _, diags := ctx.Plan(m, state, DefaultPlanOpts) 6564 if diags.HasErrors() { 6565 t.Fatal(diags.Err()) 6566 } 6567 } 6568 6569 func TestContext2Plan_dataRemovalNoProvider(t *testing.T) { 6570 m := testModuleInline(t, map[string]string{ 6571 "main.tf": ` 6572 resource "test_instance" "a" { 6573 } 6574 `, 6575 }) 6576 6577 p := testProvider("test") 6578 6579 state := states.NewState() 6580 root := state.EnsureModule(addrs.RootModuleInstance) 6581 root.SetResourceInstanceCurrent( 6582 mustResourceInstanceAddr("test_instance.a").Resource, 6583 &states.ResourceInstanceObjectSrc{ 6584 Status: states.ObjectReady, 6585 AttrsJSON: []byte(`{"id":"a","data":"foo"}`), 6586 Dependencies: []addrs.ConfigResource{}, 6587 }, 6588 mustProviderConfig(`provider["registry.terraform.io/hashicorp/test"]`), 6589 ) 6590 6591 // the provider for this data source is no longer in the config, but that 6592 // should not matter for state removal. 6593 root.SetResourceInstanceCurrent( 6594 mustResourceInstanceAddr("data.test_data_source.d").Resource, 6595 &states.ResourceInstanceObjectSrc{ 6596 Status: states.ObjectReady, 6597 AttrsJSON: []byte(`{"id":"d"}`), 6598 Dependencies: []addrs.ConfigResource{}, 6599 }, 6600 mustProviderConfig(`provider["registry.terraform.io/local/test"]`), 6601 ) 6602 6603 ctx := testContext2(t, &ContextOpts{ 6604 Providers: map[addrs.Provider]providers.Factory{ 6605 addrs.NewDefaultProvider("test"): testProviderFuncFixed(p), 6606 // We still need to be able to locate the provider to decode the 6607 // state, since we do not know during init that this provider is 6608 // only used for an orphaned data source. 6609 addrs.NewProvider("registry.terraform.io", "local", "test"): testProviderFuncFixed(p), 6610 }, 6611 }) 6612 _, diags := ctx.Plan(m, state, DefaultPlanOpts) 6613 if diags.HasErrors() { 6614 t.Fatal(diags.Err()) 6615 } 6616 } 6617 6618 func TestContext2Plan_noSensitivityChange(t *testing.T) { 6619 m := testModuleInline(t, map[string]string{ 6620 "main.tf": ` 6621 variable "sensitive_var" { 6622 default = "hello" 6623 sensitive = true 6624 } 6625 6626 resource "test_resource" "foo" { 6627 value = var.sensitive_var 6628 sensitive_value = var.sensitive_var 6629 }`, 6630 }) 6631 6632 p := testProvider("test") 6633 6634 ctx := testContext2(t, &ContextOpts{ 6635 Providers: map[addrs.Provider]providers.Factory{ 6636 addrs.NewDefaultProvider("test"): testProviderFuncFixed(p), 6637 }, 6638 }) 6639 state := states.BuildState(func(s *states.SyncState) { 6640 s.SetResourceInstanceCurrent( 6641 addrs.Resource{ 6642 Mode: addrs.ManagedResourceMode, 6643 Type: "test_resource", 6644 Name: "foo", 6645 }.Instance(addrs.NoKey).Absolute(addrs.RootModuleInstance), 6646 &states.ResourceInstanceObjectSrc{ 6647 Status: states.ObjectReady, 6648 AttrsJSON: []byte(`{"id":"foo", "value":"hello", "sensitive_value":"hello"}`), 6649 AttrSensitivePaths: []cty.PathValueMarks{ 6650 {Path: cty.Path{cty.GetAttrStep{Name: "value"}}, Marks: cty.NewValueMarks(marks.Sensitive)}, 6651 {Path: cty.Path{cty.GetAttrStep{Name: "sensitive_value"}}, Marks: cty.NewValueMarks(marks.Sensitive)}, 6652 }, 6653 }, 6654 addrs.AbsProviderConfig{ 6655 Provider: addrs.NewDefaultProvider("test"), 6656 Module: addrs.RootModule, 6657 }, 6658 ) 6659 }) 6660 plan, diags := ctx.Plan(m, state, DefaultPlanOpts) 6661 if diags.HasErrors() { 6662 t.Fatal(diags.Err()) 6663 } 6664 6665 for _, c := range plan.Changes.Resources { 6666 if c.Action != plans.NoOp { 6667 t.Fatalf("expected no changes, got %s for %q", c.Action, c.Addr) 6668 } 6669 } 6670 } 6671 6672 func TestContext2Plan_variableCustomValidationsSensitive(t *testing.T) { 6673 m := testModule(t, "validate-variable-custom-validations-child-sensitive") 6674 6675 p := testProvider("test") 6676 ctx := testContext2(t, &ContextOpts{ 6677 Providers: map[addrs.Provider]providers.Factory{ 6678 addrs.NewDefaultProvider("test"): testProviderFuncFixed(p), 6679 }, 6680 }) 6681 6682 _, diags := ctx.Plan(m, states.NewState(), DefaultPlanOpts) 6683 if !diags.HasErrors() { 6684 t.Fatal("succeeded; want errors") 6685 } 6686 if got, want := diags.Err().Error(), `Invalid value for variable: Value must not be "nope".`; !strings.Contains(got, want) { 6687 t.Fatalf("wrong error:\ngot: %s\nwant: message containing %q", got, want) 6688 } 6689 } 6690 6691 func TestContext2Plan_nullOutputNoOp(t *testing.T) { 6692 // this should always plan a NoOp change for the output 6693 m := testModuleInline(t, map[string]string{ 6694 "main.tf": ` 6695 output "planned" { 6696 value = false ? 1 : null 6697 } 6698 `, 6699 }) 6700 6701 ctx := testContext2(t, &ContextOpts{}) 6702 state := states.BuildState(func(s *states.SyncState) { 6703 r := s.Module(addrs.RootModuleInstance) 6704 r.SetOutputValue("planned", cty.NullVal(cty.DynamicPseudoType), false) 6705 }) 6706 plan, diags := ctx.Plan(m, state, DefaultPlanOpts) 6707 if diags.HasErrors() { 6708 t.Fatal(diags.Err()) 6709 } 6710 6711 for _, c := range plan.Changes.Outputs { 6712 if c.Action != plans.NoOp { 6713 t.Fatalf("expected no changes, got %s for %q", c.Action, c.Addr) 6714 } 6715 } 6716 } 6717 6718 func TestContext2Plan_createOutput(t *testing.T) { 6719 // this should always plan a NoOp change for the output 6720 m := testModuleInline(t, map[string]string{ 6721 "main.tf": ` 6722 output "planned" { 6723 value = 1 6724 } 6725 `, 6726 }) 6727 6728 ctx := testContext2(t, &ContextOpts{}) 6729 plan, diags := ctx.Plan(m, states.NewState(), DefaultPlanOpts) 6730 if diags.HasErrors() { 6731 t.Fatal(diags.Err()) 6732 } 6733 6734 for _, c := range plan.Changes.Outputs { 6735 if c.Action != plans.Create { 6736 t.Fatalf("expected Create change, got %s for %q", c.Action, c.Addr) 6737 } 6738 } 6739 } 6740 6741 //////////////////////////////////////////////////////////////////////////////// 6742 // NOTE: Due to the size of this file, new tests should be added to 6743 // context_plan2_test.go. 6744 ////////////////////////////////////////////////////////////////////////////////