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