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