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