github.com/opentofu/opentofu@v1.7.1/internal/tofu/context_apply_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 "encoding/json" 11 "errors" 12 "fmt" 13 "log" 14 "reflect" 15 "runtime" 16 "sort" 17 "strings" 18 "sync" 19 "sync/atomic" 20 "testing" 21 "time" 22 23 "github.com/davecgh/go-spew/spew" 24 "github.com/go-test/deep" 25 "github.com/google/go-cmp/cmp" 26 "github.com/zclconf/go-cty/cty" 27 "github.com/zclconf/go-cty/cty/gocty" 28 29 "github.com/opentofu/opentofu/internal/addrs" 30 "github.com/opentofu/opentofu/internal/configs" 31 "github.com/opentofu/opentofu/internal/configs/configschema" 32 "github.com/opentofu/opentofu/internal/configs/hcl2shim" 33 "github.com/opentofu/opentofu/internal/lang/marks" 34 "github.com/opentofu/opentofu/internal/plans" 35 "github.com/opentofu/opentofu/internal/providers" 36 "github.com/opentofu/opentofu/internal/provisioners" 37 "github.com/opentofu/opentofu/internal/states" 38 "github.com/opentofu/opentofu/internal/tfdiags" 39 ) 40 41 func TestContext2Apply_basic(t *testing.T) { 42 m := testModule(t, "apply-good") 43 p := testProvider("aws") 44 p.PlanResourceChangeFn = testDiffFn 45 p.ApplyResourceChangeFn = testApplyFn 46 ctx := testContext2(t, &ContextOpts{ 47 Providers: map[addrs.Provider]providers.Factory{ 48 addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p), 49 }, 50 }) 51 52 plan, diags := ctx.Plan(m, states.NewState(), DefaultPlanOpts) 53 assertNoErrors(t, diags) 54 55 state, diags := ctx.Apply(plan, m) 56 if diags.HasErrors() { 57 t.Fatalf("diags: %s", diags.Err()) 58 } 59 60 mod := state.RootModule() 61 if len(mod.Resources) < 2 { 62 t.Fatalf("bad: %#v", mod.Resources) 63 } 64 65 actual := strings.TrimSpace(state.String()) 66 expected := strings.TrimSpace(testTofuApplyStr) 67 if actual != expected { 68 t.Fatalf("wrong result\n\ngot:\n%s\n\nwant:\n%s", actual, expected) 69 } 70 } 71 72 func TestContext2Apply_stop(t *testing.T) { 73 t.Parallel() 74 75 m := testModule(t, "apply-stop") 76 stopCh := make(chan struct{}) 77 waitCh := make(chan struct{}) 78 stoppedCh := make(chan struct{}) 79 stopCalled := uint32(0) 80 applyStopped := uint32(0) 81 p := &MockProvider{ 82 GetProviderSchemaResponse: &providers.GetProviderSchemaResponse{ 83 ResourceTypes: map[string]providers.Schema{ 84 "indefinite": { 85 Version: 1, 86 Block: &configschema.Block{ 87 Attributes: map[string]*configschema.Attribute{ 88 "result": { 89 Type: cty.String, 90 Computed: true, 91 }, 92 }, 93 }, 94 }, 95 }, 96 }, 97 PlanResourceChangeFn: func(prcr providers.PlanResourceChangeRequest) providers.PlanResourceChangeResponse { 98 log.Printf("[TRACE] TestContext2Apply_stop: no-op PlanResourceChange") 99 return providers.PlanResourceChangeResponse{ 100 PlannedState: cty.ObjectVal(map[string]cty.Value{ 101 "result": cty.UnknownVal(cty.String), 102 }), 103 } 104 }, 105 ApplyResourceChangeFn: func(arcr providers.ApplyResourceChangeRequest) providers.ApplyResourceChangeResponse { 106 // This will unblock the main test code once we reach this 107 // point, so that it'll then be guaranteed to call Stop 108 // while we're waiting in here. 109 close(waitCh) 110 111 log.Printf("[TRACE] TestContext2Apply_stop: ApplyResourceChange waiting for Stop call") 112 // This will block until StopFn closes this channel below. 113 <-stopCh 114 atomic.AddUint32(&applyStopped, 1) 115 // This unblocks StopFn below, thereby acknowledging the request 116 // to stop. 117 close(stoppedCh) 118 return providers.ApplyResourceChangeResponse{ 119 NewState: cty.ObjectVal(map[string]cty.Value{ 120 "result": cty.StringVal("complete"), 121 }), 122 } 123 }, 124 StopFn: func() error { 125 // Closing this channel will unblock the channel read in 126 // ApplyResourceChangeFn above. 127 log.Printf("[TRACE] TestContext2Apply_stop: Stop called") 128 atomic.AddUint32(&stopCalled, 1) 129 close(stopCh) 130 // This will block until ApplyResourceChange has reacted to 131 // being stopped. 132 log.Printf("[TRACE] TestContext2Apply_stop: Waiting for ApplyResourceChange to react to being stopped") 133 <-stoppedCh 134 log.Printf("[TRACE] TestContext2Apply_stop: Stop is completing") 135 return nil 136 }, 137 } 138 139 hook := &testHook{} 140 ctx := testContext2(t, &ContextOpts{ 141 Hooks: []Hook{hook}, 142 Providers: map[addrs.Provider]providers.Factory{ 143 addrs.MustParseProviderSourceString("terraform.io/test/indefinite"): testProviderFuncFixed(p), 144 }, 145 }) 146 147 plan, diags := ctx.Plan(m, states.NewState(), DefaultPlanOpts) 148 assertNoErrors(t, diags) 149 150 // We'll reset the hook events before we apply because we only care about 151 // the apply-time events. 152 hook.Calls = hook.Calls[:0] 153 154 // We'll apply in the background so that we can call Stop in the foreground. 155 stateCh := make(chan *states.State) 156 go func(plan *plans.Plan) { 157 state, _ := ctx.Apply(plan, m) 158 stateCh <- state 159 }(plan) 160 161 // We'll wait until the provider signals that we've reached the 162 // ApplyResourceChange function, so we can guarantee the expected 163 // order of operations so our hook events below will always match. 164 t.Log("waiting for the apply phase to get started") 165 <-waitCh 166 167 // This will block until the apply operation has unwound, so we should 168 // be able to observe all of the apply side-effects afterwards. 169 t.Log("waiting for ctx.Stop to return") 170 ctx.Stop() 171 172 t.Log("waiting for apply goroutine to return state") 173 state := <-stateCh 174 175 t.Log("apply is all complete") 176 if state == nil { 177 t.Fatalf("final state is nil") 178 } 179 180 if got, want := atomic.LoadUint32(&stopCalled), uint32(1); got != want { 181 t.Errorf("provider's Stop method was not called") 182 } 183 if got, want := atomic.LoadUint32(&applyStopped), uint32(1); got != want { 184 // This should not happen if things are working correctly but this is 185 // to catch weird situations such as if a bug in this test causes us 186 // to inadvertently stop OpenTofu before it reaches te apply phase, 187 // or if the apply operation fails in a way that causes it not to reach 188 // the ApplyResourceChange function. 189 t.Errorf("somehow provider's ApplyResourceChange didn't react to being stopped") 190 } 191 192 // Because we interrupted the apply phase while applying the resource, 193 // we should have halted immediately after we finished visiting that 194 // resource. We don't visit indefinite.bar at all. 195 gotEvents := hook.Calls 196 wantEvents := []*testHookCall{ 197 {"PreDiff", "indefinite.foo"}, 198 {"PostDiff", "indefinite.foo"}, 199 {"PreApply", "indefinite.foo"}, 200 {"PostApply", "indefinite.foo"}, 201 {"PostStateUpdate", ""}, // State gets updated one more time to include the apply result. 202 } 203 // The "Stopping" event gets sent to the hook asynchronously from the others 204 // because it is triggered in the ctx.Stop call above, rather than from 205 // the goroutine where ctx.Apply was running, and therefore it doesn't 206 // appear in a guaranteed position in gotEvents. We already checked above 207 // that the provider's Stop method was called, so we'll just strip that 208 // event out of our gotEvents. 209 seenStopped := false 210 for i, call := range gotEvents { 211 if call.Action == "Stopping" { 212 seenStopped = true 213 // We'll shift up everything else in the slice to create the 214 // effect of the Stopping event not having been present at all, 215 // which should therefore make this slice match "wantEvents". 216 copy(gotEvents[i:], gotEvents[i+1:]) 217 gotEvents = gotEvents[:len(gotEvents)-1] 218 break 219 } 220 } 221 if diff := cmp.Diff(wantEvents, gotEvents); diff != "" { 222 t.Errorf("wrong hook events\n%s", diff) 223 } 224 if !seenStopped { 225 t.Errorf("'Stopping' event did not get sent to the hook") 226 } 227 228 rov := state.OutputValue(addrs.OutputValue{Name: "result"}.Absolute(addrs.RootModuleInstance)) 229 if rov != nil && rov.Value != cty.NilVal && !rov.Value.IsNull() { 230 t.Errorf("'result' output value unexpectedly populated: %#v", rov.Value) 231 } 232 233 resourceAddr := addrs.Resource{ 234 Mode: addrs.ManagedResourceMode, 235 Type: "indefinite", 236 Name: "foo", 237 }.Instance(addrs.NoKey).Absolute(addrs.RootModuleInstance) 238 rv := state.ResourceInstance(resourceAddr) 239 if rv == nil || rv.Current == nil { 240 t.Fatalf("no state entry for %s", resourceAddr) 241 } 242 243 resourceAddr.Resource.Resource.Name = "bar" 244 rv = state.ResourceInstance(resourceAddr) 245 if rv != nil && rv.Current != nil { 246 t.Fatalf("unexpected state entry for %s", resourceAddr) 247 } 248 } 249 250 func TestContext2Apply_unstable(t *testing.T) { 251 // This tests behavior when the configuration contains an unstable value, 252 // such as the result of uuid() or timestamp(), where each call produces 253 // a different result. 254 // 255 // This is an important case to test because we need to ensure that 256 // we don't re-call the function during the apply phase: the value should 257 // be fixed during plan 258 259 m := testModule(t, "apply-unstable") 260 p := testProvider("test") 261 p.PlanResourceChangeFn = testDiffFn 262 ctx := testContext2(t, &ContextOpts{ 263 Providers: map[addrs.Provider]providers.Factory{ 264 addrs.NewDefaultProvider("test"): testProviderFuncFixed(p), 265 }, 266 }) 267 268 plan, diags := ctx.Plan(m, states.NewState(), DefaultPlanOpts) 269 if diags.HasErrors() { 270 t.Fatalf("unexpected error during Plan: %s", diags.Err()) 271 } 272 273 addr := addrs.Resource{ 274 Mode: addrs.ManagedResourceMode, 275 Type: "test_resource", 276 Name: "foo", 277 }.Instance(addrs.NoKey).Absolute(addrs.RootModuleInstance) 278 schema := p.GetProviderSchemaResponse.ResourceTypes["test_resource"].Block 279 rds := plan.Changes.ResourceInstance(addr) 280 rd, err := rds.Decode(schema.ImpliedType()) 281 if err != nil { 282 t.Fatal(err) 283 } 284 if rd.After.GetAttr("random").IsKnown() { 285 t.Fatalf("Attribute 'random' has known value %#v; should be unknown in plan", rd.After.GetAttr("random")) 286 } 287 288 state, diags := ctx.Apply(plan, m) 289 if diags.HasErrors() { 290 t.Fatalf("unexpected error during Apply: %s", diags.Err()) 291 } 292 293 mod := state.Module(addr.Module) 294 rss := state.ResourceInstance(addr) 295 296 if len(mod.Resources) != 1 { 297 t.Fatalf("wrong number of resources %d; want 1", len(mod.Resources)) 298 } 299 300 rs, err := rss.Current.Decode(schema.ImpliedType()) 301 if err != nil { 302 t.Fatalf("decode error: %v", err) 303 } 304 got := rs.Value.GetAttr("random") 305 if !got.IsKnown() { 306 t.Fatalf("random is still unknown after apply") 307 } 308 if got, want := len(got.AsString()), 36; got != want { 309 t.Fatalf("random string has wrong length %d; want %d", got, want) 310 } 311 } 312 313 func TestContext2Apply_escape(t *testing.T) { 314 m := testModule(t, "apply-escape") 315 p := testProvider("aws") 316 p.PlanResourceChangeFn = testDiffFn 317 p.ApplyResourceChangeFn = testApplyFn 318 ctx := testContext2(t, &ContextOpts{ 319 Providers: map[addrs.Provider]providers.Factory{ 320 addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p), 321 }, 322 }) 323 324 plan, diags := ctx.Plan(m, states.NewState(), DefaultPlanOpts) 325 assertNoErrors(t, diags) 326 327 state, diags := ctx.Apply(plan, m) 328 if diags.HasErrors() { 329 t.Fatalf("diags: %s", diags.Err()) 330 } 331 332 checkStateString(t, state, ` 333 aws_instance.bar: 334 ID = foo 335 provider = provider["registry.opentofu.org/hashicorp/aws"] 336 foo = "bar" 337 type = aws_instance 338 `) 339 } 340 341 func TestContext2Apply_resourceCountOneList(t *testing.T) { 342 m := testModule(t, "apply-resource-count-one-list") 343 p := testProvider("null") 344 p.PlanResourceChangeFn = testDiffFn 345 p.ApplyResourceChangeFn = testApplyFn 346 ctx := testContext2(t, &ContextOpts{ 347 Providers: map[addrs.Provider]providers.Factory{ 348 addrs.NewDefaultProvider("null"): testProviderFuncFixed(p), 349 }, 350 }) 351 352 plan, diags := ctx.Plan(m, states.NewState(), DefaultPlanOpts) 353 assertNoErrors(t, diags) 354 355 state, diags := ctx.Apply(plan, m) 356 assertNoDiagnostics(t, diags) 357 358 got := strings.TrimSpace(state.String()) 359 want := strings.TrimSpace(`null_resource.foo.0: 360 ID = foo 361 provider = provider["registry.opentofu.org/hashicorp/null"] 362 363 Outputs: 364 365 test = [foo]`) 366 if got != want { 367 t.Fatalf("got:\n%s\n\nwant:\n%s\n", got, want) 368 } 369 } 370 func TestContext2Apply_resourceCountZeroList(t *testing.T) { 371 m := testModule(t, "apply-resource-count-zero-list") 372 p := testProvider("null") 373 p.PlanResourceChangeFn = testDiffFn 374 ctx := testContext2(t, &ContextOpts{ 375 Providers: map[addrs.Provider]providers.Factory{ 376 addrs.NewDefaultProvider("null"): testProviderFuncFixed(p), 377 }, 378 }) 379 380 plan, diags := ctx.Plan(m, states.NewState(), DefaultPlanOpts) 381 assertNoErrors(t, diags) 382 383 state, diags := ctx.Apply(plan, m) 384 if diags.HasErrors() { 385 t.Fatalf("diags: %s", diags.Err()) 386 } 387 388 got := strings.TrimSpace(state.String()) 389 want := strings.TrimSpace(`<no state> 390 Outputs: 391 392 test = []`) 393 if got != want { 394 t.Fatalf("wrong state\n\ngot:\n%s\n\nwant:\n%s\n", got, want) 395 } 396 } 397 398 func TestContext2Apply_resourceDependsOnModule(t *testing.T) { 399 m := testModule(t, "apply-resource-depends-on-module") 400 p := testProvider("aws") 401 p.PlanResourceChangeFn = testDiffFn 402 403 // verify the apply happens in the correct order 404 var mu sync.Mutex 405 var order []string 406 407 p.ApplyResourceChangeFn = func(req providers.ApplyResourceChangeRequest) (resp providers.ApplyResourceChangeResponse) { 408 ami := req.PlannedState.GetAttr("ami").AsString() 409 switch ami { 410 case "child": 411 412 // make the child slower than the parent 413 time.Sleep(50 * time.Millisecond) 414 415 mu.Lock() 416 order = append(order, "child") 417 mu.Unlock() 418 case "parent": 419 mu.Lock() 420 order = append(order, "parent") 421 mu.Unlock() 422 } 423 424 return testApplyFn(req) 425 } 426 427 ctx := testContext2(t, &ContextOpts{ 428 Providers: map[addrs.Provider]providers.Factory{ 429 addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p), 430 }, 431 }) 432 433 plan, diags := ctx.Plan(m, states.NewState(), DefaultPlanOpts) 434 assertNoErrors(t, diags) 435 436 state, diags := ctx.Apply(plan, m) 437 if diags.HasErrors() { 438 t.Fatalf("diags: %s", diags.Err()) 439 } 440 441 if !reflect.DeepEqual(order, []string{"child", "parent"}) { 442 t.Fatal("resources applied out of order") 443 } 444 445 checkStateString(t, state, testTofuApplyResourceDependsOnModuleStr) 446 } 447 448 // Test that without a config, the Dependencies in the state are enough 449 // to maintain proper ordering. 450 func TestContext2Apply_resourceDependsOnModuleStateOnly(t *testing.T) { 451 m := testModule(t, "apply-resource-depends-on-module-empty") 452 p := testProvider("aws") 453 p.PlanResourceChangeFn = testDiffFn 454 455 state := states.NewState() 456 root := state.EnsureModule(addrs.RootModuleInstance) 457 root.SetResourceInstanceCurrent( 458 mustResourceInstanceAddr("aws_instance.a").Resource, 459 &states.ResourceInstanceObjectSrc{ 460 Status: states.ObjectReady, 461 AttrsJSON: []byte(`{"id":"parent"}`), 462 Dependencies: []addrs.ConfigResource{mustConfigResourceAddr("module.child.aws_instance.child")}, 463 }, 464 mustProviderConfig(`provider["registry.opentofu.org/hashicorp/aws"]`), 465 ) 466 child := state.EnsureModule(addrs.RootModuleInstance.Child("child", addrs.NoKey)) 467 child.SetResourceInstanceCurrent( 468 mustResourceInstanceAddr("aws_instance.child").Resource, 469 &states.ResourceInstanceObjectSrc{ 470 Status: states.ObjectReady, 471 AttrsJSON: []byte(`{"id":"child"}`), 472 }, 473 mustProviderConfig(`provider["registry.opentofu.org/hashicorp/aws"]`), 474 ) 475 476 { 477 // verify the apply happens in the correct order 478 var mu sync.Mutex 479 var order []string 480 481 p.ApplyResourceChangeFn = func(req providers.ApplyResourceChangeRequest) (resp providers.ApplyResourceChangeResponse) { 482 id := req.PriorState.GetAttr("id") 483 if id.IsKnown() && id.AsString() == "parent" { 484 // make the dep slower than the parent 485 time.Sleep(50 * time.Millisecond) 486 487 mu.Lock() 488 order = append(order, "child") 489 mu.Unlock() 490 } else { 491 mu.Lock() 492 order = append(order, "parent") 493 mu.Unlock() 494 } 495 496 return testApplyFn(req) 497 } 498 499 ctx := testContext2(t, &ContextOpts{ 500 Providers: map[addrs.Provider]providers.Factory{ 501 addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p), 502 }, 503 }) 504 505 plan, diags := ctx.Plan(m, state, DefaultPlanOpts) 506 assertNoErrors(t, diags) 507 508 state, diags := ctx.Apply(plan, m) 509 assertNoErrors(t, diags) 510 511 if !reflect.DeepEqual(order, []string{"child", "parent"}) { 512 t.Fatal("resources applied out of order") 513 } 514 515 checkStateString(t, state, "<no state>") 516 } 517 } 518 519 func TestContext2Apply_resourceDependsOnModuleDestroy(t *testing.T) { 520 m := testModule(t, "apply-resource-depends-on-module") 521 p := testProvider("aws") 522 p.PlanResourceChangeFn = testDiffFn 523 524 var globalState *states.State 525 { 526 ctx := testContext2(t, &ContextOpts{ 527 Providers: map[addrs.Provider]providers.Factory{ 528 addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p), 529 }, 530 }) 531 532 plan, diags := ctx.Plan(m, states.NewState(), DefaultPlanOpts) 533 assertNoErrors(t, diags) 534 535 state, diags := ctx.Apply(plan, m) 536 if diags.HasErrors() { 537 t.Fatalf("diags: %s", diags.Err()) 538 } 539 540 globalState = state 541 } 542 543 { 544 // Wait for the dependency, sleep, and verify the graph never 545 // called a child. 546 var called int32 547 var checked bool 548 p.ApplyResourceChangeFn = func(req providers.ApplyResourceChangeRequest) (resp providers.ApplyResourceChangeResponse) { 549 ami := req.PriorState.GetAttr("ami").AsString() 550 if ami == "parent" { 551 checked = true 552 553 // Sleep to allow parallel execution 554 time.Sleep(50 * time.Millisecond) 555 556 // Verify that called is 0 (dep not called) 557 if atomic.LoadInt32(&called) != 0 { 558 resp.Diagnostics = resp.Diagnostics.Append(fmt.Errorf("module child should not be called")) 559 return resp 560 } 561 } 562 563 atomic.AddInt32(&called, 1) 564 return testApplyFn(req) 565 } 566 567 ctx := testContext2(t, &ContextOpts{ 568 Providers: map[addrs.Provider]providers.Factory{ 569 addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p), 570 }, 571 }) 572 573 plan, diags := ctx.Plan(m, globalState, &PlanOpts{ 574 Mode: plans.DestroyMode, 575 }) 576 assertNoErrors(t, diags) 577 578 state, diags := ctx.Apply(plan, m) 579 if diags.HasErrors() { 580 t.Fatalf("diags: %s", diags.Err()) 581 } 582 583 if !checked { 584 t.Fatal("should check") 585 } 586 587 checkStateString(t, state, `<no state>`) 588 } 589 } 590 591 func TestContext2Apply_resourceDependsOnModuleGrandchild(t *testing.T) { 592 m := testModule(t, "apply-resource-depends-on-module-deep") 593 p := testProvider("aws") 594 p.PlanResourceChangeFn = testDiffFn 595 596 { 597 // Wait for the dependency, sleep, and verify the graph never 598 // called a child. 599 var called int32 600 var checked bool 601 p.ApplyResourceChangeFn = func(req providers.ApplyResourceChangeRequest) (resp providers.ApplyResourceChangeResponse) { 602 planned := req.PlannedState.AsValueMap() 603 if ami, ok := planned["ami"]; ok && ami.AsString() == "grandchild" { 604 checked = true 605 606 // Sleep to allow parallel execution 607 time.Sleep(50 * time.Millisecond) 608 609 // Verify that called is 0 (dep not called) 610 if atomic.LoadInt32(&called) != 0 { 611 resp.Diagnostics = resp.Diagnostics.Append(fmt.Errorf("aws_instance.a should not be called")) 612 return resp 613 } 614 } 615 616 atomic.AddInt32(&called, 1) 617 return testApplyFn(req) 618 } 619 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 assertNoErrors(t, diags) 628 629 state, diags := ctx.Apply(plan, m) 630 if diags.HasErrors() { 631 t.Fatalf("diags: %s", diags.Err()) 632 } 633 634 if !checked { 635 t.Fatal("should check") 636 } 637 638 checkStateString(t, state, testTofuApplyResourceDependsOnModuleDeepStr) 639 } 640 } 641 642 func TestContext2Apply_resourceDependsOnModuleInModule(t *testing.T) { 643 m := testModule(t, "apply-resource-depends-on-module-in-module") 644 p := testProvider("aws") 645 p.PlanResourceChangeFn = testDiffFn 646 647 { 648 // Wait for the dependency, sleep, and verify the graph never 649 // called a child. 650 var called int32 651 var checked bool 652 p.ApplyResourceChangeFn = func(req providers.ApplyResourceChangeRequest) (resp providers.ApplyResourceChangeResponse) { 653 planned := req.PlannedState.AsValueMap() 654 if ami, ok := planned["ami"]; ok && ami.AsString() == "grandchild" { 655 checked = true 656 657 // Sleep to allow parallel execution 658 time.Sleep(50 * time.Millisecond) 659 660 // Verify that called is 0 (dep not called) 661 if atomic.LoadInt32(&called) != 0 { 662 resp.Diagnostics = resp.Diagnostics.Append(fmt.Errorf("something else was applied before grandchild; grandchild should be first")) 663 return resp 664 } 665 } 666 667 atomic.AddInt32(&called, 1) 668 return testApplyFn(req) 669 } 670 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(), DefaultPlanOpts) 678 assertNoErrors(t, diags) 679 680 state, diags := ctx.Apply(plan, m) 681 if diags.HasErrors() { 682 t.Fatalf("diags: %s", diags.Err()) 683 } 684 685 if !checked { 686 t.Fatal("should check") 687 } 688 689 checkStateString(t, state, testTofuApplyResourceDependsOnModuleInModuleStr) 690 } 691 } 692 693 func TestContext2Apply_mapVarBetweenModules(t *testing.T) { 694 m := testModule(t, "apply-map-var-through-module") 695 p := testProvider("null") 696 p.PlanResourceChangeFn = testDiffFn 697 p.ApplyResourceChangeFn = testApplyFn 698 ctx := testContext2(t, &ContextOpts{ 699 Providers: map[addrs.Provider]providers.Factory{ 700 addrs.NewDefaultProvider("null"): testProviderFuncFixed(p), 701 }, 702 }) 703 704 plan, diags := ctx.Plan(m, states.NewState(), SimplePlanOpts(plans.NormalMode, testInputValuesUnset(m.Module.Variables))) 705 assertNoErrors(t, diags) 706 707 state, diags := ctx.Apply(plan, m) 708 if diags.HasErrors() { 709 t.Fatalf("diags: %s", diags.Err()) 710 } 711 712 actual := strings.TrimSpace(state.String()) 713 expected := strings.TrimSpace(`<no state> 714 Outputs: 715 716 amis_from_module = {eu-west-1:ami-789012 eu-west-2:ami-989484 us-west-1:ami-123456 us-west-2:ami-456789 } 717 718 module.test: 719 null_resource.noop: 720 ID = foo 721 provider = provider["registry.opentofu.org/hashicorp/null"] 722 723 Outputs: 724 725 amis_out = {eu-west-1:ami-789012 eu-west-2:ami-989484 us-west-1:ami-123456 us-west-2:ami-456789 }`) 726 if actual != expected { 727 t.Fatalf("expected: \n%s\n\ngot: \n%s\n", expected, actual) 728 } 729 } 730 731 func TestContext2Apply_refCount(t *testing.T) { 732 m := testModule(t, "apply-ref-count") 733 p := testProvider("aws") 734 p.PlanResourceChangeFn = testDiffFn 735 p.ApplyResourceChangeFn = testApplyFn 736 ctx := testContext2(t, &ContextOpts{ 737 Providers: map[addrs.Provider]providers.Factory{ 738 addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p), 739 }, 740 }) 741 742 plan, diags := ctx.Plan(m, states.NewState(), DefaultPlanOpts) 743 assertNoErrors(t, diags) 744 745 state, diags := ctx.Apply(plan, m) 746 if diags.HasErrors() { 747 t.Fatalf("diags: %s", diags.Err()) 748 } 749 750 mod := state.RootModule() 751 if len(mod.Resources) < 2 { 752 t.Fatalf("bad: %#v", mod.Resources) 753 } 754 755 actual := strings.TrimSpace(state.String()) 756 expected := strings.TrimSpace(testTofuApplyRefCountStr) 757 if actual != expected { 758 t.Fatalf("wrong result\n\ngot:\n%s\n\nwant:\n%s", actual, expected) 759 } 760 } 761 762 func TestContext2Apply_providerAlias(t *testing.T) { 763 m := testModule(t, "apply-provider-alias") 764 765 // Each provider instance must be completely independent to ensure that we 766 // are verifying the correct state of each. 767 p := func() (providers.Interface, error) { 768 p := testProvider("aws") 769 p.PlanResourceChangeFn = testDiffFn 770 p.ApplyResourceChangeFn = testApplyFn 771 return p, nil 772 } 773 ctx := testContext2(t, &ContextOpts{ 774 Providers: map[addrs.Provider]providers.Factory{ 775 addrs.NewDefaultProvider("aws"): p, 776 }, 777 }) 778 779 plan, diags := ctx.Plan(m, states.NewState(), DefaultPlanOpts) 780 assertNoErrors(t, diags) 781 782 state, diags := ctx.Apply(plan, m) 783 if diags.HasErrors() { 784 t.Fatalf("diags: %s", diags.Err()) 785 } 786 787 mod := state.RootModule() 788 if len(mod.Resources) < 2 { 789 t.Fatalf("bad: %#v", mod.Resources) 790 } 791 792 actual := strings.TrimSpace(state.String()) 793 expected := strings.TrimSpace(testTofuApplyProviderAliasStr) 794 if actual != expected { 795 t.Fatalf("wrong result\n\ngot:\n%s\n\nwant:\n%s", actual, expected) 796 } 797 } 798 799 // Two providers that are configured should both be configured prior to apply 800 func TestContext2Apply_providerAliasConfigure(t *testing.T) { 801 m := testModule(t, "apply-provider-alias-configure") 802 803 // Each provider instance must be completely independent to ensure that we 804 // are verifying the correct state of each. 805 p := func() (providers.Interface, error) { 806 p := testProvider("another") 807 p.ApplyResourceChangeFn = testApplyFn 808 p.PlanResourceChangeFn = testDiffFn 809 return p, nil 810 } 811 812 ctx := testContext2(t, &ContextOpts{ 813 Providers: map[addrs.Provider]providers.Factory{ 814 addrs.NewDefaultProvider("another"): p, 815 }, 816 }) 817 818 plan, diags := ctx.Plan(m, states.NewState(), DefaultPlanOpts) 819 if diags.HasErrors() { 820 t.Fatalf("diags: %s", diags.Err()) 821 } else { 822 t.Logf(legacyDiffComparisonString(plan.Changes)) 823 } 824 825 // Configure to record calls AFTER Plan above 826 var configCount int32 827 p = func() (providers.Interface, error) { 828 p := testProvider("another") 829 p.ApplyResourceChangeFn = testApplyFn 830 p.PlanResourceChangeFn = testDiffFn 831 p.ConfigureProviderFn = func(req providers.ConfigureProviderRequest) (resp providers.ConfigureProviderResponse) { 832 atomic.AddInt32(&configCount, 1) 833 834 foo := req.Config.GetAttr("foo").AsString() 835 if foo != "bar" { 836 resp.Diagnostics = resp.Diagnostics.Append(fmt.Errorf("foo: %#v", foo)) 837 } 838 839 return 840 } 841 return p, nil 842 } 843 844 ctx = testContext2(t, &ContextOpts{ 845 Providers: map[addrs.Provider]providers.Factory{ 846 addrs.NewDefaultProvider("another"): p, 847 }, 848 }) 849 850 state, diags := ctx.Apply(plan, m) 851 if diags.HasErrors() { 852 t.Fatalf("diags: %s", diags.Err()) 853 } 854 855 if configCount != 2 { 856 t.Fatalf("provider config expected 2 calls, got: %d", configCount) 857 } 858 859 actual := strings.TrimSpace(state.String()) 860 expected := strings.TrimSpace(testTofuApplyProviderAliasConfigStr) 861 if actual != expected { 862 t.Fatalf("wrong result\n\ngot:\n%s\n\nwant:\n%s", actual, expected) 863 } 864 } 865 866 // GH-2870 867 func TestContext2Apply_providerWarning(t *testing.T) { 868 m := testModule(t, "apply-provider-warning") 869 p := testProvider("aws") 870 p.PlanResourceChangeFn = testDiffFn 871 p.ApplyResourceChangeFn = testApplyFn 872 p.ValidateResourceConfigFn = func(req providers.ValidateResourceConfigRequest) (resp providers.ValidateResourceConfigResponse) { 873 resp.Diagnostics = resp.Diagnostics.Append(tfdiags.SimpleWarning("just a warning")) 874 return 875 } 876 ctx := testContext2(t, &ContextOpts{ 877 Providers: map[addrs.Provider]providers.Factory{ 878 addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p), 879 }, 880 }) 881 882 plan, diags := ctx.Plan(m, states.NewState(), DefaultPlanOpts) 883 assertNoErrors(t, diags) 884 885 state, diags := ctx.Apply(plan, m) 886 if diags.HasErrors() { 887 t.Fatalf("diags: %s", diags.Err()) 888 } 889 890 actual := strings.TrimSpace(state.String()) 891 expected := strings.TrimSpace(` 892 aws_instance.foo: 893 ID = foo 894 provider = provider["registry.opentofu.org/hashicorp/aws"] 895 type = aws_instance 896 `) 897 if actual != expected { 898 t.Fatalf("got: \n%s\n\nexpected:\n%s", actual, expected) 899 } 900 901 if !p.ConfigureProviderCalled { 902 t.Fatalf("provider Configure() was never called!") 903 } 904 } 905 906 func TestContext2Apply_emptyModule(t *testing.T) { 907 // A module with only outputs (no resources) 908 m := testModule(t, "apply-empty-module") 909 p := testProvider("aws") 910 p.PlanResourceChangeFn = testDiffFn 911 ctx := testContext2(t, &ContextOpts{ 912 Providers: map[addrs.Provider]providers.Factory{ 913 addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p), 914 }, 915 }) 916 917 plan, diags := ctx.Plan(m, states.NewState(), DefaultPlanOpts) 918 assertNoErrors(t, diags) 919 920 state, diags := ctx.Apply(plan, m) 921 if diags.HasErrors() { 922 t.Fatalf("diags: %s", diags.Err()) 923 } 924 925 actual := strings.TrimSpace(state.String()) 926 actual = strings.Replace(actual, " ", "", -1) 927 expected := strings.TrimSpace(testTofuApplyEmptyModuleStr) 928 if actual != expected { 929 t.Fatalf("bad: \n%s\nexpect:\n%s", actual, expected) 930 } 931 } 932 933 func TestContext2Apply_createBeforeDestroy(t *testing.T) { 934 m := testModule(t, "apply-good-create-before") 935 p := testProvider("aws") 936 p.PlanResourceChangeFn = testDiffFn 937 p.ApplyResourceChangeFn = testApplyFn 938 state := states.NewState() 939 root := state.EnsureModule(addrs.RootModuleInstance) 940 root.SetResourceInstanceCurrent( 941 mustResourceInstanceAddr("aws_instance.bar").Resource, 942 &states.ResourceInstanceObjectSrc{ 943 Status: states.ObjectReady, 944 AttrsJSON: []byte(`{"id":"bar", "require_new": "abc"}`), 945 }, 946 mustProviderConfig(`provider["registry.opentofu.org/hashicorp/aws"]`), 947 ) 948 ctx := testContext2(t, &ContextOpts{ 949 Providers: map[addrs.Provider]providers.Factory{ 950 addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p), 951 }, 952 }) 953 954 plan, diags := ctx.Plan(m, state, DefaultPlanOpts) 955 if diags.HasErrors() { 956 t.Fatalf("diags: %s", diags.Err()) 957 } else { 958 t.Logf(legacyDiffComparisonString(plan.Changes)) 959 } 960 961 state, diags = ctx.Apply(plan, m) 962 if diags.HasErrors() { 963 t.Fatalf("diags: %s", diags.Err()) 964 } 965 966 mod := state.RootModule() 967 if got, want := len(mod.Resources), 1; got != want { 968 t.Logf("state:\n%s", state) 969 t.Fatalf("wrong number of resources %d; want %d", got, want) 970 } 971 972 actual := strings.TrimSpace(state.String()) 973 expected := strings.TrimSpace(testTofuApplyCreateBeforeStr) 974 if actual != expected { 975 t.Fatalf("expected:\n%s\ngot:\n%s", expected, actual) 976 } 977 } 978 979 func TestContext2Apply_createBeforeDestroyUpdate(t *testing.T) { 980 m := testModule(t, "apply-good-create-before-update") 981 p := testProvider("aws") 982 p.PlanResourceChangeFn = testDiffFn 983 984 // signal that resource foo has started applying 985 fooChan := make(chan struct{}) 986 987 p.ApplyResourceChangeFn = func(req providers.ApplyResourceChangeRequest) (resp providers.ApplyResourceChangeResponse) { 988 id := req.PriorState.GetAttr("id").AsString() 989 switch id { 990 case "bar": 991 select { 992 case <-fooChan: 993 resp.Diagnostics = resp.Diagnostics.Append(errors.New("bar must be updated before foo is destroyed")) 994 return resp 995 case <-time.After(100 * time.Millisecond): 996 // wait a moment to ensure that foo is not going to be destroyed first 997 } 998 case "foo": 999 close(fooChan) 1000 } 1001 1002 return testApplyFn(req) 1003 } 1004 1005 state := states.NewState() 1006 root := state.EnsureModule(addrs.RootModuleInstance) 1007 fooAddr := mustResourceInstanceAddr("aws_instance.foo") 1008 root.SetResourceInstanceCurrent( 1009 fooAddr.Resource, 1010 &states.ResourceInstanceObjectSrc{ 1011 Status: states.ObjectReady, 1012 AttrsJSON: []byte(`{"id":"foo","foo":"bar"}`), 1013 CreateBeforeDestroy: true, 1014 }, 1015 mustProviderConfig(`provider["registry.opentofu.org/hashicorp/aws"]`), 1016 ) 1017 root.SetResourceInstanceCurrent( 1018 mustResourceInstanceAddr("aws_instance.bar").Resource, 1019 &states.ResourceInstanceObjectSrc{ 1020 Status: states.ObjectReady, 1021 AttrsJSON: []byte(`{"id":"bar","foo":"bar"}`), 1022 CreateBeforeDestroy: true, 1023 Dependencies: []addrs.ConfigResource{fooAddr.ContainingResource().Config()}, 1024 }, 1025 mustProviderConfig(`provider["registry.opentofu.org/hashicorp/aws"]`), 1026 ) 1027 1028 ctx := testContext2(t, &ContextOpts{ 1029 Providers: map[addrs.Provider]providers.Factory{ 1030 addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p), 1031 }, 1032 }) 1033 1034 plan, diags := ctx.Plan(m, state, DefaultPlanOpts) 1035 if diags.HasErrors() { 1036 t.Fatalf("diags: %s", diags.Err()) 1037 } else { 1038 t.Logf(legacyDiffComparisonString(plan.Changes)) 1039 } 1040 1041 state, diags = ctx.Apply(plan, m) 1042 if diags.HasErrors() { 1043 t.Fatalf("diags: %s", diags.Err()) 1044 } 1045 1046 mod := state.RootModule() 1047 if len(mod.Resources) != 1 { 1048 t.Fatalf("bad: %s", state) 1049 } 1050 1051 actual := strings.TrimSpace(state.String()) 1052 expected := strings.TrimSpace(testTofuApplyCreateBeforeUpdateStr) 1053 if actual != expected { 1054 t.Fatalf("wrong result\n\ngot:\n%s\n\nwant:\n%s", actual, expected) 1055 } 1056 } 1057 1058 // This tests that when a CBD resource depends on a non-CBD resource, 1059 // we can still properly apply changes that require new for both. 1060 func TestContext2Apply_createBeforeDestroy_dependsNonCBD(t *testing.T) { 1061 m := testModule(t, "apply-cbd-depends-non-cbd") 1062 p := testProvider("aws") 1063 p.PlanResourceChangeFn = testDiffFn 1064 p.ApplyResourceChangeFn = testApplyFn 1065 1066 state := states.NewState() 1067 root := state.EnsureModule(addrs.RootModuleInstance) 1068 root.SetResourceInstanceCurrent( 1069 mustResourceInstanceAddr("aws_instance.bar").Resource, 1070 &states.ResourceInstanceObjectSrc{ 1071 Status: states.ObjectReady, 1072 AttrsJSON: []byte(`{"id":"bar", "require_new": "abc"}`), 1073 }, 1074 mustProviderConfig(`provider["registry.opentofu.org/hashicorp/aws"]`), 1075 ) 1076 root.SetResourceInstanceCurrent( 1077 mustResourceInstanceAddr("aws_instance.foo").Resource, 1078 &states.ResourceInstanceObjectSrc{ 1079 Status: states.ObjectReady, 1080 AttrsJSON: []byte(`{"id":"foo", "require_new": "abc"}`), 1081 }, 1082 mustProviderConfig(`provider["registry.opentofu.org/hashicorp/aws"]`), 1083 ) 1084 1085 ctx := testContext2(t, &ContextOpts{ 1086 Providers: map[addrs.Provider]providers.Factory{ 1087 addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p), 1088 }, 1089 }) 1090 1091 plan, diags := ctx.Plan(m, state, DefaultPlanOpts) 1092 if diags.HasErrors() { 1093 t.Fatalf("diags: %s", diags.Err()) 1094 } else { 1095 t.Logf(legacyDiffComparisonString(plan.Changes)) 1096 } 1097 1098 state, diags = ctx.Apply(plan, m) 1099 if diags.HasErrors() { 1100 t.Fatalf("diags: %s", diags.Err()) 1101 } 1102 1103 checkStateString(t, state, ` 1104 aws_instance.bar: 1105 ID = foo 1106 provider = provider["registry.opentofu.org/hashicorp/aws"] 1107 require_new = yes 1108 type = aws_instance 1109 value = foo 1110 1111 Dependencies: 1112 aws_instance.foo 1113 aws_instance.foo: 1114 ID = foo 1115 provider = provider["registry.opentofu.org/hashicorp/aws"] 1116 require_new = yes 1117 type = aws_instance 1118 `) 1119 } 1120 1121 func TestContext2Apply_createBeforeDestroy_hook(t *testing.T) { 1122 h := new(MockHook) 1123 m := testModule(t, "apply-good-create-before") 1124 p := testProvider("aws") 1125 p.PlanResourceChangeFn = testDiffFn 1126 p.ApplyResourceChangeFn = testApplyFn 1127 state := states.NewState() 1128 root := state.EnsureModule(addrs.RootModuleInstance) 1129 root.SetResourceInstanceCurrent( 1130 mustResourceInstanceAddr("aws_instance.bar").Resource, 1131 &states.ResourceInstanceObjectSrc{ 1132 Status: states.ObjectReady, 1133 AttrsJSON: []byte(`{"id":"bar", "require_new": "abc"}`), 1134 }, 1135 mustProviderConfig(`provider["registry.opentofu.org/hashicorp/aws"]`), 1136 ) 1137 1138 var actual []cty.Value 1139 var actualLock sync.Mutex 1140 h.PostApplyFn = func(addr addrs.AbsResourceInstance, gen states.Generation, sv cty.Value, e error) (HookAction, error) { 1141 actualLock.Lock() 1142 1143 defer actualLock.Unlock() 1144 actual = append(actual, sv) 1145 return HookActionContinue, nil 1146 } 1147 1148 ctx := testContext2(t, &ContextOpts{ 1149 Hooks: []Hook{h}, 1150 Providers: map[addrs.Provider]providers.Factory{ 1151 addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p), 1152 }, 1153 }) 1154 1155 plan, diags := ctx.Plan(m, state, DefaultPlanOpts) 1156 if diags.HasErrors() { 1157 t.Fatalf("diags: %s", diags.Err()) 1158 } else { 1159 t.Logf(legacyDiffComparisonString(plan.Changes)) 1160 } 1161 1162 if _, diags := ctx.Apply(plan, m); diags.HasErrors() { 1163 t.Fatalf("apply errors: %s", diags.Err()) 1164 } 1165 1166 expected := []cty.Value{ 1167 cty.ObjectVal(map[string]cty.Value{ 1168 "id": cty.StringVal("foo"), 1169 "require_new": cty.StringVal("xyz"), 1170 "type": cty.StringVal("aws_instance"), 1171 }), 1172 cty.NullVal(cty.DynamicPseudoType), 1173 } 1174 1175 cmpOpt := cmp.Transformer("ctyshim", hcl2shim.ConfigValueFromHCL2) 1176 if !cmp.Equal(actual, expected, cmpOpt) { 1177 t.Fatalf("wrong state snapshot sequence\n%s", cmp.Diff(expected, actual, cmpOpt)) 1178 } 1179 } 1180 1181 // Test that we can perform an apply with CBD in a count with deposed instances. 1182 func TestContext2Apply_createBeforeDestroy_deposedCount(t *testing.T) { 1183 m := testModule(t, "apply-cbd-count") 1184 p := testProvider("aws") 1185 p.PlanResourceChangeFn = testDiffFn 1186 p.ApplyResourceChangeFn = testApplyFn 1187 1188 state := states.NewState() 1189 root := state.EnsureModule(addrs.RootModuleInstance) 1190 root.SetResourceInstanceCurrent( 1191 mustResourceInstanceAddr("aws_instance.bar[0]").Resource, 1192 &states.ResourceInstanceObjectSrc{ 1193 Status: states.ObjectTainted, 1194 AttrsJSON: []byte(`{"id":"bar"}`), 1195 }, 1196 mustProviderConfig(`provider["registry.opentofu.org/hashicorp/aws"]`), 1197 ) 1198 root.SetResourceInstanceDeposed( 1199 mustResourceInstanceAddr("aws_instance.bar[0]").Resource, 1200 states.NewDeposedKey(), 1201 &states.ResourceInstanceObjectSrc{ 1202 Status: states.ObjectTainted, 1203 AttrsJSON: []byte(`{"id":"foo"}`), 1204 }, 1205 mustProviderConfig(`provider["registry.opentofu.org/hashicorp/aws"]`), 1206 ) 1207 root.SetResourceInstanceCurrent( 1208 mustResourceInstanceAddr("aws_instance.bar[1]").Resource, 1209 &states.ResourceInstanceObjectSrc{ 1210 Status: states.ObjectTainted, 1211 AttrsJSON: []byte(`{"id":"bar"}`), 1212 }, 1213 mustProviderConfig(`provider["registry.opentofu.org/hashicorp/aws"]`), 1214 ) 1215 root.SetResourceInstanceDeposed( 1216 mustResourceInstanceAddr("aws_instance.bar[1]").Resource, 1217 states.NewDeposedKey(), 1218 &states.ResourceInstanceObjectSrc{ 1219 Status: states.ObjectTainted, 1220 AttrsJSON: []byte(`{"id":"bar"}`), 1221 }, 1222 mustProviderConfig(`provider["registry.opentofu.org/hashicorp/aws"]`), 1223 ) 1224 1225 ctx := testContext2(t, &ContextOpts{ 1226 Providers: map[addrs.Provider]providers.Factory{ 1227 addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p), 1228 }, 1229 }) 1230 1231 plan, diags := ctx.Plan(m, state, DefaultPlanOpts) 1232 if diags.HasErrors() { 1233 t.Fatalf("diags: %s", diags.Err()) 1234 } else { 1235 t.Logf(legacyDiffComparisonString(plan.Changes)) 1236 } 1237 1238 state, diags = ctx.Apply(plan, m) 1239 if diags.HasErrors() { 1240 t.Fatalf("diags: %s", diags.Err()) 1241 } 1242 1243 checkStateString(t, state, ` 1244 aws_instance.bar.0: 1245 ID = foo 1246 provider = provider["registry.opentofu.org/hashicorp/aws"] 1247 foo = bar 1248 type = aws_instance 1249 aws_instance.bar.1: 1250 ID = foo 1251 provider = provider["registry.opentofu.org/hashicorp/aws"] 1252 foo = bar 1253 type = aws_instance 1254 `) 1255 } 1256 1257 // Test that when we have a deposed instance but a good primary, we still 1258 // destroy the deposed instance. 1259 func TestContext2Apply_createBeforeDestroy_deposedOnly(t *testing.T) { 1260 m := testModule(t, "apply-cbd-deposed-only") 1261 p := testProvider("aws") 1262 p.PlanResourceChangeFn = testDiffFn 1263 p.ApplyResourceChangeFn = testApplyFn 1264 1265 state := states.NewState() 1266 root := state.EnsureModule(addrs.RootModuleInstance) 1267 root.SetResourceInstanceCurrent( 1268 mustResourceInstanceAddr("aws_instance.bar").Resource, 1269 &states.ResourceInstanceObjectSrc{ 1270 Status: states.ObjectReady, 1271 AttrsJSON: []byte(`{"id":"bar"}`), 1272 }, 1273 mustProviderConfig(`provider["registry.opentofu.org/hashicorp/aws"]`), 1274 ) 1275 root.SetResourceInstanceDeposed( 1276 mustResourceInstanceAddr("aws_instance.bar").Resource, 1277 states.NewDeposedKey(), 1278 &states.ResourceInstanceObjectSrc{ 1279 Status: states.ObjectTainted, 1280 AttrsJSON: []byte(`{"id":"foo"}`), 1281 }, 1282 mustProviderConfig(`provider["registry.opentofu.org/hashicorp/aws"]`), 1283 ) 1284 1285 ctx := testContext2(t, &ContextOpts{ 1286 Providers: map[addrs.Provider]providers.Factory{ 1287 addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p), 1288 }, 1289 }) 1290 1291 plan, diags := ctx.Plan(m, state, DefaultPlanOpts) 1292 if diags.HasErrors() { 1293 t.Fatalf("diags: %s", diags.Err()) 1294 } else { 1295 t.Logf(legacyDiffComparisonString(plan.Changes)) 1296 } 1297 1298 state, diags = ctx.Apply(plan, m) 1299 if diags.HasErrors() { 1300 t.Fatalf("diags: %s", diags.Err()) 1301 } 1302 1303 checkStateString(t, state, ` 1304 aws_instance.bar: 1305 ID = bar 1306 provider = provider["registry.opentofu.org/hashicorp/aws"] 1307 type = aws_instance 1308 `) 1309 } 1310 1311 func TestContext2Apply_destroyComputed(t *testing.T) { 1312 m := testModule(t, "apply-destroy-computed") 1313 p := testProvider("aws") 1314 p.PlanResourceChangeFn = testDiffFn 1315 state := states.NewState() 1316 root := state.EnsureModule(addrs.RootModuleInstance) 1317 root.SetResourceInstanceCurrent( 1318 mustResourceInstanceAddr("aws_instance.foo").Resource, 1319 &states.ResourceInstanceObjectSrc{ 1320 Status: states.ObjectReady, 1321 AttrsJSON: []byte(`{"id":"foo", "output": "value"}`), 1322 }, 1323 mustProviderConfig(`provider["registry.opentofu.org/hashicorp/aws"]`), 1324 ) 1325 ctx := testContext2(t, &ContextOpts{ 1326 Providers: map[addrs.Provider]providers.Factory{ 1327 addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p), 1328 }, 1329 }) 1330 1331 plan, diags := ctx.Plan(m, state, &PlanOpts{ 1332 Mode: plans.DestroyMode, 1333 }) 1334 if diags.HasErrors() { 1335 logDiagnostics(t, diags) 1336 t.Fatal("plan failed") 1337 } else { 1338 t.Logf("plan:\n\n%s", legacyDiffComparisonString(plan.Changes)) 1339 } 1340 1341 if _, diags := ctx.Apply(plan, m); diags.HasErrors() { 1342 logDiagnostics(t, diags) 1343 t.Fatal("apply failed") 1344 } 1345 } 1346 1347 // Test that the destroy operation uses depends_on as a source of ordering. 1348 func TestContext2Apply_destroyDependsOn(t *testing.T) { 1349 // It is possible for this to be racy, so we loop a number of times 1350 // just to check. 1351 for i := 0; i < 10; i++ { 1352 testContext2Apply_destroyDependsOn(t) 1353 } 1354 } 1355 1356 func testContext2Apply_destroyDependsOn(t *testing.T) { 1357 m := testModule(t, "apply-destroy-depends-on") 1358 p := testProvider("aws") 1359 p.PlanResourceChangeFn = testDiffFn 1360 1361 state := states.NewState() 1362 root := state.EnsureModule(addrs.RootModuleInstance) 1363 root.SetResourceInstanceCurrent( 1364 mustResourceInstanceAddr("aws_instance.bar").Resource, 1365 &states.ResourceInstanceObjectSrc{ 1366 Status: states.ObjectReady, 1367 AttrsJSON: []byte(`{"id":"bar"}`), 1368 }, 1369 mustProviderConfig(`provider["registry.opentofu.org/hashicorp/aws"]`), 1370 ) 1371 root.SetResourceInstanceCurrent( 1372 mustResourceInstanceAddr("aws_instance.foo").Resource, 1373 &states.ResourceInstanceObjectSrc{ 1374 Status: states.ObjectReady, 1375 AttrsJSON: []byte(`{"id":"foo"}`), 1376 Dependencies: []addrs.ConfigResource{mustConfigResourceAddr("aws_instance.bar")}, 1377 }, 1378 mustProviderConfig(`provider["registry.opentofu.org/hashicorp/aws"]`), 1379 ) 1380 1381 // Record the order we see Apply 1382 var actual []string 1383 var actualLock sync.Mutex 1384 p.ApplyResourceChangeFn = func(req providers.ApplyResourceChangeRequest) providers.ApplyResourceChangeResponse { 1385 actualLock.Lock() 1386 defer actualLock.Unlock() 1387 id := req.PriorState.GetAttr("id").AsString() 1388 actual = append(actual, id) 1389 1390 return testApplyFn(req) 1391 } 1392 1393 ctx := testContext2(t, &ContextOpts{ 1394 Providers: map[addrs.Provider]providers.Factory{ 1395 addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p), 1396 }, 1397 Parallelism: 1, // To check ordering 1398 }) 1399 1400 plan, diags := ctx.Plan(m, state, &PlanOpts{ 1401 Mode: plans.DestroyMode, 1402 }) 1403 assertNoErrors(t, diags) 1404 1405 if _, diags := ctx.Apply(plan, m); diags.HasErrors() { 1406 t.Fatalf("apply errors: %s", diags.Err()) 1407 } 1408 1409 expected := []string{"foo", "bar"} 1410 if !reflect.DeepEqual(actual, expected) { 1411 t.Fatalf("wrong order\ngot: %#v\nwant: %#v", actual, expected) 1412 } 1413 } 1414 1415 // Test that destroy ordering is correct with dependencies only 1416 // in the state. 1417 func TestContext2Apply_destroyDependsOnStateOnly(t *testing.T) { 1418 newState := states.NewState() 1419 root := newState.EnsureModule(addrs.RootModuleInstance) 1420 root.SetResourceInstanceCurrent( 1421 addrs.Resource{ 1422 Mode: addrs.ManagedResourceMode, 1423 Type: "aws_instance", 1424 Name: "foo", 1425 }.Instance(addrs.NoKey), 1426 &states.ResourceInstanceObjectSrc{ 1427 Status: states.ObjectReady, 1428 AttrsJSON: []byte(`{"id":"foo"}`), 1429 Dependencies: []addrs.ConfigResource{}, 1430 }, 1431 addrs.AbsProviderConfig{ 1432 Provider: addrs.NewDefaultProvider("aws"), 1433 Module: addrs.RootModule, 1434 }, 1435 ) 1436 root.SetResourceInstanceCurrent( 1437 addrs.Resource{ 1438 Mode: addrs.ManagedResourceMode, 1439 Type: "aws_instance", 1440 Name: "bar", 1441 }.Instance(addrs.NoKey), 1442 &states.ResourceInstanceObjectSrc{ 1443 Status: states.ObjectReady, 1444 AttrsJSON: []byte(`{"id":"bar"}`), 1445 Dependencies: []addrs.ConfigResource{ 1446 { 1447 Resource: addrs.Resource{ 1448 Mode: addrs.ManagedResourceMode, 1449 Type: "aws_instance", 1450 Name: "foo", 1451 }, 1452 Module: root.Addr.Module(), 1453 }, 1454 }, 1455 }, 1456 addrs.AbsProviderConfig{ 1457 Provider: addrs.NewDefaultProvider("aws"), 1458 Module: addrs.RootModule, 1459 }, 1460 ) 1461 1462 // It is possible for this to be racy, so we loop a number of times 1463 // just to check. 1464 for i := 0; i < 10; i++ { 1465 t.Run("new", func(t *testing.T) { 1466 testContext2Apply_destroyDependsOnStateOnly(t, newState) 1467 }) 1468 } 1469 } 1470 1471 func testContext2Apply_destroyDependsOnStateOnly(t *testing.T, state *states.State) { 1472 state = state.DeepCopy() 1473 m := testModule(t, "empty") 1474 p := testProvider("aws") 1475 p.PlanResourceChangeFn = testDiffFn 1476 // Record the order we see Apply 1477 var actual []string 1478 var actualLock sync.Mutex 1479 p.ApplyResourceChangeFn = func(req providers.ApplyResourceChangeRequest) providers.ApplyResourceChangeResponse { 1480 actualLock.Lock() 1481 defer actualLock.Unlock() 1482 id := req.PriorState.GetAttr("id").AsString() 1483 actual = append(actual, id) 1484 return testApplyFn(req) 1485 } 1486 1487 ctx := testContext2(t, &ContextOpts{ 1488 Providers: map[addrs.Provider]providers.Factory{ 1489 addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p), 1490 }, 1491 Parallelism: 1, // To check ordering 1492 }) 1493 1494 plan, diags := ctx.Plan(m, state, &PlanOpts{ 1495 Mode: plans.DestroyMode, 1496 }) 1497 assertNoErrors(t, diags) 1498 1499 if _, diags := ctx.Apply(plan, m); diags.HasErrors() { 1500 t.Fatalf("apply errors: %s", diags.Err()) 1501 } 1502 1503 expected := []string{"bar", "foo"} 1504 if !reflect.DeepEqual(actual, expected) { 1505 t.Fatalf("wrong order\ngot: %#v\nwant: %#v", actual, expected) 1506 } 1507 } 1508 1509 // Test that destroy ordering is correct with dependencies only 1510 // in the state within a module (GH-11749) 1511 func TestContext2Apply_destroyDependsOnStateOnlyModule(t *testing.T) { 1512 newState := states.NewState() 1513 child := newState.EnsureModule(addrs.RootModuleInstance.Child("child", addrs.NoKey)) 1514 child.SetResourceInstanceCurrent( 1515 addrs.Resource{ 1516 Mode: addrs.ManagedResourceMode, 1517 Type: "aws_instance", 1518 Name: "foo", 1519 }.Instance(addrs.NoKey), 1520 &states.ResourceInstanceObjectSrc{ 1521 Status: states.ObjectReady, 1522 AttrsJSON: []byte(`{"id":"foo"}`), 1523 Dependencies: []addrs.ConfigResource{}, 1524 }, 1525 addrs.AbsProviderConfig{ 1526 Provider: addrs.NewDefaultProvider("aws"), 1527 Module: addrs.RootModule, 1528 }, 1529 ) 1530 child.SetResourceInstanceCurrent( 1531 addrs.Resource{ 1532 Mode: addrs.ManagedResourceMode, 1533 Type: "aws_instance", 1534 Name: "bar", 1535 }.Instance(addrs.NoKey), 1536 &states.ResourceInstanceObjectSrc{ 1537 Status: states.ObjectReady, 1538 AttrsJSON: []byte(`{"id":"bar"}`), 1539 Dependencies: []addrs.ConfigResource{ 1540 { 1541 Resource: addrs.Resource{ 1542 Mode: addrs.ManagedResourceMode, 1543 Type: "aws_instance", 1544 Name: "foo", 1545 }, 1546 Module: child.Addr.Module(), 1547 }, 1548 }, 1549 }, 1550 addrs.AbsProviderConfig{ 1551 Provider: addrs.NewDefaultProvider("aws"), 1552 Module: addrs.RootModule, 1553 }, 1554 ) 1555 1556 // It is possible for this to be racy, so we loop a number of times 1557 // just to check. 1558 for i := 0; i < 10; i++ { 1559 t.Run("new", func(t *testing.T) { 1560 testContext2Apply_destroyDependsOnStateOnlyModule(t, newState) 1561 }) 1562 } 1563 } 1564 1565 func testContext2Apply_destroyDependsOnStateOnlyModule(t *testing.T, state *states.State) { 1566 state = state.DeepCopy() 1567 m := testModule(t, "empty") 1568 p := testProvider("aws") 1569 p.PlanResourceChangeFn = testDiffFn 1570 1571 // Record the order we see Apply 1572 var actual []string 1573 var actualLock sync.Mutex 1574 p.ApplyResourceChangeFn = func(req providers.ApplyResourceChangeRequest) providers.ApplyResourceChangeResponse { 1575 actualLock.Lock() 1576 defer actualLock.Unlock() 1577 id := req.PriorState.GetAttr("id").AsString() 1578 actual = append(actual, id) 1579 return testApplyFn(req) 1580 } 1581 1582 ctx := testContext2(t, &ContextOpts{ 1583 Providers: map[addrs.Provider]providers.Factory{ 1584 addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p), 1585 }, 1586 Parallelism: 1, // To check ordering 1587 }) 1588 1589 plan, diags := ctx.Plan(m, state, &PlanOpts{ 1590 Mode: plans.DestroyMode, 1591 }) 1592 assertNoErrors(t, diags) 1593 1594 if _, diags := ctx.Apply(plan, m); diags.HasErrors() { 1595 t.Fatalf("apply errors: %s", diags.Err()) 1596 } 1597 1598 expected := []string{"bar", "foo"} 1599 if !reflect.DeepEqual(actual, expected) { 1600 t.Fatalf("wrong order\ngot: %#v\nwant: %#v", actual, expected) 1601 } 1602 } 1603 1604 func TestContext2Apply_dataBasic(t *testing.T) { 1605 m := testModule(t, "apply-data-basic") 1606 p := testProvider("null") 1607 p.PlanResourceChangeFn = testDiffFn 1608 p.ReadDataSourceResponse = &providers.ReadDataSourceResponse{ 1609 State: cty.ObjectVal(map[string]cty.Value{ 1610 "id": cty.StringVal("yo"), 1611 "foo": cty.NullVal(cty.String), 1612 }), 1613 } 1614 1615 hook := new(MockHook) 1616 ctx := testContext2(t, &ContextOpts{ 1617 Hooks: []Hook{hook}, 1618 Providers: map[addrs.Provider]providers.Factory{ 1619 addrs.NewDefaultProvider("null"): testProviderFuncFixed(p), 1620 }, 1621 }) 1622 1623 plan, diags := ctx.Plan(m, states.NewState(), DefaultPlanOpts) 1624 if diags.HasErrors() { 1625 t.Fatalf("diags: %s", diags.Err()) 1626 } else { 1627 t.Logf(legacyDiffComparisonString(plan.Changes)) 1628 } 1629 1630 state, diags := ctx.Apply(plan, m) 1631 assertNoErrors(t, diags) 1632 1633 actual := strings.TrimSpace(state.String()) 1634 expected := strings.TrimSpace(testTofuApplyDataBasicStr) 1635 if actual != expected { 1636 t.Fatalf("wrong result\n\ngot:\n%s\n\nwant:\n%s", actual, expected) 1637 } 1638 1639 if !hook.PreApplyCalled { 1640 t.Fatal("PreApply not called for data source read") 1641 } 1642 if !hook.PostApplyCalled { 1643 t.Fatal("PostApply not called for data source read") 1644 } 1645 } 1646 1647 func TestContext2Apply_destroyData(t *testing.T) { 1648 m := testModule(t, "apply-destroy-data-resource") 1649 p := testProvider("null") 1650 p.PlanResourceChangeFn = testDiffFn 1651 p.ReadDataSourceFn = func(req providers.ReadDataSourceRequest) providers.ReadDataSourceResponse { 1652 return providers.ReadDataSourceResponse{ 1653 State: req.Config, 1654 } 1655 } 1656 1657 state := states.NewState() 1658 root := state.EnsureModule(addrs.RootModuleInstance) 1659 root.SetResourceInstanceCurrent( 1660 mustResourceInstanceAddr("data.null_data_source.testing").Resource, 1661 &states.ResourceInstanceObjectSrc{ 1662 Status: states.ObjectReady, 1663 AttrsJSON: []byte(`{"id":"-"}`), 1664 }, 1665 mustProviderConfig(`provider["registry.opentofu.org/hashicorp/null"]`), 1666 ) 1667 1668 hook := &testHook{} 1669 ctx := testContext2(t, &ContextOpts{ 1670 Providers: map[addrs.Provider]providers.Factory{ 1671 addrs.NewDefaultProvider("null"): testProviderFuncFixed(p), 1672 }, 1673 Hooks: []Hook{hook}, 1674 }) 1675 1676 plan, diags := ctx.Plan(m, state, &PlanOpts{ 1677 Mode: plans.DestroyMode, 1678 }) 1679 if diags.HasErrors() { 1680 t.Fatalf("diags: %s", diags.Err()) 1681 } else { 1682 t.Logf(legacyDiffComparisonString(plan.Changes)) 1683 } 1684 1685 newState, diags := ctx.Apply(plan, m) 1686 if diags.HasErrors() { 1687 t.Fatalf("diags: %s", diags.Err()) 1688 } 1689 1690 if got := len(newState.Modules); got != 1 { 1691 t.Fatalf("state has %d modules after destroy; want 1", got) 1692 } 1693 1694 if got := len(newState.RootModule().Resources); got != 0 { 1695 t.Fatalf("state has %d resources after destroy; want 0", got) 1696 } 1697 1698 wantHookCalls := []*testHookCall{ 1699 {"PreApply", "data.null_data_source.testing"}, 1700 {"PostApply", "data.null_data_source.testing"}, 1701 {"PostStateUpdate", ""}, 1702 } 1703 if !reflect.DeepEqual(hook.Calls, wantHookCalls) { 1704 t.Errorf("wrong hook calls\ngot: %swant: %s", spew.Sdump(hook.Calls), spew.Sdump(wantHookCalls)) 1705 } 1706 } 1707 1708 // https://github.com/hashicorp/terraform/pull/5096 1709 func TestContext2Apply_destroySkipsCBD(t *testing.T) { 1710 // Config contains CBD resource depending on non-CBD resource, which triggers 1711 // a cycle if they are both replaced, but should _not_ trigger a cycle when 1712 // just doing a `tofu destroy`. 1713 m := testModule(t, "apply-destroy-cbd") 1714 p := testProvider("aws") 1715 p.PlanResourceChangeFn = testDiffFn 1716 state := states.NewState() 1717 root := state.EnsureModule(addrs.RootModuleInstance) 1718 root.SetResourceInstanceCurrent( 1719 mustResourceInstanceAddr("aws_instance.foo").Resource, 1720 &states.ResourceInstanceObjectSrc{ 1721 Status: states.ObjectReady, 1722 AttrsJSON: []byte(`{"id":"foo"}`), 1723 }, 1724 mustProviderConfig(`provider["registry.opentofu.org/hashicorp/aws"]`), 1725 ) 1726 root.SetResourceInstanceCurrent( 1727 mustResourceInstanceAddr("aws_instance.bar").Resource, 1728 &states.ResourceInstanceObjectSrc{ 1729 Status: states.ObjectReady, 1730 AttrsJSON: []byte(`{"id":"foo"}`), 1731 }, 1732 mustProviderConfig(`provider["registry.opentofu.org/hashicorp/aws"]`), 1733 ) 1734 1735 ctx := testContext2(t, &ContextOpts{ 1736 Providers: map[addrs.Provider]providers.Factory{ 1737 addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p), 1738 }, 1739 }) 1740 1741 plan, diags := ctx.Plan(m, state, &PlanOpts{ 1742 Mode: plans.DestroyMode, 1743 }) 1744 if diags.HasErrors() { 1745 t.Fatalf("diags: %s", diags.Err()) 1746 } else { 1747 t.Logf(legacyDiffComparisonString(plan.Changes)) 1748 } 1749 1750 if _, diags := ctx.Apply(plan, m); diags.HasErrors() { 1751 t.Fatalf("apply errors: %s", diags.Err()) 1752 } 1753 } 1754 1755 func TestContext2Apply_destroyModuleVarProviderConfig(t *testing.T) { 1756 m := testModule(t, "apply-destroy-mod-var-provider-config") 1757 p := func() (providers.Interface, error) { 1758 p := testProvider("aws") 1759 p.PlanResourceChangeFn = testDiffFn 1760 return p, nil 1761 } 1762 state := states.NewState() 1763 child := state.EnsureModule(addrs.RootModuleInstance.Child("child", addrs.NoKey)) 1764 child.SetResourceInstanceCurrent( 1765 mustResourceInstanceAddr("aws_instance.foo").Resource, 1766 &states.ResourceInstanceObjectSrc{ 1767 Status: states.ObjectReady, 1768 AttrsJSON: []byte(`{"id":"foo"}`), 1769 }, 1770 mustProviderConfig(`provider["registry.opentofu.org/hashicorp/aws"]`), 1771 ) 1772 ctx := testContext2(t, &ContextOpts{ 1773 Providers: map[addrs.Provider]providers.Factory{ 1774 addrs.NewDefaultProvider("aws"): p, 1775 }, 1776 }) 1777 1778 plan, diags := ctx.Plan(m, state, &PlanOpts{ 1779 Mode: plans.DestroyMode, 1780 }) 1781 assertNoErrors(t, diags) 1782 1783 _, diags = ctx.Apply(plan, m) 1784 if diags.HasErrors() { 1785 t.Fatalf("diags: %s", diags.Err()) 1786 } 1787 } 1788 1789 func TestContext2Apply_destroyCrossProviders(t *testing.T) { 1790 m := testModule(t, "apply-destroy-cross-providers") 1791 1792 p_aws := testProvider("aws") 1793 p_aws.ApplyResourceChangeFn = testApplyFn 1794 p_aws.PlanResourceChangeFn = testDiffFn 1795 p_aws.GetProviderSchemaResponse = getProviderSchemaResponseFromProviderSchema(&ProviderSchema{ 1796 ResourceTypes: map[string]*configschema.Block{ 1797 "aws_instance": { 1798 Attributes: map[string]*configschema.Attribute{ 1799 "id": { 1800 Type: cty.String, 1801 Computed: true, 1802 }, 1803 }, 1804 }, 1805 "aws_vpc": { 1806 Attributes: map[string]*configschema.Attribute{ 1807 "id": { 1808 Type: cty.String, 1809 Computed: true, 1810 }, 1811 "value": { 1812 Type: cty.String, 1813 Optional: true, 1814 }, 1815 }, 1816 }, 1817 }, 1818 }) 1819 1820 providers := map[addrs.Provider]providers.Factory{ 1821 addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p_aws), 1822 } 1823 1824 ctx, m, state := getContextForApply_destroyCrossProviders(t, m, providers) 1825 1826 plan, diags := ctx.Plan(m, state, &PlanOpts{ 1827 Mode: plans.DestroyMode, 1828 }) 1829 assertNoErrors(t, diags) 1830 1831 if _, diags := ctx.Apply(plan, m); diags.HasErrors() { 1832 logDiagnostics(t, diags) 1833 t.Fatal("apply failed") 1834 } 1835 } 1836 1837 func getContextForApply_destroyCrossProviders(t *testing.T, m *configs.Config, providerFactories map[addrs.Provider]providers.Factory) (*Context, *configs.Config, *states.State) { 1838 state := states.NewState() 1839 root := state.EnsureModule(addrs.RootModuleInstance) 1840 root.SetResourceInstanceCurrent( 1841 mustResourceInstanceAddr("aws_instance.shared").Resource, 1842 &states.ResourceInstanceObjectSrc{ 1843 Status: states.ObjectReady, 1844 AttrsJSON: []byte(`{"id":"test"}`), 1845 }, 1846 mustProviderConfig(`provider["registry.opentofu.org/hashicorp/aws"]`), 1847 ) 1848 child := state.EnsureModule(addrs.RootModuleInstance.Child("child", addrs.NoKey)) 1849 child.SetResourceInstanceCurrent( 1850 mustResourceInstanceAddr("aws_vpc.bar").Resource, 1851 &states.ResourceInstanceObjectSrc{ 1852 Status: states.ObjectReady, 1853 AttrsJSON: []byte(`{"id": "vpc-aaabbb12", "value":"test"}`), 1854 }, 1855 mustProviderConfig(`provider["registry.opentofu.org/hashicorp/aws"]`), 1856 ) 1857 1858 ctx := testContext2(t, &ContextOpts{ 1859 Providers: providerFactories, 1860 }) 1861 1862 return ctx, m, state 1863 } 1864 1865 func TestContext2Apply_minimal(t *testing.T) { 1866 m := testModule(t, "apply-minimal") 1867 p := testProvider("aws") 1868 p.PlanResourceChangeFn = testDiffFn 1869 p.ApplyResourceChangeFn = testApplyFn 1870 ctx := testContext2(t, &ContextOpts{ 1871 Providers: map[addrs.Provider]providers.Factory{ 1872 addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p), 1873 }, 1874 }) 1875 1876 plan, diags := ctx.Plan(m, states.NewState(), DefaultPlanOpts) 1877 assertNoErrors(t, diags) 1878 1879 state, diags := ctx.Apply(plan, m) 1880 if diags.HasErrors() { 1881 t.Fatalf("diags: %s", diags.Err()) 1882 } 1883 1884 actual := strings.TrimSpace(state.String()) 1885 expected := strings.TrimSpace(testTofuApplyMinimalStr) 1886 if actual != expected { 1887 t.Fatalf("wrong result\n\ngot:\n%s\n\nwant:\n%s", actual, expected) 1888 } 1889 } 1890 1891 func TestContext2Apply_cancel(t *testing.T) { 1892 stopped := false 1893 1894 m := testModule(t, "apply-cancel") 1895 p := testProvider("aws") 1896 ctx := testContext2(t, &ContextOpts{ 1897 Providers: map[addrs.Provider]providers.Factory{ 1898 addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p), 1899 }, 1900 }) 1901 1902 p.ApplyResourceChangeFn = func(req providers.ApplyResourceChangeRequest) providers.ApplyResourceChangeResponse { 1903 if !stopped { 1904 stopped = true 1905 go ctx.Stop() 1906 1907 for { 1908 if ctx.sh.Stopped() { 1909 break 1910 } 1911 time.Sleep(10 * time.Millisecond) 1912 } 1913 } 1914 return testApplyFn(req) 1915 } 1916 p.PlanResourceChangeFn = testDiffFn 1917 1918 plan, diags := ctx.Plan(m, states.NewState(), DefaultPlanOpts) 1919 assertNoErrors(t, diags) 1920 1921 // Start the Apply in a goroutine 1922 var applyDiags tfdiags.Diagnostics 1923 stateCh := make(chan *states.State) 1924 go func() { 1925 state, diags := ctx.Apply(plan, m) 1926 applyDiags = diags 1927 1928 stateCh <- state 1929 }() 1930 1931 state := <-stateCh 1932 // only expecting an early exit error 1933 if !applyDiags.HasErrors() { 1934 t.Fatal("expected early exit error") 1935 } 1936 1937 for _, d := range applyDiags { 1938 desc := d.Description() 1939 if desc.Summary != "execution halted" { 1940 t.Fatalf("unexpected error: %v", applyDiags.Err()) 1941 } 1942 } 1943 1944 actual := strings.TrimSpace(state.String()) 1945 expected := strings.TrimSpace(testTofuApplyCancelStr) 1946 if actual != expected { 1947 t.Fatalf("wrong result\n\ngot:\n%s\n\nwant:\n%s", actual, expected) 1948 } 1949 1950 if !p.StopCalled { 1951 t.Fatal("stop should be called") 1952 } 1953 } 1954 1955 func TestContext2Apply_cancelBlock(t *testing.T) { 1956 m := testModule(t, "apply-cancel-block") 1957 p := testProvider("aws") 1958 ctx := testContext2(t, &ContextOpts{ 1959 Providers: map[addrs.Provider]providers.Factory{ 1960 addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p), 1961 }, 1962 }) 1963 1964 applyCh := make(chan struct{}) 1965 p.PlanResourceChangeFn = testDiffFn 1966 p.ApplyResourceChangeFn = func(req providers.ApplyResourceChangeRequest) providers.ApplyResourceChangeResponse { 1967 close(applyCh) 1968 1969 for !ctx.sh.Stopped() { 1970 // Wait for stop to be called. We call Gosched here so that 1971 // the other goroutines can always be scheduled to set Stopped. 1972 runtime.Gosched() 1973 } 1974 1975 // Sleep 1976 time.Sleep(100 * time.Millisecond) 1977 return testApplyFn(req) 1978 } 1979 1980 plan, diags := ctx.Plan(m, states.NewState(), DefaultPlanOpts) 1981 assertNoErrors(t, diags) 1982 1983 // Start the Apply in a goroutine 1984 var applyDiags tfdiags.Diagnostics 1985 stateCh := make(chan *states.State) 1986 go func() { 1987 state, diags := ctx.Apply(plan, m) 1988 applyDiags = diags 1989 1990 stateCh <- state 1991 }() 1992 1993 stopDone := make(chan struct{}) 1994 go func() { 1995 defer close(stopDone) 1996 <-applyCh 1997 ctx.Stop() 1998 }() 1999 2000 // Make sure that stop blocks 2001 select { 2002 case <-stopDone: 2003 t.Fatal("stop should block") 2004 case <-time.After(10 * time.Millisecond): 2005 } 2006 2007 // Wait for stop 2008 select { 2009 case <-stopDone: 2010 case <-time.After(500 * time.Millisecond): 2011 t.Fatal("stop should be done") 2012 } 2013 2014 // Wait for apply to complete 2015 state := <-stateCh 2016 // only expecting an early exit error 2017 if !applyDiags.HasErrors() { 2018 t.Fatal("expected early exit error") 2019 } 2020 2021 for _, d := range applyDiags { 2022 desc := d.Description() 2023 if desc.Summary != "execution halted" { 2024 t.Fatalf("unexpected error: %v", applyDiags.Err()) 2025 } 2026 } 2027 2028 checkStateString(t, state, ` 2029 aws_instance.foo: 2030 ID = foo 2031 provider = provider["registry.opentofu.org/hashicorp/aws"] 2032 num = 2 2033 type = aws_instance 2034 `) 2035 } 2036 2037 func TestContext2Apply_cancelProvisioner(t *testing.T) { 2038 m := testModule(t, "apply-cancel-provisioner") 2039 p := testProvider("aws") 2040 p.PlanResourceChangeFn = testDiffFn 2041 p.ApplyResourceChangeFn = testApplyFn 2042 2043 pr := testProvisioner() 2044 pr.GetSchemaResponse = provisioners.GetSchemaResponse{ 2045 Provisioner: &configschema.Block{ 2046 Attributes: map[string]*configschema.Attribute{ 2047 "foo": { 2048 Type: cty.String, 2049 Optional: true, 2050 }, 2051 }, 2052 }, 2053 } 2054 2055 ctx := testContext2(t, &ContextOpts{ 2056 Providers: map[addrs.Provider]providers.Factory{ 2057 addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p), 2058 }, 2059 Provisioners: map[string]provisioners.Factory{ 2060 "shell": testProvisionerFuncFixed(pr), 2061 }, 2062 }) 2063 2064 prStopped := make(chan struct{}) 2065 pr.ProvisionResourceFn = func(req provisioners.ProvisionResourceRequest) (resp provisioners.ProvisionResourceResponse) { 2066 // Start the stop process 2067 go ctx.Stop() 2068 2069 <-prStopped 2070 return 2071 } 2072 pr.StopFn = func() error { 2073 close(prStopped) 2074 return nil 2075 } 2076 2077 plan, diags := ctx.Plan(m, states.NewState(), DefaultPlanOpts) 2078 assertNoErrors(t, diags) 2079 2080 // Start the Apply in a goroutine 2081 var applyDiags tfdiags.Diagnostics 2082 stateCh := make(chan *states.State) 2083 go func() { 2084 state, diags := ctx.Apply(plan, m) 2085 applyDiags = diags 2086 2087 stateCh <- state 2088 }() 2089 2090 // Wait for completion 2091 state := <-stateCh 2092 2093 // we are expecting only an early exit error 2094 if !applyDiags.HasErrors() { 2095 t.Fatal("expected early exit error") 2096 } 2097 2098 for _, d := range applyDiags { 2099 desc := d.Description() 2100 if desc.Summary != "execution halted" { 2101 t.Fatalf("unexpected error: %v", applyDiags.Err()) 2102 } 2103 } 2104 2105 checkStateString(t, state, ` 2106 aws_instance.foo: (tainted) 2107 ID = foo 2108 provider = provider["registry.opentofu.org/hashicorp/aws"] 2109 num = 2 2110 type = aws_instance 2111 `) 2112 2113 if !pr.StopCalled { 2114 t.Fatal("stop should be called") 2115 } 2116 } 2117 2118 func TestContext2Apply_compute(t *testing.T) { 2119 m := testModule(t, "apply-compute") 2120 p := testProvider("aws") 2121 p.PlanResourceChangeFn = testDiffFn 2122 p.ApplyResourceChangeFn = testApplyFn 2123 p.GetProviderSchemaResponse = getProviderSchemaResponseFromProviderSchema(&ProviderSchema{ 2124 ResourceTypes: map[string]*configschema.Block{ 2125 "aws_instance": { 2126 Attributes: map[string]*configschema.Attribute{ 2127 "num": { 2128 Type: cty.Number, 2129 Optional: true, 2130 }, 2131 "compute": { 2132 Type: cty.String, 2133 Optional: true, 2134 }, 2135 "compute_value": { 2136 Type: cty.String, 2137 Optional: true, 2138 }, 2139 "foo": { 2140 Type: cty.String, 2141 Optional: true, 2142 }, 2143 "id": { 2144 Type: cty.String, 2145 Computed: true, 2146 }, 2147 "type": { 2148 Type: cty.String, 2149 Computed: true, 2150 }, 2151 "value": { // Populated from compute_value because compute = "value" in the config fixture 2152 Type: cty.String, 2153 Computed: true, 2154 }, 2155 }, 2156 }, 2157 }, 2158 }) 2159 2160 ctx := testContext2(t, &ContextOpts{ 2161 Providers: map[addrs.Provider]providers.Factory{ 2162 addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p), 2163 }, 2164 }) 2165 2166 plan, diags := ctx.Plan(m, states.NewState(), &PlanOpts{ 2167 SetVariables: InputValues{ 2168 "value": &InputValue{ 2169 Value: cty.NumberIntVal(1), 2170 SourceType: ValueFromCaller, 2171 }, 2172 }, 2173 }) 2174 assertNoErrors(t, diags) 2175 2176 state, diags := ctx.Apply(plan, m) 2177 if diags.HasErrors() { 2178 t.Fatalf("unexpected errors: %s", diags.Err()) 2179 } 2180 2181 actual := strings.TrimSpace(state.String()) 2182 expected := strings.TrimSpace(testTofuApplyComputeStr) 2183 if actual != expected { 2184 t.Fatalf("wrong result\n\ngot:\n%s\n\nwant:\n%s", actual, expected) 2185 } 2186 } 2187 2188 func TestContext2Apply_countDecrease(t *testing.T) { 2189 m := testModule(t, "apply-count-dec") 2190 p := testProvider("aws") 2191 p.PlanResourceChangeFn = testDiffFn 2192 p.ApplyResourceChangeFn = testApplyFn 2193 state := states.NewState() 2194 root := state.EnsureModule(addrs.RootModuleInstance) 2195 root.SetResourceInstanceCurrent( 2196 mustResourceInstanceAddr("aws_instance.foo[0]").Resource, 2197 &states.ResourceInstanceObjectSrc{ 2198 Status: states.ObjectReady, 2199 AttrsJSON: []byte(`{"id":"bar","foo": "foo","type": "aws_instance"}`), 2200 }, 2201 mustProviderConfig(`provider["registry.opentofu.org/hashicorp/aws"]`), 2202 ) 2203 root.SetResourceInstanceCurrent( 2204 mustResourceInstanceAddr("aws_instance.foo[1]").Resource, 2205 &states.ResourceInstanceObjectSrc{ 2206 Status: states.ObjectReady, 2207 AttrsJSON: []byte(`{"id":"bar","foo": "foo","type": "aws_instance"}`), 2208 }, 2209 mustProviderConfig(`provider["registry.opentofu.org/hashicorp/aws"]`), 2210 ) 2211 root.SetResourceInstanceCurrent( 2212 mustResourceInstanceAddr("aws_instance.foo[2]").Resource, 2213 &states.ResourceInstanceObjectSrc{ 2214 Status: states.ObjectReady, 2215 AttrsJSON: []byte(`{"id":"bar", "foo": "foo", "type": "aws_instance"}`), 2216 }, 2217 mustProviderConfig(`provider["registry.opentofu.org/hashicorp/aws"]`), 2218 ) 2219 2220 ctx := testContext2(t, &ContextOpts{ 2221 Providers: map[addrs.Provider]providers.Factory{ 2222 addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p), 2223 }, 2224 }) 2225 2226 plan, diags := ctx.Plan(m, state, DefaultPlanOpts) 2227 assertNoErrors(t, diags) 2228 2229 s, diags := ctx.Apply(plan, m) 2230 assertNoErrors(t, diags) 2231 2232 actual := strings.TrimSpace(s.String()) 2233 expected := strings.TrimSpace(testTofuApplyCountDecStr) 2234 if actual != expected { 2235 t.Fatalf("wrong result\n\ngot:\n%s\n\nwant:\n%s", actual, expected) 2236 } 2237 } 2238 2239 func TestContext2Apply_countDecreaseToOneX(t *testing.T) { 2240 m := testModule(t, "apply-count-dec-one") 2241 p := testProvider("aws") 2242 p.PlanResourceChangeFn = testDiffFn 2243 state := states.NewState() 2244 root := state.EnsureModule(addrs.RootModuleInstance) 2245 root.SetResourceInstanceCurrent( 2246 mustResourceInstanceAddr("aws_instance.foo[0]").Resource, 2247 &states.ResourceInstanceObjectSrc{ 2248 Status: states.ObjectReady, 2249 AttrsJSON: []byte(`{"id":"bar", "foo": "foo", "type": "aws_instance"}`), 2250 }, 2251 mustProviderConfig(`provider["registry.opentofu.org/hashicorp/aws"]`), 2252 ) 2253 root.SetResourceInstanceCurrent( 2254 mustResourceInstanceAddr("aws_instance.foo[1]").Resource, 2255 &states.ResourceInstanceObjectSrc{ 2256 Status: states.ObjectReady, 2257 AttrsJSON: []byte(`{"id":"bar"}`), 2258 }, 2259 mustProviderConfig(`provider["registry.opentofu.org/hashicorp/aws"]`), 2260 ) 2261 root.SetResourceInstanceCurrent( 2262 mustResourceInstanceAddr("aws_instance.foo[2]").Resource, 2263 &states.ResourceInstanceObjectSrc{ 2264 Status: states.ObjectReady, 2265 AttrsJSON: []byte(`{"id":"bar"}`), 2266 }, 2267 mustProviderConfig(`provider["registry.opentofu.org/hashicorp/aws"]`), 2268 ) 2269 2270 ctx := testContext2(t, &ContextOpts{ 2271 Providers: map[addrs.Provider]providers.Factory{ 2272 addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p), 2273 }, 2274 }) 2275 2276 plan, diags := ctx.Plan(m, state, DefaultPlanOpts) 2277 assertNoErrors(t, diags) 2278 2279 s, diags := ctx.Apply(plan, m) 2280 if diags.HasErrors() { 2281 t.Fatalf("diags: %s", diags.Err()) 2282 } 2283 2284 actual := strings.TrimSpace(s.String()) 2285 expected := strings.TrimSpace(testTofuApplyCountDecToOneStr) 2286 if actual != expected { 2287 t.Fatalf("wrong result\n\ngot:\n%s\n\nwant:\n%s", actual, expected) 2288 } 2289 } 2290 2291 // https://github.com/PeoplePerHour/terraform/pull/11 2292 // 2293 // This tests a rare but possible situation where we have both a no-key and 2294 // a zero-key instance of the same resource in the configuration when we 2295 // disable count. 2296 // 2297 // The main way to get here is for a provider to fail to destroy the zero-key 2298 // instance but succeed in creating the no-key instance, since those two 2299 // can typically happen concurrently. There are various other ways to get here 2300 // that might be considered user error, such as using "tofu state mv" 2301 // to create a strange combination of different key types on the same resource. 2302 // 2303 // This test indirectly exercises an intentional interaction between 2304 // refactoring.ImpliedMoveStatements and refactoring.ApplyMoves: we'll first 2305 // generate an implied move statement from aws_instance.foo[0] to 2306 // aws_instance.foo, but then refactoring.ApplyMoves should notice that and 2307 // ignore the statement, in the same way as it would if an explicit move 2308 // statement specified the same situation. 2309 func TestContext2Apply_countDecreaseToOneCorrupted(t *testing.T) { 2310 m := testModule(t, "apply-count-dec-one") 2311 p := testProvider("aws") 2312 p.PlanResourceChangeFn = testDiffFn 2313 state := states.NewState() 2314 root := state.EnsureModule(addrs.RootModuleInstance) 2315 root.SetResourceInstanceCurrent( 2316 mustResourceInstanceAddr("aws_instance.foo").Resource, 2317 &states.ResourceInstanceObjectSrc{ 2318 Status: states.ObjectReady, 2319 AttrsJSON: []byte(`{"id":"bar", "foo": "foo", "type": "aws_instance"}`), 2320 }, 2321 mustProviderConfig(`provider["registry.opentofu.org/hashicorp/aws"]`), 2322 ) 2323 root.SetResourceInstanceCurrent( 2324 mustResourceInstanceAddr("aws_instance.foo[0]").Resource, 2325 &states.ResourceInstanceObjectSrc{ 2326 Status: states.ObjectReady, 2327 AttrsJSON: []byte(`{"id":"baz", "type": "aws_instance"}`), 2328 }, 2329 mustProviderConfig(`provider["registry.opentofu.org/hashicorp/aws"]`), 2330 ) 2331 2332 ctx := testContext2(t, &ContextOpts{ 2333 Providers: map[addrs.Provider]providers.Factory{ 2334 addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p), 2335 }, 2336 }) 2337 2338 plan, diags := ctx.Plan(m, state, DefaultPlanOpts) 2339 assertNoErrors(t, diags) 2340 { 2341 got := strings.TrimSpace(legacyPlanComparisonString(state, plan.Changes)) 2342 want := strings.TrimSpace(testTofuApplyCountDecToOneCorruptedPlanStr) 2343 if got != want { 2344 t.Fatalf("wrong plan result\ngot:\n%s\nwant:\n%s", got, want) 2345 } 2346 } 2347 { 2348 change := plan.Changes.ResourceInstance(mustResourceInstanceAddr("aws_instance.foo[0]")) 2349 if change == nil { 2350 t.Fatalf("no planned change for instance zero") 2351 } 2352 if got, want := change.Action, plans.Delete; got != want { 2353 t.Errorf("wrong action for instance zero %s; want %s", got, want) 2354 } 2355 if got, want := change.ActionReason, plans.ResourceInstanceDeleteBecauseWrongRepetition; got != want { 2356 t.Errorf("wrong action reason for instance zero %s; want %s", got, want) 2357 } 2358 } 2359 { 2360 change := plan.Changes.ResourceInstance(mustResourceInstanceAddr("aws_instance.foo")) 2361 if change == nil { 2362 t.Fatalf("no planned change for no-key instance") 2363 } 2364 if got, want := change.Action, plans.NoOp; got != want { 2365 t.Errorf("wrong action for no-key instance %s; want %s", got, want) 2366 } 2367 if got, want := change.ActionReason, plans.ResourceInstanceChangeNoReason; got != want { 2368 t.Errorf("wrong action reason for no-key instance %s; want %s", got, want) 2369 } 2370 } 2371 2372 s, diags := ctx.Apply(plan, m) 2373 if diags.HasErrors() { 2374 t.Fatalf("diags: %s", diags.Err()) 2375 } 2376 2377 actual := strings.TrimSpace(s.String()) 2378 expected := strings.TrimSpace(testTofuApplyCountDecToOneCorruptedStr) 2379 if actual != expected { 2380 t.Fatalf("wrong final state\n\ngot:\n%s\n\nwant:\n%s", actual, expected) 2381 } 2382 } 2383 2384 func TestContext2Apply_countTainted(t *testing.T) { 2385 m := testModule(t, "apply-count-tainted") 2386 p := testProvider("aws") 2387 p.PlanResourceChangeFn = testDiffFn 2388 p.ApplyResourceChangeFn = testApplyFn 2389 state := states.NewState() 2390 root := state.EnsureModule(addrs.RootModuleInstance) 2391 root.SetResourceInstanceCurrent( 2392 mustResourceInstanceAddr("aws_instance.foo[0]").Resource, 2393 &states.ResourceInstanceObjectSrc{ 2394 Status: states.ObjectTainted, 2395 AttrsJSON: []byte(`{"id":"bar", "type": "aws_instance", "foo": "foo"}`), 2396 }, 2397 mustProviderConfig(`provider["registry.opentofu.org/hashicorp/aws"]`), 2398 ) 2399 ctx := testContext2(t, &ContextOpts{ 2400 Providers: map[addrs.Provider]providers.Factory{ 2401 addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p), 2402 }, 2403 }) 2404 2405 plan, diags := ctx.Plan(m, state, DefaultPlanOpts) 2406 assertNoErrors(t, diags) 2407 { 2408 got := strings.TrimSpace(legacyDiffComparisonString(plan.Changes)) 2409 want := strings.TrimSpace(` 2410 DESTROY/CREATE: aws_instance.foo[0] 2411 foo: "foo" => "foo" 2412 id: "bar" => "<computed>" 2413 type: "aws_instance" => "<computed>" 2414 CREATE: aws_instance.foo[1] 2415 foo: "" => "foo" 2416 id: "" => "<computed>" 2417 type: "" => "<computed>" 2418 `) 2419 if got != want { 2420 t.Fatalf("wrong plan\n\ngot:\n%s\n\nwant:\n%s", got, want) 2421 } 2422 } 2423 2424 s, diags := ctx.Apply(plan, m) 2425 assertNoErrors(t, diags) 2426 2427 got := strings.TrimSpace(s.String()) 2428 want := strings.TrimSpace(` 2429 aws_instance.foo.0: 2430 ID = foo 2431 provider = provider["registry.opentofu.org/hashicorp/aws"] 2432 foo = foo 2433 type = aws_instance 2434 aws_instance.foo.1: 2435 ID = foo 2436 provider = provider["registry.opentofu.org/hashicorp/aws"] 2437 foo = foo 2438 type = aws_instance 2439 `) 2440 if got != want { 2441 t.Fatalf("wrong final state\n\ngot:\n%s\n\nwant:\n%s", got, want) 2442 } 2443 } 2444 2445 func TestContext2Apply_countVariable(t *testing.T) { 2446 m := testModule(t, "apply-count-variable") 2447 p := testProvider("aws") 2448 p.PlanResourceChangeFn = testDiffFn 2449 p.ApplyResourceChangeFn = testApplyFn 2450 ctx := testContext2(t, &ContextOpts{ 2451 Providers: map[addrs.Provider]providers.Factory{ 2452 addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p), 2453 }, 2454 }) 2455 2456 plan, diags := ctx.Plan(m, states.NewState(), SimplePlanOpts(plans.NormalMode, testInputValuesUnset(m.Module.Variables))) 2457 assertNoErrors(t, diags) 2458 2459 state, diags := ctx.Apply(plan, m) 2460 if diags.HasErrors() { 2461 t.Fatalf("diags: %s", diags.Err()) 2462 } 2463 2464 actual := strings.TrimSpace(state.String()) 2465 expected := strings.TrimSpace(testTofuApplyCountVariableStr) 2466 if actual != expected { 2467 t.Fatalf("wrong result\n\ngot:\n%s\n\nwant:\n%s", actual, expected) 2468 } 2469 } 2470 2471 func TestContext2Apply_countVariableRef(t *testing.T) { 2472 m := testModule(t, "apply-count-variable-ref") 2473 p := testProvider("aws") 2474 p.PlanResourceChangeFn = testDiffFn 2475 p.ApplyResourceChangeFn = testApplyFn 2476 ctx := testContext2(t, &ContextOpts{ 2477 Providers: map[addrs.Provider]providers.Factory{ 2478 addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p), 2479 }, 2480 }) 2481 2482 plan, diags := ctx.Plan(m, states.NewState(), SimplePlanOpts(plans.NormalMode, testInputValuesUnset(m.Module.Variables))) 2483 assertNoErrors(t, diags) 2484 2485 state, diags := ctx.Apply(plan, m) 2486 if diags.HasErrors() { 2487 t.Fatalf("diags: %s", diags.Err()) 2488 } 2489 2490 actual := strings.TrimSpace(state.String()) 2491 expected := strings.TrimSpace(testTofuApplyCountVariableRefStr) 2492 if actual != expected { 2493 t.Fatalf("wrong result\n\ngot:\n%s\n\nwant:\n%s", actual, expected) 2494 } 2495 } 2496 2497 func TestContext2Apply_provisionerInterpCount(t *testing.T) { 2498 // This test ensures that a provisioner can interpolate a resource count 2499 // even though the provisioner expression is evaluated during the plan 2500 // walk. https://github.com/hashicorp/terraform/issues/16840 2501 2502 m, snap := testModuleWithSnapshot(t, "apply-provisioner-interp-count") 2503 2504 p := testProvider("aws") 2505 p.PlanResourceChangeFn = testDiffFn 2506 2507 pr := testProvisioner() 2508 2509 Providers := map[addrs.Provider]providers.Factory{ 2510 addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p), 2511 } 2512 2513 provisioners := map[string]provisioners.Factory{ 2514 "local-exec": testProvisionerFuncFixed(pr), 2515 } 2516 ctx := testContext2(t, &ContextOpts{ 2517 Providers: Providers, 2518 Provisioners: provisioners, 2519 }) 2520 2521 plan, diags := ctx.Plan(m, states.NewState(), SimplePlanOpts(plans.NormalMode, testInputValuesUnset(m.Module.Variables))) 2522 assertNoErrors(t, diags) 2523 2524 // We'll marshal and unmarshal the plan here, to ensure that we have 2525 // a clean new context as would be created if we separately ran 2526 // tofu plan -out=tfplan && tofu apply tfplan 2527 ctxOpts, m, plan, err := contextOptsForPlanViaFile(t, snap, plan) 2528 if err != nil { 2529 t.Fatal(err) 2530 } 2531 ctxOpts.Providers = Providers 2532 ctxOpts.Provisioners = provisioners 2533 ctx, diags = NewContext(ctxOpts) 2534 if diags.HasErrors() { 2535 t.Fatalf("failed to create context for plan: %s", diags.Err()) 2536 } 2537 2538 // Applying the plan should now succeed 2539 _, diags = ctx.Apply(plan, m) 2540 if diags.HasErrors() { 2541 t.Fatalf("apply failed unexpectedly: %s", diags.Err()) 2542 } 2543 2544 // Verify apply was invoked 2545 if !pr.ProvisionResourceCalled { 2546 t.Fatalf("provisioner was not called") 2547 } 2548 } 2549 2550 func TestContext2Apply_foreachVariable(t *testing.T) { 2551 m := testModule(t, "plan-for-each-unknown-value") 2552 p := testProvider("aws") 2553 p.PlanResourceChangeFn = testDiffFn 2554 p.ApplyResourceChangeFn = testApplyFn 2555 ctx := testContext2(t, &ContextOpts{ 2556 Providers: map[addrs.Provider]providers.Factory{ 2557 addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p), 2558 }, 2559 }) 2560 2561 plan, diags := ctx.Plan(m, states.NewState(), &PlanOpts{ 2562 Mode: plans.NormalMode, 2563 SetVariables: InputValues{ 2564 "foo": &InputValue{ 2565 Value: cty.StringVal("hello"), 2566 }, 2567 }, 2568 }) 2569 assertNoErrors(t, diags) 2570 2571 state, diags := ctx.Apply(plan, m) 2572 if diags.HasErrors() { 2573 t.Fatalf("diags: %s", diags.Err()) 2574 } 2575 2576 actual := strings.TrimSpace(state.String()) 2577 expected := strings.TrimSpace(testTofuApplyForEachVariableStr) 2578 if actual != expected { 2579 t.Fatalf("wrong result\n\ngot:\n%s\n\nwant:\n%s", actual, expected) 2580 } 2581 } 2582 2583 func TestContext2Apply_moduleBasic(t *testing.T) { 2584 m := testModule(t, "apply-module") 2585 p := testProvider("aws") 2586 p.PlanResourceChangeFn = testDiffFn 2587 p.ApplyResourceChangeFn = testApplyFn 2588 ctx := testContext2(t, &ContextOpts{ 2589 Providers: map[addrs.Provider]providers.Factory{ 2590 addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p), 2591 }, 2592 }) 2593 2594 plan, diags := ctx.Plan(m, states.NewState(), DefaultPlanOpts) 2595 assertNoErrors(t, diags) 2596 2597 state, diags := ctx.Apply(plan, m) 2598 if diags.HasErrors() { 2599 t.Fatalf("diags: %s", diags.Err()) 2600 } 2601 2602 actual := strings.TrimSpace(state.String()) 2603 expected := strings.TrimSpace(testTofuApplyModuleStr) 2604 if actual != expected { 2605 t.Fatalf("bad, expected:\n%s\n\nactual:\n%s", expected, actual) 2606 } 2607 } 2608 2609 func TestContext2Apply_moduleDestroyOrder(t *testing.T) { 2610 m := testModule(t, "apply-module-destroy-order") 2611 p := testProvider("aws") 2612 p.PlanResourceChangeFn = testDiffFn 2613 2614 // Create a custom apply function to track the order they were destroyed 2615 var order []string 2616 var orderLock sync.Mutex 2617 p.ApplyResourceChangeFn = func(req providers.ApplyResourceChangeRequest) (resp providers.ApplyResourceChangeResponse) { 2618 id := req.PriorState.GetAttr("id").AsString() 2619 2620 if id == "b" { 2621 // Pause briefly to make any race conditions more visible, since 2622 // missing edges here can cause undeterministic ordering. 2623 time.Sleep(100 * time.Millisecond) 2624 } 2625 2626 orderLock.Lock() 2627 defer orderLock.Unlock() 2628 2629 order = append(order, id) 2630 resp.NewState = req.PlannedState 2631 return resp 2632 } 2633 2634 p.GetProviderSchemaResponse = getProviderSchemaResponseFromProviderSchema(&ProviderSchema{ 2635 ResourceTypes: map[string]*configschema.Block{ 2636 "aws_instance": { 2637 Attributes: map[string]*configschema.Attribute{ 2638 "id": {Type: cty.String, Required: true}, 2639 "blah": {Type: cty.String, Optional: true}, 2640 "value": {Type: cty.String, Optional: true}, 2641 }, 2642 }, 2643 }, 2644 }) 2645 2646 state := states.NewState() 2647 child := state.EnsureModule(addrs.RootModuleInstance.Child("child", addrs.NoKey)) 2648 child.SetResourceInstanceCurrent( 2649 mustResourceInstanceAddr("aws_instance.a").Resource, 2650 &states.ResourceInstanceObjectSrc{ 2651 Status: states.ObjectReady, 2652 AttrsJSON: []byte(`{"id":"a"}`), 2653 }, 2654 mustProviderConfig(`provider["registry.opentofu.org/hashicorp/aws"]`), 2655 ) 2656 root := state.EnsureModule(addrs.RootModuleInstance) 2657 root.SetResourceInstanceCurrent( 2658 mustResourceInstanceAddr("aws_instance.b").Resource, 2659 &states.ResourceInstanceObjectSrc{ 2660 Status: states.ObjectReady, 2661 AttrsJSON: []byte(`{"id":"b"}`), 2662 Dependencies: []addrs.ConfigResource{mustConfigResourceAddr("module.child.aws_instance.a")}, 2663 }, 2664 mustProviderConfig(`provider["registry.opentofu.org/hashicorp/aws"]`), 2665 ) 2666 2667 ctx := testContext2(t, &ContextOpts{ 2668 Providers: map[addrs.Provider]providers.Factory{ 2669 addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p), 2670 }, 2671 }) 2672 2673 plan, diags := ctx.Plan(m, state, &PlanOpts{ 2674 Mode: plans.DestroyMode, 2675 }) 2676 assertNoErrors(t, diags) 2677 2678 state, diags = ctx.Apply(plan, m) 2679 if diags.HasErrors() { 2680 t.Fatalf("diags: %s", diags.Err()) 2681 } 2682 2683 expected := []string{"b", "a"} 2684 if !reflect.DeepEqual(order, expected) { 2685 t.Errorf("wrong order\ngot: %#v\nwant: %#v", order, expected) 2686 } 2687 2688 { 2689 actual := strings.TrimSpace(state.String()) 2690 expected := strings.TrimSpace(testTofuApplyModuleDestroyOrderStr) 2691 if actual != expected { 2692 t.Errorf("wrong final state\n\ngot:\n%s\n\nwant:\n%s", actual, expected) 2693 } 2694 } 2695 } 2696 2697 func TestContext2Apply_moduleInheritAlias(t *testing.T) { 2698 m := testModule(t, "apply-module-provider-inherit-alias") 2699 p := testProvider("aws") 2700 p.PlanResourceChangeFn = testDiffFn 2701 p.ApplyResourceChangeFn = testApplyFn 2702 2703 p.ConfigureProviderFn = func(req providers.ConfigureProviderRequest) (resp providers.ConfigureProviderResponse) { 2704 val := req.Config.GetAttr("value") 2705 if val.IsNull() { 2706 return 2707 } 2708 2709 root := req.Config.GetAttr("root") 2710 if !root.IsNull() { 2711 resp.Diagnostics = resp.Diagnostics.Append(fmt.Errorf("child should not get root")) 2712 } 2713 2714 return 2715 } 2716 2717 ctx := testContext2(t, &ContextOpts{ 2718 Providers: map[addrs.Provider]providers.Factory{ 2719 addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p), 2720 }, 2721 }) 2722 2723 plan, diags := ctx.Plan(m, states.NewState(), DefaultPlanOpts) 2724 assertNoErrors(t, diags) 2725 2726 state, diags := ctx.Apply(plan, m) 2727 if diags.HasErrors() { 2728 t.Fatalf("diags: %s", diags.Err()) 2729 } 2730 2731 checkStateString(t, state, ` 2732 <no state> 2733 module.child: 2734 aws_instance.foo: 2735 ID = foo 2736 provider = provider["registry.opentofu.org/hashicorp/aws"].eu 2737 type = aws_instance 2738 `) 2739 } 2740 2741 func TestContext2Apply_orphanResource(t *testing.T) { 2742 // This is a two-step test: 2743 // 1. Apply a configuration with resources that have count set. 2744 // This should place the empty resource object in the state to record 2745 // that each exists, and record any instances. 2746 // 2. Apply an empty configuration against the same state, which should 2747 // then clean up both the instances and the containing resource objects. 2748 p := testProvider("test") 2749 p.PlanResourceChangeFn = testDiffFn 2750 p.ApplyResourceChangeFn = testApplyFn 2751 p.GetProviderSchemaResponse = getProviderSchemaResponseFromProviderSchema(&ProviderSchema{ 2752 ResourceTypes: map[string]*configschema.Block{ 2753 "test_thing": { 2754 Attributes: map[string]*configschema.Attribute{ 2755 "id": {Type: cty.String, Computed: true}, 2756 "foo": {Type: cty.String, Optional: true}, 2757 }, 2758 }, 2759 }, 2760 }) 2761 2762 // Step 1: create the resources and instances 2763 m := testModule(t, "apply-orphan-resource") 2764 ctx := testContext2(t, &ContextOpts{ 2765 Providers: map[addrs.Provider]providers.Factory{ 2766 addrs.NewDefaultProvider("test"): testProviderFuncFixed(p), 2767 }, 2768 }) 2769 plan, diags := ctx.Plan(m, states.NewState(), DefaultPlanOpts) 2770 assertNoErrors(t, diags) 2771 state, diags := ctx.Apply(plan, m) 2772 assertNoErrors(t, diags) 2773 2774 // At this point both resources should be recorded in the state, along 2775 // with the single instance associated with test_thing.one. 2776 want := states.BuildState(func(s *states.SyncState) { 2777 providerAddr := addrs.AbsProviderConfig{ 2778 Provider: addrs.NewDefaultProvider("test"), 2779 Module: addrs.RootModule, 2780 } 2781 oneAddr := addrs.Resource{ 2782 Mode: addrs.ManagedResourceMode, 2783 Type: "test_thing", 2784 Name: "one", 2785 }.Absolute(addrs.RootModuleInstance) 2786 s.SetResourceProvider(oneAddr, providerAddr) 2787 s.SetResourceInstanceCurrent(oneAddr.Instance(addrs.IntKey(0)), &states.ResourceInstanceObjectSrc{ 2788 Status: states.ObjectReady, 2789 AttrsJSON: []byte(`{"id":"foo"}`), 2790 }, providerAddr) 2791 }) 2792 2793 if state.String() != want.String() { 2794 t.Fatalf("wrong state after step 1\n%s", cmp.Diff(want, state)) 2795 } 2796 2797 // Step 2: update with an empty config, to destroy everything 2798 m = testModule(t, "empty") 2799 ctx = testContext2(t, &ContextOpts{ 2800 Providers: map[addrs.Provider]providers.Factory{ 2801 addrs.NewDefaultProvider("test"): testProviderFuncFixed(p), 2802 }, 2803 }) 2804 plan, diags = ctx.Plan(m, state, DefaultPlanOpts) 2805 assertNoErrors(t, diags) 2806 { 2807 addr := mustResourceInstanceAddr("test_thing.one[0]") 2808 change := plan.Changes.ResourceInstance(addr) 2809 if change == nil { 2810 t.Fatalf("no planned change for %s", addr) 2811 } 2812 if got, want := change.Action, plans.Delete; got != want { 2813 t.Errorf("wrong action for %s %s; want %s", addr, got, want) 2814 } 2815 if got, want := change.ActionReason, plans.ResourceInstanceDeleteBecauseNoResourceConfig; got != want { 2816 t.Errorf("wrong action for %s %s; want %s", addr, got, want) 2817 } 2818 } 2819 2820 state, diags = ctx.Apply(plan, m) 2821 assertNoErrors(t, diags) 2822 2823 // The state should now be _totally_ empty, with just an empty root module 2824 // (since that always exists) and no resources at all. 2825 want = states.NewState() 2826 want.CheckResults = &states.CheckResults{} 2827 if !cmp.Equal(state, want) { 2828 t.Fatalf("wrong state after step 2\ngot: %swant: %s", spew.Sdump(state), spew.Sdump(want)) 2829 } 2830 2831 } 2832 2833 func TestContext2Apply_moduleOrphanInheritAlias(t *testing.T) { 2834 m := testModule(t, "apply-module-provider-inherit-alias-orphan") 2835 p := testProvider("aws") 2836 p.PlanResourceChangeFn = testDiffFn 2837 2838 p.ConfigureProviderFn = func(req providers.ConfigureProviderRequest) (resp providers.ConfigureProviderResponse) { 2839 val := req.Config.GetAttr("value") 2840 if val.IsNull() { 2841 return 2842 } 2843 2844 root := req.Config.GetAttr("root") 2845 if !root.IsNull() { 2846 resp.Diagnostics = resp.Diagnostics.Append(fmt.Errorf("child should not get root")) 2847 } 2848 2849 return 2850 } 2851 2852 // Create a state with an orphan module 2853 state := states.NewState() 2854 child := state.EnsureModule(addrs.RootModuleInstance.Child("child", addrs.NoKey)) 2855 child.SetResourceInstanceCurrent( 2856 mustResourceInstanceAddr("aws_instance.bar").Resource, 2857 &states.ResourceInstanceObjectSrc{ 2858 Status: states.ObjectReady, 2859 AttrsJSON: []byte(`{"id":"bar"}`), 2860 }, 2861 mustProviderConfig(`provider["registry.opentofu.org/hashicorp/aws"]`), 2862 ) 2863 2864 ctx := testContext2(t, &ContextOpts{ 2865 Providers: map[addrs.Provider]providers.Factory{ 2866 addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p), 2867 }, 2868 }) 2869 2870 plan, diags := ctx.Plan(m, state, DefaultPlanOpts) 2871 assertNoErrors(t, diags) 2872 { 2873 addr := mustResourceInstanceAddr("module.child.aws_instance.bar") 2874 change := plan.Changes.ResourceInstance(addr) 2875 if change == nil { 2876 t.Fatalf("no planned change for %s", addr) 2877 } 2878 if got, want := change.Action, plans.Delete; got != want { 2879 t.Errorf("wrong action for %s %s; want %s", addr, got, want) 2880 } 2881 // This should ideally be ResourceInstanceDeleteBecauseNoModule, but 2882 // the codepath deciding this doesn't currently have enough information 2883 // to differentiate, and so this is a compromise. 2884 if got, want := change.ActionReason, plans.ResourceInstanceDeleteBecauseNoResourceConfig; got != want { 2885 t.Errorf("wrong action for %s %s; want %s", addr, got, want) 2886 } 2887 } 2888 2889 state, diags = ctx.Apply(plan, m) 2890 if diags.HasErrors() { 2891 t.Fatalf("diags: %s", diags.Err()) 2892 } 2893 2894 if !p.ConfigureProviderCalled { 2895 t.Fatal("must call configure") 2896 } 2897 2898 checkStateString(t, state, "<no state>") 2899 } 2900 2901 func TestContext2Apply_moduleOrphanProvider(t *testing.T) { 2902 m := testModule(t, "apply-module-orphan-provider-inherit") 2903 p := testProvider("aws") 2904 p.PlanResourceChangeFn = testDiffFn 2905 2906 p.ConfigureProviderFn = func(req providers.ConfigureProviderRequest) (resp providers.ConfigureProviderResponse) { 2907 val := req.Config.GetAttr("value") 2908 if val.IsNull() { 2909 resp.Diagnostics = resp.Diagnostics.Append(fmt.Errorf("value is not found")) 2910 } 2911 2912 return 2913 } 2914 2915 // Create a state with an orphan module 2916 state := states.NewState() 2917 child := state.EnsureModule(addrs.RootModuleInstance.Child("child", addrs.NoKey)) 2918 child.SetResourceInstanceCurrent( 2919 mustResourceInstanceAddr("aws_instance.bar").Resource, 2920 &states.ResourceInstanceObjectSrc{ 2921 Status: states.ObjectReady, 2922 AttrsJSON: []byte(`{"id":"bar"}`), 2923 }, 2924 mustProviderConfig(`provider["registry.opentofu.org/hashicorp/aws"]`), 2925 ) 2926 2927 ctx := testContext2(t, &ContextOpts{ 2928 Providers: map[addrs.Provider]providers.Factory{ 2929 addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p), 2930 }, 2931 }) 2932 2933 plan, diags := ctx.Plan(m, state, DefaultPlanOpts) 2934 assertNoErrors(t, diags) 2935 2936 if _, diags := ctx.Apply(plan, m); diags.HasErrors() { 2937 t.Fatalf("apply errors: %s", diags.Err()) 2938 } 2939 } 2940 2941 func TestContext2Apply_moduleOrphanGrandchildProvider(t *testing.T) { 2942 m := testModule(t, "apply-module-orphan-provider-inherit") 2943 p := testProvider("aws") 2944 p.PlanResourceChangeFn = testDiffFn 2945 2946 p.ConfigureProviderFn = func(req providers.ConfigureProviderRequest) (resp providers.ConfigureProviderResponse) { 2947 val := req.Config.GetAttr("value") 2948 if val.IsNull() { 2949 resp.Diagnostics = resp.Diagnostics.Append(fmt.Errorf("value is not found")) 2950 } 2951 2952 return 2953 } 2954 2955 // Create a state with an orphan module that is nested (grandchild) 2956 state := states.NewState() 2957 child := state.EnsureModule(addrs.RootModuleInstance.Child("parent", addrs.NoKey).Child("child", addrs.NoKey)) 2958 child.SetResourceInstanceCurrent( 2959 mustResourceInstanceAddr("aws_instance.bar").Resource, 2960 &states.ResourceInstanceObjectSrc{ 2961 Status: states.ObjectReady, 2962 AttrsJSON: []byte(`{"id":"bar"}`), 2963 }, 2964 mustProviderConfig(`provider["registry.opentofu.org/hashicorp/aws"]`), 2965 ) 2966 2967 ctx := testContext2(t, &ContextOpts{ 2968 Providers: map[addrs.Provider]providers.Factory{ 2969 addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p), 2970 }, 2971 }) 2972 2973 plan, diags := ctx.Plan(m, state, DefaultPlanOpts) 2974 assertNoErrors(t, diags) 2975 2976 if _, diags := ctx.Apply(plan, m); diags.HasErrors() { 2977 t.Fatalf("apply errors: %s", diags.Err()) 2978 } 2979 } 2980 2981 func TestContext2Apply_moduleGrandchildProvider(t *testing.T) { 2982 m := testModule(t, "apply-module-grandchild-provider-inherit") 2983 p := testProvider("aws") 2984 p.PlanResourceChangeFn = testDiffFn 2985 2986 var callLock sync.Mutex 2987 called := false 2988 p.ConfigureProviderFn = func(req providers.ConfigureProviderRequest) (resp providers.ConfigureProviderResponse) { 2989 val := req.Config.GetAttr("value") 2990 if val.IsNull() { 2991 resp.Diagnostics = resp.Diagnostics.Append(fmt.Errorf("value is not found")) 2992 } 2993 2994 callLock.Lock() 2995 called = true 2996 callLock.Unlock() 2997 2998 return 2999 } 3000 3001 ctx := testContext2(t, &ContextOpts{ 3002 Providers: map[addrs.Provider]providers.Factory{ 3003 addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p), 3004 }, 3005 }) 3006 3007 plan, diags := ctx.Plan(m, states.NewState(), DefaultPlanOpts) 3008 assertNoErrors(t, diags) 3009 3010 if _, diags := ctx.Apply(plan, m); diags.HasErrors() { 3011 t.Fatalf("apply errors: %s", diags.Err()) 3012 } 3013 3014 callLock.Lock() 3015 defer callLock.Unlock() 3016 if called != true { 3017 t.Fatalf("err: configure never called") 3018 } 3019 } 3020 3021 // This tests an issue where all the providers in a module but not 3022 // in the root weren't being added to the root properly. In this test 3023 // case: aws is explicitly added to root, but "test" should be added to. 3024 // With the bug, it wasn't. 3025 func TestContext2Apply_moduleOnlyProvider(t *testing.T) { 3026 m := testModule(t, "apply-module-only-provider") 3027 p := testProvider("aws") 3028 p.PlanResourceChangeFn = testDiffFn 3029 p.ApplyResourceChangeFn = testApplyFn 3030 pTest := testProvider("test") 3031 pTest.ApplyResourceChangeFn = testApplyFn 3032 pTest.PlanResourceChangeFn = testDiffFn 3033 3034 ctx := testContext2(t, &ContextOpts{ 3035 Providers: map[addrs.Provider]providers.Factory{ 3036 addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p), 3037 addrs.NewDefaultProvider("test"): testProviderFuncFixed(pTest), 3038 }, 3039 }) 3040 3041 plan, diags := ctx.Plan(m, states.NewState(), DefaultPlanOpts) 3042 assertNoErrors(t, diags) 3043 3044 state, diags := ctx.Apply(plan, m) 3045 if diags.HasErrors() { 3046 t.Fatalf("diags: %s", diags.Err()) 3047 } 3048 3049 actual := strings.TrimSpace(state.String()) 3050 expected := strings.TrimSpace(testTofuApplyModuleOnlyProviderStr) 3051 if actual != expected { 3052 t.Fatalf("wrong result\n\ngot:\n%s\n\nwant:\n%s", actual, expected) 3053 } 3054 } 3055 3056 func TestContext2Apply_moduleProviderAlias(t *testing.T) { 3057 m := testModule(t, "apply-module-provider-alias") 3058 p := testProvider("aws") 3059 p.PlanResourceChangeFn = testDiffFn 3060 p.ApplyResourceChangeFn = testApplyFn 3061 ctx := testContext2(t, &ContextOpts{ 3062 Providers: map[addrs.Provider]providers.Factory{ 3063 addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p), 3064 }, 3065 }) 3066 3067 plan, diags := ctx.Plan(m, states.NewState(), DefaultPlanOpts) 3068 assertNoErrors(t, diags) 3069 3070 state, diags := ctx.Apply(plan, m) 3071 if diags.HasErrors() { 3072 t.Fatalf("diags: %s", diags.Err()) 3073 } 3074 3075 actual := strings.TrimSpace(state.String()) 3076 expected := strings.TrimSpace(testTofuApplyModuleProviderAliasStr) 3077 if actual != expected { 3078 t.Fatalf("wrong result\n\ngot:\n%s\n\nwant:\n%s", actual, expected) 3079 } 3080 } 3081 3082 func TestContext2Apply_moduleProviderAliasTargets(t *testing.T) { 3083 m := testModule(t, "apply-module-provider-alias") 3084 p := testProvider("aws") 3085 p.PlanResourceChangeFn = testDiffFn 3086 ctx := testContext2(t, &ContextOpts{ 3087 Providers: map[addrs.Provider]providers.Factory{ 3088 addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p), 3089 }, 3090 }) 3091 3092 plan, diags := ctx.Plan(m, states.NewState(), &PlanOpts{ 3093 Mode: plans.NormalMode, 3094 Targets: []addrs.Targetable{ 3095 addrs.ConfigResource{ 3096 Module: addrs.RootModule, 3097 Resource: addrs.Resource{ 3098 Mode: addrs.ManagedResourceMode, 3099 Type: "nonexistent", 3100 Name: "thing", 3101 }, 3102 }, 3103 }, 3104 }) 3105 assertNoErrors(t, diags) 3106 3107 state, diags := ctx.Apply(plan, m) 3108 if diags.HasErrors() { 3109 t.Fatalf("diags: %s", diags.Err()) 3110 } 3111 3112 actual := strings.TrimSpace(state.String()) 3113 expected := strings.TrimSpace(` 3114 <no state> 3115 `) 3116 if actual != expected { 3117 t.Fatalf("wrong result\n\ngot:\n%s\n\nwant:\n%s", actual, expected) 3118 } 3119 } 3120 3121 func TestContext2Apply_moduleProviderCloseNested(t *testing.T) { 3122 m := testModule(t, "apply-module-provider-close-nested") 3123 p := testProvider("aws") 3124 p.PlanResourceChangeFn = testDiffFn 3125 state := states.NewState() 3126 root := state.EnsureModule(addrs.RootModuleInstance) 3127 root.SetResourceInstanceCurrent( 3128 mustResourceInstanceAddr("aws_instance.foo").Resource, 3129 &states.ResourceInstanceObjectSrc{ 3130 Status: states.ObjectReady, 3131 AttrsJSON: []byte(`{"id":"bar"}`), 3132 }, 3133 mustProviderConfig(`provider["registry.opentofu.org/hashicorp/aws"]`), 3134 ) 3135 3136 ctx := testContext2(t, &ContextOpts{ 3137 Providers: map[addrs.Provider]providers.Factory{ 3138 addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p), 3139 }, 3140 }) 3141 3142 plan, diags := ctx.Plan(m, state, &PlanOpts{ 3143 Mode: plans.DestroyMode, 3144 }) 3145 assertNoErrors(t, diags) 3146 3147 if _, diags := ctx.Apply(plan, m); diags.HasErrors() { 3148 t.Fatalf("apply errors: %s", diags.Err()) 3149 } 3150 } 3151 3152 // Tests that variables used as module vars that reference data that 3153 // already exists in the state and requires no diff works properly. This 3154 // fixes an issue faced where module variables were pruned because they were 3155 // accessing "non-existent" resources (they existed, just not in the graph 3156 // cause they weren't in the diff). 3157 func TestContext2Apply_moduleVarRefExisting(t *testing.T) { 3158 m := testModule(t, "apply-ref-existing") 3159 p := testProvider("aws") 3160 p.PlanResourceChangeFn = testDiffFn 3161 p.ApplyResourceChangeFn = testApplyFn 3162 state := states.NewState() 3163 root := state.EnsureModule(addrs.RootModuleInstance) 3164 root.SetResourceInstanceCurrent( 3165 mustResourceInstanceAddr("aws_instance.foo").Resource, 3166 &states.ResourceInstanceObjectSrc{ 3167 Status: states.ObjectReady, 3168 AttrsJSON: []byte(`{"id":"foo","foo":"bar"}`), 3169 }, 3170 mustProviderConfig(`provider["registry.opentofu.org/hashicorp/aws"]`), 3171 ) 3172 3173 ctx := testContext2(t, &ContextOpts{ 3174 Providers: map[addrs.Provider]providers.Factory{ 3175 addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p), 3176 }, 3177 }) 3178 3179 plan, diags := ctx.Plan(m, state, DefaultPlanOpts) 3180 assertNoErrors(t, diags) 3181 3182 state, diags = ctx.Apply(plan, m) 3183 if diags.HasErrors() { 3184 t.Fatalf("diags: %s", diags.Err()) 3185 } 3186 3187 actual := strings.TrimSpace(state.String()) 3188 expected := strings.TrimSpace(testTofuApplyModuleVarRefExistingStr) 3189 if actual != expected { 3190 t.Fatalf("wrong result\n\ngot:\n%s\n\nwant:\n%s", actual, expected) 3191 } 3192 } 3193 3194 func TestContext2Apply_moduleVarResourceCount(t *testing.T) { 3195 m := testModule(t, "apply-module-var-resource-count") 3196 p := testProvider("aws") 3197 p.PlanResourceChangeFn = testDiffFn 3198 ctx := testContext2(t, &ContextOpts{ 3199 Providers: map[addrs.Provider]providers.Factory{ 3200 addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p), 3201 }, 3202 }) 3203 3204 plan, diags := ctx.Plan(m, states.NewState(), &PlanOpts{ 3205 Mode: plans.DestroyMode, 3206 SetVariables: InputValues{ 3207 "num": &InputValue{ 3208 Value: cty.NumberIntVal(2), 3209 SourceType: ValueFromCaller, 3210 }, 3211 }, 3212 }) 3213 assertNoErrors(t, diags) 3214 3215 state, diags := ctx.Apply(plan, m) 3216 assertNoErrors(t, diags) 3217 3218 ctx = testContext2(t, &ContextOpts{ 3219 Providers: map[addrs.Provider]providers.Factory{ 3220 addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p), 3221 }, 3222 }) 3223 3224 plan, diags = ctx.Plan(m, state, &PlanOpts{ 3225 Mode: plans.NormalMode, 3226 SetVariables: InputValues{ 3227 "num": &InputValue{ 3228 Value: cty.NumberIntVal(5), 3229 SourceType: ValueFromCaller, 3230 }, 3231 }, 3232 }) 3233 assertNoErrors(t, diags) 3234 3235 if _, diags := ctx.Apply(plan, m); diags.HasErrors() { 3236 t.Fatalf("apply errors: %s", diags.Err()) 3237 } 3238 } 3239 3240 // GH-819 3241 func TestContext2Apply_moduleBool(t *testing.T) { 3242 m := testModule(t, "apply-module-bool") 3243 p := testProvider("aws") 3244 p.PlanResourceChangeFn = testDiffFn 3245 p.ApplyResourceChangeFn = testApplyFn 3246 ctx := testContext2(t, &ContextOpts{ 3247 Providers: map[addrs.Provider]providers.Factory{ 3248 addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p), 3249 }, 3250 }) 3251 3252 plan, diags := ctx.Plan(m, states.NewState(), DefaultPlanOpts) 3253 assertNoErrors(t, diags) 3254 3255 state, diags := ctx.Apply(plan, m) 3256 if diags.HasErrors() { 3257 t.Fatalf("diags: %s", diags.Err()) 3258 } 3259 3260 actual := strings.TrimSpace(state.String()) 3261 expected := strings.TrimSpace(testTofuApplyModuleBoolStr) 3262 if actual != expected { 3263 t.Fatalf("wrong result\n\ngot:\n%s\n\nwant:\n%s", actual, expected) 3264 } 3265 } 3266 3267 // Tests that a module can be targeted and everything is properly created. 3268 // This adds to the plan test to also just verify that apply works. 3269 func TestContext2Apply_moduleTarget(t *testing.T) { 3270 m := testModule(t, "plan-targeted-cross-module") 3271 p := testProvider("aws") 3272 p.PlanResourceChangeFn = testDiffFn 3273 p.ApplyResourceChangeFn = testApplyFn 3274 ctx := testContext2(t, &ContextOpts{ 3275 Providers: map[addrs.Provider]providers.Factory{ 3276 addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p), 3277 }, 3278 }) 3279 3280 plan, diags := ctx.Plan(m, states.NewState(), &PlanOpts{ 3281 Mode: plans.NormalMode, 3282 Targets: []addrs.Targetable{ 3283 addrs.RootModuleInstance.Child("B", addrs.NoKey), 3284 }, 3285 }) 3286 assertNoErrors(t, diags) 3287 3288 state, diags := ctx.Apply(plan, m) 3289 if diags.HasErrors() { 3290 t.Fatalf("diags: %s", diags.Err()) 3291 } 3292 3293 checkStateString(t, state, ` 3294 <no state> 3295 module.A: 3296 aws_instance.foo: 3297 ID = foo 3298 provider = provider["registry.opentofu.org/hashicorp/aws"] 3299 foo = bar 3300 type = aws_instance 3301 3302 Outputs: 3303 3304 value = foo 3305 module.B: 3306 aws_instance.bar: 3307 ID = foo 3308 provider = provider["registry.opentofu.org/hashicorp/aws"] 3309 foo = foo 3310 type = aws_instance 3311 3312 Dependencies: 3313 module.A.aws_instance.foo 3314 `) 3315 } 3316 3317 func TestContext2Apply_multiProvider(t *testing.T) { 3318 m := testModule(t, "apply-multi-provider") 3319 p := testProvider("aws") 3320 p.PlanResourceChangeFn = testDiffFn 3321 p.ApplyResourceChangeFn = testApplyFn 3322 3323 pDO := testProvider("do") 3324 pDO.ApplyResourceChangeFn = testApplyFn 3325 pDO.PlanResourceChangeFn = testDiffFn 3326 3327 ctx := testContext2(t, &ContextOpts{ 3328 Providers: map[addrs.Provider]providers.Factory{ 3329 addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p), 3330 addrs.NewDefaultProvider("do"): testProviderFuncFixed(pDO), 3331 }, 3332 }) 3333 3334 plan, diags := ctx.Plan(m, states.NewState(), DefaultPlanOpts) 3335 assertNoErrors(t, diags) 3336 3337 state, diags := ctx.Apply(plan, m) 3338 if diags.HasErrors() { 3339 t.Fatalf("diags: %s", diags.Err()) 3340 } 3341 3342 mod := state.RootModule() 3343 if len(mod.Resources) < 2 { 3344 t.Fatalf("bad: %#v", mod.Resources) 3345 } 3346 3347 actual := strings.TrimSpace(state.String()) 3348 expected := strings.TrimSpace(testTofuApplyMultiProviderStr) 3349 if actual != expected { 3350 t.Fatalf("wrong result\n\ngot:\n%s\n\nwant:\n%s", actual, expected) 3351 } 3352 } 3353 3354 func TestContext2Apply_multiProviderDestroy(t *testing.T) { 3355 m := testModule(t, "apply-multi-provider-destroy") 3356 p := testProvider("aws") 3357 p.PlanResourceChangeFn = testDiffFn 3358 p.GetProviderSchemaResponse = getProviderSchemaResponseFromProviderSchema(&ProviderSchema{ 3359 Provider: &configschema.Block{ 3360 Attributes: map[string]*configschema.Attribute{ 3361 "addr": {Type: cty.String, Optional: true}, 3362 }, 3363 }, 3364 ResourceTypes: map[string]*configschema.Block{ 3365 "aws_instance": { 3366 Attributes: map[string]*configschema.Attribute{ 3367 "id": {Type: cty.String, Computed: true}, 3368 "foo": {Type: cty.String, Optional: true}, 3369 }, 3370 }, 3371 }, 3372 }) 3373 3374 p2 := testProvider("vault") 3375 p2.ApplyResourceChangeFn = testApplyFn 3376 p2.PlanResourceChangeFn = testDiffFn 3377 p2.GetProviderSchemaResponse = getProviderSchemaResponseFromProviderSchema(&ProviderSchema{ 3378 ResourceTypes: map[string]*configschema.Block{ 3379 "vault_instance": { 3380 Attributes: map[string]*configschema.Attribute{ 3381 "id": {Type: cty.String, Computed: true}, 3382 }, 3383 }, 3384 }, 3385 }) 3386 3387 var state *states.State 3388 3389 // First, create the instances 3390 { 3391 ctx := testContext2(t, &ContextOpts{ 3392 Providers: map[addrs.Provider]providers.Factory{ 3393 addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p), 3394 addrs.NewDefaultProvider("vault"): testProviderFuncFixed(p2), 3395 }, 3396 }) 3397 3398 plan, diags := ctx.Plan(m, states.NewState(), DefaultPlanOpts) 3399 assertNoErrors(t, diags) 3400 3401 s, diags := ctx.Apply(plan, m) 3402 assertNoErrors(t, diags) 3403 3404 state = s 3405 } 3406 3407 // Destroy them 3408 { 3409 // Verify that aws_instance.bar is destroyed first 3410 var checked bool 3411 var called int32 3412 var lock sync.Mutex 3413 applyFn := func(req providers.ApplyResourceChangeRequest) (resp providers.ApplyResourceChangeResponse) { 3414 lock.Lock() 3415 defer lock.Unlock() 3416 3417 if req.TypeName == "aws_instance" { 3418 checked = true 3419 3420 // Sleep to allow parallel execution 3421 time.Sleep(50 * time.Millisecond) 3422 3423 // Verify that called is 0 (dep not called) 3424 if atomic.LoadInt32(&called) != 0 { 3425 resp.Diagnostics = resp.Diagnostics.Append(fmt.Errorf("nothing else should be called")) 3426 return resp 3427 } 3428 } 3429 3430 atomic.AddInt32(&called, 1) 3431 return testApplyFn(req) 3432 } 3433 3434 // Set the apply functions 3435 p.ApplyResourceChangeFn = applyFn 3436 p2.ApplyResourceChangeFn = applyFn 3437 3438 ctx := testContext2(t, &ContextOpts{ 3439 Providers: map[addrs.Provider]providers.Factory{ 3440 addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p), 3441 addrs.NewDefaultProvider("vault"): testProviderFuncFixed(p2), 3442 }, 3443 }) 3444 3445 plan, diags := ctx.Plan(m, state, &PlanOpts{ 3446 Mode: plans.DestroyMode, 3447 }) 3448 assertNoErrors(t, diags) 3449 3450 s, diags := ctx.Apply(plan, m) 3451 assertNoErrors(t, diags) 3452 3453 if !checked { 3454 t.Fatal("should be checked") 3455 } 3456 3457 state = s 3458 } 3459 3460 checkStateString(t, state, `<no state>`) 3461 } 3462 3463 // This is like the multiProviderDestroy test except it tests that 3464 // dependent resources within a child module that inherit provider 3465 // configuration are still destroyed first. 3466 func TestContext2Apply_multiProviderDestroyChild(t *testing.T) { 3467 m := testModule(t, "apply-multi-provider-destroy-child") 3468 p := testProvider("aws") 3469 p.PlanResourceChangeFn = testDiffFn 3470 p.GetProviderSchemaResponse = getProviderSchemaResponseFromProviderSchema(&ProviderSchema{ 3471 Provider: &configschema.Block{ 3472 Attributes: map[string]*configschema.Attribute{ 3473 "value": {Type: cty.String, Optional: true}, 3474 }, 3475 }, 3476 ResourceTypes: map[string]*configschema.Block{ 3477 "aws_instance": { 3478 Attributes: map[string]*configschema.Attribute{ 3479 "id": {Type: cty.String, Computed: true}, 3480 "foo": {Type: cty.String, Optional: true}, 3481 }, 3482 }, 3483 }, 3484 }) 3485 3486 p2 := testProvider("vault") 3487 p2.ApplyResourceChangeFn = testApplyFn 3488 p2.PlanResourceChangeFn = testDiffFn 3489 p2.GetProviderSchemaResponse = getProviderSchemaResponseFromProviderSchema(&ProviderSchema{ 3490 Provider: &configschema.Block{}, 3491 ResourceTypes: map[string]*configschema.Block{ 3492 "vault_instance": { 3493 Attributes: map[string]*configschema.Attribute{ 3494 "id": {Type: cty.String, Computed: true}, 3495 }, 3496 }, 3497 }, 3498 }) 3499 3500 var state *states.State 3501 3502 // First, create the instances 3503 { 3504 ctx := testContext2(t, &ContextOpts{ 3505 Providers: map[addrs.Provider]providers.Factory{ 3506 addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p), 3507 addrs.NewDefaultProvider("vault"): testProviderFuncFixed(p2), 3508 }, 3509 }) 3510 3511 plan, diags := ctx.Plan(m, states.NewState(), DefaultPlanOpts) 3512 assertNoErrors(t, diags) 3513 3514 s, diags := ctx.Apply(plan, m) 3515 if diags.HasErrors() { 3516 t.Fatalf("diags: %s", diags.Err()) 3517 } 3518 3519 state = s 3520 } 3521 3522 // Destroy them 3523 { 3524 // Verify that aws_instance.bar is destroyed first 3525 var checked bool 3526 var called int32 3527 var lock sync.Mutex 3528 applyFn := func(req providers.ApplyResourceChangeRequest) (resp providers.ApplyResourceChangeResponse) { 3529 lock.Lock() 3530 defer lock.Unlock() 3531 3532 if req.TypeName == "aws_instance" { 3533 checked = true 3534 3535 // Sleep to allow parallel execution 3536 time.Sleep(50 * time.Millisecond) 3537 3538 // Verify that called is 0 (dep not called) 3539 if atomic.LoadInt32(&called) != 0 { 3540 resp.Diagnostics = resp.Diagnostics.Append(fmt.Errorf("nothing else should be called")) 3541 return resp 3542 } 3543 } 3544 3545 atomic.AddInt32(&called, 1) 3546 return testApplyFn(req) 3547 } 3548 3549 // Set the apply functions 3550 p.ApplyResourceChangeFn = applyFn 3551 p2.ApplyResourceChangeFn = applyFn 3552 3553 ctx := testContext2(t, &ContextOpts{ 3554 Providers: map[addrs.Provider]providers.Factory{ 3555 addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p), 3556 addrs.NewDefaultProvider("vault"): testProviderFuncFixed(p2), 3557 }, 3558 }) 3559 3560 plan, diags := ctx.Plan(m, state, &PlanOpts{ 3561 Mode: plans.DestroyMode, 3562 }) 3563 assertNoErrors(t, diags) 3564 3565 s, diags := ctx.Apply(plan, m) 3566 if diags.HasErrors() { 3567 t.Fatalf("diags: %s", diags.Err()) 3568 } 3569 3570 if !checked { 3571 t.Fatal("should be checked") 3572 } 3573 3574 state = s 3575 } 3576 3577 checkStateString(t, state, ` 3578 <no state> 3579 `) 3580 } 3581 3582 func TestContext2Apply_multiVar(t *testing.T) { 3583 m := testModule(t, "apply-multi-var") 3584 p := testProvider("aws") 3585 p.PlanResourceChangeFn = testDiffFn 3586 3587 // First, apply with a count of 3 3588 ctx := testContext2(t, &ContextOpts{ 3589 Providers: map[addrs.Provider]providers.Factory{ 3590 addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p), 3591 }, 3592 }) 3593 3594 plan, diags := ctx.Plan(m, states.NewState(), &PlanOpts{ 3595 Mode: plans.NormalMode, 3596 SetVariables: InputValues{ 3597 "num": &InputValue{ 3598 Value: cty.NumberIntVal(3), 3599 SourceType: ValueFromCaller, 3600 }, 3601 }, 3602 }) 3603 assertNoErrors(t, diags) 3604 3605 state, diags := ctx.Apply(plan, m) 3606 if diags.HasErrors() { 3607 t.Fatalf("diags: %s", diags.Err()) 3608 } 3609 3610 actual := state.RootModule().OutputValues["output"] 3611 expected := cty.StringVal("bar0,bar1,bar2") 3612 if actual == nil || actual.Value != expected { 3613 t.Fatalf("wrong value\ngot: %#v\nwant: %#v", actual.Value, expected) 3614 } 3615 3616 t.Logf("Initial state: %s", state.String()) 3617 3618 // Apply again, reduce the count to 1 3619 { 3620 ctx := testContext2(t, &ContextOpts{ 3621 Providers: map[addrs.Provider]providers.Factory{ 3622 addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p), 3623 }, 3624 }) 3625 3626 plan, diags := ctx.Plan(m, state, &PlanOpts{ 3627 Mode: plans.NormalMode, 3628 SetVariables: InputValues{ 3629 "num": &InputValue{ 3630 Value: cty.NumberIntVal(1), 3631 SourceType: ValueFromCaller, 3632 }, 3633 }, 3634 }) 3635 assertNoErrors(t, diags) 3636 3637 state, diags := ctx.Apply(plan, m) 3638 if diags.HasErrors() { 3639 t.Fatalf("diags: %s", diags.Err()) 3640 } 3641 3642 t.Logf("End state: %s", state.String()) 3643 3644 actual := state.RootModule().OutputValues["output"] 3645 if actual == nil { 3646 t.Fatal("missing output") 3647 } 3648 3649 expected := cty.StringVal("bar0") 3650 if actual.Value != expected { 3651 t.Fatalf("wrong value\ngot: %#v\nwant: %#v", actual.Value, expected) 3652 } 3653 } 3654 } 3655 3656 // This is a holistic test of multi-var (aka "splat variable") handling 3657 // across several different OpenTofu subsystems. This is here because 3658 // historically there were quirky differences in handling across different 3659 // parts of OpenTofu and so here we want to assert the expected behavior and 3660 // ensure that it remains consistent in future. 3661 func TestContext2Apply_multiVarComprehensive(t *testing.T) { 3662 m := testModule(t, "apply-multi-var-comprehensive") 3663 p := testProvider("test") 3664 3665 configs := map[string]cty.Value{} 3666 var configsLock sync.Mutex 3667 3668 p.ApplyResourceChangeFn = testApplyFn 3669 p.PlanResourceChangeFn = func(req providers.PlanResourceChangeRequest) providers.PlanResourceChangeResponse { 3670 proposed := req.ProposedNewState 3671 configsLock.Lock() 3672 defer configsLock.Unlock() 3673 key := proposed.GetAttr("key").AsString() 3674 // This test was originally written using the legacy p.PlanResourceChangeFn interface, 3675 // and so the assertions below expect an old-style ResourceConfig, which 3676 // we'll construct via our shim for now to avoid rewriting all of the 3677 // assertions. 3678 configs[key] = req.ProposedNewState 3679 3680 retVals := make(map[string]cty.Value) 3681 for it := proposed.ElementIterator(); it.Next(); { 3682 idxVal, val := it.Element() 3683 idx := idxVal.AsString() 3684 3685 switch idx { 3686 case "id": 3687 retVals[idx] = cty.UnknownVal(cty.String) 3688 case "name": 3689 retVals[idx] = cty.StringVal(key) 3690 default: 3691 retVals[idx] = val 3692 } 3693 } 3694 3695 return providers.PlanResourceChangeResponse{ 3696 PlannedState: cty.ObjectVal(retVals), 3697 } 3698 } 3699 3700 p.GetProviderSchemaResponse = getProviderSchemaResponseFromProviderSchema(&ProviderSchema{ 3701 ResourceTypes: map[string]*configschema.Block{ 3702 "test_thing": { 3703 Attributes: map[string]*configschema.Attribute{ 3704 "key": {Type: cty.String, Required: true}, 3705 3706 "source_id": {Type: cty.String, Optional: true}, 3707 "source_name": {Type: cty.String, Optional: true}, 3708 "first_source_id": {Type: cty.String, Optional: true}, 3709 "first_source_name": {Type: cty.String, Optional: true}, 3710 "source_ids": {Type: cty.List(cty.String), Optional: true}, 3711 "source_names": {Type: cty.List(cty.String), Optional: true}, 3712 "source_ids_from_func": {Type: cty.List(cty.String), Optional: true}, 3713 "source_names_from_func": {Type: cty.List(cty.String), Optional: true}, 3714 "source_ids_wrapped": {Type: cty.List(cty.List(cty.String)), Optional: true}, 3715 "source_names_wrapped": {Type: cty.List(cty.List(cty.String)), Optional: true}, 3716 3717 "id": {Type: cty.String, Computed: true}, 3718 "name": {Type: cty.String, Computed: true}, 3719 }, 3720 }, 3721 }, 3722 }) 3723 3724 // First, apply with a count of 3 3725 ctx := testContext2(t, &ContextOpts{ 3726 Providers: map[addrs.Provider]providers.Factory{ 3727 addrs.NewDefaultProvider("test"): testProviderFuncFixed(p), 3728 }, 3729 }) 3730 3731 plan, diags := ctx.Plan(m, states.NewState(), &PlanOpts{ 3732 Mode: plans.NormalMode, 3733 SetVariables: InputValues{ 3734 "num": &InputValue{ 3735 Value: cty.NumberIntVal(3), 3736 SourceType: ValueFromCaller, 3737 }, 3738 }, 3739 }) 3740 assertNoErrors(t, diags) 3741 3742 checkConfig := func(key string, want cty.Value) { 3743 configsLock.Lock() 3744 defer configsLock.Unlock() 3745 3746 got, ok := configs[key] 3747 if !ok { 3748 t.Errorf("no config recorded for %s; expected a configuration", key) 3749 return 3750 } 3751 3752 t.Run("config for "+key, func(t *testing.T) { 3753 for _, problem := range deep.Equal(got, want) { 3754 t.Errorf(problem) 3755 } 3756 }) 3757 } 3758 3759 checkConfig("multi_count_var.0", cty.ObjectVal(map[string]cty.Value{ 3760 "source_id": cty.UnknownVal(cty.String), 3761 "source_name": cty.StringVal("source.0"), 3762 })) 3763 checkConfig("multi_count_var.2", cty.ObjectVal(map[string]cty.Value{ 3764 "source_id": cty.UnknownVal(cty.String), 3765 "source_name": cty.StringVal("source.2"), 3766 })) 3767 checkConfig("multi_count_derived.0", cty.ObjectVal(map[string]cty.Value{ 3768 "source_id": cty.UnknownVal(cty.String), 3769 "source_name": cty.StringVal("source.0"), 3770 })) 3771 checkConfig("multi_count_derived.2", cty.ObjectVal(map[string]cty.Value{ 3772 "source_id": cty.UnknownVal(cty.String), 3773 "source_name": cty.StringVal("source.2"), 3774 })) 3775 checkConfig("whole_splat", cty.ObjectVal(map[string]cty.Value{ 3776 "source_ids": cty.ListVal([]cty.Value{ 3777 cty.UnknownVal(cty.String), 3778 cty.UnknownVal(cty.String), 3779 cty.UnknownVal(cty.String), 3780 }), 3781 "source_names": cty.ListVal([]cty.Value{ 3782 cty.StringVal("source.0"), 3783 cty.StringVal("source.1"), 3784 cty.StringVal("source.2"), 3785 }), 3786 "source_ids_from_func": cty.UnknownVal(cty.String), 3787 "source_names_from_func": cty.ListVal([]cty.Value{ 3788 cty.StringVal("source.0"), 3789 cty.StringVal("source.1"), 3790 cty.StringVal("source.2"), 3791 }), 3792 "source_ids_wrapped": cty.ListVal([]cty.Value{ 3793 cty.ListVal([]cty.Value{ 3794 cty.UnknownVal(cty.String), 3795 cty.UnknownVal(cty.String), 3796 cty.UnknownVal(cty.String), 3797 }), 3798 }), 3799 "source_names_wrapped": cty.ListVal([]cty.Value{ 3800 cty.ListVal([]cty.Value{ 3801 cty.StringVal("source.0"), 3802 cty.StringVal("source.1"), 3803 cty.StringVal("source.2"), 3804 }), 3805 }), 3806 "first_source_id": cty.UnknownVal(cty.String), 3807 "first_source_name": cty.StringVal("source.0"), 3808 })) 3809 checkConfig("child.whole_splat", cty.ObjectVal(map[string]cty.Value{ 3810 "source_ids": cty.ListVal([]cty.Value{ 3811 cty.UnknownVal(cty.String), 3812 cty.UnknownVal(cty.String), 3813 cty.UnknownVal(cty.String), 3814 }), 3815 "source_names": cty.ListVal([]cty.Value{ 3816 cty.StringVal("source.0"), 3817 cty.StringVal("source.1"), 3818 cty.StringVal("source.2"), 3819 }), 3820 "source_ids_wrapped": cty.ListVal([]cty.Value{ 3821 cty.ListVal([]cty.Value{ 3822 cty.UnknownVal(cty.String), 3823 cty.UnknownVal(cty.String), 3824 cty.UnknownVal(cty.String), 3825 }), 3826 }), 3827 "source_names_wrapped": cty.ListVal([]cty.Value{ 3828 cty.ListVal([]cty.Value{ 3829 cty.StringVal("source.0"), 3830 cty.StringVal("source.1"), 3831 cty.StringVal("source.2"), 3832 }), 3833 }), 3834 })) 3835 3836 t.Run("apply", func(t *testing.T) { 3837 state, diags := ctx.Apply(plan, m) 3838 if diags.HasErrors() { 3839 t.Fatalf("error during apply: %s", diags.Err()) 3840 } 3841 3842 want := map[string]interface{}{ 3843 "source_ids": []interface{}{"foo", "foo", "foo"}, 3844 "source_names": []interface{}{ 3845 "source.0", 3846 "source.1", 3847 "source.2", 3848 }, 3849 } 3850 got := map[string]interface{}{} 3851 for k, s := range state.RootModule().OutputValues { 3852 got[k] = hcl2shim.ConfigValueFromHCL2(s.Value) 3853 } 3854 if !reflect.DeepEqual(got, want) { 3855 t.Errorf( 3856 "wrong outputs\ngot: %s\nwant: %s", 3857 spew.Sdump(got), spew.Sdump(want), 3858 ) 3859 } 3860 }) 3861 } 3862 3863 // Test that multi-var (splat) access is ordered by count, not by 3864 // value. 3865 func TestContext2Apply_multiVarOrder(t *testing.T) { 3866 m := testModule(t, "apply-multi-var-order") 3867 p := testProvider("aws") 3868 p.PlanResourceChangeFn = testDiffFn 3869 3870 // First, apply with a count of 3 3871 ctx := testContext2(t, &ContextOpts{ 3872 Providers: map[addrs.Provider]providers.Factory{ 3873 addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p), 3874 }, 3875 }) 3876 3877 plan, diags := ctx.Plan(m, states.NewState(), SimplePlanOpts(plans.NormalMode, testInputValuesUnset(m.Module.Variables))) 3878 assertNoErrors(t, diags) 3879 3880 state, diags := ctx.Apply(plan, m) 3881 if diags.HasErrors() { 3882 t.Fatalf("diags: %s", diags.Err()) 3883 } 3884 3885 t.Logf("State: %s", state.String()) 3886 3887 actual := state.RootModule().OutputValues["should-be-11"] 3888 expected := cty.StringVal("index-11") 3889 if actual == nil || actual.Value != expected { 3890 t.Fatalf("wrong value\ngot: %#v\nwant: %#v", actual.Value, expected) 3891 } 3892 } 3893 3894 // Test that multi-var (splat) access is ordered by count, not by 3895 // value, through interpolations. 3896 func TestContext2Apply_multiVarOrderInterp(t *testing.T) { 3897 m := testModule(t, "apply-multi-var-order-interp") 3898 p := testProvider("aws") 3899 p.PlanResourceChangeFn = testDiffFn 3900 3901 // First, apply with a count of 3 3902 ctx := testContext2(t, &ContextOpts{ 3903 Providers: map[addrs.Provider]providers.Factory{ 3904 addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p), 3905 }, 3906 }) 3907 3908 plan, diags := ctx.Plan(m, states.NewState(), SimplePlanOpts(plans.NormalMode, testInputValuesUnset(m.Module.Variables))) 3909 assertNoErrors(t, diags) 3910 3911 state, diags := ctx.Apply(plan, m) 3912 if diags.HasErrors() { 3913 t.Fatalf("diags: %s", diags.Err()) 3914 } 3915 3916 t.Logf("State: %s", state.String()) 3917 3918 actual := state.RootModule().OutputValues["should-be-11"] 3919 expected := cty.StringVal("baz-index-11") 3920 if actual == nil || actual.Value != expected { 3921 t.Fatalf("wrong value\ngot: %#v\nwant: %#v", actual.Value, expected) 3922 } 3923 } 3924 3925 // Based on GH-10440 where a graph edge wasn't properly being created 3926 // between a modified resource and a count instance being destroyed. 3927 func TestContext2Apply_multiVarCountDec(t *testing.T) { 3928 var s *states.State 3929 3930 // First create resources. Nothing sneaky here. 3931 { 3932 m := testModule(t, "apply-multi-var-count-dec") 3933 p := testProvider("aws") 3934 p.PlanResourceChangeFn = testDiffFn 3935 p.ApplyResourceChangeFn = testApplyFn 3936 ctx := testContext2(t, &ContextOpts{ 3937 Providers: map[addrs.Provider]providers.Factory{ 3938 addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p), 3939 }, 3940 }) 3941 3942 log.Print("\n========\nStep 1 Plan\n========") 3943 plan, diags := ctx.Plan(m, states.NewState(), &PlanOpts{ 3944 Mode: plans.NormalMode, 3945 SetVariables: InputValues{ 3946 "num": &InputValue{ 3947 Value: cty.NumberIntVal(2), 3948 SourceType: ValueFromCaller, 3949 }, 3950 }, 3951 }) 3952 assertNoErrors(t, diags) 3953 3954 log.Print("\n========\nStep 1 Apply\n========") 3955 state, diags := ctx.Apply(plan, m) 3956 if diags.HasErrors() { 3957 t.Fatalf("diags: %s", diags.Err()) 3958 } 3959 3960 t.Logf("Step 1 state:\n%s", state) 3961 3962 s = state 3963 } 3964 3965 // Decrease the count by 1 and verify that everything happens in the 3966 // right order. 3967 m := testModule(t, "apply-multi-var-count-dec") 3968 p := testProvider("aws") 3969 p.PlanResourceChangeFn = testDiffFn 3970 3971 // Verify that aws_instance.bar is modified first and nothing 3972 // else happens at the same time. 3973 { 3974 var checked bool 3975 var called int32 3976 var lock sync.Mutex 3977 p.ApplyResourceChangeFn = func(req providers.ApplyResourceChangeRequest) (resp providers.ApplyResourceChangeResponse) { 3978 lock.Lock() 3979 defer lock.Unlock() 3980 3981 if !req.PlannedState.IsNull() { 3982 s := req.PlannedState.AsValueMap() 3983 if ami, ok := s["ami"]; ok && !ami.IsNull() && ami.AsString() == "special" { 3984 checked = true 3985 3986 // Sleep to allow parallel execution 3987 time.Sleep(50 * time.Millisecond) 3988 3989 // Verify that called is 0 (dep not called) 3990 if atomic.LoadInt32(&called) != 1 { 3991 resp.Diagnostics = resp.Diagnostics.Append(fmt.Errorf("nothing else should be called")) 3992 return 3993 } 3994 } 3995 } 3996 atomic.AddInt32(&called, 1) 3997 return testApplyFn(req) 3998 } 3999 4000 ctx := testContext2(t, &ContextOpts{ 4001 Providers: map[addrs.Provider]providers.Factory{ 4002 addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p), 4003 }, 4004 }) 4005 4006 log.Print("\n========\nStep 2 Plan\n========") 4007 plan, diags := ctx.Plan(m, s, &PlanOpts{ 4008 Mode: plans.NormalMode, 4009 SetVariables: InputValues{ 4010 "num": &InputValue{ 4011 Value: cty.NumberIntVal(1), 4012 SourceType: ValueFromCaller, 4013 }, 4014 }, 4015 }) 4016 assertNoErrors(t, diags) 4017 4018 t.Logf("Step 2 plan:\n%s", legacyDiffComparisonString(plan.Changes)) 4019 4020 log.Print("\n========\nStep 2 Apply\n========") 4021 _, diags = ctx.Apply(plan, m) 4022 if diags.HasErrors() { 4023 t.Fatalf("apply errors: %s", diags.Err()) 4024 } 4025 4026 if !checked { 4027 t.Error("apply never called") 4028 } 4029 } 4030 } 4031 4032 // Test that we can resolve a multi-var (splat) for the first resource 4033 // created in a non-root module, which happens when the module state doesn't 4034 // exist yet. 4035 // https://github.com/hashicorp/terraform/issues/14438 4036 func TestContext2Apply_multiVarMissingState(t *testing.T) { 4037 m := testModule(t, "apply-multi-var-missing-state") 4038 p := testProvider("test") 4039 p.PlanResourceChangeFn = testDiffFn 4040 p.GetProviderSchemaResponse = getProviderSchemaResponseFromProviderSchema(&ProviderSchema{ 4041 ResourceTypes: map[string]*configschema.Block{ 4042 "test_thing": { 4043 Attributes: map[string]*configschema.Attribute{ 4044 "a_ids": {Type: cty.String, Optional: true}, 4045 "id": {Type: cty.String, Computed: true}, 4046 }, 4047 }, 4048 }, 4049 }) 4050 4051 // First, apply with a count of 3 4052 ctx := testContext2(t, &ContextOpts{ 4053 Providers: map[addrs.Provider]providers.Factory{ 4054 addrs.NewDefaultProvider("test"): testProviderFuncFixed(p), 4055 }, 4056 }) 4057 4058 plan, diags := ctx.Plan(m, states.NewState(), DefaultPlanOpts) 4059 assertNoErrors(t, diags) 4060 4061 // Before the relevant bug was fixed, OpenTofu would panic during apply. 4062 if _, diags := ctx.Apply(plan, m); diags.HasErrors() { 4063 t.Fatalf("apply failed: %s", diags.Err()) 4064 } 4065 4066 // If we get here with no errors or panics then our test was successful. 4067 } 4068 4069 func TestContext2Apply_outputOrphan(t *testing.T) { 4070 m := testModule(t, "apply-output-orphan") 4071 p := testProvider("aws") 4072 p.PlanResourceChangeFn = testDiffFn 4073 4074 state := states.NewState() 4075 root := state.EnsureModule(addrs.RootModuleInstance) 4076 root.SetOutputValue("foo", cty.StringVal("bar"), false) 4077 root.SetOutputValue("bar", cty.StringVal("baz"), false) 4078 4079 ctx := testContext2(t, &ContextOpts{ 4080 Providers: map[addrs.Provider]providers.Factory{ 4081 addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p), 4082 }, 4083 }) 4084 4085 plan, diags := ctx.Plan(m, state, DefaultPlanOpts) 4086 assertNoErrors(t, diags) 4087 4088 state, diags = ctx.Apply(plan, m) 4089 if diags.HasErrors() { 4090 t.Fatalf("diags: %s", diags.Err()) 4091 } 4092 4093 actual := strings.TrimSpace(state.String()) 4094 expected := strings.TrimSpace(testTofuApplyOutputOrphanStr) 4095 if actual != expected { 4096 t.Fatalf("wrong result\n\ngot:\n%s\n\nwant:\n%s", actual, expected) 4097 } 4098 } 4099 4100 func TestContext2Apply_outputOrphanModule(t *testing.T) { 4101 m := testModule(t, "apply-output-orphan-module") 4102 p := testProvider("aws") 4103 p.PlanResourceChangeFn = testDiffFn 4104 4105 state := states.NewState() 4106 4107 ctx := testContext2(t, &ContextOpts{ 4108 Providers: map[addrs.Provider]providers.Factory{ 4109 addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p), 4110 }, 4111 }) 4112 4113 plan, diags := ctx.Plan(m, state, DefaultPlanOpts) 4114 assertNoErrors(t, diags) 4115 4116 s, diags := ctx.Apply(plan, m) 4117 if diags.HasErrors() { 4118 t.Fatalf("diags: %s", diags.Err()) 4119 } 4120 4121 actual := strings.TrimSpace(s.String()) 4122 expected := strings.TrimSpace(testTofuApplyOutputOrphanModuleStr) 4123 if actual != expected { 4124 t.Fatalf("expected:\n%s\n\ngot:\n%s", expected, actual) 4125 } 4126 4127 // now apply with no module in the config, which should remove the 4128 // remaining output 4129 ctx = testContext2(t, &ContextOpts{ 4130 Providers: map[addrs.Provider]providers.Factory{ 4131 addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p), 4132 }, 4133 }) 4134 4135 emptyConfig := configs.NewEmptyConfig() 4136 4137 // NOTE: While updating this test to pass the state in as a Plan argument, 4138 // rather than into the testContext2 call above, it previously said 4139 // State: state.DeepCopy(), which is a little weird since we just 4140 // created "s" above as the result of the previous apply, but I've preserved 4141 // it to avoid changing the flow of this test in case that's important 4142 // for some reason. 4143 plan, diags = ctx.Plan(emptyConfig, state.DeepCopy(), DefaultPlanOpts) 4144 assertNoErrors(t, diags) 4145 4146 state, diags = ctx.Apply(plan, emptyConfig) 4147 if diags.HasErrors() { 4148 t.Fatalf("diags: %s", diags.Err()) 4149 } 4150 4151 if !state.Empty() { 4152 t.Fatalf("wrong final state %s\nwant empty state", spew.Sdump(state)) 4153 } 4154 } 4155 4156 func TestContext2Apply_providerComputedVar(t *testing.T) { 4157 m := testModule(t, "apply-provider-computed") 4158 p := testProvider("aws") 4159 p.PlanResourceChangeFn = testDiffFn 4160 4161 pTest := testProvider("test") 4162 pTest.ApplyResourceChangeFn = testApplyFn 4163 pTest.PlanResourceChangeFn = testDiffFn 4164 4165 ctx := testContext2(t, &ContextOpts{ 4166 Providers: map[addrs.Provider]providers.Factory{ 4167 addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p), 4168 addrs.NewDefaultProvider("test"): testProviderFuncFixed(pTest), 4169 }, 4170 }) 4171 4172 p.ConfigureProviderFn = func(req providers.ConfigureProviderRequest) (resp providers.ConfigureProviderResponse) { 4173 val := req.Config.GetAttr("value") 4174 if val.IsNull() { 4175 resp.Diagnostics = resp.Diagnostics.Append(fmt.Errorf("value is not found")) 4176 return 4177 } 4178 return 4179 } 4180 4181 plan, diags := ctx.Plan(m, states.NewState(), DefaultPlanOpts) 4182 assertNoErrors(t, diags) 4183 4184 if _, diags := ctx.Apply(plan, m); diags.HasErrors() { 4185 t.Fatalf("apply errors: %s", diags.Err()) 4186 } 4187 } 4188 4189 func TestContext2Apply_providerConfigureDisabled(t *testing.T) { 4190 m := testModule(t, "apply-provider-configure-disabled") 4191 p := testProvider("aws") 4192 p.PlanResourceChangeFn = testDiffFn 4193 4194 p.ConfigureProviderFn = func(req providers.ConfigureProviderRequest) (resp providers.ConfigureProviderResponse) { 4195 val := req.Config.GetAttr("value") 4196 if val.IsNull() { 4197 resp.Diagnostics = resp.Diagnostics.Append(fmt.Errorf("value is not found")) 4198 } 4199 4200 return 4201 } 4202 4203 ctx := testContext2(t, &ContextOpts{ 4204 Providers: map[addrs.Provider]providers.Factory{ 4205 addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p), 4206 }, 4207 }) 4208 4209 plan, diags := ctx.Plan(m, states.NewState(), DefaultPlanOpts) 4210 assertNoErrors(t, diags) 4211 4212 if _, diags := ctx.Apply(plan, m); diags.HasErrors() { 4213 t.Fatalf("apply errors: %s", diags.Err()) 4214 } 4215 4216 if !p.ConfigureProviderCalled { 4217 t.Fatal("configure never called") 4218 } 4219 } 4220 4221 func TestContext2Apply_provisionerModule(t *testing.T) { 4222 m := testModule(t, "apply-provisioner-module") 4223 4224 p := testProvider("aws") 4225 p.PlanResourceChangeFn = testDiffFn 4226 p.ApplyResourceChangeFn = testApplyFn 4227 4228 pr := testProvisioner() 4229 pr.GetSchemaResponse = provisioners.GetSchemaResponse{ 4230 Provisioner: &configschema.Block{ 4231 Attributes: map[string]*configschema.Attribute{ 4232 "foo": {Type: cty.String, Optional: true}, 4233 }, 4234 }, 4235 } 4236 4237 ctx := testContext2(t, &ContextOpts{ 4238 Providers: map[addrs.Provider]providers.Factory{ 4239 addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p), 4240 }, 4241 Provisioners: map[string]provisioners.Factory{ 4242 "shell": testProvisionerFuncFixed(pr), 4243 }, 4244 }) 4245 4246 plan, diags := ctx.Plan(m, states.NewState(), DefaultPlanOpts) 4247 assertNoErrors(t, diags) 4248 4249 state, diags := ctx.Apply(plan, m) 4250 if diags.HasErrors() { 4251 t.Fatalf("diags: %s", diags.Err()) 4252 } 4253 4254 actual := strings.TrimSpace(state.String()) 4255 expected := strings.TrimSpace(testTofuApplyProvisionerModuleStr) 4256 if actual != expected { 4257 t.Fatalf("wrong result\n\ngot:\n%s\n\nwant:\n%s", actual, expected) 4258 } 4259 4260 // Verify apply was invoked 4261 if !pr.ProvisionResourceCalled { 4262 t.Fatalf("provisioner not invoked") 4263 } 4264 } 4265 4266 func TestContext2Apply_Provisioner_compute(t *testing.T) { 4267 m := testModule(t, "apply-provisioner-compute") 4268 p := testProvider("aws") 4269 pr := testProvisioner() 4270 p.PlanResourceChangeFn = testDiffFn 4271 p.ApplyResourceChangeFn = testApplyFn 4272 pr.ProvisionResourceFn = func(req provisioners.ProvisionResourceRequest) (resp provisioners.ProvisionResourceResponse) { 4273 4274 val := req.Config.GetAttr("command").AsString() 4275 if val != "computed_value" { 4276 t.Fatalf("bad value for foo: %q", val) 4277 } 4278 req.UIOutput.Output(fmt.Sprintf("Executing: %q", val)) 4279 4280 return 4281 } 4282 h := new(MockHook) 4283 ctx := testContext2(t, &ContextOpts{ 4284 Hooks: []Hook{h}, 4285 Providers: map[addrs.Provider]providers.Factory{ 4286 addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p), 4287 }, 4288 Provisioners: map[string]provisioners.Factory{ 4289 "shell": testProvisionerFuncFixed(pr), 4290 }, 4291 }) 4292 4293 plan, diags := ctx.Plan(m, states.NewState(), &PlanOpts{ 4294 Mode: plans.NormalMode, 4295 SetVariables: InputValues{ 4296 "value": &InputValue{ 4297 Value: cty.NumberIntVal(1), 4298 SourceType: ValueFromCaller, 4299 }, 4300 }, 4301 }) 4302 assertNoErrors(t, diags) 4303 4304 state, diags := ctx.Apply(plan, m) 4305 if diags.HasErrors() { 4306 t.Fatalf("diags: %s", diags.Err()) 4307 } 4308 4309 actual := strings.TrimSpace(state.String()) 4310 expected := strings.TrimSpace(testTofuApplyProvisionerStr) 4311 if actual != expected { 4312 t.Fatalf("wrong result\n\ngot:\n%s\n\nwant:\n%s", actual, expected) 4313 } 4314 4315 // Verify apply was invoked 4316 if !pr.ProvisionResourceCalled { 4317 t.Fatalf("provisioner not invoked") 4318 } 4319 4320 // Verify output was rendered 4321 if !h.ProvisionOutputCalled { 4322 t.Fatalf("ProvisionOutput hook not called") 4323 } 4324 if got, want := h.ProvisionOutputMessage, `Executing: "computed_value"`; got != want { 4325 t.Errorf("expected output to be %q, but was %q", want, got) 4326 } 4327 } 4328 4329 func TestContext2Apply_provisionerCreateFail(t *testing.T) { 4330 m := testModule(t, "apply-provisioner-fail-create") 4331 p := testProvider("aws") 4332 pr := testProvisioner() 4333 p.PlanResourceChangeFn = testDiffFn 4334 4335 p.ApplyResourceChangeFn = func(req providers.ApplyResourceChangeRequest) providers.ApplyResourceChangeResponse { 4336 resp := testApplyFn(req) 4337 resp.Diagnostics = resp.Diagnostics.Append(fmt.Errorf("error")) 4338 4339 return resp 4340 } 4341 4342 ctx := testContext2(t, &ContextOpts{ 4343 Providers: map[addrs.Provider]providers.Factory{ 4344 addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p), 4345 }, 4346 Provisioners: map[string]provisioners.Factory{ 4347 "shell": testProvisionerFuncFixed(pr), 4348 }, 4349 }) 4350 4351 plan, diags := ctx.Plan(m, states.NewState(), DefaultPlanOpts) 4352 assertNoErrors(t, diags) 4353 4354 state, diags := ctx.Apply(plan, m) 4355 if diags == nil { 4356 t.Fatal("should error") 4357 } 4358 4359 got := strings.TrimSpace(state.String()) 4360 want := strings.TrimSpace(testTofuApplyProvisionerFailCreateStr) 4361 if got != want { 4362 t.Fatalf("wrong result\n\ngot:\n%s\n\nwant:\n%s", got, want) 4363 } 4364 } 4365 4366 func TestContext2Apply_provisionerCreateFailNoId(t *testing.T) { 4367 m := testModule(t, "apply-provisioner-fail-create") 4368 p := testProvider("aws") 4369 pr := testProvisioner() 4370 p.PlanResourceChangeFn = testDiffFn 4371 4372 p.ApplyResourceChangeFn = func(req providers.ApplyResourceChangeRequest) (resp providers.ApplyResourceChangeResponse) { 4373 resp.Diagnostics = resp.Diagnostics.Append(fmt.Errorf("error")) 4374 return 4375 } 4376 4377 ctx := testContext2(t, &ContextOpts{ 4378 Providers: map[addrs.Provider]providers.Factory{ 4379 addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p), 4380 }, 4381 Provisioners: map[string]provisioners.Factory{ 4382 "shell": testProvisionerFuncFixed(pr), 4383 }, 4384 }) 4385 4386 plan, diags := ctx.Plan(m, states.NewState(), DefaultPlanOpts) 4387 assertNoErrors(t, diags) 4388 4389 state, diags := ctx.Apply(plan, m) 4390 if diags == nil { 4391 t.Fatal("should error") 4392 } 4393 4394 actual := strings.TrimSpace(state.String()) 4395 expected := strings.TrimSpace(testTofuApplyProvisionerFailCreateNoIdStr) 4396 if actual != expected { 4397 t.Fatalf("wrong result\n\ngot:\n%s\n\nwant:\n%s", actual, expected) 4398 } 4399 } 4400 4401 func TestContext2Apply_provisionerFail(t *testing.T) { 4402 m := testModule(t, "apply-provisioner-fail") 4403 p := testProvider("aws") 4404 p.PlanResourceChangeFn = testDiffFn 4405 p.ApplyResourceChangeFn = testApplyFn 4406 pr := testProvisioner() 4407 pr.ProvisionResourceFn = func(req provisioners.ProvisionResourceRequest) (resp provisioners.ProvisionResourceResponse) { 4408 resp.Diagnostics = resp.Diagnostics.Append(fmt.Errorf("EXPLOSION")) 4409 return 4410 } 4411 4412 ctx := testContext2(t, &ContextOpts{ 4413 Providers: map[addrs.Provider]providers.Factory{ 4414 addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p), 4415 }, 4416 Provisioners: map[string]provisioners.Factory{ 4417 "shell": testProvisionerFuncFixed(pr), 4418 }, 4419 }) 4420 4421 plan, diags := ctx.Plan(m, states.NewState(), DefaultPlanOpts) 4422 assertNoErrors(t, diags) 4423 4424 state, diags := ctx.Apply(plan, m) 4425 if diags == nil { 4426 t.Fatal("should error") 4427 } 4428 4429 actual := strings.TrimSpace(state.String()) 4430 expected := strings.TrimSpace(testTofuApplyProvisionerFailStr) 4431 if actual != expected { 4432 t.Fatalf("wrong result\n\ngot:\n%s\n\nwant:\n%s", actual, expected) 4433 } 4434 } 4435 4436 func TestContext2Apply_provisionerFail_createBeforeDestroy(t *testing.T) { 4437 m := testModule(t, "apply-provisioner-fail-create-before") 4438 p := testProvider("aws") 4439 pr := testProvisioner() 4440 p.PlanResourceChangeFn = testDiffFn 4441 p.ApplyResourceChangeFn = testApplyFn 4442 pr.ProvisionResourceFn = func(req provisioners.ProvisionResourceRequest) (resp provisioners.ProvisionResourceResponse) { 4443 resp.Diagnostics = resp.Diagnostics.Append(fmt.Errorf("EXPLOSION")) 4444 return 4445 } 4446 4447 state := states.NewState() 4448 root := state.EnsureModule(addrs.RootModuleInstance) 4449 root.SetResourceInstanceCurrent( 4450 mustResourceInstanceAddr("aws_instance.bar").Resource, 4451 &states.ResourceInstanceObjectSrc{ 4452 Status: states.ObjectReady, 4453 AttrsJSON: []byte(`{"id":"bar","require_new":"abc"}`), 4454 }, 4455 mustProviderConfig(`provider["registry.opentofu.org/hashicorp/aws"]`), 4456 ) 4457 4458 ctx := testContext2(t, &ContextOpts{ 4459 Providers: map[addrs.Provider]providers.Factory{ 4460 addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p), 4461 }, 4462 Provisioners: map[string]provisioners.Factory{ 4463 "shell": testProvisionerFuncFixed(pr), 4464 }, 4465 }) 4466 4467 plan, diags := ctx.Plan(m, state, DefaultPlanOpts) 4468 assertNoErrors(t, diags) 4469 4470 state, diags = ctx.Apply(plan, m) 4471 if !diags.HasErrors() { 4472 t.Fatal("should error") 4473 } 4474 4475 actual := strings.TrimSpace(state.String()) 4476 expected := strings.TrimSpace(testTofuApplyProvisionerFailCreateBeforeDestroyStr) 4477 if actual != expected { 4478 t.Fatalf("expected:\n%s\n:got\n%s", expected, actual) 4479 } 4480 } 4481 4482 func TestContext2Apply_error_createBeforeDestroy(t *testing.T) { 4483 m := testModule(t, "apply-error-create-before") 4484 p := testProvider("aws") 4485 4486 state := states.NewState() 4487 root := state.EnsureModule(addrs.RootModuleInstance) 4488 root.SetResourceInstanceCurrent( 4489 mustResourceInstanceAddr("aws_instance.bar").Resource, 4490 &states.ResourceInstanceObjectSrc{ 4491 Status: states.ObjectReady, 4492 AttrsJSON: []byte(`{"id":"bar", "require_new": "abc","type":"aws_instance"}`), 4493 }, 4494 mustProviderConfig(`provider["registry.opentofu.org/hashicorp/aws"]`), 4495 ) 4496 4497 ctx := testContext2(t, &ContextOpts{ 4498 Providers: map[addrs.Provider]providers.Factory{ 4499 addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p), 4500 }, 4501 }) 4502 p.ApplyResourceChangeFn = func(req providers.ApplyResourceChangeRequest) (resp providers.ApplyResourceChangeResponse) { 4503 resp.Diagnostics = resp.Diagnostics.Append(fmt.Errorf("placeholder error from ApplyFn")) 4504 return 4505 } 4506 p.PlanResourceChangeFn = testDiffFn 4507 4508 plan, diags := ctx.Plan(m, state, DefaultPlanOpts) 4509 assertNoErrors(t, diags) 4510 4511 state, diags = ctx.Apply(plan, m) 4512 if !diags.HasErrors() { 4513 t.Fatal("should have error") 4514 } 4515 if got, want := diags.Err().Error(), "placeholder error from ApplyFn"; got != want { 4516 // We're looking for our artificial error from ApplyFn above, whose 4517 // message is literally "placeholder error from ApplyFn". 4518 t.Fatalf("wrong error\ngot: %s\nwant: %s", got, want) 4519 } 4520 4521 actual := strings.TrimSpace(state.String()) 4522 expected := strings.TrimSpace(testTofuApplyErrorCreateBeforeDestroyStr) 4523 if actual != expected { 4524 t.Fatalf("wrong final state\ngot:\n%s\n\nwant:\n%s", actual, expected) 4525 } 4526 } 4527 4528 func TestContext2Apply_errorDestroy_createBeforeDestroy(t *testing.T) { 4529 m := testModule(t, "apply-error-create-before") 4530 p := testProvider("aws") 4531 4532 state := states.NewState() 4533 root := state.EnsureModule(addrs.RootModuleInstance) 4534 root.SetResourceInstanceCurrent( 4535 mustResourceInstanceAddr("aws_instance.bar").Resource, 4536 &states.ResourceInstanceObjectSrc{ 4537 Status: states.ObjectReady, 4538 AttrsJSON: []byte(`{"id":"bar", "require_new": "abc"}`), 4539 }, 4540 mustProviderConfig(`provider["registry.opentofu.org/hashicorp/aws"]`), 4541 ) 4542 4543 ctx := testContext2(t, &ContextOpts{ 4544 Providers: map[addrs.Provider]providers.Factory{ 4545 addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p), 4546 }, 4547 }) 4548 p.ApplyResourceChangeFn = func(req providers.ApplyResourceChangeRequest) (resp providers.ApplyResourceChangeResponse) { 4549 // Fail the destroy! 4550 if req.PlannedState.IsNull() { 4551 resp.NewState = req.PriorState 4552 resp.Diagnostics = resp.Diagnostics.Append(fmt.Errorf("error")) 4553 return 4554 } 4555 4556 return testApplyFn(req) 4557 } 4558 p.PlanResourceChangeFn = testDiffFn 4559 4560 plan, diags := ctx.Plan(m, state, DefaultPlanOpts) 4561 assertNoErrors(t, diags) 4562 4563 state, diags = ctx.Apply(plan, m) 4564 if !diags.HasErrors() { 4565 t.Fatal("should have error") 4566 } 4567 4568 actual := strings.TrimSpace(state.String()) 4569 expected := strings.TrimSpace(testTofuApplyErrorDestroyCreateBeforeDestroyStr) 4570 if actual != expected { 4571 t.Fatalf("bad: actual:\n%s\n\nexpected:\n%s", actual, expected) 4572 } 4573 } 4574 4575 func TestContext2Apply_multiDepose_createBeforeDestroy(t *testing.T) { 4576 m := testModule(t, "apply-multi-depose-create-before-destroy") 4577 p := testProvider("aws") 4578 ps := map[addrs.Provider]providers.Factory{addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p)} 4579 p.GetProviderSchemaResponse = getProviderSchemaResponseFromProviderSchema(&ProviderSchema{ 4580 ResourceTypes: map[string]*configschema.Block{ 4581 "aws_instance": { 4582 Attributes: map[string]*configschema.Attribute{ 4583 "require_new": {Type: cty.String, Optional: true}, 4584 "id": {Type: cty.String, Computed: true}, 4585 }, 4586 }, 4587 }, 4588 }) 4589 4590 state := states.NewState() 4591 root := state.EnsureModule(addrs.RootModuleInstance) 4592 root.SetResourceInstanceCurrent( 4593 mustResourceInstanceAddr("aws_instance.web").Resource, 4594 &states.ResourceInstanceObjectSrc{ 4595 Status: states.ObjectReady, 4596 AttrsJSON: []byte(`{"id":"foo"}`), 4597 }, 4598 mustProviderConfig(`provider["registry.opentofu.org/hashicorp/aws"]`), 4599 ) 4600 4601 p.PlanResourceChangeFn = testDiffFn 4602 4603 ctx := testContext2(t, &ContextOpts{ 4604 Providers: ps, 4605 }) 4606 createdInstanceId := "bar" 4607 // Create works 4608 createFunc := func(req providers.ApplyResourceChangeRequest) (resp providers.ApplyResourceChangeResponse) { 4609 s := req.PlannedState.AsValueMap() 4610 s["id"] = cty.StringVal(createdInstanceId) 4611 resp.NewState = cty.ObjectVal(s) 4612 return 4613 } 4614 4615 // Destroy starts broken 4616 destroyFunc := func(req providers.ApplyResourceChangeRequest) (resp providers.ApplyResourceChangeResponse) { 4617 resp.NewState = req.PriorState 4618 resp.Diagnostics = resp.Diagnostics.Append(fmt.Errorf("destroy failed")) 4619 return 4620 } 4621 p.ApplyResourceChangeFn = func(req providers.ApplyResourceChangeRequest) (resp providers.ApplyResourceChangeResponse) { 4622 if req.PlannedState.IsNull() { 4623 return destroyFunc(req) 4624 } else { 4625 return createFunc(req) 4626 } 4627 } 4628 4629 plan, diags := ctx.Plan(m, state, &PlanOpts{ 4630 Mode: plans.NormalMode, 4631 SetVariables: InputValues{ 4632 "require_new": &InputValue{ 4633 Value: cty.StringVal("yes"), 4634 }, 4635 }, 4636 }) 4637 assertNoErrors(t, diags) 4638 4639 // Destroy is broken, so even though CBD successfully replaces the instance, 4640 // we'll have to save the Deposed instance to destroy later 4641 state, diags = ctx.Apply(plan, m) 4642 if !diags.HasErrors() { 4643 t.Fatal("should have error") 4644 } 4645 4646 checkStateString(t, state, ` 4647 aws_instance.web: (1 deposed) 4648 ID = bar 4649 provider = provider["registry.opentofu.org/hashicorp/aws"] 4650 require_new = yes 4651 Deposed ID 1 = foo 4652 `) 4653 4654 createdInstanceId = "baz" 4655 ctx = testContext2(t, &ContextOpts{ 4656 Providers: ps, 4657 }) 4658 4659 plan, diags = ctx.Plan(m, state, &PlanOpts{ 4660 Mode: plans.NormalMode, 4661 SetVariables: InputValues{ 4662 "require_new": &InputValue{ 4663 Value: cty.StringVal("baz"), 4664 }, 4665 }, 4666 }) 4667 assertNoErrors(t, diags) 4668 4669 // We're replacing the primary instance once again. Destroy is _still_ 4670 // broken, so the Deposed list gets longer 4671 state, diags = ctx.Apply(plan, m) 4672 if !diags.HasErrors() { 4673 t.Fatal("should have error") 4674 } 4675 4676 // For this one we can't rely on checkStateString because its result is 4677 // not deterministic when multiple deposed objects are present. Instead, 4678 // we will probe the state object directly. 4679 { 4680 is := state.RootModule().Resources["aws_instance.web"].Instances[addrs.NoKey] 4681 if is.Current == nil { 4682 t.Fatalf("no current object for aws_instance web; should have one") 4683 } 4684 if !bytes.Contains(is.Current.AttrsJSON, []byte("baz")) { 4685 t.Fatalf("incorrect current object attrs %s; want id=baz", is.Current.AttrsJSON) 4686 } 4687 if got, want := len(is.Deposed), 2; got != want { 4688 t.Fatalf("wrong number of deposed instances %d; want %d", got, want) 4689 } 4690 var foos, bars int 4691 for _, obj := range is.Deposed { 4692 if bytes.Contains(obj.AttrsJSON, []byte("foo")) { 4693 foos++ 4694 } 4695 if bytes.Contains(obj.AttrsJSON, []byte("bar")) { 4696 bars++ 4697 } 4698 } 4699 if got, want := foos, 1; got != want { 4700 t.Fatalf("wrong number of deposed instances with id=foo %d; want %d", got, want) 4701 } 4702 if got, want := bars, 1; got != want { 4703 t.Fatalf("wrong number of deposed instances with id=bar %d; want %d", got, want) 4704 } 4705 } 4706 4707 // Destroy partially fixed! 4708 destroyFunc = func(req providers.ApplyResourceChangeRequest) (resp providers.ApplyResourceChangeResponse) { 4709 s := req.PriorState.AsValueMap() 4710 id := s["id"].AsString() 4711 if id == "foo" || id == "baz" { 4712 resp.NewState = cty.NullVal(req.PriorState.Type()) 4713 } else { 4714 resp.NewState = req.PriorState 4715 resp.Diagnostics = resp.Diagnostics.Append(fmt.Errorf("destroy partially failed")) 4716 } 4717 return 4718 } 4719 4720 createdInstanceId = "qux" 4721 ctx = testContext2(t, &ContextOpts{ 4722 Providers: ps, 4723 }) 4724 plan, diags = ctx.Plan(m, state, &PlanOpts{ 4725 Mode: plans.NormalMode, 4726 SetVariables: InputValues{ 4727 "require_new": &InputValue{ 4728 Value: cty.StringVal("qux"), 4729 }, 4730 }, 4731 }) 4732 assertNoErrors(t, diags) 4733 4734 state, diags = ctx.Apply(plan, m) 4735 // Expect error because 1/2 of Deposed destroys failed 4736 if !diags.HasErrors() { 4737 t.Fatal("should have error") 4738 } 4739 4740 // foo and baz are now gone, bar sticks around 4741 checkStateString(t, state, ` 4742 aws_instance.web: (1 deposed) 4743 ID = qux 4744 provider = provider["registry.opentofu.org/hashicorp/aws"] 4745 require_new = qux 4746 Deposed ID 1 = bar 4747 `) 4748 4749 // Destroy working fully! 4750 destroyFunc = func(req providers.ApplyResourceChangeRequest) (resp providers.ApplyResourceChangeResponse) { 4751 resp.NewState = cty.NullVal(req.PriorState.Type()) 4752 return 4753 } 4754 4755 createdInstanceId = "quux" 4756 ctx = testContext2(t, &ContextOpts{ 4757 Providers: ps, 4758 }) 4759 plan, diags = ctx.Plan(m, state, &PlanOpts{ 4760 Mode: plans.NormalMode, 4761 SetVariables: InputValues{ 4762 "require_new": &InputValue{ 4763 Value: cty.StringVal("quux"), 4764 }, 4765 }, 4766 }) 4767 assertNoErrors(t, diags) 4768 state, diags = ctx.Apply(plan, m) 4769 if diags.HasErrors() { 4770 t.Fatal("should not have error:", diags.Err()) 4771 } 4772 4773 // And finally the state is clean 4774 checkStateString(t, state, ` 4775 aws_instance.web: 4776 ID = quux 4777 provider = provider["registry.opentofu.org/hashicorp/aws"] 4778 require_new = quux 4779 `) 4780 } 4781 4782 // Verify that a normal provisioner with on_failure "continue" set won't 4783 // taint the resource and continues executing. 4784 func TestContext2Apply_provisionerFailContinue(t *testing.T) { 4785 m := testModule(t, "apply-provisioner-fail-continue") 4786 p := testProvider("aws") 4787 pr := testProvisioner() 4788 p.PlanResourceChangeFn = testDiffFn 4789 p.ApplyResourceChangeFn = testApplyFn 4790 4791 pr.ProvisionResourceFn = func(req provisioners.ProvisionResourceRequest) (resp provisioners.ProvisionResourceResponse) { 4792 resp.Diagnostics = resp.Diagnostics.Append(fmt.Errorf("provisioner error")) 4793 return 4794 } 4795 4796 ctx := testContext2(t, &ContextOpts{ 4797 Providers: map[addrs.Provider]providers.Factory{ 4798 addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p), 4799 }, 4800 Provisioners: map[string]provisioners.Factory{ 4801 "shell": testProvisionerFuncFixed(pr), 4802 }, 4803 }) 4804 4805 plan, diags := ctx.Plan(m, states.NewState(), DefaultPlanOpts) 4806 assertNoErrors(t, diags) 4807 4808 state, diags := ctx.Apply(plan, m) 4809 if diags.HasErrors() { 4810 t.Fatalf("diags: %s", diags.Err()) 4811 } 4812 4813 checkStateString(t, state, ` 4814 aws_instance.foo: 4815 ID = foo 4816 provider = provider["registry.opentofu.org/hashicorp/aws"] 4817 foo = bar 4818 type = aws_instance 4819 `) 4820 4821 // Verify apply was invoked 4822 if !pr.ProvisionResourceCalled { 4823 t.Fatalf("provisioner not invoked") 4824 } 4825 } 4826 4827 // Verify that a normal provisioner with on_failure "continue" records 4828 // the error with the hook. 4829 func TestContext2Apply_provisionerFailContinueHook(t *testing.T) { 4830 h := new(MockHook) 4831 m := testModule(t, "apply-provisioner-fail-continue") 4832 p := testProvider("aws") 4833 pr := testProvisioner() 4834 p.PlanResourceChangeFn = testDiffFn 4835 pr.ProvisionResourceFn = func(req provisioners.ProvisionResourceRequest) (resp provisioners.ProvisionResourceResponse) { 4836 resp.Diagnostics = resp.Diagnostics.Append(fmt.Errorf("provisioner error")) 4837 return 4838 } 4839 4840 ctx := testContext2(t, &ContextOpts{ 4841 Hooks: []Hook{h}, 4842 Providers: map[addrs.Provider]providers.Factory{ 4843 addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p), 4844 }, 4845 Provisioners: map[string]provisioners.Factory{ 4846 "shell": testProvisionerFuncFixed(pr), 4847 }, 4848 }) 4849 4850 plan, diags := ctx.Plan(m, states.NewState(), DefaultPlanOpts) 4851 assertNoErrors(t, diags) 4852 4853 if _, diags := ctx.Apply(plan, m); diags.HasErrors() { 4854 t.Fatalf("apply errors: %s", diags.Err()) 4855 } 4856 4857 if !h.PostProvisionInstanceStepCalled { 4858 t.Fatal("PostProvisionInstanceStep not called") 4859 } 4860 if h.PostProvisionInstanceStepErrorArg == nil { 4861 t.Fatal("should have error") 4862 } 4863 } 4864 4865 func TestContext2Apply_provisionerDestroy(t *testing.T) { 4866 m := testModule(t, "apply-provisioner-destroy") 4867 p := testProvider("aws") 4868 pr := testProvisioner() 4869 p.PlanResourceChangeFn = testDiffFn 4870 pr.ProvisionResourceFn = func(req provisioners.ProvisionResourceRequest) (resp provisioners.ProvisionResourceResponse) { 4871 val := req.Config.GetAttr("command").AsString() 4872 if val != "destroy a bar" { 4873 t.Fatalf("bad value for foo: %q", val) 4874 } 4875 4876 return 4877 } 4878 4879 state := states.NewState() 4880 root := state.RootModule() 4881 root.SetResourceInstanceCurrent( 4882 mustResourceInstanceAddr(`aws_instance.foo["a"]`).Resource, 4883 &states.ResourceInstanceObjectSrc{ 4884 Status: states.ObjectReady, 4885 AttrsJSON: []byte(`{"id":"bar","foo":"bar"}`), 4886 }, 4887 mustProviderConfig(`provider["registry.opentofu.org/hashicorp/aws"]`), 4888 ) 4889 4890 ctx := testContext2(t, &ContextOpts{ 4891 Providers: map[addrs.Provider]providers.Factory{ 4892 addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p), 4893 }, 4894 Provisioners: map[string]provisioners.Factory{ 4895 "shell": testProvisionerFuncFixed(pr), 4896 }, 4897 }) 4898 4899 plan, diags := ctx.Plan(m, state, SimplePlanOpts(plans.DestroyMode, testInputValuesUnset(m.Module.Variables))) 4900 assertNoErrors(t, diags) 4901 4902 state, diags = ctx.Apply(plan, m) 4903 if diags.HasErrors() { 4904 t.Fatalf("diags: %s", diags.Err()) 4905 } 4906 4907 checkStateString(t, state, `<no state>`) 4908 4909 // Verify apply was invoked 4910 if !pr.ProvisionResourceCalled { 4911 t.Fatalf("provisioner not invoked") 4912 } 4913 } 4914 4915 // Verify that on destroy provisioner failure, nothing happens to the instance 4916 func TestContext2Apply_provisionerDestroyFail(t *testing.T) { 4917 m := testModule(t, "apply-provisioner-destroy") 4918 p := testProvider("aws") 4919 pr := testProvisioner() 4920 p.PlanResourceChangeFn = testDiffFn 4921 pr.ProvisionResourceFn = func(req provisioners.ProvisionResourceRequest) (resp provisioners.ProvisionResourceResponse) { 4922 resp.Diagnostics = resp.Diagnostics.Append(fmt.Errorf("provisioner error")) 4923 return 4924 } 4925 4926 state := states.NewState() 4927 root := state.RootModule() 4928 root.SetResourceInstanceCurrent( 4929 mustResourceInstanceAddr(`aws_instance.foo["a"]`).Resource, 4930 &states.ResourceInstanceObjectSrc{ 4931 Status: states.ObjectReady, 4932 AttrsJSON: []byte(`{"id":"bar","foo":"bar"}`), 4933 }, 4934 mustProviderConfig(`provider["registry.opentofu.org/hashicorp/aws"]`), 4935 ) 4936 4937 ctx := testContext2(t, &ContextOpts{ 4938 Providers: map[addrs.Provider]providers.Factory{ 4939 addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p), 4940 }, 4941 Provisioners: map[string]provisioners.Factory{ 4942 "shell": testProvisionerFuncFixed(pr), 4943 }, 4944 }) 4945 4946 plan, diags := ctx.Plan(m, state, SimplePlanOpts(plans.DestroyMode, testInputValuesUnset(m.Module.Variables))) 4947 assertNoErrors(t, diags) 4948 4949 state, diags = ctx.Apply(plan, m) 4950 if diags == nil { 4951 t.Fatal("should error") 4952 } 4953 4954 checkStateString(t, state, ` 4955 aws_instance.foo["a"]: 4956 ID = bar 4957 provider = provider["registry.opentofu.org/hashicorp/aws"] 4958 foo = bar 4959 `) 4960 4961 // Verify apply was invoked 4962 if !pr.ProvisionResourceCalled { 4963 t.Fatalf("provisioner not invoked") 4964 } 4965 } 4966 4967 // Verify that on destroy provisioner failure with "continue" that 4968 // we continue to the next provisioner. 4969 func TestContext2Apply_provisionerDestroyFailContinue(t *testing.T) { 4970 m := testModule(t, "apply-provisioner-destroy-continue") 4971 p := testProvider("aws") 4972 pr := testProvisioner() 4973 p.PlanResourceChangeFn = testDiffFn 4974 4975 var l sync.Mutex 4976 var calls []string 4977 pr.ProvisionResourceFn = func(req provisioners.ProvisionResourceRequest) (resp provisioners.ProvisionResourceResponse) { 4978 val := req.Config.GetAttr("command") 4979 if val.IsNull() { 4980 t.Fatalf("bad value for foo: %#v", val) 4981 } 4982 4983 l.Lock() 4984 defer l.Unlock() 4985 calls = append(calls, val.AsString()) 4986 resp.Diagnostics = resp.Diagnostics.Append(fmt.Errorf("provisioner error")) 4987 return 4988 } 4989 4990 state := states.NewState() 4991 root := state.RootModule() 4992 root.SetResourceInstanceCurrent( 4993 mustResourceInstanceAddr(`aws_instance.foo["a"]`).Resource, 4994 &states.ResourceInstanceObjectSrc{ 4995 Status: states.ObjectReady, 4996 AttrsJSON: []byte(`{"id":"bar"}`), 4997 }, 4998 mustProviderConfig(`provider["registry.opentofu.org/hashicorp/aws"]`), 4999 ) 5000 5001 ctx := testContext2(t, &ContextOpts{ 5002 Providers: map[addrs.Provider]providers.Factory{ 5003 addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p), 5004 }, 5005 Provisioners: map[string]provisioners.Factory{ 5006 "shell": testProvisionerFuncFixed(pr), 5007 }, 5008 }) 5009 5010 plan, diags := ctx.Plan(m, state, &PlanOpts{ 5011 Mode: plans.DestroyMode, 5012 }) 5013 assertNoErrors(t, diags) 5014 5015 state, diags = ctx.Apply(plan, m) 5016 if diags.HasErrors() { 5017 t.Fatalf("diags: %s", diags.Err()) 5018 } 5019 5020 checkStateString(t, state, `<no state>`) 5021 5022 // Verify apply was invoked 5023 if !pr.ProvisionResourceCalled { 5024 t.Fatalf("provisioner not invoked") 5025 } 5026 5027 expected := []string{"one", "two"} 5028 if !reflect.DeepEqual(calls, expected) { 5029 t.Fatalf("wrong commands\ngot: %#v\nwant: %#v", calls, expected) 5030 } 5031 } 5032 5033 // Verify that on destroy provisioner failure with "continue" that 5034 // we continue to the next provisioner. But if the next provisioner defines 5035 // to fail, then we fail after running it. 5036 func TestContext2Apply_provisionerDestroyFailContinueFail(t *testing.T) { 5037 m := testModule(t, "apply-provisioner-destroy-fail") 5038 p := testProvider("aws") 5039 pr := testProvisioner() 5040 p.PlanResourceChangeFn = testDiffFn 5041 5042 var l sync.Mutex 5043 var calls []string 5044 pr.ProvisionResourceFn = func(req provisioners.ProvisionResourceRequest) (resp provisioners.ProvisionResourceResponse) { 5045 val := req.Config.GetAttr("command") 5046 if val.IsNull() { 5047 t.Fatalf("bad value for foo: %#v", val) 5048 } 5049 5050 l.Lock() 5051 defer l.Unlock() 5052 calls = append(calls, val.AsString()) 5053 resp.Diagnostics = resp.Diagnostics.Append(fmt.Errorf("provisioner error")) 5054 return 5055 } 5056 5057 state := states.NewState() 5058 root := state.EnsureModule(addrs.RootModuleInstance) 5059 root.SetResourceInstanceCurrent( 5060 mustResourceInstanceAddr("aws_instance.foo").Resource, 5061 &states.ResourceInstanceObjectSrc{ 5062 Status: states.ObjectReady, 5063 AttrsJSON: []byte(`{"id":"bar"}`), 5064 }, 5065 mustProviderConfig(`provider["registry.opentofu.org/hashicorp/aws"]`), 5066 ) 5067 5068 ctx := testContext2(t, &ContextOpts{ 5069 Providers: map[addrs.Provider]providers.Factory{ 5070 addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p), 5071 }, 5072 Provisioners: map[string]provisioners.Factory{ 5073 "shell": testProvisionerFuncFixed(pr), 5074 }, 5075 }) 5076 5077 plan, diags := ctx.Plan(m, state, &PlanOpts{ 5078 Mode: plans.DestroyMode, 5079 }) 5080 assertNoErrors(t, diags) 5081 5082 state, diags = ctx.Apply(plan, m) 5083 if diags == nil { 5084 t.Fatal("apply succeeded; wanted error from second provisioner") 5085 } 5086 5087 checkStateString(t, state, ` 5088 aws_instance.foo: 5089 ID = bar 5090 provider = provider["registry.opentofu.org/hashicorp/aws"] 5091 `) 5092 5093 // Verify apply was invoked 5094 if !pr.ProvisionResourceCalled { 5095 t.Fatalf("provisioner not invoked") 5096 } 5097 5098 expected := []string{"one", "two"} 5099 if !reflect.DeepEqual(calls, expected) { 5100 t.Fatalf("bad: %#v", calls) 5101 } 5102 } 5103 5104 // Verify destroy provisioners are not run for tainted instances. 5105 func TestContext2Apply_provisionerDestroyTainted(t *testing.T) { 5106 m := testModule(t, "apply-provisioner-destroy") 5107 p := testProvider("aws") 5108 pr := testProvisioner() 5109 p.PlanResourceChangeFn = testDiffFn 5110 p.ApplyResourceChangeFn = testApplyFn 5111 5112 destroyCalled := false 5113 pr.ProvisionResourceFn = func(req provisioners.ProvisionResourceRequest) (resp provisioners.ProvisionResourceResponse) { 5114 expected := "create a b" 5115 val := req.Config.GetAttr("command") 5116 if val.AsString() != expected { 5117 t.Fatalf("bad value for command: %#v", val) 5118 } 5119 5120 return 5121 } 5122 5123 state := states.NewState() 5124 root := state.RootModule() 5125 root.SetResourceInstanceCurrent( 5126 mustResourceInstanceAddr(`aws_instance.foo["a"]`).Resource, 5127 &states.ResourceInstanceObjectSrc{ 5128 Status: states.ObjectTainted, 5129 AttrsJSON: []byte(`{"id":"bar"}`), 5130 }, 5131 mustProviderConfig(`provider["registry.opentofu.org/hashicorp/aws"]`), 5132 ) 5133 5134 ctx := testContext2(t, &ContextOpts{ 5135 Providers: map[addrs.Provider]providers.Factory{ 5136 addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p), 5137 }, 5138 Provisioners: map[string]provisioners.Factory{ 5139 "shell": testProvisionerFuncFixed(pr), 5140 }, 5141 }) 5142 5143 plan, diags := ctx.Plan(m, state, &PlanOpts{ 5144 Mode: plans.NormalMode, 5145 SetVariables: InputValues{ 5146 "input": &InputValue{ 5147 Value: cty.MapVal(map[string]cty.Value{ 5148 "a": cty.StringVal("b"), 5149 }), 5150 SourceType: ValueFromInput, 5151 }, 5152 }, 5153 }) 5154 assertNoErrors(t, diags) 5155 5156 state, diags = ctx.Apply(plan, m) 5157 if diags.HasErrors() { 5158 t.Fatalf("diags: %s", diags.Err()) 5159 } 5160 5161 checkStateString(t, state, ` 5162 aws_instance.foo["a"]: 5163 ID = foo 5164 provider = provider["registry.opentofu.org/hashicorp/aws"] 5165 foo = bar 5166 type = aws_instance 5167 `) 5168 5169 // Verify apply was invoked 5170 if !pr.ProvisionResourceCalled { 5171 t.Fatalf("provisioner not invoked") 5172 } 5173 5174 if destroyCalled { 5175 t.Fatal("destroy should not be called") 5176 } 5177 } 5178 5179 func TestContext2Apply_provisionerResourceRef(t *testing.T) { 5180 m := testModule(t, "apply-provisioner-resource-ref") 5181 p := testProvider("aws") 5182 p.PlanResourceChangeFn = testDiffFn 5183 p.ApplyResourceChangeFn = testApplyFn 5184 5185 pr := testProvisioner() 5186 pr.ProvisionResourceFn = func(req provisioners.ProvisionResourceRequest) (resp provisioners.ProvisionResourceResponse) { 5187 val := req.Config.GetAttr("command") 5188 if val.AsString() != "2" { 5189 t.Fatalf("bad value for command: %#v", val) 5190 } 5191 5192 return 5193 } 5194 5195 ctx := testContext2(t, &ContextOpts{ 5196 Providers: map[addrs.Provider]providers.Factory{ 5197 addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p), 5198 }, 5199 Provisioners: map[string]provisioners.Factory{ 5200 "shell": testProvisionerFuncFixed(pr), 5201 }, 5202 }) 5203 5204 plan, diags := ctx.Plan(m, states.NewState(), DefaultPlanOpts) 5205 assertNoErrors(t, diags) 5206 5207 state, diags := ctx.Apply(plan, m) 5208 if diags.HasErrors() { 5209 t.Fatalf("diags: %s", diags.Err()) 5210 } 5211 5212 actual := strings.TrimSpace(state.String()) 5213 expected := strings.TrimSpace(testTofuApplyProvisionerResourceRefStr) 5214 if actual != expected { 5215 t.Fatalf("wrong result\n\ngot:\n%s\n\nwant:\n%s", actual, expected) 5216 } 5217 5218 // Verify apply was invoked 5219 if !pr.ProvisionResourceCalled { 5220 t.Fatalf("provisioner not invoked") 5221 } 5222 } 5223 5224 func TestContext2Apply_provisionerSelfRef(t *testing.T) { 5225 m := testModule(t, "apply-provisioner-self-ref") 5226 p := testProvider("aws") 5227 pr := testProvisioner() 5228 p.PlanResourceChangeFn = testDiffFn 5229 p.ApplyResourceChangeFn = testApplyFn 5230 pr.ProvisionResourceFn = func(req provisioners.ProvisionResourceRequest) (resp provisioners.ProvisionResourceResponse) { 5231 val := req.Config.GetAttr("command") 5232 if val.AsString() != "bar" { 5233 t.Fatalf("bad value for command: %#v", val) 5234 } 5235 5236 return 5237 } 5238 5239 ctx := testContext2(t, &ContextOpts{ 5240 Providers: map[addrs.Provider]providers.Factory{ 5241 addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p), 5242 }, 5243 Provisioners: map[string]provisioners.Factory{ 5244 "shell": testProvisionerFuncFixed(pr), 5245 }, 5246 }) 5247 5248 plan, diags := ctx.Plan(m, states.NewState(), DefaultPlanOpts) 5249 assertNoErrors(t, diags) 5250 5251 state, diags := ctx.Apply(plan, m) 5252 if diags.HasErrors() { 5253 t.Fatalf("diags: %s", diags.Err()) 5254 } 5255 5256 actual := strings.TrimSpace(state.String()) 5257 expected := strings.TrimSpace(testTofuApplyProvisionerSelfRefStr) 5258 if actual != expected { 5259 t.Fatalf("wrong result\n\ngot:\n%s\n\nwant:\n%s", actual, expected) 5260 } 5261 5262 // Verify apply was invoked 5263 if !pr.ProvisionResourceCalled { 5264 t.Fatalf("provisioner not invoked") 5265 } 5266 } 5267 5268 func TestContext2Apply_provisionerMultiSelfRef(t *testing.T) { 5269 var lock sync.Mutex 5270 commands := make([]string, 0, 5) 5271 5272 m := testModule(t, "apply-provisioner-multi-self-ref") 5273 p := testProvider("aws") 5274 pr := testProvisioner() 5275 p.PlanResourceChangeFn = testDiffFn 5276 p.ApplyResourceChangeFn = testApplyFn 5277 pr.ProvisionResourceFn = func(req provisioners.ProvisionResourceRequest) (resp provisioners.ProvisionResourceResponse) { 5278 lock.Lock() 5279 defer lock.Unlock() 5280 5281 val := req.Config.GetAttr("command") 5282 if val.IsNull() { 5283 t.Fatalf("bad value for command: %#v", val) 5284 } 5285 5286 commands = append(commands, val.AsString()) 5287 return 5288 } 5289 5290 ctx := testContext2(t, &ContextOpts{ 5291 Providers: map[addrs.Provider]providers.Factory{ 5292 addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p), 5293 }, 5294 Provisioners: map[string]provisioners.Factory{ 5295 "shell": testProvisionerFuncFixed(pr), 5296 }, 5297 }) 5298 5299 plan, diags := ctx.Plan(m, states.NewState(), DefaultPlanOpts) 5300 assertNoErrors(t, diags) 5301 5302 state, diags := ctx.Apply(plan, m) 5303 if diags.HasErrors() { 5304 t.Fatalf("diags: %s", diags.Err()) 5305 } 5306 5307 actual := strings.TrimSpace(state.String()) 5308 expected := strings.TrimSpace(testTofuApplyProvisionerMultiSelfRefStr) 5309 if actual != expected { 5310 t.Fatalf("wrong result\n\ngot:\n%s\n\nwant:\n%s", actual, expected) 5311 } 5312 5313 // Verify apply was invoked 5314 if !pr.ProvisionResourceCalled { 5315 t.Fatalf("provisioner not invoked") 5316 } 5317 5318 // Verify our result 5319 sort.Strings(commands) 5320 expectedCommands := []string{"number 0", "number 1", "number 2"} 5321 if !reflect.DeepEqual(commands, expectedCommands) { 5322 t.Fatalf("bad: %#v", commands) 5323 } 5324 } 5325 5326 func TestContext2Apply_provisionerMultiSelfRefSingle(t *testing.T) { 5327 var lock sync.Mutex 5328 order := make([]string, 0, 5) 5329 5330 m := testModule(t, "apply-provisioner-multi-self-ref-single") 5331 p := testProvider("aws") 5332 pr := testProvisioner() 5333 p.PlanResourceChangeFn = testDiffFn 5334 p.ApplyResourceChangeFn = testApplyFn 5335 pr.ProvisionResourceFn = func(req provisioners.ProvisionResourceRequest) (resp provisioners.ProvisionResourceResponse) { 5336 lock.Lock() 5337 defer lock.Unlock() 5338 5339 val := req.Config.GetAttr("order") 5340 if val.IsNull() { 5341 t.Fatalf("no val for order") 5342 } 5343 5344 order = append(order, val.AsString()) 5345 return 5346 } 5347 5348 ctx := testContext2(t, &ContextOpts{ 5349 Providers: map[addrs.Provider]providers.Factory{ 5350 addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p), 5351 }, 5352 Provisioners: map[string]provisioners.Factory{ 5353 "shell": testProvisionerFuncFixed(pr), 5354 }, 5355 }) 5356 5357 plan, diags := ctx.Plan(m, states.NewState(), DefaultPlanOpts) 5358 assertNoErrors(t, diags) 5359 5360 state, diags := ctx.Apply(plan, m) 5361 if diags.HasErrors() { 5362 t.Fatalf("diags: %s", diags.Err()) 5363 } 5364 5365 actual := strings.TrimSpace(state.String()) 5366 expected := strings.TrimSpace(testTofuApplyProvisionerMultiSelfRefSingleStr) 5367 if actual != expected { 5368 t.Fatalf("wrong result\n\ngot:\n%s\n\nwant:\n%s", actual, expected) 5369 } 5370 5371 // Verify apply was invoked 5372 if !pr.ProvisionResourceCalled { 5373 t.Fatalf("provisioner not invoked") 5374 } 5375 5376 // Verify our result 5377 sort.Strings(order) 5378 expectedOrder := []string{"0", "1", "2"} 5379 if !reflect.DeepEqual(order, expectedOrder) { 5380 t.Fatalf("bad: %#v", order) 5381 } 5382 } 5383 5384 func TestContext2Apply_provisionerExplicitSelfRef(t *testing.T) { 5385 m := testModule(t, "apply-provisioner-explicit-self-ref") 5386 p := testProvider("aws") 5387 pr := testProvisioner() 5388 p.PlanResourceChangeFn = testDiffFn 5389 pr.ProvisionResourceFn = func(req provisioners.ProvisionResourceRequest) (resp provisioners.ProvisionResourceResponse) { 5390 val := req.Config.GetAttr("command") 5391 if val.IsNull() || val.AsString() != "bar" { 5392 t.Fatalf("bad value for command: %#v", val) 5393 } 5394 5395 return 5396 } 5397 5398 var state *states.State 5399 { 5400 ctx := testContext2(t, &ContextOpts{ 5401 Providers: map[addrs.Provider]providers.Factory{ 5402 addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p), 5403 }, 5404 Provisioners: map[string]provisioners.Factory{ 5405 "shell": testProvisionerFuncFixed(pr), 5406 }, 5407 }) 5408 5409 plan, diags := ctx.Plan(m, states.NewState(), DefaultPlanOpts) 5410 if diags.HasErrors() { 5411 t.Fatalf("diags: %s", diags.Err()) 5412 } 5413 5414 state, diags = ctx.Apply(plan, m) 5415 if diags.HasErrors() { 5416 t.Fatalf("diags: %s", diags.Err()) 5417 } 5418 5419 // Verify apply was invoked 5420 if !pr.ProvisionResourceCalled { 5421 t.Fatalf("provisioner not invoked") 5422 } 5423 } 5424 5425 { 5426 ctx := testContext2(t, &ContextOpts{ 5427 Providers: map[addrs.Provider]providers.Factory{ 5428 addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p), 5429 }, 5430 Provisioners: map[string]provisioners.Factory{ 5431 "shell": testProvisionerFuncFixed(pr), 5432 }, 5433 }) 5434 5435 plan, diags := ctx.Plan(m, state, &PlanOpts{ 5436 Mode: plans.DestroyMode, 5437 }) 5438 if diags.HasErrors() { 5439 t.Fatalf("diags: %s", diags.Err()) 5440 } 5441 5442 state, diags = ctx.Apply(plan, m) 5443 if diags.HasErrors() { 5444 t.Fatalf("diags: %s", diags.Err()) 5445 } 5446 5447 checkStateString(t, state, `<no state>`) 5448 } 5449 } 5450 5451 func TestContext2Apply_provisionerForEachSelfRef(t *testing.T) { 5452 m := testModule(t, "apply-provisioner-for-each-self") 5453 p := testProvider("aws") 5454 pr := testProvisioner() 5455 p.PlanResourceChangeFn = testDiffFn 5456 5457 pr.ProvisionResourceFn = func(req provisioners.ProvisionResourceRequest) (resp provisioners.ProvisionResourceResponse) { 5458 val := req.Config.GetAttr("command") 5459 if val.IsNull() { 5460 t.Fatalf("bad value for command: %#v", val) 5461 } 5462 5463 return resp 5464 } 5465 5466 ctx := testContext2(t, &ContextOpts{ 5467 Providers: map[addrs.Provider]providers.Factory{ 5468 addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p), 5469 }, 5470 Provisioners: map[string]provisioners.Factory{ 5471 "shell": testProvisionerFuncFixed(pr), 5472 }, 5473 }) 5474 5475 plan, diags := ctx.Plan(m, states.NewState(), DefaultPlanOpts) 5476 assertNoErrors(t, diags) 5477 5478 _, diags = ctx.Apply(plan, m) 5479 if diags.HasErrors() { 5480 t.Fatalf("diags: %s", diags.Err()) 5481 } 5482 } 5483 5484 // Provisioner should NOT run on a diff, only create 5485 func TestContext2Apply_Provisioner_Diff(t *testing.T) { 5486 m := testModule(t, "apply-provisioner-diff") 5487 p := testProvider("aws") 5488 pr := testProvisioner() 5489 p.PlanResourceChangeFn = testDiffFn 5490 p.ApplyResourceChangeFn = testApplyFn 5491 ctx := testContext2(t, &ContextOpts{ 5492 Providers: map[addrs.Provider]providers.Factory{ 5493 addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p), 5494 }, 5495 Provisioners: map[string]provisioners.Factory{ 5496 "shell": testProvisionerFuncFixed(pr), 5497 }, 5498 }) 5499 5500 plan, diags := ctx.Plan(m, states.NewState(), DefaultPlanOpts) 5501 assertNoErrors(t, diags) 5502 5503 state, diags := ctx.Apply(plan, m) 5504 if diags.HasErrors() { 5505 logDiagnostics(t, diags) 5506 t.Fatal("apply failed") 5507 } 5508 5509 actual := strings.TrimSpace(state.String()) 5510 expected := strings.TrimSpace(testTofuApplyProvisionerDiffStr) 5511 if actual != expected { 5512 t.Fatalf("wrong result\n\ngot:\n%s\n\nwant:\n%s", actual, expected) 5513 } 5514 5515 // Verify apply was invoked 5516 if !pr.ProvisionResourceCalled { 5517 t.Fatalf("provisioner was not called on first apply") 5518 } 5519 pr.ProvisionResourceCalled = false 5520 5521 // Change the state to force a diff 5522 mod := state.RootModule() 5523 obj := mod.Resources["aws_instance.bar"].Instances[addrs.NoKey].Current 5524 var attrs map[string]interface{} 5525 err := json.Unmarshal(obj.AttrsJSON, &attrs) 5526 if err != nil { 5527 t.Fatal(err) 5528 } 5529 attrs["foo"] = "baz" 5530 obj.AttrsJSON, err = json.Marshal(attrs) 5531 if err != nil { 5532 t.Fatal(err) 5533 } 5534 5535 // Re-create context with state 5536 ctx = testContext2(t, &ContextOpts{ 5537 Providers: map[addrs.Provider]providers.Factory{ 5538 addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p), 5539 }, 5540 Provisioners: map[string]provisioners.Factory{ 5541 "shell": testProvisionerFuncFixed(pr), 5542 }, 5543 }) 5544 5545 plan, diags = ctx.Plan(m, state, DefaultPlanOpts) 5546 assertNoErrors(t, diags) 5547 5548 state2, diags := ctx.Apply(plan, m) 5549 if diags.HasErrors() { 5550 logDiagnostics(t, diags) 5551 t.Fatal("apply failed") 5552 } 5553 5554 actual = strings.TrimSpace(state2.String()) 5555 if actual != expected { 5556 t.Fatalf("wrong result\n\ngot:\n%s\n\nwant:\n%s", actual, expected) 5557 } 5558 5559 // Verify apply was NOT invoked 5560 if pr.ProvisionResourceCalled { 5561 t.Fatalf("provisioner was called on second apply; should not have been") 5562 } 5563 } 5564 5565 func TestContext2Apply_outputDiffVars(t *testing.T) { 5566 m := testModule(t, "apply-good") 5567 p := testProvider("aws") 5568 5569 state := states.NewState() 5570 root := state.EnsureModule(addrs.RootModuleInstance) 5571 root.SetResourceInstanceCurrent( 5572 mustResourceInstanceAddr("aws_instance.baz").Resource, 5573 &states.ResourceInstanceObjectSrc{ 5574 Status: states.ObjectReady, 5575 AttrsJSON: []byte(`{"id":"bar"}`), 5576 }, 5577 mustProviderConfig(`provider["registry.opentofu.org/hashicorp/aws"]`), 5578 ) 5579 5580 ctx := testContext2(t, &ContextOpts{ 5581 Providers: map[addrs.Provider]providers.Factory{ 5582 addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p), 5583 }, 5584 }) 5585 5586 p.PlanResourceChangeFn = testDiffFn 5587 //func(info *InstanceInfo, s *InstanceState, rc *ResourceConfig) (*InstanceDiff, error) { 5588 // d := &InstanceDiff{ 5589 // Attributes: map[string]*ResourceAttrDiff{}, 5590 // } 5591 // if new, ok := rc.Get("value"); ok { 5592 // d.Attributes["value"] = &ResourceAttrDiff{ 5593 // New: new.(string), 5594 // } 5595 // } 5596 // if new, ok := rc.Get("foo"); ok { 5597 // d.Attributes["foo"] = &ResourceAttrDiff{ 5598 // New: new.(string), 5599 // } 5600 // } else if rc.IsComputed("foo") { 5601 // d.Attributes["foo"] = &ResourceAttrDiff{ 5602 // NewComputed: true, 5603 // Type: DiffAttrOutput, // This doesn't actually really do anything anymore, but this test originally set it. 5604 // } 5605 // } 5606 // if new, ok := rc.Get("num"); ok { 5607 // d.Attributes["num"] = &ResourceAttrDiff{ 5608 // New: fmt.Sprintf("%#v", new), 5609 // } 5610 // } 5611 // return d, nil 5612 // } 5613 5614 plan, diags := ctx.Plan(m, state, DefaultPlanOpts) 5615 assertNoErrors(t, diags) 5616 5617 _, diags = ctx.Apply(plan, m) 5618 assertNoErrors(t, diags) 5619 } 5620 5621 func TestContext2Apply_destroyX(t *testing.T) { 5622 m := testModule(t, "apply-destroy") 5623 h := new(HookRecordApplyOrder) 5624 p := testProvider("aws") 5625 p.PlanResourceChangeFn = testDiffFn 5626 ctx := testContext2(t, &ContextOpts{ 5627 Hooks: []Hook{h}, 5628 Providers: map[addrs.Provider]providers.Factory{ 5629 addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p), 5630 }, 5631 }) 5632 5633 // First plan and apply a create operation 5634 plan, diags := ctx.Plan(m, states.NewState(), DefaultPlanOpts) 5635 assertNoErrors(t, diags) 5636 5637 state, diags := ctx.Apply(plan, m) 5638 if diags.HasErrors() { 5639 t.Fatalf("diags: %s", diags.Err()) 5640 } 5641 5642 // Next, plan and apply a destroy operation 5643 h.Active = true 5644 ctx = testContext2(t, &ContextOpts{ 5645 Hooks: []Hook{h}, 5646 Providers: map[addrs.Provider]providers.Factory{ 5647 addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p), 5648 }, 5649 }) 5650 5651 plan, diags = ctx.Plan(m, state, &PlanOpts{ 5652 Mode: plans.DestroyMode, 5653 }) 5654 assertNoErrors(t, diags) 5655 5656 state, diags = ctx.Apply(plan, m) 5657 if diags.HasErrors() { 5658 t.Fatalf("diags: %s", diags.Err()) 5659 } 5660 5661 // Test that things were destroyed 5662 actual := strings.TrimSpace(state.String()) 5663 expected := strings.TrimSpace(testTofuApplyDestroyStr) 5664 if actual != expected { 5665 t.Fatalf("wrong result\n\ngot:\n%s\n\nwant:\n%s", actual, expected) 5666 } 5667 5668 // Test that things were destroyed _in the right order_ 5669 expected2 := []string{"aws_instance.bar", "aws_instance.foo"} 5670 actual2 := h.IDs 5671 if !reflect.DeepEqual(actual2, expected2) { 5672 t.Fatalf("expected: %#v\n\ngot:%#v", expected2, actual2) 5673 } 5674 } 5675 5676 func TestContext2Apply_destroyOrder(t *testing.T) { 5677 m := testModule(t, "apply-destroy") 5678 h := new(HookRecordApplyOrder) 5679 p := testProvider("aws") 5680 p.PlanResourceChangeFn = testDiffFn 5681 ctx := testContext2(t, &ContextOpts{ 5682 Hooks: []Hook{h}, 5683 Providers: map[addrs.Provider]providers.Factory{ 5684 addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p), 5685 }, 5686 }) 5687 5688 // First plan and apply a create operation 5689 plan, diags := ctx.Plan(m, states.NewState(), DefaultPlanOpts) 5690 assertNoErrors(t, diags) 5691 5692 state, diags := ctx.Apply(plan, m) 5693 if diags.HasErrors() { 5694 t.Fatalf("diags: %s", diags.Err()) 5695 } 5696 5697 t.Logf("State 1: %s", state) 5698 5699 // Next, plan and apply a destroy 5700 h.Active = true 5701 ctx = testContext2(t, &ContextOpts{ 5702 Hooks: []Hook{h}, 5703 Providers: map[addrs.Provider]providers.Factory{ 5704 addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p), 5705 }, 5706 }) 5707 5708 plan, diags = ctx.Plan(m, state, &PlanOpts{ 5709 Mode: plans.DestroyMode, 5710 }) 5711 assertNoErrors(t, diags) 5712 5713 state, diags = ctx.Apply(plan, m) 5714 if diags.HasErrors() { 5715 t.Fatalf("diags: %s", diags.Err()) 5716 } 5717 5718 // Test that things were destroyed 5719 actual := strings.TrimSpace(state.String()) 5720 expected := strings.TrimSpace(testTofuApplyDestroyStr) 5721 if actual != expected { 5722 t.Fatalf("wrong result\n\ngot:\n%s\n\nwant:\n%s", actual, expected) 5723 } 5724 5725 // Test that things were destroyed _in the right order_ 5726 expected2 := []string{"aws_instance.bar", "aws_instance.foo"} 5727 actual2 := h.IDs 5728 if !reflect.DeepEqual(actual2, expected2) { 5729 t.Fatalf("expected: %#v\n\ngot:%#v", expected2, actual2) 5730 } 5731 } 5732 5733 // https://github.com/hashicorp/terraform/issues/2767 5734 func TestContext2Apply_destroyModulePrefix(t *testing.T) { 5735 m := testModule(t, "apply-destroy-module-resource-prefix") 5736 h := new(MockHook) 5737 p := testProvider("aws") 5738 p.PlanResourceChangeFn = testDiffFn 5739 ctx := testContext2(t, &ContextOpts{ 5740 Hooks: []Hook{h}, 5741 Providers: map[addrs.Provider]providers.Factory{ 5742 addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p), 5743 }, 5744 }) 5745 5746 // First plan and apply a create operation 5747 plan, diags := ctx.Plan(m, states.NewState(), DefaultPlanOpts) 5748 assertNoErrors(t, diags) 5749 5750 state, diags := ctx.Apply(plan, m) 5751 if diags.HasErrors() { 5752 t.Fatalf("diags: %s", diags.Err()) 5753 } 5754 5755 // Verify that we got the apply info correct 5756 if v := h.PreApplyAddr.String(); v != "module.child.aws_instance.foo" { 5757 t.Fatalf("bad: %s", v) 5758 } 5759 5760 // Next, plan and apply a destroy operation and reset the hook 5761 h = new(MockHook) 5762 ctx = testContext2(t, &ContextOpts{ 5763 Hooks: []Hook{h}, 5764 Providers: map[addrs.Provider]providers.Factory{ 5765 addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p), 5766 }, 5767 }) 5768 5769 plan, diags = ctx.Plan(m, state, &PlanOpts{ 5770 Mode: plans.DestroyMode, 5771 }) 5772 assertNoErrors(t, diags) 5773 5774 _, diags = ctx.Apply(plan, m) 5775 if diags.HasErrors() { 5776 t.Fatalf("diags: %s", diags.Err()) 5777 } 5778 5779 // Test that things were destroyed 5780 if v := h.PreApplyAddr.String(); v != "module.child.aws_instance.foo" { 5781 t.Fatalf("bad: %s", v) 5782 } 5783 } 5784 5785 func TestContext2Apply_destroyNestedModule(t *testing.T) { 5786 m := testModule(t, "apply-destroy-nested-module") 5787 p := testProvider("aws") 5788 p.PlanResourceChangeFn = testDiffFn 5789 5790 state := states.NewState() 5791 root := state.EnsureModule(addrs.RootModuleInstance) 5792 root.SetResourceInstanceCurrent( 5793 mustResourceInstanceAddr("aws_instance.bar").Resource, 5794 &states.ResourceInstanceObjectSrc{ 5795 Status: states.ObjectReady, 5796 AttrsJSON: []byte(`{"id":"bar"}`), 5797 }, 5798 mustProviderConfig(`provider["registry.opentofu.org/hashicorp/aws"]`), 5799 ) 5800 5801 ctx := testContext2(t, &ContextOpts{ 5802 Providers: map[addrs.Provider]providers.Factory{ 5803 addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p), 5804 }, 5805 }) 5806 5807 // First plan and apply a create operation 5808 plan, diags := ctx.Plan(m, state, DefaultPlanOpts) 5809 assertNoErrors(t, diags) 5810 5811 s, diags := ctx.Apply(plan, m) 5812 if diags.HasErrors() { 5813 t.Fatalf("diags: %s", diags.Err()) 5814 } 5815 5816 // Test that things were destroyed 5817 actual := strings.TrimSpace(s.String()) 5818 if actual != "<no state>" { 5819 t.Fatalf("expected no state, got: %s", actual) 5820 } 5821 } 5822 5823 func TestContext2Apply_destroyDeeplyNestedModule(t *testing.T) { 5824 m := testModule(t, "apply-destroy-deeply-nested-module") 5825 p := testProvider("aws") 5826 p.PlanResourceChangeFn = testDiffFn 5827 5828 state := states.NewState() 5829 root := state.EnsureModule(addrs.RootModuleInstance) 5830 root.SetResourceInstanceCurrent( 5831 mustResourceInstanceAddr("aws_instance.bar").Resource, 5832 &states.ResourceInstanceObjectSrc{ 5833 Status: states.ObjectReady, 5834 AttrsJSON: []byte(`{"id":"bar"}`), 5835 }, 5836 mustProviderConfig(`provider["registry.opentofu.org/hashicorp/aws"]`), 5837 ) 5838 5839 ctx := testContext2(t, &ContextOpts{ 5840 Providers: map[addrs.Provider]providers.Factory{ 5841 addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p), 5842 }, 5843 }) 5844 5845 // First plan and apply a create operation 5846 plan, diags := ctx.Plan(m, state, DefaultPlanOpts) 5847 assertNoErrors(t, diags) 5848 5849 s, diags := ctx.Apply(plan, m) 5850 if diags.HasErrors() { 5851 t.Fatalf("diags: %s", diags.Err()) 5852 } 5853 5854 // Test that things were destroyed 5855 if !s.Empty() { 5856 t.Fatalf("wrong final state %s\nwant empty state", spew.Sdump(s)) 5857 } 5858 } 5859 5860 // https://github.com/hashicorp/terraform/issues/5440 5861 func TestContext2Apply_destroyModuleWithAttrsReferencingResource(t *testing.T) { 5862 m, snap := testModuleWithSnapshot(t, "apply-destroy-module-with-attrs") 5863 p := testProvider("aws") 5864 p.PlanResourceChangeFn = testDiffFn 5865 5866 var state *states.State 5867 { 5868 ctx := testContext2(t, &ContextOpts{ 5869 Providers: map[addrs.Provider]providers.Factory{ 5870 addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p), 5871 }, 5872 }) 5873 5874 // First plan and apply a create operation 5875 plan, diags := ctx.Plan(m, states.NewState(), DefaultPlanOpts) 5876 if diags.HasErrors() { 5877 t.Fatalf("plan diags: %s", diags.Err()) 5878 } else { 5879 t.Logf("Step 1 plan: %s", legacyDiffComparisonString(plan.Changes)) 5880 } 5881 5882 state, diags = ctx.Apply(plan, m) 5883 if diags.HasErrors() { 5884 t.Fatalf("apply errs: %s", diags.Err()) 5885 } 5886 5887 t.Logf("Step 1 state: %s", state) 5888 } 5889 5890 h := new(HookRecordApplyOrder) 5891 h.Active = true 5892 5893 { 5894 ctx := testContext2(t, &ContextOpts{ 5895 Hooks: []Hook{h}, 5896 Providers: map[addrs.Provider]providers.Factory{ 5897 addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p), 5898 }, 5899 }) 5900 5901 // First plan and apply a create operation 5902 plan, diags := ctx.Plan(m, state, &PlanOpts{ 5903 Mode: plans.DestroyMode, 5904 }) 5905 if diags.HasErrors() { 5906 t.Fatalf("destroy plan err: %s", diags.Err()) 5907 } 5908 5909 t.Logf("Step 2 plan: %s", legacyDiffComparisonString(plan.Changes)) 5910 5911 ctxOpts, m, plan, err := contextOptsForPlanViaFile(t, snap, plan) 5912 if err != nil { 5913 t.Fatalf("failed to round-trip through planfile: %s", err) 5914 } 5915 5916 ctxOpts.Providers = map[addrs.Provider]providers.Factory{ 5917 addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p), 5918 } 5919 5920 ctx, diags = NewContext(ctxOpts) 5921 if diags.HasErrors() { 5922 t.Fatalf("err: %s", diags.Err()) 5923 } 5924 5925 state, diags = ctx.Apply(plan, m) 5926 if diags.HasErrors() { 5927 t.Fatalf("destroy apply err: %s", diags.Err()) 5928 } 5929 5930 t.Logf("Step 2 state: %s", state) 5931 } 5932 5933 // Test that things were destroyed 5934 if state.HasManagedResourceInstanceObjects() { 5935 t.Fatal("expected empty state, got:", state) 5936 } 5937 } 5938 5939 func TestContext2Apply_destroyWithModuleVariableAndCount(t *testing.T) { 5940 m, snap := testModuleWithSnapshot(t, "apply-destroy-mod-var-and-count") 5941 p := testProvider("aws") 5942 p.PlanResourceChangeFn = testDiffFn 5943 5944 var state *states.State 5945 { 5946 ctx := testContext2(t, &ContextOpts{ 5947 Providers: map[addrs.Provider]providers.Factory{ 5948 addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p), 5949 }, 5950 }) 5951 5952 // First plan and apply a create operation 5953 plan, diags := ctx.Plan(m, states.NewState(), DefaultPlanOpts) 5954 assertNoErrors(t, diags) 5955 5956 state, diags = ctx.Apply(plan, m) 5957 if diags.HasErrors() { 5958 t.Fatalf("apply err: %s", diags.Err()) 5959 } 5960 } 5961 5962 h := new(HookRecordApplyOrder) 5963 h.Active = true 5964 5965 { 5966 ctx := testContext2(t, &ContextOpts{ 5967 Hooks: []Hook{h}, 5968 Providers: map[addrs.Provider]providers.Factory{ 5969 addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p), 5970 }, 5971 }) 5972 5973 // First plan and apply a create operation 5974 plan, diags := ctx.Plan(m, state, &PlanOpts{ 5975 Mode: plans.DestroyMode, 5976 }) 5977 if diags.HasErrors() { 5978 t.Fatalf("destroy plan err: %s", diags.Err()) 5979 } 5980 5981 ctxOpts, m, plan, err := contextOptsForPlanViaFile(t, snap, plan) 5982 if err != nil { 5983 t.Fatalf("failed to round-trip through planfile: %s", err) 5984 } 5985 5986 ctxOpts.Providers = 5987 map[addrs.Provider]providers.Factory{ 5988 addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p), 5989 } 5990 5991 ctx, diags = NewContext(ctxOpts) 5992 if diags.HasErrors() { 5993 t.Fatalf("err: %s", diags.Err()) 5994 } 5995 5996 state, diags = ctx.Apply(plan, m) 5997 if diags.HasErrors() { 5998 t.Fatalf("destroy apply err: %s", diags.Err()) 5999 } 6000 } 6001 6002 // Test that things were destroyed 6003 actual := strings.TrimSpace(state.String()) 6004 expected := strings.TrimSpace(` 6005 <no state>`) 6006 if actual != expected { 6007 t.Fatalf("expected: \n%s\n\nbad: \n%s", expected, actual) 6008 } 6009 } 6010 6011 func TestContext2Apply_destroyTargetWithModuleVariableAndCount(t *testing.T) { 6012 m := testModule(t, "apply-destroy-mod-var-and-count") 6013 p := testProvider("aws") 6014 p.PlanResourceChangeFn = testDiffFn 6015 6016 var state *states.State 6017 { 6018 ctx := testContext2(t, &ContextOpts{ 6019 Providers: map[addrs.Provider]providers.Factory{ 6020 addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p), 6021 }, 6022 }) 6023 6024 // First plan and apply a create operation 6025 plan, diags := ctx.Plan(m, states.NewState(), DefaultPlanOpts) 6026 assertNoErrors(t, diags) 6027 6028 state, diags = ctx.Apply(plan, m) 6029 if diags.HasErrors() { 6030 t.Fatalf("apply err: %s", diags.Err()) 6031 } 6032 } 6033 6034 { 6035 ctx := testContext2(t, &ContextOpts{ 6036 Providers: map[addrs.Provider]providers.Factory{ 6037 addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p), 6038 }, 6039 }) 6040 6041 plan, diags := ctx.Plan(m, state, &PlanOpts{ 6042 Mode: plans.DestroyMode, 6043 Targets: []addrs.Targetable{ 6044 addrs.RootModuleInstance.Child("child", addrs.NoKey), 6045 }, 6046 }) 6047 if diags.HasErrors() { 6048 t.Fatalf("plan err: %s", diags) 6049 } 6050 if len(diags) != 1 { 6051 // Should have one warning that -target is in effect. 6052 t.Fatalf("got %d diagnostics in plan; want 1", len(diags)) 6053 } 6054 if got, want := diags[0].Severity(), tfdiags.Warning; got != want { 6055 t.Errorf("wrong diagnostic severity %#v; want %#v", got, want) 6056 } 6057 if got, want := diags[0].Description().Summary, "Resource targeting is in effect"; got != want { 6058 t.Errorf("wrong diagnostic summary %#v; want %#v", got, want) 6059 } 6060 6061 // Destroy, targeting the module explicitly 6062 state, diags = ctx.Apply(plan, m) 6063 if diags.HasErrors() { 6064 t.Fatalf("destroy apply err: %s", diags) 6065 } 6066 if len(diags) != 1 { 6067 t.Fatalf("got %d diagnostics; want 1", len(diags)) 6068 } 6069 if got, want := diags[0].Severity(), tfdiags.Warning; got != want { 6070 t.Errorf("wrong diagnostic severity %#v; want %#v", got, want) 6071 } 6072 if got, want := diags[0].Description().Summary, "Applied changes may be incomplete"; got != want { 6073 t.Errorf("wrong diagnostic summary %#v; want %#v", got, want) 6074 } 6075 } 6076 6077 // Test that things were destroyed 6078 actual := strings.TrimSpace(state.String()) 6079 expected := strings.TrimSpace(`<no state>`) 6080 if actual != expected { 6081 t.Fatalf("expected: \n%s\n\nbad: \n%s", expected, actual) 6082 } 6083 } 6084 6085 func TestContext2Apply_destroyWithModuleVariableAndCountNested(t *testing.T) { 6086 m, snap := testModuleWithSnapshot(t, "apply-destroy-mod-var-and-count-nested") 6087 p := testProvider("aws") 6088 p.PlanResourceChangeFn = testDiffFn 6089 6090 var state *states.State 6091 { 6092 ctx := testContext2(t, &ContextOpts{ 6093 Providers: map[addrs.Provider]providers.Factory{ 6094 addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p), 6095 }, 6096 }) 6097 6098 // First plan and apply a create operation 6099 plan, diags := ctx.Plan(m, states.NewState(), SimplePlanOpts(plans.NormalMode, testInputValuesUnset(m.Module.Variables))) 6100 assertNoErrors(t, diags) 6101 6102 state, diags = ctx.Apply(plan, m) 6103 if diags.HasErrors() { 6104 t.Fatalf("apply err: %s", diags.Err()) 6105 } 6106 } 6107 6108 h := new(HookRecordApplyOrder) 6109 h.Active = true 6110 6111 { 6112 ctx := testContext2(t, &ContextOpts{ 6113 Hooks: []Hook{h}, 6114 Providers: map[addrs.Provider]providers.Factory{ 6115 addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p), 6116 }, 6117 }) 6118 6119 // First plan and apply a create operation 6120 plan, diags := ctx.Plan(m, state, SimplePlanOpts(plans.DestroyMode, testInputValuesUnset(m.Module.Variables))) 6121 if diags.HasErrors() { 6122 t.Fatalf("destroy plan err: %s", diags.Err()) 6123 } 6124 6125 ctxOpts, m, plan, err := contextOptsForPlanViaFile(t, snap, plan) 6126 if err != nil { 6127 t.Fatalf("failed to round-trip through planfile: %s", err) 6128 } 6129 6130 ctxOpts.Providers = 6131 map[addrs.Provider]providers.Factory{ 6132 addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p), 6133 } 6134 6135 ctx, diags = NewContext(ctxOpts) 6136 if diags.HasErrors() { 6137 t.Fatalf("err: %s", diags.Err()) 6138 } 6139 6140 state, diags = ctx.Apply(plan, m) 6141 if diags.HasErrors() { 6142 t.Fatalf("destroy apply err: %s", diags.Err()) 6143 } 6144 } 6145 6146 // Test that things were destroyed 6147 actual := strings.TrimSpace(state.String()) 6148 expected := strings.TrimSpace(` 6149 <no state>`) 6150 if actual != expected { 6151 t.Fatalf("expected: \n%s\n\nbad: \n%s", expected, actual) 6152 } 6153 } 6154 6155 func TestContext2Apply_destroyOutputs(t *testing.T) { 6156 m := testModule(t, "apply-destroy-outputs") 6157 p := testProvider("test") 6158 p.PlanResourceChangeFn = testDiffFn 6159 6160 p.ReadDataSourceFn = func(req providers.ReadDataSourceRequest) providers.ReadDataSourceResponse { 6161 // add the required id 6162 m := req.Config.AsValueMap() 6163 m["id"] = cty.StringVal("foo") 6164 6165 return providers.ReadDataSourceResponse{ 6166 State: cty.ObjectVal(m), 6167 } 6168 } 6169 6170 ctx := testContext2(t, &ContextOpts{ 6171 Providers: map[addrs.Provider]providers.Factory{ 6172 addrs.NewDefaultProvider("test"): testProviderFuncFixed(p), 6173 }, 6174 }) 6175 6176 // First plan and apply a create operation 6177 plan, diags := ctx.Plan(m, states.NewState(), DefaultPlanOpts) 6178 assertNoErrors(t, diags) 6179 6180 state, diags := ctx.Apply(plan, m) 6181 6182 if diags.HasErrors() { 6183 t.Fatalf("diags: %s", diags.Err()) 6184 } 6185 6186 // Next, plan and apply a destroy operation 6187 ctx = testContext2(t, &ContextOpts{ 6188 Providers: map[addrs.Provider]providers.Factory{ 6189 addrs.NewDefaultProvider("test"): testProviderFuncFixed(p), 6190 }, 6191 }) 6192 6193 plan, diags = ctx.Plan(m, state, &PlanOpts{ 6194 Mode: plans.DestroyMode, 6195 }) 6196 assertNoErrors(t, diags) 6197 6198 state, diags = ctx.Apply(plan, m) 6199 if diags.HasErrors() { 6200 t.Fatalf("diags: %s", diags.Err()) 6201 } 6202 6203 mod := state.RootModule() 6204 if len(mod.Resources) > 0 { 6205 t.Fatalf("expected no resources, got: %#v", mod) 6206 } 6207 6208 // destroying again should produce no errors 6209 ctx = testContext2(t, &ContextOpts{ 6210 Providers: map[addrs.Provider]providers.Factory{ 6211 addrs.NewDefaultProvider("test"): testProviderFuncFixed(p), 6212 }, 6213 }) 6214 plan, diags = ctx.Plan(m, state, &PlanOpts{ 6215 Mode: plans.DestroyMode, 6216 }) 6217 assertNoErrors(t, diags) 6218 6219 if _, diags := ctx.Apply(plan, m); diags.HasErrors() { 6220 t.Fatal(diags.Err()) 6221 } 6222 } 6223 6224 func TestContext2Apply_destroyOrphan(t *testing.T) { 6225 m := testModule(t, "apply-error") 6226 p := testProvider("aws") 6227 state := states.NewState() 6228 root := state.EnsureModule(addrs.RootModuleInstance) 6229 root.SetResourceInstanceCurrent( 6230 mustResourceInstanceAddr("aws_instance.baz").Resource, 6231 &states.ResourceInstanceObjectSrc{ 6232 Status: states.ObjectReady, 6233 AttrsJSON: []byte(`{"id":"bar"}`), 6234 }, 6235 mustProviderConfig(`provider["registry.opentofu.org/hashicorp/aws"]`), 6236 ) 6237 ctx := testContext2(t, &ContextOpts{ 6238 Providers: map[addrs.Provider]providers.Factory{ 6239 addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p), 6240 }, 6241 }) 6242 6243 p.PlanResourceChangeFn = testDiffFn 6244 6245 plan, diags := ctx.Plan(m, state, DefaultPlanOpts) 6246 assertNoErrors(t, diags) 6247 6248 s, diags := ctx.Apply(plan, m) 6249 if diags.HasErrors() { 6250 t.Fatalf("diags: %s", diags.Err()) 6251 } 6252 6253 mod := s.RootModule() 6254 if _, ok := mod.Resources["aws_instance.baz"]; ok { 6255 t.Fatalf("bad: %#v", mod.Resources) 6256 } 6257 } 6258 6259 func TestContext2Apply_destroyTaintedProvisioner(t *testing.T) { 6260 m := testModule(t, "apply-destroy-provisioner") 6261 p := testProvider("aws") 6262 pr := testProvisioner() 6263 p.PlanResourceChangeFn = testDiffFn 6264 6265 state := states.NewState() 6266 root := state.EnsureModule(addrs.RootModuleInstance) 6267 root.SetResourceInstanceCurrent( 6268 mustResourceInstanceAddr("aws_instance.foo").Resource, 6269 &states.ResourceInstanceObjectSrc{ 6270 Status: states.ObjectReady, 6271 AttrsJSON: []byte(`{"id":"bar"}`), 6272 }, 6273 mustProviderConfig(`provider["registry.opentofu.org/hashicorp/aws"]`), 6274 ) 6275 6276 ctx := testContext2(t, &ContextOpts{ 6277 Providers: map[addrs.Provider]providers.Factory{ 6278 addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p), 6279 }, 6280 Provisioners: map[string]provisioners.Factory{ 6281 "shell": testProvisionerFuncFixed(pr), 6282 }, 6283 }) 6284 6285 plan, diags := ctx.Plan(m, state, &PlanOpts{ 6286 Mode: plans.DestroyMode, 6287 }) 6288 assertNoErrors(t, diags) 6289 6290 s, diags := ctx.Apply(plan, m) 6291 if diags.HasErrors() { 6292 t.Fatalf("diags: %s", diags.Err()) 6293 } 6294 6295 if pr.ProvisionResourceCalled { 6296 t.Fatal("provisioner should not be called") 6297 } 6298 6299 actual := strings.TrimSpace(s.String()) 6300 expected := strings.TrimSpace("<no state>") 6301 if actual != expected { 6302 t.Fatalf("wrong result\n\ngot:\n%s\n\nwant:\n%s", actual, expected) 6303 } 6304 } 6305 6306 func TestContext2Apply_error(t *testing.T) { 6307 errored := false 6308 6309 m := testModule(t, "apply-error") 6310 p := testProvider("aws") 6311 ctx := testContext2(t, &ContextOpts{ 6312 Providers: map[addrs.Provider]providers.Factory{ 6313 addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p), 6314 }, 6315 }) 6316 6317 p.ApplyResourceChangeFn = func(req providers.ApplyResourceChangeRequest) (resp providers.ApplyResourceChangeResponse) { 6318 if errored { 6319 resp.NewState = req.PlannedState 6320 resp.Diagnostics = resp.Diagnostics.Append(fmt.Errorf("error")) 6321 return 6322 } 6323 errored = true 6324 6325 return testApplyFn(req) 6326 } 6327 p.PlanResourceChangeFn = testDiffFn 6328 6329 plan, diags := ctx.Plan(m, states.NewState(), DefaultPlanOpts) 6330 assertNoErrors(t, diags) 6331 6332 state, diags := ctx.Apply(plan, m) 6333 if diags == nil { 6334 t.Fatal("should have error") 6335 } 6336 6337 actual := strings.TrimSpace(state.String()) 6338 expected := strings.TrimSpace(testTofuApplyErrorStr) 6339 if actual != expected { 6340 t.Fatalf("expected:\n%s\n\ngot:\n%s", expected, actual) 6341 } 6342 } 6343 6344 func TestContext2Apply_errorDestroy(t *testing.T) { 6345 m := testModule(t, "empty") 6346 p := testProvider("test") 6347 6348 p.GetProviderSchemaResponse = getProviderSchemaResponseFromProviderSchema(&ProviderSchema{ 6349 ResourceTypes: map[string]*configschema.Block{ 6350 "test_thing": { 6351 Attributes: map[string]*configschema.Attribute{ 6352 "id": {Type: cty.String, Optional: true}, 6353 }, 6354 }, 6355 }, 6356 }) 6357 p.PlanResourceChangeFn = func(req providers.PlanResourceChangeRequest) providers.PlanResourceChangeResponse { 6358 // Should actually be called for this test, because OpenTofu Core 6359 // constructs the plan for a destroy operation itself. 6360 return providers.PlanResourceChangeResponse{ 6361 PlannedState: req.ProposedNewState, 6362 } 6363 } 6364 p.ApplyResourceChangeFn = func(req providers.ApplyResourceChangeRequest) providers.ApplyResourceChangeResponse { 6365 // The apply (in this case, a destroy) always fails, so we can verify 6366 // that the object stays in the state after a destroy fails even though 6367 // we aren't returning a new state object here. 6368 return providers.ApplyResourceChangeResponse{ 6369 Diagnostics: tfdiags.Diagnostics(nil).Append(fmt.Errorf("failed")), 6370 } 6371 } 6372 6373 ctx := testContext2(t, &ContextOpts{ 6374 Providers: map[addrs.Provider]providers.Factory{ 6375 addrs.NewDefaultProvider("test"): testProviderFuncFixed(p), 6376 }, 6377 }) 6378 6379 state := states.BuildState(func(ss *states.SyncState) { 6380 ss.SetResourceInstanceCurrent( 6381 addrs.Resource{ 6382 Mode: addrs.ManagedResourceMode, 6383 Type: "test_thing", 6384 Name: "foo", 6385 }.Instance(addrs.NoKey).Absolute(addrs.RootModuleInstance), 6386 &states.ResourceInstanceObjectSrc{ 6387 Status: states.ObjectReady, 6388 AttrsJSON: []byte(`{"id":"baz"}`), 6389 }, 6390 addrs.AbsProviderConfig{ 6391 Provider: addrs.NewDefaultProvider("test"), 6392 Module: addrs.RootModule, 6393 }, 6394 ) 6395 }) 6396 plan, diags := ctx.Plan(m, state, DefaultPlanOpts) 6397 assertNoErrors(t, diags) 6398 6399 state, diags = ctx.Apply(plan, m) 6400 if !diags.HasErrors() { 6401 t.Fatal("should have error") 6402 } 6403 6404 actual := strings.TrimSpace(state.String()) 6405 expected := strings.TrimSpace(` 6406 test_thing.foo: 6407 ID = baz 6408 provider = provider["registry.opentofu.org/hashicorp/test"] 6409 `) // test_thing.foo is still here, even though provider returned no new state along with its error 6410 if actual != expected { 6411 t.Fatalf("expected:\n%s\n\ngot:\n%s", expected, actual) 6412 } 6413 } 6414 6415 func TestContext2Apply_errorCreateInvalidNew(t *testing.T) { 6416 m := testModule(t, "apply-error") 6417 6418 p := testProvider("aws") 6419 p.GetProviderSchemaResponse = getProviderSchemaResponseFromProviderSchema(&ProviderSchema{ 6420 ResourceTypes: map[string]*configschema.Block{ 6421 "aws_instance": { 6422 Attributes: map[string]*configschema.Attribute{ 6423 "value": {Type: cty.String, Optional: true}, 6424 "foo": {Type: cty.String, Optional: true}, 6425 }, 6426 }, 6427 }, 6428 }) 6429 p.PlanResourceChangeFn = func(req providers.PlanResourceChangeRequest) providers.PlanResourceChangeResponse { 6430 return providers.PlanResourceChangeResponse{ 6431 PlannedState: req.ProposedNewState, 6432 } 6433 } 6434 p.ApplyResourceChangeFn = func(req providers.ApplyResourceChangeRequest) providers.ApplyResourceChangeResponse { 6435 // We're intentionally returning an inconsistent new state here 6436 // because we want to test that OpenTofu ignores the inconsistency 6437 // when accompanied by another error. 6438 return providers.ApplyResourceChangeResponse{ 6439 NewState: cty.ObjectVal(map[string]cty.Value{ 6440 "value": cty.StringVal("wrong wrong wrong wrong"), 6441 "foo": cty.StringVal("absolutely brimming over with wrongability"), 6442 }), 6443 Diagnostics: tfdiags.Diagnostics(nil).Append(fmt.Errorf("forced error")), 6444 } 6445 } 6446 6447 ctx := testContext2(t, &ContextOpts{ 6448 Providers: map[addrs.Provider]providers.Factory{ 6449 addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p), 6450 }, 6451 }) 6452 6453 plan, diags := ctx.Plan(m, states.NewState(), DefaultPlanOpts) 6454 assertNoErrors(t, diags) 6455 6456 state, diags := ctx.Apply(plan, m) 6457 if diags == nil { 6458 t.Fatal("should have error") 6459 } 6460 if got, want := len(diags), 1; got != want { 6461 // There should be no additional diagnostics generated by OpenTofu's own eval logic, 6462 // because the provider's own error supersedes them. 6463 t.Errorf("wrong number of diagnostics %d; want %d\n%s", got, want, diags.Err()) 6464 } 6465 if got, want := diags.Err().Error(), "forced error"; !strings.Contains(got, want) { 6466 t.Errorf("returned error does not contain %q, but it should\n%s", want, diags.Err()) 6467 } 6468 if got, want := len(state.RootModule().Resources), 1; got != want { 6469 t.Errorf("%d resources in state before prune; should have %d\n%s", got, want, spew.Sdump(state)) 6470 } 6471 state.PruneResourceHusks() // aws_instance.bar with no instances gets left behind when we bail out, but that's okay 6472 if got, want := len(state.RootModule().Resources), 1; got != want { 6473 t.Errorf("%d resources in state after prune; should have only one (aws_instance.foo, tainted)\n%s", got, spew.Sdump(state)) 6474 } 6475 } 6476 6477 func TestContext2Apply_errorUpdateNullNew(t *testing.T) { 6478 m := testModule(t, "apply-error") 6479 6480 p := testProvider("aws") 6481 p.GetProviderSchemaResponse = getProviderSchemaResponseFromProviderSchema(&ProviderSchema{ 6482 ResourceTypes: map[string]*configschema.Block{ 6483 "aws_instance": { 6484 Attributes: map[string]*configschema.Attribute{ 6485 "value": {Type: cty.String, Optional: true}, 6486 "foo": {Type: cty.String, Optional: true}, 6487 }, 6488 }, 6489 }, 6490 }) 6491 p.PlanResourceChangeFn = func(req providers.PlanResourceChangeRequest) providers.PlanResourceChangeResponse { 6492 return providers.PlanResourceChangeResponse{ 6493 PlannedState: req.ProposedNewState, 6494 } 6495 } 6496 p.ApplyResourceChangeFn = func(req providers.ApplyResourceChangeRequest) providers.ApplyResourceChangeResponse { 6497 // We're intentionally returning no NewState here because we want to 6498 // test that OpenTofu retains the prior state, rather than treating 6499 // the returned null as "no state" (object deleted). 6500 return providers.ApplyResourceChangeResponse{ 6501 Diagnostics: tfdiags.Diagnostics(nil).Append(fmt.Errorf("forced error")), 6502 } 6503 } 6504 6505 ctx := testContext2(t, &ContextOpts{ 6506 Providers: map[addrs.Provider]providers.Factory{ 6507 addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p), 6508 }, 6509 }) 6510 6511 state := states.BuildState(func(ss *states.SyncState) { 6512 ss.SetResourceInstanceCurrent( 6513 addrs.Resource{ 6514 Mode: addrs.ManagedResourceMode, 6515 Type: "aws_instance", 6516 Name: "foo", 6517 }.Instance(addrs.NoKey).Absolute(addrs.RootModuleInstance), 6518 &states.ResourceInstanceObjectSrc{ 6519 Status: states.ObjectReady, 6520 AttrsJSON: []byte(`{"value":"old"}`), 6521 }, 6522 addrs.AbsProviderConfig{ 6523 Provider: addrs.NewDefaultProvider("aws"), 6524 Module: addrs.RootModule, 6525 }, 6526 ) 6527 }) 6528 plan, diags := ctx.Plan(m, state, DefaultPlanOpts) 6529 assertNoErrors(t, diags) 6530 6531 state, diags = ctx.Apply(plan, m) 6532 if !diags.HasErrors() { 6533 t.Fatal("should have error") 6534 } 6535 if got, want := len(diags), 1; got != want { 6536 // There should be no additional diagnostics generated by OpenTofu's own eval logic, 6537 // because the provider's own error supersedes them. 6538 t.Errorf("wrong number of diagnostics %d; want %d\n%s", got, want, diags.Err()) 6539 } 6540 if got, want := diags.Err().Error(), "forced error"; !strings.Contains(got, want) { 6541 t.Errorf("returned error does not contain %q, but it should\n%s", want, diags.Err()) 6542 } 6543 state.PruneResourceHusks() 6544 if got, want := len(state.RootModule().Resources), 1; got != want { 6545 t.Fatalf("%d resources in state; should have only one (aws_instance.foo, unmodified)\n%s", got, spew.Sdump(state)) 6546 } 6547 6548 is := state.ResourceInstance(addrs.Resource{ 6549 Mode: addrs.ManagedResourceMode, 6550 Type: "aws_instance", 6551 Name: "foo", 6552 }.Instance(addrs.NoKey).Absolute(addrs.RootModuleInstance)) 6553 if is == nil { 6554 t.Fatalf("aws_instance.foo is not in the state after apply") 6555 } 6556 if got, want := is.Current.AttrsJSON, []byte(`"old"`); !bytes.Contains(got, want) { 6557 t.Fatalf("incorrect attributes for aws_instance.foo\ngot: %s\nwant: JSON containing %s\n\n%s", got, want, spew.Sdump(is)) 6558 } 6559 } 6560 6561 func TestContext2Apply_errorPartial(t *testing.T) { 6562 errored := false 6563 6564 m := testModule(t, "apply-error") 6565 p := testProvider("aws") 6566 6567 state := states.NewState() 6568 root := state.EnsureModule(addrs.RootModuleInstance) 6569 root.SetResourceInstanceCurrent( 6570 mustResourceInstanceAddr("aws_instance.bar").Resource, 6571 &states.ResourceInstanceObjectSrc{ 6572 Status: states.ObjectReady, 6573 AttrsJSON: []byte(`{"id":"bar","type":"aws_instance"}`), 6574 }, 6575 mustProviderConfig(`provider["registry.opentofu.org/hashicorp/aws"]`), 6576 ) 6577 6578 ctx := testContext2(t, &ContextOpts{ 6579 Providers: map[addrs.Provider]providers.Factory{ 6580 addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p), 6581 }, 6582 }) 6583 6584 p.ApplyResourceChangeFn = func(req providers.ApplyResourceChangeRequest) (resp providers.ApplyResourceChangeResponse) { 6585 if errored { 6586 resp.Diagnostics = resp.Diagnostics.Append(fmt.Errorf("error")) 6587 return 6588 } 6589 errored = true 6590 6591 return testApplyFn(req) 6592 } 6593 p.PlanResourceChangeFn = testDiffFn 6594 6595 plan, diags := ctx.Plan(m, state, DefaultPlanOpts) 6596 assertNoErrors(t, diags) 6597 6598 s, diags := ctx.Apply(plan, m) 6599 if diags == nil { 6600 t.Fatal("should have error") 6601 } 6602 6603 mod := s.RootModule() 6604 if len(mod.Resources) != 2 { 6605 t.Fatalf("bad: %#v", mod.Resources) 6606 } 6607 6608 actual := strings.TrimSpace(s.String()) 6609 expected := strings.TrimSpace(testTofuApplyErrorPartialStr) 6610 if actual != expected { 6611 t.Fatalf("expected:\n%s\n\ngot:\n%s", expected, actual) 6612 } 6613 } 6614 6615 func TestContext2Apply_hook(t *testing.T) { 6616 m := testModule(t, "apply-good") 6617 h := new(MockHook) 6618 p := testProvider("aws") 6619 p.PlanResourceChangeFn = testDiffFn 6620 ctx := testContext2(t, &ContextOpts{ 6621 Hooks: []Hook{h}, 6622 Providers: map[addrs.Provider]providers.Factory{ 6623 addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p), 6624 }, 6625 }) 6626 6627 plan, diags := ctx.Plan(m, states.NewState(), DefaultPlanOpts) 6628 assertNoErrors(t, diags) 6629 6630 if _, diags := ctx.Apply(plan, m); diags.HasErrors() { 6631 t.Fatalf("apply errors: %s", diags.Err()) 6632 } 6633 6634 if !h.PreApplyCalled { 6635 t.Fatal("should be called") 6636 } 6637 if !h.PostApplyCalled { 6638 t.Fatal("should be called") 6639 } 6640 if !h.PostStateUpdateCalled { 6641 t.Fatalf("should call post state update") 6642 } 6643 } 6644 6645 func TestContext2Apply_hookOrphan(t *testing.T) { 6646 m := testModule(t, "apply-blank") 6647 h := new(MockHook) 6648 p := testProvider("aws") 6649 p.PlanResourceChangeFn = testDiffFn 6650 6651 state := states.NewState() 6652 root := state.EnsureModule(addrs.RootModuleInstance) 6653 root.SetResourceInstanceCurrent( 6654 mustResourceInstanceAddr("aws_instance.bar").Resource, 6655 &states.ResourceInstanceObjectSrc{ 6656 Status: states.ObjectReady, 6657 AttrsJSON: []byte(`{"id":"bar"}`), 6658 }, 6659 mustProviderConfig(`provider["registry.opentofu.org/hashicorp/aws"]`), 6660 ) 6661 6662 ctx := testContext2(t, &ContextOpts{ 6663 Hooks: []Hook{h}, 6664 Providers: map[addrs.Provider]providers.Factory{ 6665 addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p), 6666 }, 6667 }) 6668 6669 plan, diags := ctx.Plan(m, state, DefaultPlanOpts) 6670 assertNoErrors(t, diags) 6671 6672 if _, diags := ctx.Apply(plan, m); diags.HasErrors() { 6673 t.Fatalf("apply errors: %s", diags.Err()) 6674 } 6675 6676 if !h.PreApplyCalled { 6677 t.Fatal("should be called") 6678 } 6679 if !h.PostApplyCalled { 6680 t.Fatal("should be called") 6681 } 6682 if !h.PostStateUpdateCalled { 6683 t.Fatalf("should call post state update") 6684 } 6685 } 6686 6687 func TestContext2Apply_idAttr(t *testing.T) { 6688 m := testModule(t, "apply-idattr") 6689 p := testProvider("aws") 6690 ctx := testContext2(t, &ContextOpts{ 6691 Providers: map[addrs.Provider]providers.Factory{ 6692 addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p), 6693 }, 6694 }) 6695 6696 p.PlanResourceChangeFn = testDiffFn 6697 p.ApplyResourceChangeFn = testApplyFn 6698 6699 plan, diags := ctx.Plan(m, states.NewState(), DefaultPlanOpts) 6700 assertNoErrors(t, diags) 6701 6702 state, diags := ctx.Apply(plan, m) 6703 if diags.HasErrors() { 6704 t.Fatalf("apply errors: %s", diags.Err()) 6705 } 6706 6707 mod := state.RootModule() 6708 rs, ok := mod.Resources["aws_instance.foo"] 6709 if !ok { 6710 t.Fatal("not in state") 6711 } 6712 var attrs map[string]interface{} 6713 err := json.Unmarshal(rs.Instances[addrs.NoKey].Current.AttrsJSON, &attrs) 6714 if err != nil { 6715 t.Fatal(err) 6716 } 6717 if got, want := attrs["id"], "foo"; got != want { 6718 t.Fatalf("wrong id\ngot: %#v\nwant: %#v", got, want) 6719 } 6720 } 6721 6722 func TestContext2Apply_outputBasic(t *testing.T) { 6723 m := testModule(t, "apply-output") 6724 p := testProvider("aws") 6725 p.PlanResourceChangeFn = testDiffFn 6726 p.ApplyResourceChangeFn = testApplyFn 6727 ctx := testContext2(t, &ContextOpts{ 6728 Providers: map[addrs.Provider]providers.Factory{ 6729 addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p), 6730 }, 6731 }) 6732 6733 plan, diags := ctx.Plan(m, states.NewState(), DefaultPlanOpts) 6734 assertNoErrors(t, diags) 6735 6736 state, diags := ctx.Apply(plan, m) 6737 if diags.HasErrors() { 6738 t.Fatalf("diags: %s", diags.Err()) 6739 } 6740 6741 actual := strings.TrimSpace(state.String()) 6742 expected := strings.TrimSpace(testTofuApplyOutputStr) 6743 if actual != expected { 6744 t.Fatalf("wrong result\n\ngot:\n%s\n\nwant:\n%s", actual, expected) 6745 } 6746 } 6747 6748 func TestContext2Apply_outputAdd(t *testing.T) { 6749 m1 := testModule(t, "apply-output-add-before") 6750 p1 := testProvider("aws") 6751 p1.ApplyResourceChangeFn = testApplyFn 6752 p1.PlanResourceChangeFn = testDiffFn 6753 ctx1 := testContext2(t, &ContextOpts{ 6754 Providers: map[addrs.Provider]providers.Factory{ 6755 addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p1), 6756 }, 6757 }) 6758 6759 plan1, diags := ctx1.Plan(m1, states.NewState(), DefaultPlanOpts) 6760 assertNoErrors(t, diags) 6761 6762 state1, diags := ctx1.Apply(plan1, m1) 6763 if diags.HasErrors() { 6764 t.Fatalf("diags: %s", diags.Err()) 6765 } 6766 6767 m2 := testModule(t, "apply-output-add-after") 6768 p2 := testProvider("aws") 6769 p2.ApplyResourceChangeFn = testApplyFn 6770 p2.PlanResourceChangeFn = testDiffFn 6771 ctx2 := testContext2(t, &ContextOpts{ 6772 Providers: map[addrs.Provider]providers.Factory{ 6773 addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p2), 6774 }, 6775 }) 6776 6777 plan2, diags := ctx1.Plan(m2, state1, DefaultPlanOpts) 6778 assertNoErrors(t, diags) 6779 6780 state2, diags := ctx2.Apply(plan2, m2) 6781 if diags.HasErrors() { 6782 t.Fatalf("diags: %s", diags.Err()) 6783 } 6784 6785 actual := strings.TrimSpace(state2.String()) 6786 expected := strings.TrimSpace(testTofuApplyOutputAddStr) 6787 if actual != expected { 6788 t.Fatalf("wrong result\n\ngot:\n%s\n\nwant:\n%s", actual, expected) 6789 } 6790 } 6791 6792 func TestContext2Apply_outputList(t *testing.T) { 6793 m := testModule(t, "apply-output-list") 6794 p := testProvider("aws") 6795 p.PlanResourceChangeFn = testDiffFn 6796 p.ApplyResourceChangeFn = testApplyFn 6797 ctx := testContext2(t, &ContextOpts{ 6798 Providers: map[addrs.Provider]providers.Factory{ 6799 addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p), 6800 }, 6801 }) 6802 6803 plan, diags := ctx.Plan(m, states.NewState(), DefaultPlanOpts) 6804 assertNoErrors(t, diags) 6805 6806 state, diags := ctx.Apply(plan, m) 6807 if diags.HasErrors() { 6808 t.Fatalf("diags: %s", diags.Err()) 6809 } 6810 6811 actual := strings.TrimSpace(state.String()) 6812 expected := strings.TrimSpace(testTofuApplyOutputListStr) 6813 if actual != expected { 6814 t.Fatalf("expected: \n%s\n\nbad: \n%s", expected, actual) 6815 } 6816 } 6817 6818 func TestContext2Apply_outputMulti(t *testing.T) { 6819 m := testModule(t, "apply-output-multi") 6820 p := testProvider("aws") 6821 p.PlanResourceChangeFn = testDiffFn 6822 p.ApplyResourceChangeFn = testApplyFn 6823 ctx := testContext2(t, &ContextOpts{ 6824 Providers: map[addrs.Provider]providers.Factory{ 6825 addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p), 6826 }, 6827 }) 6828 6829 plan, diags := ctx.Plan(m, states.NewState(), DefaultPlanOpts) 6830 assertNoErrors(t, diags) 6831 6832 state, diags := ctx.Apply(plan, m) 6833 if diags.HasErrors() { 6834 t.Fatalf("diags: %s", diags.Err()) 6835 } 6836 6837 actual := strings.TrimSpace(state.String()) 6838 expected := strings.TrimSpace(testTofuApplyOutputMultiStr) 6839 if actual != expected { 6840 t.Fatalf("wrong result\n\ngot:\n%s\n\nwant:\n%s", actual, expected) 6841 } 6842 } 6843 6844 func TestContext2Apply_outputMultiIndex(t *testing.T) { 6845 m := testModule(t, "apply-output-multi-index") 6846 p := testProvider("aws") 6847 p.PlanResourceChangeFn = testDiffFn 6848 p.ApplyResourceChangeFn = testApplyFn 6849 ctx := testContext2(t, &ContextOpts{ 6850 Providers: map[addrs.Provider]providers.Factory{ 6851 addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p), 6852 }, 6853 }) 6854 6855 plan, diags := ctx.Plan(m, states.NewState(), DefaultPlanOpts) 6856 assertNoErrors(t, diags) 6857 6858 state, diags := ctx.Apply(plan, m) 6859 if diags.HasErrors() { 6860 t.Fatalf("diags: %s", diags.Err()) 6861 } 6862 6863 actual := strings.TrimSpace(state.String()) 6864 expected := strings.TrimSpace(testTofuApplyOutputMultiIndexStr) 6865 if actual != expected { 6866 t.Fatalf("wrong result\n\ngot:\n%s\n\nwant:\n%s", actual, expected) 6867 } 6868 } 6869 6870 func TestContext2Apply_taintX(t *testing.T) { 6871 m := testModule(t, "apply-taint") 6872 p := testProvider("aws") 6873 // destroyCount tests against regression of 6874 // https://github.com/hashicorp/terraform/issues/1056 6875 var destroyCount = int32(0) 6876 var once sync.Once 6877 simulateProviderDelay := func() { 6878 time.Sleep(10 * time.Millisecond) 6879 } 6880 6881 p.ApplyResourceChangeFn = func(req providers.ApplyResourceChangeRequest) providers.ApplyResourceChangeResponse { 6882 once.Do(simulateProviderDelay) 6883 if req.PlannedState.IsNull() { 6884 atomic.AddInt32(&destroyCount, 1) 6885 } 6886 return testApplyFn(req) 6887 } 6888 p.PlanResourceChangeFn = testDiffFn 6889 6890 state := states.NewState() 6891 root := state.EnsureModule(addrs.RootModuleInstance) 6892 root.SetResourceInstanceCurrent( 6893 mustResourceInstanceAddr("aws_instance.bar").Resource, 6894 &states.ResourceInstanceObjectSrc{ 6895 Status: states.ObjectTainted, 6896 AttrsJSON: []byte(`{"id":"baz","num": "2", "type": "aws_instance"}`), 6897 }, 6898 mustProviderConfig(`provider["registry.opentofu.org/hashicorp/aws"]`), 6899 ) 6900 6901 ctx := testContext2(t, &ContextOpts{ 6902 Providers: map[addrs.Provider]providers.Factory{ 6903 addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p), 6904 }, 6905 }) 6906 6907 plan, diags := ctx.Plan(m, state, DefaultPlanOpts) 6908 if diags.HasErrors() { 6909 t.Fatalf("diags: %s", diags.Err()) 6910 } else { 6911 t.Logf("plan: %s", legacyDiffComparisonString(plan.Changes)) 6912 } 6913 6914 s, diags := ctx.Apply(plan, m) 6915 if diags.HasErrors() { 6916 t.Fatalf("diags: %s", diags.Err()) 6917 } 6918 6919 actual := strings.TrimSpace(s.String()) 6920 expected := strings.TrimSpace(testTofuApplyTaintStr) 6921 if actual != expected { 6922 t.Fatalf("bad:\n%s", actual) 6923 } 6924 6925 if destroyCount != 1 { 6926 t.Fatalf("Expected 1 destroy, got %d", destroyCount) 6927 } 6928 } 6929 6930 func TestContext2Apply_taintDep(t *testing.T) { 6931 m := testModule(t, "apply-taint-dep") 6932 p := testProvider("aws") 6933 p.PlanResourceChangeFn = testDiffFn 6934 p.ApplyResourceChangeFn = testApplyFn 6935 6936 state := states.NewState() 6937 root := state.EnsureModule(addrs.RootModuleInstance) 6938 root.SetResourceInstanceCurrent( 6939 mustResourceInstanceAddr("aws_instance.foo").Resource, 6940 &states.ResourceInstanceObjectSrc{ 6941 Status: states.ObjectTainted, 6942 AttrsJSON: []byte(`{"id":"baz","num": "2", "type": "aws_instance"}`), 6943 }, 6944 mustProviderConfig(`provider["registry.opentofu.org/hashicorp/aws"]`), 6945 ) 6946 root.SetResourceInstanceCurrent( 6947 mustResourceInstanceAddr("aws_instance.bar").Resource, 6948 &states.ResourceInstanceObjectSrc{ 6949 Status: states.ObjectReady, 6950 AttrsJSON: []byte(`{"id":"bar","num": "2", "type": "aws_instance", "foo": "baz"}`), 6951 Dependencies: []addrs.ConfigResource{mustConfigResourceAddr("aws_instance.foo")}, 6952 }, 6953 mustProviderConfig(`provider["registry.opentofu.org/hashicorp/aws"]`), 6954 ) 6955 6956 ctx := testContext2(t, &ContextOpts{ 6957 Providers: map[addrs.Provider]providers.Factory{ 6958 addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p), 6959 }, 6960 }) 6961 6962 plan, diags := ctx.Plan(m, state, DefaultPlanOpts) 6963 if diags.HasErrors() { 6964 t.Fatalf("diags: %s", diags.Err()) 6965 } else { 6966 t.Logf("plan: %s", legacyDiffComparisonString(plan.Changes)) 6967 } 6968 6969 s, diags := ctx.Apply(plan, m) 6970 if diags.HasErrors() { 6971 t.Fatalf("diags: %s", diags.Err()) 6972 } 6973 6974 actual := strings.TrimSpace(s.String()) 6975 expected := strings.TrimSpace(testTofuApplyTaintDepStr) 6976 if actual != expected { 6977 t.Fatalf("bad:\n%s", actual) 6978 } 6979 } 6980 6981 func TestContext2Apply_taintDepRequiresNew(t *testing.T) { 6982 m := testModule(t, "apply-taint-dep-requires-new") 6983 p := testProvider("aws") 6984 p.PlanResourceChangeFn = testDiffFn 6985 p.ApplyResourceChangeFn = testApplyFn 6986 6987 state := states.NewState() 6988 root := state.EnsureModule(addrs.RootModuleInstance) 6989 root.SetResourceInstanceCurrent( 6990 mustResourceInstanceAddr("aws_instance.foo").Resource, 6991 &states.ResourceInstanceObjectSrc{ 6992 Status: states.ObjectTainted, 6993 AttrsJSON: []byte(`{"id":"baz","num": "2", "type": "aws_instance"}`), 6994 }, 6995 mustProviderConfig(`provider["registry.opentofu.org/hashicorp/aws"]`), 6996 ) 6997 root.SetResourceInstanceCurrent( 6998 mustResourceInstanceAddr("aws_instance.bar").Resource, 6999 &states.ResourceInstanceObjectSrc{ 7000 Status: states.ObjectReady, 7001 AttrsJSON: []byte(`{"id":"bar","num": "2", "type": "aws_instance", "foo": "baz"}`), 7002 Dependencies: []addrs.ConfigResource{mustConfigResourceAddr("aws_instance.foo")}, 7003 }, 7004 mustProviderConfig(`provider["registry.opentofu.org/hashicorp/aws"]`), 7005 ) 7006 7007 ctx := testContext2(t, &ContextOpts{ 7008 Providers: map[addrs.Provider]providers.Factory{ 7009 addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p), 7010 }, 7011 }) 7012 7013 plan, diags := ctx.Plan(m, state, DefaultPlanOpts) 7014 if diags.HasErrors() { 7015 t.Fatalf("diags: %s", diags.Err()) 7016 } else { 7017 t.Logf("plan: %s", legacyDiffComparisonString(plan.Changes)) 7018 } 7019 7020 s, diags := ctx.Apply(plan, m) 7021 if diags.HasErrors() { 7022 t.Fatalf("diags: %s", diags.Err()) 7023 } 7024 7025 actual := strings.TrimSpace(s.String()) 7026 expected := strings.TrimSpace(testTofuApplyTaintDepRequireNewStr) 7027 if actual != expected { 7028 t.Fatalf("bad:\n%s", actual) 7029 } 7030 } 7031 7032 func TestContext2Apply_targeted(t *testing.T) { 7033 m := testModule(t, "apply-targeted") 7034 p := testProvider("aws") 7035 p.PlanResourceChangeFn = testDiffFn 7036 p.ApplyResourceChangeFn = testApplyFn 7037 ctx := testContext2(t, &ContextOpts{ 7038 Providers: map[addrs.Provider]providers.Factory{ 7039 addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p), 7040 }, 7041 }) 7042 7043 plan, diags := ctx.Plan(m, states.NewState(), &PlanOpts{ 7044 Mode: plans.NormalMode, 7045 Targets: []addrs.Targetable{ 7046 addrs.RootModuleInstance.Resource( 7047 addrs.ManagedResourceMode, "aws_instance", "foo", 7048 ), 7049 }, 7050 }) 7051 assertNoErrors(t, diags) 7052 7053 state, diags := ctx.Apply(plan, m) 7054 if diags.HasErrors() { 7055 t.Fatalf("diags: %s", diags.Err()) 7056 } 7057 7058 mod := state.RootModule() 7059 if len(mod.Resources) != 1 { 7060 t.Fatalf("expected 1 resource, got: %#v", mod.Resources) 7061 } 7062 7063 checkStateString(t, state, ` 7064 aws_instance.foo: 7065 ID = foo 7066 provider = provider["registry.opentofu.org/hashicorp/aws"] 7067 num = 2 7068 type = aws_instance 7069 `) 7070 } 7071 7072 func TestContext2Apply_targetedCount(t *testing.T) { 7073 m := testModule(t, "apply-targeted-count") 7074 p := testProvider("aws") 7075 p.PlanResourceChangeFn = testDiffFn 7076 p.ApplyResourceChangeFn = testApplyFn 7077 ctx := testContext2(t, &ContextOpts{ 7078 Providers: map[addrs.Provider]providers.Factory{ 7079 addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p), 7080 }, 7081 }) 7082 7083 plan, diags := ctx.Plan(m, states.NewState(), &PlanOpts{ 7084 Mode: plans.NormalMode, 7085 Targets: []addrs.Targetable{ 7086 addrs.RootModuleInstance.Resource( 7087 addrs.ManagedResourceMode, "aws_instance", "foo", 7088 ), 7089 }, 7090 }) 7091 assertNoErrors(t, diags) 7092 7093 state, diags := ctx.Apply(plan, m) 7094 if diags.HasErrors() { 7095 t.Fatalf("diags: %s", diags.Err()) 7096 } 7097 7098 checkStateString(t, state, ` 7099 aws_instance.foo.0: 7100 ID = foo 7101 provider = provider["registry.opentofu.org/hashicorp/aws"] 7102 type = aws_instance 7103 aws_instance.foo.1: 7104 ID = foo 7105 provider = provider["registry.opentofu.org/hashicorp/aws"] 7106 type = aws_instance 7107 aws_instance.foo.2: 7108 ID = foo 7109 provider = provider["registry.opentofu.org/hashicorp/aws"] 7110 type = aws_instance 7111 `) 7112 } 7113 7114 func TestContext2Apply_targetedCountIndex(t *testing.T) { 7115 m := testModule(t, "apply-targeted-count") 7116 p := testProvider("aws") 7117 p.PlanResourceChangeFn = testDiffFn 7118 p.ApplyResourceChangeFn = testApplyFn 7119 ctx := testContext2(t, &ContextOpts{ 7120 Providers: map[addrs.Provider]providers.Factory{ 7121 addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p), 7122 }, 7123 }) 7124 7125 plan, diags := ctx.Plan(m, states.NewState(), &PlanOpts{ 7126 Mode: plans.NormalMode, 7127 Targets: []addrs.Targetable{ 7128 addrs.RootModuleInstance.ResourceInstance( 7129 addrs.ManagedResourceMode, "aws_instance", "foo", addrs.IntKey(1), 7130 ), 7131 }, 7132 }) 7133 assertNoErrors(t, diags) 7134 7135 state, diags := ctx.Apply(plan, m) 7136 if diags.HasErrors() { 7137 t.Fatalf("diags: %s", diags.Err()) 7138 } 7139 7140 checkStateString(t, state, ` 7141 aws_instance.foo.1: 7142 ID = foo 7143 provider = provider["registry.opentofu.org/hashicorp/aws"] 7144 type = aws_instance 7145 `) 7146 } 7147 7148 func TestContext2Apply_targetedDestroy(t *testing.T) { 7149 m := testModule(t, "destroy-targeted") 7150 p := testProvider("aws") 7151 p.PlanResourceChangeFn = testDiffFn 7152 7153 state := states.NewState() 7154 root := state.EnsureModule(addrs.RootModuleInstance) 7155 root.SetResourceInstanceCurrent( 7156 mustResourceInstanceAddr("aws_instance.a").Resource, 7157 &states.ResourceInstanceObjectSrc{ 7158 Status: states.ObjectReady, 7159 AttrsJSON: []byte(`{"id":"bar"}`), 7160 }, 7161 mustProviderConfig(`provider["registry.opentofu.org/hashicorp/aws"]`), 7162 ) 7163 root.SetOutputValue("out", cty.StringVal("bar"), false) 7164 7165 child := state.EnsureModule(addrs.RootModuleInstance.Child("child", addrs.NoKey)) 7166 child.SetResourceInstanceCurrent( 7167 mustResourceInstanceAddr("aws_instance.b").Resource, 7168 &states.ResourceInstanceObjectSrc{ 7169 Status: states.ObjectReady, 7170 AttrsJSON: []byte(`{"id":"i-bcd345"}`), 7171 }, 7172 mustProviderConfig(`provider["registry.opentofu.org/hashicorp/aws"]`), 7173 ) 7174 7175 ctx := testContext2(t, &ContextOpts{ 7176 Providers: map[addrs.Provider]providers.Factory{ 7177 addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p), 7178 }, 7179 }) 7180 7181 if diags := ctx.Validate(m); diags.HasErrors() { 7182 t.Fatalf("validate errors: %s", diags.Err()) 7183 } 7184 7185 plan, diags := ctx.Plan(m, state, &PlanOpts{ 7186 Mode: plans.DestroyMode, 7187 Targets: []addrs.Targetable{ 7188 addrs.RootModuleInstance.Resource( 7189 addrs.ManagedResourceMode, "aws_instance", "a", 7190 ), 7191 }, 7192 }) 7193 assertNoErrors(t, diags) 7194 7195 state, diags = ctx.Apply(plan, m) 7196 if diags.HasErrors() { 7197 t.Fatalf("diags: %s", diags.Err()) 7198 } 7199 7200 mod := state.RootModule() 7201 if len(mod.Resources) != 0 { 7202 t.Fatalf("expected 0 resources, got: %#v", mod.Resources) 7203 } 7204 7205 // the root output should not get removed; only the targeted resource. 7206 // 7207 // Note: earlier versions of this test expected 0 outputs, but it turns out 7208 // that was because Validate - not apply or destroy - removed the output 7209 // (which depends on the targeted resource) from state. That version of this 7210 // test did not match actual tofu behavior: the output remains in 7211 // state. 7212 // 7213 // The reason it remains in the state is that we prune out the root module 7214 // output values from the destroy graph as part of pruning out the "update" 7215 // nodes for the resources, because otherwise the root module output values 7216 // force the resources to stay in the graph and can therefore cause 7217 // unwanted dependency cycles. 7218 // 7219 // TODO: Future refactoring may enable us to remove the output from state in 7220 // this case, and that would be Just Fine - this test can be modified to 7221 // expect 0 outputs. 7222 if len(mod.OutputValues) != 1 { 7223 t.Fatalf("expected 1 outputs, got: %#v", mod.OutputValues) 7224 } 7225 7226 // the module instance should remain 7227 mod = state.Module(addrs.RootModuleInstance.Child("child", addrs.NoKey)) 7228 if len(mod.Resources) != 1 { 7229 t.Fatalf("expected 1 resources, got: %#v", mod.Resources) 7230 } 7231 } 7232 7233 func TestContext2Apply_targetedDestroyCountDeps(t *testing.T) { 7234 m := testModule(t, "apply-destroy-targeted-count") 7235 p := testProvider("aws") 7236 p.PlanResourceChangeFn = testDiffFn 7237 7238 state := states.NewState() 7239 root := state.EnsureModule(addrs.RootModuleInstance) 7240 root.SetResourceInstanceCurrent( 7241 mustResourceInstanceAddr("aws_instance.foo").Resource, 7242 &states.ResourceInstanceObjectSrc{ 7243 Status: states.ObjectReady, 7244 AttrsJSON: []byte(`{"id":"i-bcd345"}`), 7245 }, 7246 mustProviderConfig(`provider["registry.opentofu.org/hashicorp/aws"]`), 7247 ) 7248 root.SetResourceInstanceCurrent( 7249 mustResourceInstanceAddr("aws_instance.bar").Resource, 7250 &states.ResourceInstanceObjectSrc{ 7251 Status: states.ObjectReady, 7252 AttrsJSON: []byte(`{"id":"i-abc123"}`), 7253 Dependencies: []addrs.ConfigResource{mustConfigResourceAddr("aws_instance.foo")}, 7254 }, 7255 mustProviderConfig(`provider["registry.opentofu.org/hashicorp/aws"]`), 7256 ) 7257 7258 ctx := testContext2(t, &ContextOpts{ 7259 Providers: map[addrs.Provider]providers.Factory{ 7260 addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p), 7261 }, 7262 }) 7263 7264 plan, diags := ctx.Plan(m, state, &PlanOpts{ 7265 Mode: plans.DestroyMode, 7266 Targets: []addrs.Targetable{ 7267 addrs.RootModuleInstance.Resource( 7268 addrs.ManagedResourceMode, "aws_instance", "foo", 7269 ), 7270 }, 7271 }) 7272 assertNoErrors(t, diags) 7273 7274 state, diags = ctx.Apply(plan, m) 7275 if diags.HasErrors() { 7276 t.Fatalf("diags: %s", diags.Err()) 7277 } 7278 7279 checkStateString(t, state, `<no state>`) 7280 } 7281 7282 // https://github.com/hashicorp/terraform/issues/4462 7283 func TestContext2Apply_targetedDestroyModule(t *testing.T) { 7284 m := testModule(t, "apply-targeted-module") 7285 p := testProvider("aws") 7286 p.PlanResourceChangeFn = testDiffFn 7287 7288 state := states.NewState() 7289 root := state.EnsureModule(addrs.RootModuleInstance) 7290 root.SetResourceInstanceCurrent( 7291 mustResourceInstanceAddr("aws_instance.foo").Resource, 7292 &states.ResourceInstanceObjectSrc{ 7293 Status: states.ObjectReady, 7294 AttrsJSON: []byte(`{"id":"i-bcd345"}`), 7295 }, 7296 mustProviderConfig(`provider["registry.opentofu.org/hashicorp/aws"]`), 7297 ) 7298 root.SetResourceInstanceCurrent( 7299 mustResourceInstanceAddr("aws_instance.bar").Resource, 7300 &states.ResourceInstanceObjectSrc{ 7301 Status: states.ObjectReady, 7302 AttrsJSON: []byte(`{"id":"i-abc123"}`), 7303 }, 7304 mustProviderConfig(`provider["registry.opentofu.org/hashicorp/aws"]`), 7305 ) 7306 child := state.EnsureModule(addrs.RootModuleInstance.Child("child", addrs.NoKey)) 7307 child.SetResourceInstanceCurrent( 7308 mustResourceInstanceAddr("aws_instance.foo").Resource, 7309 &states.ResourceInstanceObjectSrc{ 7310 Status: states.ObjectReady, 7311 AttrsJSON: []byte(`{"id":"i-bcd345"}`), 7312 }, 7313 mustProviderConfig(`provider["registry.opentofu.org/hashicorp/aws"]`), 7314 ) 7315 child.SetResourceInstanceCurrent( 7316 mustResourceInstanceAddr("aws_instance.bar").Resource, 7317 &states.ResourceInstanceObjectSrc{ 7318 Status: states.ObjectReady, 7319 AttrsJSON: []byte(`{"id":"i-abc123"}`), 7320 }, 7321 mustProviderConfig(`provider["registry.opentofu.org/hashicorp/aws"]`), 7322 ) 7323 7324 ctx := testContext2(t, &ContextOpts{ 7325 Providers: map[addrs.Provider]providers.Factory{ 7326 addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p), 7327 }, 7328 }) 7329 7330 plan, diags := ctx.Plan(m, state, &PlanOpts{ 7331 Mode: plans.DestroyMode, 7332 Targets: []addrs.Targetable{ 7333 addrs.RootModuleInstance.Child("child", addrs.NoKey).Resource( 7334 addrs.ManagedResourceMode, "aws_instance", "foo", 7335 ), 7336 }, 7337 }) 7338 assertNoErrors(t, diags) 7339 7340 state, diags = ctx.Apply(plan, m) 7341 if diags.HasErrors() { 7342 t.Fatalf("diags: %s", diags.Err()) 7343 } 7344 7345 checkStateString(t, state, ` 7346 aws_instance.bar: 7347 ID = i-abc123 7348 provider = provider["registry.opentofu.org/hashicorp/aws"] 7349 aws_instance.foo: 7350 ID = i-bcd345 7351 provider = provider["registry.opentofu.org/hashicorp/aws"] 7352 7353 module.child: 7354 aws_instance.bar: 7355 ID = i-abc123 7356 provider = provider["registry.opentofu.org/hashicorp/aws"] 7357 `) 7358 } 7359 7360 func TestContext2Apply_targetedDestroyCountIndex(t *testing.T) { 7361 m := testModule(t, "apply-targeted-count") 7362 p := testProvider("aws") 7363 p.PlanResourceChangeFn = testDiffFn 7364 7365 foo := &states.ResourceInstanceObjectSrc{ 7366 Status: states.ObjectReady, 7367 AttrsJSON: []byte(`{"id":"i-bcd345"}`), 7368 } 7369 bar := &states.ResourceInstanceObjectSrc{ 7370 Status: states.ObjectReady, 7371 AttrsJSON: []byte(`{"id":"i-abc123"}`), 7372 } 7373 7374 state := states.NewState() 7375 root := state.EnsureModule(addrs.RootModuleInstance) 7376 root.SetResourceInstanceCurrent( 7377 mustResourceInstanceAddr("aws_instance.foo[0]").Resource, 7378 foo, 7379 mustProviderConfig(`provider["registry.opentofu.org/hashicorp/aws"]`), 7380 ) 7381 root.SetResourceInstanceCurrent( 7382 mustResourceInstanceAddr("aws_instance.foo[1]").Resource, 7383 foo, 7384 mustProviderConfig(`provider["registry.opentofu.org/hashicorp/aws"]`), 7385 ) 7386 root.SetResourceInstanceCurrent( 7387 mustResourceInstanceAddr("aws_instance.foo[2]").Resource, 7388 foo, 7389 mustProviderConfig(`provider["registry.opentofu.org/hashicorp/aws"]`), 7390 ) 7391 root.SetResourceInstanceCurrent( 7392 mustResourceInstanceAddr("aws_instance.bar[0]").Resource, 7393 bar, 7394 mustProviderConfig(`provider["registry.opentofu.org/hashicorp/aws"]`), 7395 ) 7396 root.SetResourceInstanceCurrent( 7397 mustResourceInstanceAddr("aws_instance.bar[1]").Resource, 7398 bar, 7399 mustProviderConfig(`provider["registry.opentofu.org/hashicorp/aws"]`), 7400 ) 7401 root.SetResourceInstanceCurrent( 7402 mustResourceInstanceAddr("aws_instance.bar[2]").Resource, 7403 bar, 7404 mustProviderConfig(`provider["registry.opentofu.org/hashicorp/aws"]`), 7405 ) 7406 7407 ctx := testContext2(t, &ContextOpts{ 7408 Providers: map[addrs.Provider]providers.Factory{ 7409 addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p), 7410 }, 7411 }) 7412 7413 plan, diags := ctx.Plan(m, state, &PlanOpts{ 7414 Mode: plans.DestroyMode, 7415 Targets: []addrs.Targetable{ 7416 addrs.RootModuleInstance.ResourceInstance( 7417 addrs.ManagedResourceMode, "aws_instance", "foo", addrs.IntKey(2), 7418 ), 7419 addrs.RootModuleInstance.ResourceInstance( 7420 addrs.ManagedResourceMode, "aws_instance", "bar", addrs.IntKey(1), 7421 ), 7422 }, 7423 }) 7424 assertNoErrors(t, diags) 7425 7426 state, diags = ctx.Apply(plan, m) 7427 if diags.HasErrors() { 7428 t.Fatalf("diags: %s", diags.Err()) 7429 } 7430 7431 checkStateString(t, state, ` 7432 aws_instance.bar.0: 7433 ID = i-abc123 7434 provider = provider["registry.opentofu.org/hashicorp/aws"] 7435 aws_instance.bar.2: 7436 ID = i-abc123 7437 provider = provider["registry.opentofu.org/hashicorp/aws"] 7438 aws_instance.foo.0: 7439 ID = i-bcd345 7440 provider = provider["registry.opentofu.org/hashicorp/aws"] 7441 aws_instance.foo.1: 7442 ID = i-bcd345 7443 provider = provider["registry.opentofu.org/hashicorp/aws"] 7444 `) 7445 } 7446 7447 func TestContext2Apply_targetedModule(t *testing.T) { 7448 m := testModule(t, "apply-targeted-module") 7449 p := testProvider("aws") 7450 p.PlanResourceChangeFn = testDiffFn 7451 p.ApplyResourceChangeFn = testApplyFn 7452 ctx := testContext2(t, &ContextOpts{ 7453 Providers: map[addrs.Provider]providers.Factory{ 7454 addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p), 7455 }, 7456 }) 7457 7458 plan, diags := ctx.Plan(m, states.NewState(), &PlanOpts{ 7459 Mode: plans.NormalMode, 7460 Targets: []addrs.Targetable{ 7461 addrs.RootModuleInstance.Child("child", addrs.NoKey), 7462 }, 7463 }) 7464 assertNoErrors(t, diags) 7465 7466 state, diags := ctx.Apply(plan, m) 7467 if diags.HasErrors() { 7468 t.Fatalf("diags: %s", diags.Err()) 7469 } 7470 7471 mod := state.Module(addrs.RootModuleInstance.Child("child", addrs.NoKey)) 7472 if mod == nil { 7473 t.Fatalf("no child module found in the state!\n\n%#v", state) 7474 } 7475 if len(mod.Resources) != 2 { 7476 t.Fatalf("expected 2 resources, got: %#v", mod.Resources) 7477 } 7478 7479 checkStateString(t, state, ` 7480 <no state> 7481 module.child: 7482 aws_instance.bar: 7483 ID = foo 7484 provider = provider["registry.opentofu.org/hashicorp/aws"] 7485 num = 2 7486 type = aws_instance 7487 aws_instance.foo: 7488 ID = foo 7489 provider = provider["registry.opentofu.org/hashicorp/aws"] 7490 num = 2 7491 type = aws_instance 7492 `) 7493 } 7494 7495 // GH-1858 7496 func TestContext2Apply_targetedModuleDep(t *testing.T) { 7497 m := testModule(t, "apply-targeted-module-dep") 7498 p := testProvider("aws") 7499 p.PlanResourceChangeFn = testDiffFn 7500 p.ApplyResourceChangeFn = testApplyFn 7501 ctx := testContext2(t, &ContextOpts{ 7502 Providers: map[addrs.Provider]providers.Factory{ 7503 addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p), 7504 }, 7505 }) 7506 7507 plan, diags := ctx.Plan(m, states.NewState(), &PlanOpts{ 7508 Mode: plans.NormalMode, 7509 Targets: []addrs.Targetable{ 7510 addrs.RootModuleInstance.Resource( 7511 addrs.ManagedResourceMode, "aws_instance", "foo", 7512 ), 7513 }, 7514 }) 7515 if diags.HasErrors() { 7516 t.Fatalf("diags: %s", diags.Err()) 7517 } else { 7518 t.Logf("Diff: %s", legacyDiffComparisonString(plan.Changes)) 7519 } 7520 7521 state, diags := ctx.Apply(plan, m) 7522 if diags.HasErrors() { 7523 t.Fatalf("diags: %s", diags.Err()) 7524 } 7525 7526 checkStateString(t, state, ` 7527 aws_instance.foo: 7528 ID = foo 7529 provider = provider["registry.opentofu.org/hashicorp/aws"] 7530 foo = foo 7531 type = aws_instance 7532 7533 Dependencies: 7534 module.child.aws_instance.mod 7535 7536 module.child: 7537 aws_instance.mod: 7538 ID = foo 7539 provider = provider["registry.opentofu.org/hashicorp/aws"] 7540 type = aws_instance 7541 7542 Outputs: 7543 7544 output = foo 7545 `) 7546 } 7547 7548 // GH-10911 untargeted outputs should not be in the graph, and therefore 7549 // not execute. 7550 func TestContext2Apply_targetedModuleUnrelatedOutputs(t *testing.T) { 7551 m := testModule(t, "apply-targeted-module-unrelated-outputs") 7552 p := testProvider("aws") 7553 p.PlanResourceChangeFn = testDiffFn 7554 p.ApplyResourceChangeFn = testApplyFn 7555 7556 state := states.NewState() 7557 _ = state.EnsureModule(addrs.RootModuleInstance.Child("child2", addrs.NoKey)) 7558 7559 ctx := testContext2(t, &ContextOpts{ 7560 Providers: map[addrs.Provider]providers.Factory{ 7561 addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p), 7562 }, 7563 }) 7564 7565 plan, diags := ctx.Plan(m, state, &PlanOpts{ 7566 Mode: plans.NormalMode, 7567 Targets: []addrs.Targetable{ 7568 addrs.RootModuleInstance.Child("child2", addrs.NoKey), 7569 }, 7570 }) 7571 assertNoErrors(t, diags) 7572 7573 s, diags := ctx.Apply(plan, m) 7574 if diags.HasErrors() { 7575 t.Fatalf("diags: %s", diags.Err()) 7576 } 7577 7578 // - module.child1's instance_id output is dropped because we don't preserve 7579 // non-root module outputs between runs (they can be recalculated from config) 7580 // - module.child2's instance_id is updated because its dependency is updated 7581 // - child2_id is updated because if its transitive dependency via module.child2 7582 checkStateString(t, s, ` 7583 <no state> 7584 Outputs: 7585 7586 child2_id = foo 7587 7588 module.child2: 7589 aws_instance.foo: 7590 ID = foo 7591 provider = provider["registry.opentofu.org/hashicorp/aws"] 7592 type = aws_instance 7593 7594 Outputs: 7595 7596 instance_id = foo 7597 `) 7598 } 7599 7600 func TestContext2Apply_targetedModuleResource(t *testing.T) { 7601 m := testModule(t, "apply-targeted-module-resource") 7602 p := testProvider("aws") 7603 p.PlanResourceChangeFn = testDiffFn 7604 p.ApplyResourceChangeFn = testApplyFn 7605 ctx := testContext2(t, &ContextOpts{ 7606 Providers: map[addrs.Provider]providers.Factory{ 7607 addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p), 7608 }, 7609 }) 7610 7611 plan, diags := ctx.Plan(m, states.NewState(), &PlanOpts{ 7612 Mode: plans.NormalMode, 7613 Targets: []addrs.Targetable{ 7614 addrs.RootModuleInstance.Child("child", addrs.NoKey).Resource( 7615 addrs.ManagedResourceMode, "aws_instance", "foo", 7616 ), 7617 }, 7618 }) 7619 assertNoErrors(t, diags) 7620 7621 state, diags := ctx.Apply(plan, m) 7622 if diags.HasErrors() { 7623 t.Fatalf("diags: %s", diags.Err()) 7624 } 7625 7626 mod := state.Module(addrs.RootModuleInstance.Child("child", addrs.NoKey)) 7627 if mod == nil || len(mod.Resources) != 1 { 7628 t.Fatalf("expected 1 resource, got: %#v", mod) 7629 } 7630 7631 checkStateString(t, state, ` 7632 <no state> 7633 module.child: 7634 aws_instance.foo: 7635 ID = foo 7636 provider = provider["registry.opentofu.org/hashicorp/aws"] 7637 num = 2 7638 type = aws_instance 7639 `) 7640 } 7641 7642 func TestContext2Apply_targetedResourceOrphanModule(t *testing.T) { 7643 m := testModule(t, "apply-targeted-resource-orphan-module") 7644 p := testProvider("aws") 7645 p.PlanResourceChangeFn = testDiffFn 7646 7647 state := states.NewState() 7648 child := state.EnsureModule(addrs.RootModuleInstance.Child("parent", addrs.NoKey)) 7649 child.SetResourceInstanceCurrent( 7650 mustResourceInstanceAddr("aws_instance.bar").Resource, 7651 &states.ResourceInstanceObjectSrc{ 7652 Status: states.ObjectReady, 7653 AttrsJSON: []byte(`{"type":"aws_instance"}`), 7654 }, 7655 mustProviderConfig(`provider["registry.opentofu.org/hashicorp/aws"]`), 7656 ) 7657 7658 ctx := testContext2(t, &ContextOpts{ 7659 Providers: map[addrs.Provider]providers.Factory{ 7660 addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p), 7661 }, 7662 }) 7663 7664 plan, diags := ctx.Plan(m, state, &PlanOpts{ 7665 Mode: plans.NormalMode, 7666 Targets: []addrs.Targetable{ 7667 addrs.RootModuleInstance.Resource( 7668 addrs.ManagedResourceMode, "aws_instance", "foo", 7669 ), 7670 }, 7671 }) 7672 assertNoErrors(t, diags) 7673 7674 if _, diags := ctx.Apply(plan, m); diags.HasErrors() { 7675 t.Fatalf("apply errors: %s", diags.Err()) 7676 } 7677 } 7678 7679 func TestContext2Apply_unknownAttribute(t *testing.T) { 7680 m := testModule(t, "apply-unknown") 7681 p := testProvider("aws") 7682 p.PlanResourceChangeFn = func(req providers.PlanResourceChangeRequest) (resp providers.PlanResourceChangeResponse) { 7683 resp = testDiffFn(req) 7684 planned := resp.PlannedState.AsValueMap() 7685 planned["unknown"] = cty.UnknownVal(cty.String) 7686 resp.PlannedState = cty.ObjectVal(planned) 7687 return resp 7688 } 7689 p.ApplyResourceChangeFn = testApplyFn 7690 7691 p.GetProviderSchemaResponse = getProviderSchemaResponseFromProviderSchema(&ProviderSchema{ 7692 ResourceTypes: map[string]*configschema.Block{ 7693 "aws_instance": { 7694 Attributes: map[string]*configschema.Attribute{ 7695 "id": {Type: cty.String, Computed: true}, 7696 "num": {Type: cty.Number, Optional: true}, 7697 "unknown": {Type: cty.String, Computed: true}, 7698 "type": {Type: cty.String, Computed: true}, 7699 }, 7700 }, 7701 }, 7702 }) 7703 7704 ctx := testContext2(t, &ContextOpts{ 7705 Providers: map[addrs.Provider]providers.Factory{ 7706 addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p), 7707 }, 7708 }) 7709 7710 plan, diags := ctx.Plan(m, states.NewState(), DefaultPlanOpts) 7711 assertNoErrors(t, diags) 7712 7713 state, diags := ctx.Apply(plan, m) 7714 if !diags.HasErrors() { 7715 t.Error("should error, because attribute 'unknown' is still unknown after apply") 7716 } 7717 7718 actual := strings.TrimSpace(state.String()) 7719 expected := strings.TrimSpace(testTofuApplyUnknownAttrStr) 7720 if actual != expected { 7721 t.Fatalf("wrong result\n\ngot:\n%s\n\nwant:\n%s", actual, expected) 7722 } 7723 } 7724 7725 func TestContext2Apply_unknownAttributeInterpolate(t *testing.T) { 7726 m := testModule(t, "apply-unknown-interpolate") 7727 p := testProvider("aws") 7728 p.PlanResourceChangeFn = testDiffFn 7729 ctx := testContext2(t, &ContextOpts{ 7730 Providers: map[addrs.Provider]providers.Factory{ 7731 addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p), 7732 }, 7733 }) 7734 7735 if _, diags := ctx.Plan(m, states.NewState(), DefaultPlanOpts); diags == nil { 7736 t.Fatal("should error") 7737 } 7738 } 7739 7740 func TestContext2Apply_vars(t *testing.T) { 7741 fixture := contextFixtureApplyVars(t) 7742 opts := fixture.ContextOpts() 7743 ctx := testContext2(t, opts) 7744 m := fixture.Config 7745 7746 diags := ctx.Validate(m) 7747 if len(diags) != 0 { 7748 t.Fatalf("bad: %s", diags.ErrWithWarnings()) 7749 } 7750 7751 variables := InputValues{ 7752 "foo": &InputValue{ 7753 Value: cty.StringVal("us-east-1"), 7754 SourceType: ValueFromCaller, 7755 }, 7756 "bar": &InputValue{ 7757 // This one is not explicitly set but that's okay because it 7758 // has a declared default, which OpenTofu Core will use instead. 7759 Value: cty.NilVal, 7760 SourceType: ValueFromCaller, 7761 }, 7762 "test_list": &InputValue{ 7763 Value: cty.ListVal([]cty.Value{ 7764 cty.StringVal("Hello"), 7765 cty.StringVal("World"), 7766 }), 7767 SourceType: ValueFromCaller, 7768 }, 7769 "test_map": &InputValue{ 7770 Value: cty.MapVal(map[string]cty.Value{ 7771 "Hello": cty.StringVal("World"), 7772 "Foo": cty.StringVal("Bar"), 7773 "Baz": cty.StringVal("Foo"), 7774 }), 7775 SourceType: ValueFromCaller, 7776 }, 7777 "amis": &InputValue{ 7778 Value: cty.MapVal(map[string]cty.Value{ 7779 "us-east-1": cty.StringVal("override"), 7780 }), 7781 SourceType: ValueFromCaller, 7782 }, 7783 } 7784 7785 plan, diags := ctx.Plan(m, states.NewState(), &PlanOpts{ 7786 Mode: plans.NormalMode, 7787 SetVariables: variables, 7788 }) 7789 assertNoErrors(t, diags) 7790 7791 state, diags := ctx.Apply(plan, m) 7792 if diags.HasErrors() { 7793 t.Fatalf("err: %s", diags.Err()) 7794 } 7795 7796 got := strings.TrimSpace(state.String()) 7797 want := strings.TrimSpace(testTofuApplyVarsStr) 7798 if got != want { 7799 t.Errorf("wrong result\n\ngot:\n%s\n\nwant:\n%s", got, want) 7800 } 7801 } 7802 7803 func TestContext2Apply_varsEnv(t *testing.T) { 7804 fixture := contextFixtureApplyVarsEnv(t) 7805 opts := fixture.ContextOpts() 7806 ctx := testContext2(t, opts) 7807 m := fixture.Config 7808 7809 diags := ctx.Validate(m) 7810 if len(diags) != 0 { 7811 t.Fatalf("bad: %s", diags.ErrWithWarnings()) 7812 } 7813 7814 variables := InputValues{ 7815 "string": &InputValue{ 7816 Value: cty.StringVal("baz"), 7817 SourceType: ValueFromEnvVar, 7818 }, 7819 "list": &InputValue{ 7820 Value: cty.ListVal([]cty.Value{ 7821 cty.StringVal("Hello"), 7822 cty.StringVal("World"), 7823 }), 7824 SourceType: ValueFromEnvVar, 7825 }, 7826 "map": &InputValue{ 7827 Value: cty.MapVal(map[string]cty.Value{ 7828 "Hello": cty.StringVal("World"), 7829 "Foo": cty.StringVal("Bar"), 7830 "Baz": cty.StringVal("Foo"), 7831 }), 7832 SourceType: ValueFromEnvVar, 7833 }, 7834 } 7835 7836 plan, diags := ctx.Plan(m, states.NewState(), &PlanOpts{ 7837 Mode: plans.NormalMode, 7838 SetVariables: variables, 7839 }) 7840 assertNoErrors(t, diags) 7841 7842 state, diags := ctx.Apply(plan, m) 7843 if diags.HasErrors() { 7844 t.Fatalf("err: %s", diags.Err()) 7845 } 7846 7847 actual := strings.TrimSpace(state.String()) 7848 expected := strings.TrimSpace(testTofuApplyVarsEnvStr) 7849 if actual != expected { 7850 t.Errorf("wrong result\n\ngot:\n%s\n\nwant:\n%s", actual, expected) 7851 } 7852 } 7853 7854 func TestContext2Apply_createBefore_depends(t *testing.T) { 7855 m := testModule(t, "apply-depends-create-before") 7856 h := new(HookRecordApplyOrder) 7857 p := testProvider("aws") 7858 p.PlanResourceChangeFn = testDiffFn 7859 p.ApplyResourceChangeFn = testApplyFn 7860 state := states.NewState() 7861 root := state.EnsureModule(addrs.RootModuleInstance) 7862 root.SetResourceInstanceCurrent( 7863 addrs.Resource{ 7864 Mode: addrs.ManagedResourceMode, 7865 Type: "aws_instance", 7866 Name: "web", 7867 }.Instance(addrs.NoKey), 7868 &states.ResourceInstanceObjectSrc{ 7869 Status: states.ObjectReady, 7870 AttrsJSON: []byte(`{"id":"bar","require_new":"ami-old"}`), 7871 }, 7872 addrs.AbsProviderConfig{ 7873 Provider: addrs.NewDefaultProvider("aws"), 7874 Module: addrs.RootModule, 7875 }, 7876 ) 7877 7878 root.SetResourceInstanceCurrent( 7879 addrs.Resource{ 7880 Mode: addrs.ManagedResourceMode, 7881 Type: "aws_instance", 7882 Name: "lb", 7883 }.Instance(addrs.NoKey), 7884 &states.ResourceInstanceObjectSrc{ 7885 Status: states.ObjectReady, 7886 AttrsJSON: []byte(`{"id":"baz","instance":"bar"}`), 7887 Dependencies: []addrs.ConfigResource{ 7888 { 7889 Resource: addrs.Resource{ 7890 Mode: addrs.ManagedResourceMode, 7891 Type: "aws_instance", 7892 Name: "web", 7893 }, 7894 Module: addrs.RootModule, 7895 }, 7896 }, 7897 }, 7898 addrs.AbsProviderConfig{ 7899 Provider: addrs.NewDefaultProvider("aws"), 7900 Module: addrs.RootModule, 7901 }, 7902 ) 7903 7904 ctx := testContext2(t, &ContextOpts{ 7905 Hooks: []Hook{h}, 7906 Providers: map[addrs.Provider]providers.Factory{ 7907 addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p), 7908 }, 7909 }) 7910 7911 plan, diags := ctx.Plan(m, state, DefaultPlanOpts) 7912 if diags.HasErrors() { 7913 logDiagnostics(t, diags) 7914 t.Fatal("plan failed") 7915 } else { 7916 t.Logf("plan:\n%s", legacyDiffComparisonString(plan.Changes)) 7917 } 7918 7919 h.Active = true 7920 state, diags = ctx.Apply(plan, m) 7921 if diags.HasErrors() { 7922 logDiagnostics(t, diags) 7923 t.Fatal("apply failed") 7924 } 7925 7926 mod := state.RootModule() 7927 if len(mod.Resources) < 2 { 7928 t.Logf("state after apply:\n%s", state.String()) 7929 t.Fatalf("only %d resources in root module; want at least 2", len(mod.Resources)) 7930 } 7931 7932 got := strings.TrimSpace(state.String()) 7933 want := strings.TrimSpace(testTofuApplyDependsCreateBeforeStr) 7934 if got != want { 7935 t.Fatalf("wrong final state\ngot:\n%s\n\nwant:\n%s", got, want) 7936 } 7937 7938 // Test that things were managed _in the right order_ 7939 order := h.States 7940 7941 diffs := h.Diffs 7942 if !order[0].IsNull() || diffs[0].Action == plans.Delete { 7943 t.Fatalf("should create new instance first: %#v", order) 7944 } 7945 7946 if order[1].GetAttr("id").AsString() != "baz" { 7947 t.Fatalf("update must happen after create: %#v", order[1]) 7948 } 7949 7950 if order[2].GetAttr("id").AsString() != "bar" || diffs[2].Action != plans.Delete { 7951 t.Fatalf("destroy must happen after update: %#v", order[2]) 7952 } 7953 } 7954 7955 func TestContext2Apply_singleDestroy(t *testing.T) { 7956 m := testModule(t, "apply-depends-create-before") 7957 h := new(HookRecordApplyOrder) 7958 p := testProvider("aws") 7959 invokeCount := 0 7960 p.ApplyResourceChangeFn = func(req providers.ApplyResourceChangeRequest) providers.ApplyResourceChangeResponse { 7961 invokeCount++ 7962 switch invokeCount { 7963 case 1: 7964 if req.PlannedState.IsNull() { 7965 t.Fatalf("should not destroy") 7966 } 7967 if id := req.PlannedState.GetAttr("id"); id.IsKnown() { 7968 t.Fatalf("should not have ID") 7969 } 7970 case 2: 7971 if req.PlannedState.IsNull() { 7972 t.Fatalf("should not destroy") 7973 } 7974 if id := req.PlannedState.GetAttr("id"); id.AsString() != "baz" { 7975 t.Fatalf("should have id") 7976 } 7977 case 3: 7978 if !req.PlannedState.IsNull() { 7979 t.Fatalf("should destroy") 7980 } 7981 default: 7982 t.Fatalf("bad invoke count %d", invokeCount) 7983 } 7984 return testApplyFn(req) 7985 } 7986 7987 p.PlanResourceChangeFn = testDiffFn 7988 state := states.NewState() 7989 root := state.EnsureModule(addrs.RootModuleInstance) 7990 root.SetResourceInstanceCurrent( 7991 addrs.Resource{ 7992 Mode: addrs.ManagedResourceMode, 7993 Type: "aws_instance", 7994 Name: "web", 7995 }.Instance(addrs.NoKey), 7996 &states.ResourceInstanceObjectSrc{ 7997 Status: states.ObjectReady, 7998 AttrsJSON: []byte(`{"id":"bar","require_new":"ami-old"}`), 7999 }, 8000 addrs.AbsProviderConfig{ 8001 Provider: addrs.NewDefaultProvider("aws"), 8002 Module: addrs.RootModule, 8003 }, 8004 ) 8005 8006 root.SetResourceInstanceCurrent( 8007 addrs.Resource{ 8008 Mode: addrs.ManagedResourceMode, 8009 Type: "aws_instance", 8010 Name: "lb", 8011 }.Instance(addrs.NoKey), 8012 &states.ResourceInstanceObjectSrc{ 8013 Status: states.ObjectReady, 8014 AttrsJSON: []byte(`{"id":"baz","instance":"bar"}`), 8015 Dependencies: []addrs.ConfigResource{ 8016 { 8017 Resource: addrs.Resource{ 8018 Mode: addrs.ManagedResourceMode, 8019 Type: "aws_instance", 8020 Name: "web", 8021 }, 8022 Module: addrs.RootModule, 8023 }, 8024 }, 8025 }, 8026 addrs.AbsProviderConfig{ 8027 Provider: addrs.NewDefaultProvider("aws"), 8028 Module: addrs.RootModule, 8029 }, 8030 ) 8031 8032 ctx := testContext2(t, &ContextOpts{ 8033 Hooks: []Hook{h}, 8034 Providers: map[addrs.Provider]providers.Factory{ 8035 addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p), 8036 }, 8037 }) 8038 8039 plan, diags := ctx.Plan(m, state, DefaultPlanOpts) 8040 assertNoErrors(t, diags) 8041 8042 h.Active = true 8043 _, diags = ctx.Apply(plan, m) 8044 if diags.HasErrors() { 8045 t.Fatalf("diags: %s", diags.Err()) 8046 } 8047 8048 if invokeCount != 3 { 8049 t.Fatalf("bad: %d", invokeCount) 8050 } 8051 } 8052 8053 // GH-7824 8054 func TestContext2Apply_issue7824(t *testing.T) { 8055 p := testProvider("template") 8056 p.PlanResourceChangeFn = testDiffFn 8057 p.GetProviderSchemaResponse = getProviderSchemaResponseFromProviderSchema(&ProviderSchema{ 8058 ResourceTypes: map[string]*configschema.Block{ 8059 "template_file": { 8060 Attributes: map[string]*configschema.Attribute{ 8061 "template": {Type: cty.String, Optional: true}, 8062 "__template_requires_new": {Type: cty.Bool, Optional: true}, 8063 }, 8064 }, 8065 }, 8066 }) 8067 8068 m, snap := testModuleWithSnapshot(t, "issue-7824") 8069 8070 // Apply cleanly step 0 8071 ctx := testContext2(t, &ContextOpts{ 8072 Providers: map[addrs.Provider]providers.Factory{ 8073 addrs.NewDefaultProvider("template"): testProviderFuncFixed(p), 8074 }, 8075 }) 8076 8077 plan, diags := ctx.Plan(m, states.NewState(), SimplePlanOpts(plans.NormalMode, testInputValuesUnset(m.Module.Variables))) 8078 if diags.HasErrors() { 8079 t.Fatalf("err: %s", diags.Err()) 8080 } 8081 8082 // Write / Read plan to simulate running it through a Plan file 8083 ctxOpts, m, plan, err := contextOptsForPlanViaFile(t, snap, plan) 8084 if err != nil { 8085 t.Fatalf("failed to round-trip through planfile: %s", err) 8086 } 8087 8088 ctxOpts.Providers = 8089 map[addrs.Provider]providers.Factory{ 8090 addrs.NewDefaultProvider("template"): testProviderFuncFixed(p), 8091 } 8092 8093 ctx, diags = NewContext(ctxOpts) 8094 if diags.HasErrors() { 8095 t.Fatalf("err: %s", diags.Err()) 8096 } 8097 8098 _, diags = ctx.Apply(plan, m) 8099 if diags.HasErrors() { 8100 t.Fatalf("err: %s", diags.Err()) 8101 } 8102 } 8103 8104 // This deals with the situation where a splat expression is used referring 8105 // to another resource whose count is non-constant. 8106 func TestContext2Apply_issue5254(t *testing.T) { 8107 // Create a provider. We use "template" here just to match the repro 8108 // we got from the issue itself. 8109 p := testProvider("template") 8110 p.PlanResourceChangeFn = testDiffFn 8111 p.ApplyResourceChangeFn = testApplyFn 8112 p.GetProviderSchemaResponse = getProviderSchemaResponseFromProviderSchema(&ProviderSchema{ 8113 ResourceTypes: map[string]*configschema.Block{ 8114 "template_file": { 8115 Attributes: map[string]*configschema.Attribute{ 8116 "template": {Type: cty.String, Optional: true}, 8117 "__template_requires_new": {Type: cty.Bool, Optional: true}, 8118 "id": {Type: cty.String, Computed: true}, 8119 "type": {Type: cty.String, Computed: true}, 8120 }, 8121 }, 8122 }, 8123 }) 8124 8125 // Apply cleanly step 0 8126 m := testModule(t, "issue-5254/step-0") 8127 ctx := testContext2(t, &ContextOpts{ 8128 Providers: map[addrs.Provider]providers.Factory{ 8129 addrs.NewDefaultProvider("template"): testProviderFuncFixed(p), 8130 }, 8131 }) 8132 8133 plan, diags := ctx.Plan(m, states.NewState(), SimplePlanOpts(plans.NormalMode, testInputValuesUnset(m.Module.Variables))) 8134 if diags.HasErrors() { 8135 t.Fatalf("err: %s", diags.Err()) 8136 } 8137 8138 state, diags := ctx.Apply(plan, m) 8139 if diags.HasErrors() { 8140 t.Fatalf("err: %s", diags.Err()) 8141 } 8142 8143 m, snap := testModuleWithSnapshot(t, "issue-5254/step-1") 8144 8145 // Application success. Now make the modification and store a plan 8146 ctx = testContext2(t, &ContextOpts{ 8147 Providers: map[addrs.Provider]providers.Factory{ 8148 addrs.NewDefaultProvider("template"): testProviderFuncFixed(p), 8149 }, 8150 }) 8151 8152 plan, diags = ctx.Plan(m, state, SimplePlanOpts(plans.NormalMode, testInputValuesUnset(m.Module.Variables))) 8153 if diags.HasErrors() { 8154 t.Fatalf("err: %s", diags.Err()) 8155 } 8156 8157 // Write / Read plan to simulate running it through a Plan file 8158 ctxOpts, m, plan, err := contextOptsForPlanViaFile(t, snap, plan) 8159 if err != nil { 8160 t.Fatalf("failed to round-trip through planfile: %s", err) 8161 } 8162 8163 ctxOpts.Providers = map[addrs.Provider]providers.Factory{ 8164 addrs.NewDefaultProvider("template"): testProviderFuncFixed(p), 8165 } 8166 8167 ctx, diags = NewContext(ctxOpts) 8168 if diags.HasErrors() { 8169 t.Fatalf("err: %s", diags.Err()) 8170 } 8171 8172 state, diags = ctx.Apply(plan, m) 8173 if diags.HasErrors() { 8174 t.Fatalf("err: %s", diags.Err()) 8175 } 8176 8177 actual := strings.TrimSpace(state.String()) 8178 expected := strings.TrimSpace(` 8179 template_file.child: 8180 ID = foo 8181 provider = provider["registry.opentofu.org/hashicorp/template"] 8182 __template_requires_new = true 8183 template = Hi 8184 type = template_file 8185 8186 Dependencies: 8187 template_file.parent 8188 template_file.parent.0: 8189 ID = foo 8190 provider = provider["registry.opentofu.org/hashicorp/template"] 8191 template = Hi 8192 type = template_file 8193 `) 8194 if actual != expected { 8195 t.Fatalf("wrong final state\ngot:\n%s\n\nwant:\n%s", actual, expected) 8196 } 8197 } 8198 8199 func TestContext2Apply_targetedWithTaintedInState(t *testing.T) { 8200 p := testProvider("aws") 8201 p.PlanResourceChangeFn = testDiffFn 8202 p.ApplyResourceChangeFn = testApplyFn 8203 m, snap := testModuleWithSnapshot(t, "apply-tainted-targets") 8204 8205 state := states.NewState() 8206 root := state.EnsureModule(addrs.RootModuleInstance) 8207 root.SetResourceInstanceCurrent( 8208 mustResourceInstanceAddr("aws_instance.ifailedprovisioners").Resource, 8209 &states.ResourceInstanceObjectSrc{ 8210 Status: states.ObjectTainted, 8211 AttrsJSON: []byte(`{"id":"ifailedprovisioners"}`), 8212 }, 8213 mustProviderConfig(`provider["registry.opentofu.org/hashicorp/aws"]`), 8214 ) 8215 8216 ctx := testContext2(t, &ContextOpts{ 8217 Providers: map[addrs.Provider]providers.Factory{ 8218 addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p), 8219 }, 8220 }) 8221 8222 plan, diags := ctx.Plan(m, state, &PlanOpts{ 8223 Mode: plans.NormalMode, 8224 Targets: []addrs.Targetable{ 8225 addrs.RootModuleInstance.Resource( 8226 addrs.ManagedResourceMode, "aws_instance", "iambeingadded", 8227 ), 8228 }, 8229 }) 8230 if diags.HasErrors() { 8231 t.Fatalf("err: %s", diags.Err()) 8232 } 8233 8234 // Write / Read plan to simulate running it through a Plan file 8235 ctxOpts, m, plan, err := contextOptsForPlanViaFile(t, snap, plan) 8236 if err != nil { 8237 t.Fatalf("failed to round-trip through planfile: %s", err) 8238 } 8239 8240 ctxOpts.Providers = map[addrs.Provider]providers.Factory{ 8241 addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p), 8242 } 8243 8244 ctx, diags = NewContext(ctxOpts) 8245 if diags.HasErrors() { 8246 t.Fatalf("err: %s", diags.Err()) 8247 } 8248 8249 s, diags := ctx.Apply(plan, m) 8250 if diags.HasErrors() { 8251 t.Fatalf("err: %s", diags.Err()) 8252 } 8253 8254 actual := strings.TrimSpace(s.String()) 8255 expected := strings.TrimSpace(` 8256 aws_instance.iambeingadded: 8257 ID = foo 8258 provider = provider["registry.opentofu.org/hashicorp/aws"] 8259 type = aws_instance 8260 aws_instance.ifailedprovisioners: (tainted) 8261 ID = ifailedprovisioners 8262 provider = provider["registry.opentofu.org/hashicorp/aws"] 8263 `) 8264 if actual != expected { 8265 t.Fatalf("expected state: \n%s\ngot: \n%s", expected, actual) 8266 } 8267 } 8268 8269 // Higher level test exposing the bug this covers in 8270 // TestResource_ignoreChangesRequired 8271 func TestContext2Apply_ignoreChangesCreate(t *testing.T) { 8272 m := testModule(t, "apply-ignore-changes-create") 8273 p := testProvider("aws") 8274 p.PlanResourceChangeFn = testDiffFn 8275 p.ApplyResourceChangeFn = testApplyFn 8276 8277 instanceSchema := p.GetProviderSchemaResponse.ResourceTypes["aws_instance"].Block 8278 instanceSchema.Attributes["required_field"] = &configschema.Attribute{ 8279 Type: cty.String, 8280 Required: true, 8281 } 8282 8283 ctx := testContext2(t, &ContextOpts{ 8284 Providers: map[addrs.Provider]providers.Factory{ 8285 addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p), 8286 }, 8287 }) 8288 8289 plan, diags := ctx.Plan(m, states.NewState(), DefaultPlanOpts) 8290 if diags.HasErrors() { 8291 t.Fatalf("diags: %s", diags.Err()) 8292 } else { 8293 t.Logf(legacyDiffComparisonString(plan.Changes)) 8294 } 8295 8296 state, diags := ctx.Apply(plan, m) 8297 if diags.HasErrors() { 8298 t.Fatalf("diags: %s", diags.Err()) 8299 } 8300 8301 mod := state.RootModule() 8302 if len(mod.Resources) != 1 { 8303 t.Fatalf("bad: %s", state) 8304 } 8305 8306 actual := strings.TrimSpace(state.String()) 8307 // Expect no changes from original state 8308 expected := strings.TrimSpace(` 8309 aws_instance.foo: 8310 ID = foo 8311 provider = provider["registry.opentofu.org/hashicorp/aws"] 8312 required_field = set 8313 type = aws_instance 8314 `) 8315 if actual != expected { 8316 t.Fatalf("expected:\n%s\ngot:\n%s", expected, actual) 8317 } 8318 } 8319 8320 func TestContext2Apply_ignoreChangesWithDep(t *testing.T) { 8321 m := testModule(t, "apply-ignore-changes-dep") 8322 p := testProvider("aws") 8323 8324 p.PlanResourceChangeFn = func(req providers.PlanResourceChangeRequest) (resp providers.PlanResourceChangeResponse) { 8325 resp.PlannedState = req.ProposedNewState 8326 8327 switch req.TypeName { 8328 case "aws_instance": 8329 resp.RequiresReplace = append(resp.RequiresReplace, cty.Path{cty.GetAttrStep{Name: "ami"}}) 8330 case "aws_eip": 8331 return testDiffFn(req) 8332 default: 8333 t.Fatalf("Unexpected type: %s", req.TypeName) 8334 } 8335 return 8336 } 8337 8338 state := states.NewState() 8339 root := state.EnsureModule(addrs.RootModuleInstance) 8340 root.SetResourceInstanceCurrent( 8341 mustResourceInstanceAddr("aws_instance.foo[0]").Resource, 8342 &states.ResourceInstanceObjectSrc{ 8343 Status: states.ObjectReady, 8344 AttrsJSON: []byte(`{"id":"i-abc123","ami":"ami-abcd1234"}`), 8345 }, 8346 mustProviderConfig(`provider["registry.opentofu.org/hashicorp/aws"]`), 8347 ) 8348 root.SetResourceInstanceCurrent( 8349 mustResourceInstanceAddr("aws_instance.foo[1]").Resource, 8350 &states.ResourceInstanceObjectSrc{ 8351 Status: states.ObjectReady, 8352 AttrsJSON: []byte(`{"id":"i-bcd234","ami":"i-bcd234"}`), 8353 }, 8354 mustProviderConfig(`provider["registry.opentofu.org/hashicorp/aws"]`), 8355 ) 8356 root.SetResourceInstanceCurrent( 8357 mustResourceInstanceAddr("aws_eip.foo[0]").Resource, 8358 &states.ResourceInstanceObjectSrc{ 8359 Status: states.ObjectReady, 8360 AttrsJSON: []byte(`{"id":"eip-abc123","instance":"i-abc123"}`), 8361 Dependencies: []addrs.ConfigResource{ 8362 { 8363 Resource: addrs.Resource{ 8364 Mode: addrs.ManagedResourceMode, 8365 Type: "aws_instance", 8366 Name: "foo", 8367 }, 8368 Module: addrs.RootModule, 8369 }, 8370 }, 8371 }, 8372 mustProviderConfig(`provider["registry.opentofu.org/hashicorp/aws"]`), 8373 ) 8374 root.SetResourceInstanceCurrent( 8375 mustResourceInstanceAddr("aws_eip.foo[1]").Resource, 8376 &states.ResourceInstanceObjectSrc{ 8377 Status: states.ObjectReady, 8378 AttrsJSON: []byte(`{"id":"eip-bcd234","instance":"i-bcd234"}`), 8379 Dependencies: []addrs.ConfigResource{ 8380 { 8381 Resource: addrs.Resource{ 8382 Mode: addrs.ManagedResourceMode, 8383 Type: "aws_instance", 8384 Name: "foo", 8385 }, 8386 Module: addrs.RootModule, 8387 }, 8388 }, 8389 }, 8390 mustProviderConfig(`provider["registry.opentofu.org/hashicorp/aws"]`), 8391 ) 8392 8393 ctx := testContext2(t, &ContextOpts{ 8394 Providers: map[addrs.Provider]providers.Factory{ 8395 addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p), 8396 }, 8397 }) 8398 8399 plan, diags := ctx.Plan(m, state.DeepCopy(), DefaultPlanOpts) 8400 assertNoErrors(t, diags) 8401 8402 s, diags := ctx.Apply(plan, m) 8403 assertNoErrors(t, diags) 8404 8405 actual := strings.TrimSpace(s.String()) 8406 expected := strings.TrimSpace(state.String()) 8407 if actual != expected { 8408 t.Fatalf("expected:\n%s\n\ngot:\n%s", expected, actual) 8409 } 8410 } 8411 8412 func TestContext2Apply_ignoreChangesAll(t *testing.T) { 8413 m := testModule(t, "apply-ignore-changes-all") 8414 p := testProvider("aws") 8415 p.PlanResourceChangeFn = testDiffFn 8416 p.ApplyResourceChangeFn = testApplyFn 8417 8418 instanceSchema := p.GetProviderSchemaResponse.ResourceTypes["aws_instance"].Block 8419 instanceSchema.Attributes["required_field"] = &configschema.Attribute{ 8420 Type: cty.String, 8421 Required: true, 8422 } 8423 8424 ctx := testContext2(t, &ContextOpts{ 8425 Providers: map[addrs.Provider]providers.Factory{ 8426 addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p), 8427 }, 8428 }) 8429 8430 plan, diags := ctx.Plan(m, states.NewState(), DefaultPlanOpts) 8431 if diags.HasErrors() { 8432 logDiagnostics(t, diags) 8433 t.Fatal("plan failed") 8434 } else { 8435 t.Logf(legacyDiffComparisonString(plan.Changes)) 8436 } 8437 8438 state, diags := ctx.Apply(plan, m) 8439 assertNoErrors(t, diags) 8440 8441 mod := state.RootModule() 8442 if len(mod.Resources) != 1 { 8443 t.Fatalf("bad: %s", state) 8444 } 8445 8446 actual := strings.TrimSpace(state.String()) 8447 // Expect no changes from original state 8448 expected := strings.TrimSpace(` 8449 aws_instance.foo: 8450 ID = foo 8451 provider = provider["registry.opentofu.org/hashicorp/aws"] 8452 required_field = set 8453 type = aws_instance 8454 `) 8455 if actual != expected { 8456 t.Fatalf("expected:\n%s\ngot:\n%s", expected, actual) 8457 } 8458 } 8459 8460 // https://github.com/hashicorp/terraform/issues/7378 8461 func TestContext2Apply_destroyNestedModuleWithAttrsReferencingResource(t *testing.T) { 8462 m, snap := testModuleWithSnapshot(t, "apply-destroy-nested-module-with-attrs") 8463 p := testProvider("null") 8464 p.PlanResourceChangeFn = testDiffFn 8465 8466 var state *states.State 8467 { 8468 ctx := testContext2(t, &ContextOpts{ 8469 Providers: map[addrs.Provider]providers.Factory{ 8470 addrs.NewDefaultProvider("null"): testProviderFuncFixed(p), 8471 }, 8472 }) 8473 8474 // First plan and apply a create operation 8475 plan, diags := ctx.Plan(m, states.NewState(), DefaultPlanOpts) 8476 assertNoErrors(t, diags) 8477 8478 state, diags = ctx.Apply(plan, m) 8479 if diags.HasErrors() { 8480 t.Fatalf("apply err: %s", diags.Err()) 8481 } 8482 } 8483 8484 { 8485 ctx := testContext2(t, &ContextOpts{ 8486 Providers: map[addrs.Provider]providers.Factory{ 8487 addrs.NewDefaultProvider("null"): testProviderFuncFixed(p), 8488 }, 8489 }) 8490 8491 plan, diags := ctx.Plan(m, state, &PlanOpts{ 8492 Mode: plans.DestroyMode, 8493 }) 8494 if diags.HasErrors() { 8495 t.Fatalf("destroy plan err: %s", diags.Err()) 8496 } 8497 8498 ctxOpts, m, plan, err := contextOptsForPlanViaFile(t, snap, plan) 8499 if err != nil { 8500 t.Fatalf("failed to round-trip through planfile: %s", err) 8501 } 8502 8503 ctxOpts.Providers = map[addrs.Provider]providers.Factory{ 8504 addrs.NewDefaultProvider("null"): testProviderFuncFixed(p), 8505 } 8506 8507 ctx, diags = NewContext(ctxOpts) 8508 if diags.HasErrors() { 8509 t.Fatalf("err: %s", diags.Err()) 8510 } 8511 8512 state, diags = ctx.Apply(plan, m) 8513 if diags.HasErrors() { 8514 t.Fatalf("destroy apply err: %s", diags.Err()) 8515 } 8516 } 8517 8518 if !state.Empty() { 8519 t.Fatalf("state after apply: %s\nwant empty state", spew.Sdump(state)) 8520 } 8521 } 8522 8523 // If a data source explicitly depends on another resource, it's because we need 8524 // that resource to be applied first. 8525 func TestContext2Apply_dataDependsOn(t *testing.T) { 8526 p := testProvider("null") 8527 m := testModuleInline(t, map[string]string{ 8528 "main.tf": ` 8529 resource "null_instance" "write" { 8530 foo = "attribute" 8531 } 8532 8533 data "null_data_source" "read" { 8534 count = 1 8535 depends_on = ["null_instance.write"] 8536 } 8537 8538 resource "null_instance" "depends" { 8539 foo = data.null_data_source.read[0].foo 8540 } 8541 `}) 8542 8543 ctx := testContext2(t, &ContextOpts{ 8544 Providers: map[addrs.Provider]providers.Factory{ 8545 addrs.NewDefaultProvider("null"): testProviderFuncFixed(p), 8546 }, 8547 }) 8548 8549 // the "provisioner" here writes to this variable, because the intent is to 8550 // create a dependency which can't be viewed through the graph, and depends 8551 // solely on the configuration providing "depends_on" 8552 provisionerOutput := "" 8553 8554 p.ApplyResourceChangeFn = func(req providers.ApplyResourceChangeRequest) providers.ApplyResourceChangeResponse { 8555 // the side effect of the resource being applied 8556 provisionerOutput = "APPLIED" 8557 return testApplyFn(req) 8558 } 8559 8560 p.PlanResourceChangeFn = testDiffFn 8561 p.ReadDataSourceFn = func(req providers.ReadDataSourceRequest) providers.ReadDataSourceResponse { 8562 return providers.ReadDataSourceResponse{ 8563 State: cty.ObjectVal(map[string]cty.Value{ 8564 "id": cty.StringVal("boop"), 8565 "foo": cty.StringVal(provisionerOutput), 8566 }), 8567 } 8568 } 8569 8570 plan, diags := ctx.Plan(m, states.NewState(), DefaultPlanOpts) 8571 assertNoErrors(t, diags) 8572 8573 state, diags := ctx.Apply(plan, m) 8574 assertNoErrors(t, diags) 8575 8576 root := state.Module(addrs.RootModuleInstance) 8577 is := root.ResourceInstance(addrs.Resource{ 8578 Mode: addrs.DataResourceMode, 8579 Type: "null_data_source", 8580 Name: "read", 8581 }.Instance(addrs.IntKey(0))) 8582 if is == nil { 8583 t.Fatal("data resource instance is not present in state; should be") 8584 } 8585 var attrs map[string]interface{} 8586 err := json.Unmarshal(is.Current.AttrsJSON, &attrs) 8587 if err != nil { 8588 t.Fatal(err) 8589 } 8590 actual := attrs["foo"] 8591 expected := "APPLIED" 8592 if actual != expected { 8593 t.Fatalf("bad:\n%s", strings.TrimSpace(state.String())) 8594 } 8595 8596 // run another plan to make sure the data source doesn't show as a change 8597 plan, diags = ctx.Plan(m, state, DefaultPlanOpts) 8598 assertNoErrors(t, diags) 8599 8600 for _, c := range plan.Changes.Resources { 8601 if c.Action != plans.NoOp { 8602 t.Fatalf("unexpected change for %s", c.Addr) 8603 } 8604 } 8605 8606 // now we cause a change in the first resource, which should trigger a plan 8607 // in the data source, and the resource that depends on the data source 8608 // must plan a change as well. 8609 m = testModuleInline(t, map[string]string{ 8610 "main.tf": ` 8611 resource "null_instance" "write" { 8612 foo = "new" 8613 } 8614 8615 data "null_data_source" "read" { 8616 depends_on = ["null_instance.write"] 8617 } 8618 8619 resource "null_instance" "depends" { 8620 foo = data.null_data_source.read.foo 8621 } 8622 `}) 8623 8624 p.ApplyResourceChangeFn = func(req providers.ApplyResourceChangeRequest) providers.ApplyResourceChangeResponse { 8625 // the side effect of the resource being applied 8626 provisionerOutput = "APPLIED_AGAIN" 8627 return testApplyFn(req) 8628 } 8629 8630 ctx = testContext2(t, &ContextOpts{ 8631 Providers: map[addrs.Provider]providers.Factory{ 8632 addrs.NewDefaultProvider("null"): testProviderFuncFixed(p), 8633 }, 8634 }) 8635 8636 plan, diags = ctx.Plan(m, state, DefaultPlanOpts) 8637 assertNoErrors(t, diags) 8638 8639 expectedChanges := map[string]plans.Action{ 8640 "null_instance.write": plans.Update, 8641 "data.null_data_source.read": plans.Read, 8642 "null_instance.depends": plans.Update, 8643 } 8644 8645 for _, c := range plan.Changes.Resources { 8646 if c.Action != expectedChanges[c.Addr.String()] { 8647 t.Errorf("unexpected %s for %s", c.Action, c.Addr) 8648 } 8649 } 8650 } 8651 8652 func TestContext2Apply_terraformWorkspace(t *testing.T) { 8653 m := testModule(t, "apply-tofu-workspace") 8654 p := testProvider("aws") 8655 p.PlanResourceChangeFn = testDiffFn 8656 8657 ctx := testContext2(t, &ContextOpts{ 8658 Meta: &ContextMeta{Env: "foo"}, 8659 Providers: map[addrs.Provider]providers.Factory{ 8660 addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p), 8661 }, 8662 }) 8663 8664 plan, diags := ctx.Plan(m, states.NewState(), DefaultPlanOpts) 8665 assertNoErrors(t, diags) 8666 8667 state, diags := ctx.Apply(plan, m) 8668 if diags.HasErrors() { 8669 t.Fatalf("diags: %s", diags.Err()) 8670 } 8671 8672 actual := state.RootModule().OutputValues["output"] 8673 expected := cty.StringVal("foo") 8674 if actual == nil || actual.Value != expected { 8675 t.Fatalf("wrong value\ngot: %#v\nwant: %#v", actual.Value, expected) 8676 } 8677 } 8678 8679 // verify that multiple config references only create a single depends_on entry 8680 func TestContext2Apply_multiRef(t *testing.T) { 8681 m := testModule(t, "apply-multi-ref") 8682 p := testProvider("aws") 8683 p.PlanResourceChangeFn = testDiffFn 8684 ctx := testContext2(t, &ContextOpts{ 8685 Providers: map[addrs.Provider]providers.Factory{ 8686 addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p), 8687 }, 8688 }) 8689 8690 plan, diags := ctx.Plan(m, states.NewState(), DefaultPlanOpts) 8691 assertNoErrors(t, diags) 8692 8693 state, diags := ctx.Apply(plan, m) 8694 if diags.HasErrors() { 8695 t.Fatalf("err: %s", diags.Err()) 8696 } 8697 8698 deps := state.Modules[""].Resources["aws_instance.other"].Instances[addrs.NoKey].Current.Dependencies 8699 if len(deps) != 1 || deps[0].String() != "aws_instance.create" { 8700 t.Fatalf("expected 1 depends_on entry for aws_instance.create, got %q", deps) 8701 } 8702 } 8703 8704 func TestContext2Apply_targetedModuleRecursive(t *testing.T) { 8705 m := testModule(t, "apply-targeted-module-recursive") 8706 p := testProvider("aws") 8707 p.PlanResourceChangeFn = testDiffFn 8708 p.ApplyResourceChangeFn = testApplyFn 8709 ctx := testContext2(t, &ContextOpts{ 8710 Providers: map[addrs.Provider]providers.Factory{ 8711 addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p), 8712 }, 8713 }) 8714 8715 plan, diags := ctx.Plan(m, states.NewState(), &PlanOpts{ 8716 Mode: plans.NormalMode, 8717 Targets: []addrs.Targetable{ 8718 addrs.RootModuleInstance.Child("child", addrs.NoKey), 8719 }, 8720 }) 8721 assertNoErrors(t, diags) 8722 8723 state, diags := ctx.Apply(plan, m) 8724 if diags.HasErrors() { 8725 t.Fatalf("err: %s", diags.Err()) 8726 } 8727 8728 mod := state.Module( 8729 addrs.RootModuleInstance.Child("child", addrs.NoKey).Child("subchild", addrs.NoKey), 8730 ) 8731 if mod == nil { 8732 t.Fatalf("no subchild module found in the state!\n\n%#v", state) 8733 } 8734 if len(mod.Resources) != 1 { 8735 t.Fatalf("expected 1 resources, got: %#v", mod.Resources) 8736 } 8737 8738 checkStateString(t, state, ` 8739 <no state> 8740 module.child.subchild: 8741 aws_instance.foo: 8742 ID = foo 8743 provider = provider["registry.opentofu.org/hashicorp/aws"] 8744 num = 2 8745 type = aws_instance 8746 `) 8747 } 8748 8749 func TestContext2Apply_localVal(t *testing.T) { 8750 m := testModule(t, "apply-local-val") 8751 ctx := testContext2(t, &ContextOpts{ 8752 Providers: map[addrs.Provider]providers.Factory{}, 8753 }) 8754 8755 plan, diags := ctx.Plan(m, states.NewState(), DefaultPlanOpts) 8756 assertNoErrors(t, diags) 8757 8758 state, diags := ctx.Apply(plan, m) 8759 if diags.HasErrors() { 8760 t.Fatalf("error during apply: %s", diags.Err()) 8761 } 8762 8763 got := strings.TrimSpace(state.String()) 8764 want := strings.TrimSpace(` 8765 <no state> 8766 Outputs: 8767 8768 result_1 = hello 8769 result_3 = hello world 8770 `) 8771 if got != want { 8772 t.Fatalf("wrong final state\ngot:\n%s\nwant:\n%s", got, want) 8773 } 8774 } 8775 8776 func TestContext2Apply_destroyWithLocals(t *testing.T) { 8777 m := testModule(t, "apply-destroy-with-locals") 8778 p := testProvider("aws") 8779 p.PlanResourceChangeFn = testDiffFn 8780 8781 state := states.NewState() 8782 root := state.EnsureModule(addrs.RootModuleInstance) 8783 root.SetResourceInstanceCurrent( 8784 mustResourceInstanceAddr("aws_instance.foo").Resource, 8785 &states.ResourceInstanceObjectSrc{ 8786 Status: states.ObjectReady, 8787 AttrsJSON: []byte(`{"id":"foo"}`), 8788 }, 8789 mustProviderConfig(`provider["registry.opentofu.org/hashicorp/aws"]`), 8790 ) 8791 root.SetOutputValue("name", cty.StringVal("test-bar"), false) 8792 8793 ctx := testContext2(t, &ContextOpts{ 8794 Providers: map[addrs.Provider]providers.Factory{ 8795 addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p), 8796 }, 8797 }) 8798 8799 plan, diags := ctx.Plan(m, state, &PlanOpts{ 8800 Mode: plans.DestroyMode, 8801 }) 8802 assertNoErrors(t, diags) 8803 8804 s, diags := ctx.Apply(plan, m) 8805 if diags.HasErrors() { 8806 t.Fatalf("error during apply: %s", diags.Err()) 8807 } 8808 8809 got := strings.TrimSpace(s.String()) 8810 want := strings.TrimSpace(`<no state>`) 8811 if got != want { 8812 t.Fatalf("wrong final state\ngot:\n%s\nwant:\n%s", got, want) 8813 } 8814 } 8815 8816 func TestContext2Apply_providerWithLocals(t *testing.T) { 8817 m := testModule(t, "provider-with-locals") 8818 p := testProvider("aws") 8819 8820 providerRegion := "" 8821 // this should not be overridden during destroy 8822 p.ConfigureProviderFn = func(req providers.ConfigureProviderRequest) (resp providers.ConfigureProviderResponse) { 8823 val := req.Config.GetAttr("region") 8824 if !val.IsNull() { 8825 providerRegion = val.AsString() 8826 } 8827 8828 return 8829 } 8830 8831 p.PlanResourceChangeFn = testDiffFn 8832 ctx := testContext2(t, &ContextOpts{ 8833 Providers: map[addrs.Provider]providers.Factory{ 8834 addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p), 8835 }, 8836 }) 8837 8838 plan, diags := ctx.Plan(m, states.NewState(), DefaultPlanOpts) 8839 assertNoErrors(t, diags) 8840 8841 state, diags := ctx.Apply(plan, m) 8842 if diags.HasErrors() { 8843 t.Fatalf("err: %s", diags.Err()) 8844 } 8845 8846 ctx = testContext2(t, &ContextOpts{ 8847 Providers: map[addrs.Provider]providers.Factory{ 8848 addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p), 8849 }, 8850 }) 8851 8852 plan, diags = ctx.Plan(m, state, &PlanOpts{ 8853 Mode: plans.DestroyMode, 8854 }) 8855 assertNoErrors(t, diags) 8856 8857 state, diags = ctx.Apply(plan, m) 8858 if diags.HasErrors() { 8859 t.Fatalf("err: %s", diags.Err()) 8860 } 8861 8862 if state.HasManagedResourceInstanceObjects() { 8863 t.Fatal("expected no state, got:", state) 8864 } 8865 8866 if providerRegion != "bar" { 8867 t.Fatalf("expected region %q, got: %q", "bar", providerRegion) 8868 } 8869 } 8870 8871 func TestContext2Apply_destroyWithProviders(t *testing.T) { 8872 m := testModule(t, "destroy-module-with-provider") 8873 p := testProvider("aws") 8874 p.PlanResourceChangeFn = testDiffFn 8875 8876 state := states.NewState() 8877 removed := state.EnsureModule(addrs.RootModuleInstance.Child("mod", addrs.NoKey).Child("removed", addrs.NoKey)) 8878 removed.SetResourceInstanceCurrent( 8879 mustResourceInstanceAddr("aws_instance.child").Resource, 8880 &states.ResourceInstanceObjectSrc{ 8881 Status: states.ObjectReady, 8882 AttrsJSON: []byte(`{"id":"bar"}`), 8883 }, 8884 mustProviderConfig(`provider["registry.opentofu.org/hashicorp/aws"].baz`), 8885 ) 8886 8887 ctx := testContext2(t, &ContextOpts{ 8888 Providers: map[addrs.Provider]providers.Factory{ 8889 addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p), 8890 }, 8891 }) 8892 8893 // test that we can't destroy if the provider is missing 8894 if _, diags := ctx.Plan(m, state, &PlanOpts{Mode: plans.DestroyMode}); diags == nil { 8895 t.Fatal("expected plan error, provider.aws.baz doesn't exist") 8896 } 8897 8898 // correct the state 8899 state.Modules["module.mod.module.removed"].Resources["aws_instance.child"].ProviderConfig = mustProviderConfig(`provider["registry.opentofu.org/hashicorp/aws"].bar`) 8900 8901 ctx = testContext2(t, &ContextOpts{ 8902 Providers: map[addrs.Provider]providers.Factory{ 8903 addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p), 8904 }, 8905 }) 8906 8907 plan, diags := ctx.Plan(m, state, &PlanOpts{ 8908 Mode: plans.DestroyMode, 8909 }) 8910 assertNoErrors(t, diags) 8911 8912 state, diags = ctx.Apply(plan, m) 8913 if diags.HasErrors() { 8914 t.Fatalf("error during apply: %s", diags.Err()) 8915 } 8916 8917 got := strings.TrimSpace(state.String()) 8918 8919 want := strings.TrimSpace("<no state>") 8920 if got != want { 8921 t.Fatalf("wrong final state\ngot:\n%s\nwant:\n%s", got, want) 8922 } 8923 } 8924 8925 func TestContext2Apply_providersFromState(t *testing.T) { 8926 m := configs.NewEmptyConfig() 8927 p := testProvider("aws") 8928 p.PlanResourceChangeFn = testDiffFn 8929 8930 implicitProviderState := states.NewState() 8931 impRoot := implicitProviderState.EnsureModule(addrs.RootModuleInstance) 8932 impRoot.SetResourceInstanceCurrent( 8933 mustResourceInstanceAddr("aws_instance.a").Resource, 8934 &states.ResourceInstanceObjectSrc{ 8935 Status: states.ObjectReady, 8936 AttrsJSON: []byte(`{"id":"bar"}`), 8937 }, 8938 mustProviderConfig(`provider["registry.opentofu.org/hashicorp/aws"]`), 8939 ) 8940 8941 aliasedProviderState := states.NewState() 8942 aliasRoot := aliasedProviderState.EnsureModule(addrs.RootModuleInstance) 8943 aliasRoot.SetResourceInstanceCurrent( 8944 mustResourceInstanceAddr("aws_instance.a").Resource, 8945 &states.ResourceInstanceObjectSrc{ 8946 Status: states.ObjectReady, 8947 AttrsJSON: []byte(`{"id":"bar"}`), 8948 }, 8949 mustProviderConfig(`provider["registry.opentofu.org/hashicorp/aws"].bar`), 8950 ) 8951 8952 moduleProviderState := states.NewState() 8953 moduleProviderRoot := moduleProviderState.EnsureModule(addrs.RootModuleInstance) 8954 moduleProviderRoot.SetResourceInstanceCurrent( 8955 mustResourceInstanceAddr("aws_instance.a").Resource, 8956 &states.ResourceInstanceObjectSrc{ 8957 Status: states.ObjectReady, 8958 AttrsJSON: []byte(`{"id":"bar"}`), 8959 }, 8960 mustProviderConfig(`module.child.provider["registry.opentofu.org/hashicorp/aws"]`), 8961 ) 8962 8963 for _, tc := range []struct { 8964 name string 8965 state *states.State 8966 output string 8967 err bool 8968 }{ 8969 { 8970 name: "add implicit provider", 8971 state: implicitProviderState, 8972 err: false, 8973 output: "<no state>", 8974 }, 8975 8976 // an aliased provider must be in the config to remove a resource 8977 { 8978 name: "add aliased provider", 8979 state: aliasedProviderState, 8980 err: true, 8981 }, 8982 8983 // a provider in a module implies some sort of config, so this isn't 8984 // allowed even without an alias 8985 { 8986 name: "add unaliased module provider", 8987 state: moduleProviderState, 8988 err: true, 8989 }, 8990 } { 8991 t.Run(tc.name, func(t *testing.T) { 8992 ctx := testContext2(t, &ContextOpts{ 8993 Providers: map[addrs.Provider]providers.Factory{ 8994 addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p), 8995 }, 8996 }) 8997 8998 plan, diags := ctx.Plan(m, tc.state, DefaultPlanOpts) 8999 if tc.err { 9000 if diags == nil { 9001 t.Fatal("expected error") 9002 } else { 9003 return 9004 } 9005 } 9006 if !tc.err && diags.HasErrors() { 9007 t.Fatal(diags.Err()) 9008 } 9009 9010 state, diags := ctx.Apply(plan, m) 9011 if diags.HasErrors() { 9012 t.Fatalf("diags: %s", diags.Err()) 9013 } 9014 9015 checkStateString(t, state, "<no state>") 9016 9017 }) 9018 } 9019 } 9020 9021 func TestContext2Apply_plannedInterpolatedCount(t *testing.T) { 9022 m, snap := testModuleWithSnapshot(t, "apply-interpolated-count") 9023 9024 p := testProvider("aws") 9025 p.PlanResourceChangeFn = testDiffFn 9026 9027 Providers := map[addrs.Provider]providers.Factory{ 9028 addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p), 9029 } 9030 9031 state := states.NewState() 9032 root := state.EnsureModule(addrs.RootModuleInstance) 9033 root.SetResourceInstanceCurrent( 9034 mustResourceInstanceAddr("aws_instance.test").Resource, 9035 &states.ResourceInstanceObjectSrc{ 9036 Status: states.ObjectReady, 9037 AttrsJSON: []byte(`{"id":"foo"}`), 9038 }, 9039 mustProviderConfig(`provider["registry.opentofu.org/hashicorp/aws"]`), 9040 ) 9041 9042 ctx := testContext2(t, &ContextOpts{ 9043 Providers: Providers, 9044 }) 9045 9046 plan, diags := ctx.Plan(m, state, SimplePlanOpts(plans.NormalMode, testInputValuesUnset(m.Module.Variables))) 9047 if diags.HasErrors() { 9048 t.Fatalf("plan failed: %s", diags.Err()) 9049 } 9050 9051 // We'll marshal and unmarshal the plan here, to ensure that we have 9052 // a clean new context as would be created if we separately ran 9053 // tofu plan -out=tfplan && tofu apply tfplan 9054 ctxOpts, m, plan, err := contextOptsForPlanViaFile(t, snap, plan) 9055 if err != nil { 9056 t.Fatalf("failed to round-trip through planfile: %s", err) 9057 } 9058 9059 ctxOpts.Providers = Providers 9060 ctx, diags = NewContext(ctxOpts) 9061 if diags.HasErrors() { 9062 t.Fatalf("err: %s", diags.Err()) 9063 } 9064 9065 // Applying the plan should now succeed 9066 _, diags = ctx.Apply(plan, m) 9067 if diags.HasErrors() { 9068 t.Fatalf("apply failed: %s", diags.Err()) 9069 } 9070 } 9071 9072 func TestContext2Apply_plannedDestroyInterpolatedCount(t *testing.T) { 9073 m, snap := testModuleWithSnapshot(t, "plan-destroy-interpolated-count") 9074 9075 p := testProvider("aws") 9076 p.PlanResourceChangeFn = testDiffFn 9077 providers := map[addrs.Provider]providers.Factory{ 9078 addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p), 9079 } 9080 9081 state := states.NewState() 9082 root := state.EnsureModule(addrs.RootModuleInstance) 9083 root.SetResourceInstanceCurrent( 9084 mustResourceInstanceAddr("aws_instance.a[0]").Resource, 9085 &states.ResourceInstanceObjectSrc{ 9086 Status: states.ObjectReady, 9087 AttrsJSON: []byte(`{"id":"foo"}`), 9088 }, 9089 mustProviderConfig(`provider["registry.opentofu.org/hashicorp/aws"]`), 9090 ) 9091 root.SetResourceInstanceCurrent( 9092 mustResourceInstanceAddr("aws_instance.a[1]").Resource, 9093 &states.ResourceInstanceObjectSrc{ 9094 Status: states.ObjectReady, 9095 AttrsJSON: []byte(`{"id":"foo"}`), 9096 }, 9097 mustProviderConfig(`provider["registry.opentofu.org/hashicorp/aws"]`), 9098 ) 9099 root.SetOutputValue("out", cty.ListVal([]cty.Value{cty.StringVal("foo"), cty.StringVal("foo")}), false) 9100 9101 ctx := testContext2(t, &ContextOpts{ 9102 Providers: providers, 9103 }) 9104 9105 plan, diags := ctx.Plan(m, state, SimplePlanOpts(plans.DestroyMode, testInputValuesUnset(m.Module.Variables))) 9106 if diags.HasErrors() { 9107 t.Fatalf("plan failed: %s", diags.Err()) 9108 } 9109 9110 // We'll marshal and unmarshal the plan here, to ensure that we have 9111 // a clean new context as would be created if we separately ran 9112 // tofu plan -out=tfplan && tofu apply tfplan 9113 ctxOpts, m, plan, err := contextOptsForPlanViaFile(t, snap, plan) 9114 if err != nil { 9115 t.Fatalf("failed to round-trip through planfile: %s", err) 9116 } 9117 9118 ctxOpts.Providers = providers 9119 ctx, diags = NewContext(ctxOpts) 9120 if diags.HasErrors() { 9121 t.Fatalf("err: %s", diags.Err()) 9122 } 9123 9124 // Applying the plan should now succeed 9125 state, diags = ctx.Apply(plan, m) 9126 if diags.HasErrors() { 9127 t.Fatalf("apply failed: %s", diags.Err()) 9128 } 9129 if !state.Empty() { 9130 t.Fatalf("state not empty: %s\n", state) 9131 } 9132 } 9133 9134 func TestContext2Apply_scaleInMultivarRef(t *testing.T) { 9135 m := testModule(t, "apply-resource-scale-in") 9136 9137 p := testProvider("aws") 9138 p.PlanResourceChangeFn = testDiffFn 9139 9140 Providers := map[addrs.Provider]providers.Factory{ 9141 addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p), 9142 } 9143 9144 state := states.NewState() 9145 root := state.EnsureModule(addrs.RootModuleInstance) 9146 root.SetResourceInstanceCurrent( 9147 mustResourceInstanceAddr("aws_instance.one").Resource, 9148 &states.ResourceInstanceObjectSrc{ 9149 Status: states.ObjectReady, 9150 AttrsJSON: []byte(`{"id":"foo"}`), 9151 }, 9152 mustProviderConfig(`provider["registry.opentofu.org/hashicorp/aws"]`), 9153 ) 9154 root.SetResourceInstanceCurrent( 9155 mustResourceInstanceAddr("aws_instance.two").Resource, 9156 &states.ResourceInstanceObjectSrc{ 9157 Status: states.ObjectReady, 9158 AttrsJSON: []byte(`{"id":"foo"}`), 9159 }, 9160 mustProviderConfig(`provider["registry.opentofu.org/hashicorp/aws"]`), 9161 ) 9162 9163 ctx := testContext2(t, &ContextOpts{ 9164 Providers: Providers, 9165 }) 9166 9167 plan, diags := ctx.Plan(m, state, &PlanOpts{ 9168 Mode: plans.NormalMode, 9169 SetVariables: InputValues{ 9170 "instance_count": { 9171 Value: cty.NumberIntVal(0), 9172 SourceType: ValueFromCaller, 9173 }, 9174 }, 9175 }) 9176 assertNoErrors(t, diags) 9177 { 9178 addr := mustResourceInstanceAddr("aws_instance.one[0]") 9179 change := plan.Changes.ResourceInstance(addr) 9180 if change == nil { 9181 t.Fatalf("no planned change for %s", addr) 9182 } 9183 // This test was originally written with Terraform v0.11 and earlier 9184 // in mind, so it declares a no-key instance of aws_instance.one, 9185 // but its configuration sets count (to zero) and so we end up first 9186 // moving the no-key instance to the zero key and then planning to 9187 // destroy the zero key. 9188 if got, want := change.PrevRunAddr, mustResourceInstanceAddr("aws_instance.one"); !want.Equal(got) { 9189 t.Errorf("wrong previous run address for %s %s; want %s", addr, got, want) 9190 } 9191 if got, want := change.Action, plans.Delete; got != want { 9192 t.Errorf("wrong action for %s %s; want %s", addr, got, want) 9193 } 9194 if got, want := change.ActionReason, plans.ResourceInstanceDeleteBecauseCountIndex; got != want { 9195 t.Errorf("wrong action reason for %s %s; want %s", addr, got, want) 9196 } 9197 } 9198 { 9199 addr := mustResourceInstanceAddr("aws_instance.two") 9200 change := plan.Changes.ResourceInstance(addr) 9201 if change == nil { 9202 t.Fatalf("no planned change for %s", addr) 9203 } 9204 if got, want := change.PrevRunAddr, mustResourceInstanceAddr("aws_instance.two"); !want.Equal(got) { 9205 t.Errorf("wrong previous run address for %s %s; want %s", addr, got, want) 9206 } 9207 if got, want := change.Action, plans.Update; got != want { 9208 t.Errorf("wrong action for %s %s; want %s", addr, got, want) 9209 } 9210 if got, want := change.ActionReason, plans.ResourceInstanceChangeNoReason; got != want { 9211 t.Errorf("wrong action reason for %s %s; want %s", addr, got, want) 9212 } 9213 } 9214 9215 // Applying the plan should now succeed 9216 _, diags = ctx.Apply(plan, m) 9217 assertNoErrors(t, diags) 9218 } 9219 9220 func TestContext2Apply_inconsistentWithPlan(t *testing.T) { 9221 m := testModule(t, "apply-inconsistent-with-plan") 9222 p := testProvider("test") 9223 p.GetProviderSchemaResponse = getProviderSchemaResponseFromProviderSchema(&ProviderSchema{ 9224 ResourceTypes: map[string]*configschema.Block{ 9225 "test": { 9226 Attributes: map[string]*configschema.Attribute{ 9227 "id": {Type: cty.String, Computed: true}, 9228 }, 9229 }, 9230 }, 9231 }) 9232 p.PlanResourceChangeFn = func(req providers.PlanResourceChangeRequest) providers.PlanResourceChangeResponse { 9233 return providers.PlanResourceChangeResponse{ 9234 PlannedState: cty.ObjectVal(map[string]cty.Value{ 9235 "id": cty.StringVal("before"), 9236 }), 9237 } 9238 } 9239 p.ApplyResourceChangeFn = func(req providers.ApplyResourceChangeRequest) providers.ApplyResourceChangeResponse { 9240 return providers.ApplyResourceChangeResponse{ 9241 NewState: cty.ObjectVal(map[string]cty.Value{ 9242 // This is intentionally incorrect: because id was fixed at "before" 9243 // during plan, it must not change during apply. 9244 "id": cty.StringVal("after"), 9245 }), 9246 } 9247 } 9248 ctx := testContext2(t, &ContextOpts{ 9249 Providers: map[addrs.Provider]providers.Factory{ 9250 addrs.NewDefaultProvider("test"): testProviderFuncFixed(p), 9251 }, 9252 }) 9253 9254 plan, diags := ctx.Plan(m, states.NewState(), DefaultPlanOpts) 9255 assertNoErrors(t, diags) 9256 9257 _, diags = ctx.Apply(plan, m) 9258 if !diags.HasErrors() { 9259 t.Fatalf("apply succeeded; want error") 9260 } 9261 if got, want := diags.Err().Error(), "Provider produced inconsistent result after apply"; !strings.Contains(got, want) { 9262 t.Fatalf("wrong error\ngot: %s\nshould contain: %s", got, want) 9263 } 9264 } 9265 9266 // Issue 19908 was about retaining an existing object in the state when an 9267 // update to it fails and the provider does not return a partially-updated 9268 // value for it. Previously we were incorrectly removing it from the state 9269 // in that case, but instead it should be retained so the update can be 9270 // retried. 9271 func TestContext2Apply_issue19908(t *testing.T) { 9272 m := testModule(t, "apply-issue19908") 9273 p := testProvider("test") 9274 p.GetProviderSchemaResponse = getProviderSchemaResponseFromProviderSchema(&ProviderSchema{ 9275 ResourceTypes: map[string]*configschema.Block{ 9276 "test": { 9277 Attributes: map[string]*configschema.Attribute{ 9278 "baz": {Type: cty.String, Required: true}, 9279 }, 9280 }, 9281 }, 9282 }) 9283 p.PlanResourceChangeFn = func(req providers.PlanResourceChangeRequest) providers.PlanResourceChangeResponse { 9284 return providers.PlanResourceChangeResponse{ 9285 PlannedState: req.ProposedNewState, 9286 } 9287 } 9288 p.ApplyResourceChangeFn = func(req providers.ApplyResourceChangeRequest) providers.ApplyResourceChangeResponse { 9289 var diags tfdiags.Diagnostics 9290 diags = diags.Append(fmt.Errorf("update failed")) 9291 return providers.ApplyResourceChangeResponse{ 9292 Diagnostics: diags, 9293 } 9294 } 9295 ctx := testContext2(t, &ContextOpts{ 9296 Providers: map[addrs.Provider]providers.Factory{ 9297 addrs.NewDefaultProvider("test"): testProviderFuncFixed(p), 9298 }, 9299 }) 9300 9301 state := states.BuildState(func(s *states.SyncState) { 9302 s.SetResourceInstanceCurrent( 9303 addrs.Resource{ 9304 Mode: addrs.ManagedResourceMode, 9305 Type: "test", 9306 Name: "foo", 9307 }.Instance(addrs.NoKey).Absolute(addrs.RootModuleInstance), 9308 &states.ResourceInstanceObjectSrc{ 9309 AttrsJSON: []byte(`{"baz":"old"}`), 9310 Status: states.ObjectReady, 9311 }, 9312 addrs.AbsProviderConfig{ 9313 Provider: addrs.NewDefaultProvider("test"), 9314 Module: addrs.RootModule, 9315 }, 9316 ) 9317 }) 9318 9319 plan, diags := ctx.Plan(m, state, DefaultPlanOpts) 9320 assertNoErrors(t, diags) 9321 9322 state, diags = ctx.Apply(plan, m) 9323 if !diags.HasErrors() { 9324 t.Fatalf("apply succeeded; want error") 9325 } 9326 if got, want := diags.Err().Error(), "update failed"; !strings.Contains(got, want) { 9327 t.Fatalf("wrong error\ngot: %s\nshould contain: %s", got, want) 9328 } 9329 9330 mod := state.RootModule() 9331 rs := mod.Resources["test.foo"] 9332 if rs == nil { 9333 t.Fatalf("test.foo not in state after apply, but should be") 9334 } 9335 is := rs.Instances[addrs.NoKey] 9336 if is == nil { 9337 t.Fatalf("test.foo not in state after apply, but should be") 9338 } 9339 obj := is.Current 9340 if obj == nil { 9341 t.Fatalf("test.foo has no current object in state after apply, but should do") 9342 } 9343 9344 if got, want := obj.Status, states.ObjectReady; got != want { 9345 t.Errorf("test.foo has wrong status %s after apply; want %s", got, want) 9346 } 9347 if got, want := obj.AttrsJSON, []byte(`"old"`); !bytes.Contains(got, want) { 9348 t.Errorf("test.foo attributes JSON doesn't contain %s after apply\ngot: %s", want, got) 9349 } 9350 } 9351 9352 func TestContext2Apply_invalidIndexRef(t *testing.T) { 9353 p := testProvider("test") 9354 p.GetProviderSchemaResponse = getProviderSchemaResponseFromProviderSchema(&ProviderSchema{ 9355 ResourceTypes: map[string]*configschema.Block{ 9356 "test_instance": { 9357 Attributes: map[string]*configschema.Attribute{ 9358 "value": {Type: cty.String, Optional: true, Computed: true}, 9359 }, 9360 }, 9361 }, 9362 }) 9363 p.PlanResourceChangeFn = testDiffFn 9364 9365 m := testModule(t, "apply-invalid-index") 9366 c := testContext2(t, &ContextOpts{ 9367 Providers: map[addrs.Provider]providers.Factory{ 9368 addrs.NewDefaultProvider("test"): testProviderFuncFixed(p), 9369 }, 9370 }) 9371 diags := c.Validate(m) 9372 if diags.HasErrors() { 9373 t.Fatalf("unexpected validation failure: %s", diags.Err()) 9374 } 9375 9376 wantErr := `The given key does not identify an element in this collection value` 9377 _, diags = c.Plan(m, states.NewState(), DefaultPlanOpts) 9378 9379 if !diags.HasErrors() { 9380 t.Fatalf("plan succeeded; want error") 9381 } 9382 gotErr := diags.Err().Error() 9383 9384 if !strings.Contains(gotErr, wantErr) { 9385 t.Fatalf("missing expected error\ngot: %s\n\nwant: error containing %q", gotErr, wantErr) 9386 } 9387 } 9388 9389 func TestContext2Apply_moduleReplaceCycle(t *testing.T) { 9390 for _, mode := range []string{"normal", "cbd"} { 9391 var m *configs.Config 9392 9393 switch mode { 9394 case "normal": 9395 m = testModule(t, "apply-module-replace-cycle") 9396 case "cbd": 9397 m = testModule(t, "apply-module-replace-cycle-cbd") 9398 } 9399 9400 p := testProvider("aws") 9401 p.PlanResourceChangeFn = testDiffFn 9402 9403 instanceSchema := &configschema.Block{ 9404 Attributes: map[string]*configschema.Attribute{ 9405 "id": {Type: cty.String, Computed: true}, 9406 "require_new": {Type: cty.String, Optional: true}, 9407 }, 9408 } 9409 9410 p.GetProviderSchemaResponse = getProviderSchemaResponseFromProviderSchema(&ProviderSchema{ 9411 ResourceTypes: map[string]*configschema.Block{ 9412 "aws_instance": instanceSchema, 9413 }, 9414 }) 9415 9416 state := states.NewState() 9417 modA := state.EnsureModule(addrs.RootModuleInstance.Child("a", addrs.NoKey)) 9418 modA.SetResourceInstanceCurrent( 9419 addrs.Resource{ 9420 Mode: addrs.ManagedResourceMode, 9421 Type: "aws_instance", 9422 Name: "a", 9423 }.Instance(addrs.NoKey), 9424 &states.ResourceInstanceObjectSrc{ 9425 Status: states.ObjectReady, 9426 AttrsJSON: []byte(`{"id":"a","require_new":"old"}`), 9427 CreateBeforeDestroy: mode == "cbd", 9428 }, 9429 addrs.AbsProviderConfig{ 9430 Provider: addrs.NewDefaultProvider("aws"), 9431 Module: addrs.RootModule, 9432 }, 9433 ) 9434 9435 modB := state.EnsureModule(addrs.RootModuleInstance.Child("b", addrs.NoKey)) 9436 modB.SetResourceInstanceCurrent( 9437 addrs.Resource{ 9438 Mode: addrs.ManagedResourceMode, 9439 Type: "aws_instance", 9440 Name: "b", 9441 }.Instance(addrs.IntKey(0)), 9442 &states.ResourceInstanceObjectSrc{ 9443 Status: states.ObjectReady, 9444 AttrsJSON: []byte(`{"id":"b","require_new":"old"}`), 9445 }, 9446 addrs.AbsProviderConfig{ 9447 Provider: addrs.NewDefaultProvider("aws"), 9448 Module: addrs.RootModule, 9449 }, 9450 ) 9451 9452 aBefore, _ := plans.NewDynamicValue( 9453 cty.ObjectVal(map[string]cty.Value{ 9454 "id": cty.StringVal("a"), 9455 "require_new": cty.StringVal("old"), 9456 }), instanceSchema.ImpliedType()) 9457 aAfter, _ := plans.NewDynamicValue( 9458 cty.ObjectVal(map[string]cty.Value{ 9459 "id": cty.UnknownVal(cty.String), 9460 "require_new": cty.StringVal("new"), 9461 }), instanceSchema.ImpliedType()) 9462 bBefore, _ := plans.NewDynamicValue( 9463 cty.ObjectVal(map[string]cty.Value{ 9464 "id": cty.StringVal("b"), 9465 "require_new": cty.StringVal("old"), 9466 }), instanceSchema.ImpliedType()) 9467 bAfter, _ := plans.NewDynamicValue( 9468 cty.ObjectVal(map[string]cty.Value{ 9469 "id": cty.UnknownVal(cty.String), 9470 "require_new": cty.UnknownVal(cty.String), 9471 }), instanceSchema.ImpliedType()) 9472 9473 var aAction plans.Action 9474 switch mode { 9475 case "normal": 9476 aAction = plans.DeleteThenCreate 9477 case "cbd": 9478 aAction = plans.CreateThenDelete 9479 } 9480 9481 ctx := testContext2(t, &ContextOpts{ 9482 Providers: map[addrs.Provider]providers.Factory{ 9483 addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p), 9484 }, 9485 }) 9486 9487 changes := &plans.Changes{ 9488 Resources: []*plans.ResourceInstanceChangeSrc{ 9489 { 9490 Addr: addrs.Resource{ 9491 Mode: addrs.ManagedResourceMode, 9492 Type: "aws_instance", 9493 Name: "a", 9494 }.Instance(addrs.NoKey).Absolute(addrs.RootModuleInstance.Child("a", addrs.NoKey)), 9495 ProviderAddr: addrs.AbsProviderConfig{ 9496 Provider: addrs.NewDefaultProvider("aws"), 9497 Module: addrs.RootModule, 9498 }, 9499 ChangeSrc: plans.ChangeSrc{ 9500 Action: aAction, 9501 Before: aBefore, 9502 After: aAfter, 9503 }, 9504 }, 9505 { 9506 Addr: addrs.Resource{ 9507 Mode: addrs.ManagedResourceMode, 9508 Type: "aws_instance", 9509 Name: "b", 9510 }.Instance(addrs.IntKey(0)).Absolute(addrs.RootModuleInstance.Child("b", addrs.NoKey)), 9511 ProviderAddr: addrs.AbsProviderConfig{ 9512 Provider: addrs.NewDefaultProvider("aws"), 9513 Module: addrs.RootModule, 9514 }, 9515 ChangeSrc: plans.ChangeSrc{ 9516 Action: plans.DeleteThenCreate, 9517 Before: bBefore, 9518 After: bAfter, 9519 }, 9520 }, 9521 }, 9522 } 9523 9524 plan := &plans.Plan{ 9525 UIMode: plans.NormalMode, 9526 Changes: changes, 9527 PriorState: state.DeepCopy(), 9528 PrevRunState: state.DeepCopy(), 9529 } 9530 9531 t.Run(mode, func(t *testing.T) { 9532 _, diags := ctx.Apply(plan, m) 9533 if diags.HasErrors() { 9534 t.Fatal(diags.Err()) 9535 } 9536 }) 9537 } 9538 } 9539 9540 func TestContext2Apply_destroyDataCycle(t *testing.T) { 9541 m, snap := testModuleWithSnapshot(t, "apply-destroy-data-cycle") 9542 p := testProvider("null") 9543 p.PlanResourceChangeFn = testDiffFn 9544 p.ReadDataSourceFn = func(req providers.ReadDataSourceRequest) providers.ReadDataSourceResponse { 9545 return providers.ReadDataSourceResponse{ 9546 State: cty.ObjectVal(map[string]cty.Value{ 9547 "id": cty.StringVal("new"), 9548 "foo": cty.NullVal(cty.String), 9549 }), 9550 } 9551 } 9552 9553 tp := testProvider("test") 9554 tp.PlanResourceChangeFn = testDiffFn 9555 9556 state := states.NewState() 9557 root := state.EnsureModule(addrs.RootModuleInstance) 9558 root.SetResourceInstanceCurrent( 9559 addrs.Resource{ 9560 Mode: addrs.ManagedResourceMode, 9561 Type: "null_resource", 9562 Name: "a", 9563 }.Instance(addrs.IntKey(0)), 9564 &states.ResourceInstanceObjectSrc{ 9565 Status: states.ObjectReady, 9566 AttrsJSON: []byte(`{"id":"a"}`), 9567 }, 9568 addrs.AbsProviderConfig{ 9569 Provider: addrs.NewDefaultProvider("null"), 9570 Module: addrs.RootModule, 9571 }, 9572 ) 9573 root.SetResourceInstanceCurrent( 9574 addrs.Resource{ 9575 Mode: addrs.ManagedResourceMode, 9576 Type: "test_resource", 9577 Name: "a", 9578 }.Instance(addrs.IntKey(0)), 9579 &states.ResourceInstanceObjectSrc{ 9580 Status: states.ObjectReady, 9581 AttrsJSON: []byte(`{"id":"a"}`), 9582 Dependencies: []addrs.ConfigResource{ 9583 { 9584 Resource: addrs.Resource{ 9585 Mode: addrs.DataResourceMode, 9586 Type: "null_data_source", 9587 Name: "d", 9588 }, 9589 Module: addrs.RootModule, 9590 }, 9591 }, 9592 }, 9593 addrs.AbsProviderConfig{ 9594 Provider: addrs.NewDefaultProvider("test"), 9595 Module: addrs.RootModule, 9596 }, 9597 ) 9598 root.SetResourceInstanceCurrent( 9599 addrs.Resource{ 9600 Mode: addrs.DataResourceMode, 9601 Type: "null_data_source", 9602 Name: "d", 9603 }.Instance(addrs.NoKey), 9604 &states.ResourceInstanceObjectSrc{ 9605 Status: states.ObjectReady, 9606 AttrsJSON: []byte(`{"id":"old"}`), 9607 }, 9608 addrs.AbsProviderConfig{ 9609 Provider: addrs.NewDefaultProvider("null"), 9610 Module: addrs.RootModule, 9611 }, 9612 ) 9613 9614 Providers := map[addrs.Provider]providers.Factory{ 9615 addrs.NewDefaultProvider("null"): testProviderFuncFixed(p), 9616 addrs.NewDefaultProvider("test"): testProviderFuncFixed(tp), 9617 } 9618 9619 ctx := testContext2(t, &ContextOpts{ 9620 Providers: Providers, 9621 }) 9622 9623 plan, diags := ctx.Plan(m, state, &PlanOpts{ 9624 Mode: plans.DestroyMode, 9625 }) 9626 diags.HasErrors() 9627 if diags.HasErrors() { 9628 t.Fatalf("diags: %s", diags.Err()) 9629 } 9630 9631 // We'll marshal and unmarshal the plan here, to ensure that we have 9632 // a clean new context as would be created if we separately ran 9633 // tofu plan -out=tfplan && tofu apply tfplan 9634 ctxOpts, m, plan, err := contextOptsForPlanViaFile(t, snap, plan) 9635 if err != nil { 9636 t.Fatal(err) 9637 } 9638 ctxOpts.Providers = Providers 9639 ctx, diags = NewContext(ctxOpts) 9640 if diags.HasErrors() { 9641 t.Fatalf("failed to create context for plan: %s", diags.Err()) 9642 } 9643 9644 tp.ConfigureProviderFn = func(req providers.ConfigureProviderRequest) (resp providers.ConfigureProviderResponse) { 9645 foo := req.Config.GetAttr("foo") 9646 if !foo.IsKnown() { 9647 resp.Diagnostics = resp.Diagnostics.Append(fmt.Errorf("unknown config value foo")) 9648 return resp 9649 } 9650 9651 if foo.AsString() != "new" { 9652 resp.Diagnostics = resp.Diagnostics.Append(fmt.Errorf("wrong config value: %q", foo.AsString())) 9653 } 9654 return resp 9655 } 9656 9657 _, diags = ctx.Apply(plan, m) 9658 if diags.HasErrors() { 9659 t.Fatalf("diags: %s", diags.Err()) 9660 } 9661 } 9662 9663 func TestContext2Apply_taintedDestroyFailure(t *testing.T) { 9664 m := testModule(t, "apply-destroy-tainted") 9665 p := testProvider("test") 9666 p.PlanResourceChangeFn = testDiffFn 9667 p.ApplyResourceChangeFn = func(req providers.ApplyResourceChangeRequest) (resp providers.ApplyResourceChangeResponse) { 9668 // All destroys fail. 9669 if req.PlannedState.IsNull() { 9670 resp.Diagnostics = resp.Diagnostics.Append(errors.New("failure")) 9671 return 9672 } 9673 9674 // c will also fail to create, meaning the existing tainted instance 9675 // becomes deposed, ans is then promoted back to current. 9676 // only C has a foo attribute 9677 planned := req.PlannedState.AsValueMap() 9678 foo, ok := planned["foo"] 9679 if ok && !foo.IsNull() && foo.AsString() == "c" { 9680 resp.Diagnostics = resp.Diagnostics.Append(errors.New("failure")) 9681 return 9682 } 9683 9684 return testApplyFn(req) 9685 } 9686 p.GetProviderSchemaResponse = getProviderSchemaResponseFromProviderSchema(&ProviderSchema{ 9687 ResourceTypes: map[string]*configschema.Block{ 9688 "test_instance": { 9689 Attributes: map[string]*configschema.Attribute{ 9690 "id": { 9691 Type: cty.String, 9692 Computed: true, 9693 }, 9694 "foo": { 9695 Type: cty.String, 9696 Optional: true, 9697 }, 9698 }, 9699 }, 9700 }, 9701 }) 9702 9703 state := states.NewState() 9704 root := state.EnsureModule(addrs.RootModuleInstance) 9705 root.SetResourceInstanceCurrent( 9706 addrs.Resource{ 9707 Mode: addrs.ManagedResourceMode, 9708 Type: "test_instance", 9709 Name: "a", 9710 }.Instance(addrs.NoKey), 9711 &states.ResourceInstanceObjectSrc{ 9712 Status: states.ObjectTainted, 9713 AttrsJSON: []byte(`{"id":"a","foo":"a"}`), 9714 }, 9715 addrs.AbsProviderConfig{ 9716 Provider: addrs.NewDefaultProvider("test"), 9717 Module: addrs.RootModule, 9718 }, 9719 ) 9720 root.SetResourceInstanceCurrent( 9721 addrs.Resource{ 9722 Mode: addrs.ManagedResourceMode, 9723 Type: "test_instance", 9724 Name: "b", 9725 }.Instance(addrs.NoKey), 9726 &states.ResourceInstanceObjectSrc{ 9727 Status: states.ObjectTainted, 9728 AttrsJSON: []byte(`{"id":"b","foo":"b"}`), 9729 }, 9730 addrs.AbsProviderConfig{ 9731 Provider: addrs.NewDefaultProvider("test"), 9732 Module: addrs.RootModule, 9733 }, 9734 ) 9735 root.SetResourceInstanceCurrent( 9736 addrs.Resource{ 9737 Mode: addrs.ManagedResourceMode, 9738 Type: "test_instance", 9739 Name: "c", 9740 }.Instance(addrs.NoKey), 9741 &states.ResourceInstanceObjectSrc{ 9742 Status: states.ObjectTainted, 9743 AttrsJSON: []byte(`{"id":"c","foo":"old"}`), 9744 }, 9745 addrs.AbsProviderConfig{ 9746 Provider: addrs.NewDefaultProvider("test"), 9747 Module: addrs.RootModule, 9748 }, 9749 ) 9750 9751 Providers := map[addrs.Provider]providers.Factory{ 9752 addrs.NewDefaultProvider("test"): testProviderFuncFixed(p), 9753 } 9754 9755 ctx := testContext2(t, &ContextOpts{ 9756 Providers: Providers, 9757 Hooks: []Hook{&testHook{}}, 9758 }) 9759 9760 plan, diags := ctx.Plan(m, state, DefaultPlanOpts) 9761 diags.HasErrors() 9762 if diags.HasErrors() { 9763 t.Fatalf("diags: %s", diags.Err()) 9764 } 9765 9766 state, diags = ctx.Apply(plan, m) 9767 if !diags.HasErrors() { 9768 t.Fatal("expected error") 9769 } 9770 9771 root = state.Module(addrs.RootModuleInstance) 9772 9773 // the instance that failed to destroy should remain tainted 9774 a := root.ResourceInstance(addrs.Resource{ 9775 Mode: addrs.ManagedResourceMode, 9776 Type: "test_instance", 9777 Name: "a", 9778 }.Instance(addrs.NoKey)) 9779 9780 if a.Current.Status != states.ObjectTainted { 9781 t.Fatal("test_instance.a should be tainted") 9782 } 9783 9784 // b is create_before_destroy, and the destroy failed, so there should be 1 9785 // deposed instance. 9786 b := root.ResourceInstance(addrs.Resource{ 9787 Mode: addrs.ManagedResourceMode, 9788 Type: "test_instance", 9789 Name: "b", 9790 }.Instance(addrs.NoKey)) 9791 9792 if b.Current.Status != states.ObjectReady { 9793 t.Fatal("test_instance.b should be Ready") 9794 } 9795 9796 if len(b.Deposed) != 1 { 9797 t.Fatal("test_instance.b failed to keep deposed instance") 9798 } 9799 9800 // the desposed c instance should be promoted back to Current, and remain 9801 // tainted 9802 c := root.ResourceInstance(addrs.Resource{ 9803 Mode: addrs.ManagedResourceMode, 9804 Type: "test_instance", 9805 Name: "c", 9806 }.Instance(addrs.NoKey)) 9807 9808 if c.Current == nil { 9809 t.Fatal("test_instance.c has no current instance, but it should") 9810 } 9811 9812 if c.Current.Status != states.ObjectTainted { 9813 t.Fatal("test_instance.c should be tainted") 9814 } 9815 9816 if len(c.Deposed) != 0 { 9817 t.Fatal("test_instance.c should have no deposed instances") 9818 } 9819 9820 if string(c.Current.AttrsJSON) != `{"foo":"old","id":"c"}` { 9821 t.Fatalf("unexpected attrs for c: %q\n", c.Current.AttrsJSON) 9822 } 9823 } 9824 9825 func TestContext2Apply_plannedConnectionRefs(t *testing.T) { 9826 m := testModule(t, "apply-plan-connection-refs") 9827 p := testProvider("test") 9828 p.PlanResourceChangeFn = testDiffFn 9829 p.ApplyResourceChangeFn = func(req providers.ApplyResourceChangeRequest) (resp providers.ApplyResourceChangeResponse) { 9830 s := req.PlannedState.AsValueMap() 9831 // delay "a" slightly, so if the reference edge is missing the "b" 9832 // provisioner will see an unknown value. 9833 if s["foo"].AsString() == "a" { 9834 time.Sleep(500 * time.Millisecond) 9835 } 9836 9837 s["id"] = cty.StringVal("ID") 9838 if ty, ok := s["type"]; ok && !ty.IsKnown() { 9839 s["type"] = cty.StringVal(req.TypeName) 9840 } 9841 resp.NewState = cty.ObjectVal(s) 9842 return resp 9843 } 9844 9845 provisionerFactory := func() (provisioners.Interface, error) { 9846 pr := testProvisioner() 9847 pr.ProvisionResourceFn = func(req provisioners.ProvisionResourceRequest) (resp provisioners.ProvisionResourceResponse) { 9848 host := req.Connection.GetAttr("host") 9849 if host.IsNull() || !host.IsKnown() { 9850 resp.Diagnostics = resp.Diagnostics.Append(fmt.Errorf("invalid host value: %#v", host)) 9851 } 9852 9853 return resp 9854 } 9855 return pr, nil 9856 } 9857 9858 Providers := map[addrs.Provider]providers.Factory{ 9859 addrs.NewDefaultProvider("test"): testProviderFuncFixed(p), 9860 } 9861 9862 provisioners := map[string]provisioners.Factory{ 9863 "shell": provisionerFactory, 9864 } 9865 9866 hook := &testHook{} 9867 ctx := testContext2(t, &ContextOpts{ 9868 Providers: Providers, 9869 Provisioners: provisioners, 9870 Hooks: []Hook{hook}, 9871 }) 9872 9873 plan, diags := ctx.Plan(m, states.NewState(), SimplePlanOpts(plans.NormalMode, testInputValuesUnset(m.Module.Variables))) 9874 diags.HasErrors() 9875 if diags.HasErrors() { 9876 t.Fatalf("diags: %s", diags.Err()) 9877 } 9878 9879 _, diags = ctx.Apply(plan, m) 9880 if diags.HasErrors() { 9881 t.Fatalf("diags: %s", diags.Err()) 9882 } 9883 } 9884 9885 func TestContext2Apply_cbdCycle(t *testing.T) { 9886 m, snap := testModuleWithSnapshot(t, "apply-cbd-cycle") 9887 p := testProvider("test") 9888 p.PlanResourceChangeFn = testDiffFn 9889 9890 state := states.NewState() 9891 root := state.EnsureModule(addrs.RootModuleInstance) 9892 root.SetResourceInstanceCurrent( 9893 addrs.Resource{ 9894 Mode: addrs.ManagedResourceMode, 9895 Type: "test_instance", 9896 Name: "a", 9897 }.Instance(addrs.NoKey), 9898 &states.ResourceInstanceObjectSrc{ 9899 Status: states.ObjectReady, 9900 AttrsJSON: []byte(`{"id":"a","require_new":"old","foo":"b"}`), 9901 Dependencies: []addrs.ConfigResource{ 9902 { 9903 Resource: addrs.Resource{ 9904 Mode: addrs.ManagedResourceMode, 9905 Type: "test_instance", 9906 Name: "b", 9907 }, 9908 Module: addrs.RootModule, 9909 }, 9910 { 9911 Resource: addrs.Resource{ 9912 Mode: addrs.ManagedResourceMode, 9913 Type: "test_instance", 9914 Name: "c", 9915 }, 9916 Module: addrs.RootModule, 9917 }, 9918 }, 9919 }, 9920 addrs.AbsProviderConfig{ 9921 Provider: addrs.NewDefaultProvider("test"), 9922 Module: addrs.RootModule, 9923 }, 9924 ) 9925 root.SetResourceInstanceCurrent( 9926 addrs.Resource{ 9927 Mode: addrs.ManagedResourceMode, 9928 Type: "test_instance", 9929 Name: "b", 9930 }.Instance(addrs.NoKey), 9931 &states.ResourceInstanceObjectSrc{ 9932 Status: states.ObjectReady, 9933 AttrsJSON: []byte(`{"id":"b","require_new":"old","foo":"c"}`), 9934 Dependencies: []addrs.ConfigResource{ 9935 { 9936 Resource: addrs.Resource{ 9937 Mode: addrs.ManagedResourceMode, 9938 Type: "test_instance", 9939 Name: "c", 9940 }, 9941 Module: addrs.RootModule, 9942 }, 9943 }, 9944 }, 9945 addrs.AbsProviderConfig{ 9946 Provider: addrs.NewDefaultProvider("test"), 9947 Module: addrs.RootModule, 9948 }, 9949 ) 9950 root.SetResourceInstanceCurrent( 9951 addrs.Resource{ 9952 Mode: addrs.ManagedResourceMode, 9953 Type: "test_instance", 9954 Name: "c", 9955 }.Instance(addrs.NoKey), 9956 &states.ResourceInstanceObjectSrc{ 9957 Status: states.ObjectReady, 9958 AttrsJSON: []byte(`{"id":"c","require_new":"old"}`), 9959 }, 9960 addrs.AbsProviderConfig{ 9961 Provider: addrs.NewDefaultProvider("test"), 9962 Module: addrs.RootModule, 9963 }, 9964 ) 9965 9966 Providers := map[addrs.Provider]providers.Factory{ 9967 addrs.NewDefaultProvider("test"): testProviderFuncFixed(p), 9968 } 9969 9970 hook := &testHook{} 9971 ctx := testContext2(t, &ContextOpts{ 9972 Providers: Providers, 9973 Hooks: []Hook{hook}, 9974 }) 9975 9976 plan, diags := ctx.Plan(m, state, DefaultPlanOpts) 9977 diags.HasErrors() 9978 if diags.HasErrors() { 9979 t.Fatalf("diags: %s", diags.Err()) 9980 } 9981 9982 // We'll marshal and unmarshal the plan here, to ensure that we have 9983 // a clean new context as would be created if we separately ran 9984 // tofu plan -out=tfplan && tofu apply tfplan 9985 ctxOpts, m, plan, err := contextOptsForPlanViaFile(t, snap, plan) 9986 if err != nil { 9987 t.Fatal(err) 9988 } 9989 ctxOpts.Providers = Providers 9990 ctx, diags = NewContext(ctxOpts) 9991 if diags.HasErrors() { 9992 t.Fatalf("failed to create context for plan: %s", diags.Err()) 9993 } 9994 9995 _, diags = ctx.Apply(plan, m) 9996 if diags.HasErrors() { 9997 t.Fatalf("diags: %s", diags.Err()) 9998 } 9999 } 10000 10001 func TestContext2Apply_ProviderMeta_apply_set(t *testing.T) { 10002 m := testModule(t, "provider-meta-set") 10003 p := testProvider("test") 10004 p.PlanResourceChangeFn = testDiffFn 10005 schema := p.ProviderSchema() 10006 schema.ProviderMeta = &configschema.Block{ 10007 Attributes: map[string]*configschema.Attribute{ 10008 "baz": { 10009 Type: cty.String, 10010 Required: true, 10011 }, 10012 }, 10013 } 10014 10015 var pmMu sync.Mutex 10016 arcPMs := map[string]cty.Value{} 10017 10018 p.ApplyResourceChangeFn = func(req providers.ApplyResourceChangeRequest) providers.ApplyResourceChangeResponse { 10019 pmMu.Lock() 10020 defer pmMu.Unlock() 10021 arcPMs[req.TypeName] = req.ProviderMeta 10022 10023 s := req.PlannedState.AsValueMap() 10024 s["id"] = cty.StringVal("ID") 10025 if ty, ok := s["type"]; ok && !ty.IsKnown() { 10026 s["type"] = cty.StringVal(req.TypeName) 10027 } 10028 return providers.ApplyResourceChangeResponse{ 10029 NewState: cty.ObjectVal(s), 10030 } 10031 } 10032 p.GetProviderSchemaResponse = getProviderSchemaResponseFromProviderSchema(schema) 10033 ctx := testContext2(t, &ContextOpts{ 10034 Providers: map[addrs.Provider]providers.Factory{ 10035 addrs.NewDefaultProvider("test"): testProviderFuncFixed(p), 10036 }, 10037 }) 10038 10039 plan, diags := ctx.Plan(m, states.NewState(), DefaultPlanOpts) 10040 assertNoErrors(t, diags) 10041 10042 _, diags = ctx.Apply(plan, m) 10043 assertNoErrors(t, diags) 10044 10045 if !p.ApplyResourceChangeCalled { 10046 t.Fatalf("ApplyResourceChange not called") 10047 } 10048 10049 expectations := map[string]cty.Value{} 10050 10051 if pm, ok := arcPMs["test_resource"]; !ok { 10052 t.Fatalf("sub-module ApplyResourceChange not called") 10053 } else if pm.IsNull() { 10054 t.Fatalf("null ProviderMeta in sub-module ApplyResourceChange") 10055 } else { 10056 expectations["quux-submodule"] = pm 10057 } 10058 10059 if pm, ok := arcPMs["test_instance"]; !ok { 10060 t.Fatalf("root module ApplyResourceChange not called") 10061 } else if pm.IsNull() { 10062 t.Fatalf("null ProviderMeta in root module ApplyResourceChange") 10063 } else { 10064 expectations["quux"] = pm 10065 } 10066 10067 type metaStruct struct { 10068 Baz string `cty:"baz"` 10069 } 10070 10071 for expected, v := range expectations { 10072 var meta metaStruct 10073 err := gocty.FromCtyValue(v, &meta) 10074 if err != nil { 10075 t.Fatalf("Error parsing cty value: %s", err) 10076 } 10077 if meta.Baz != expected { 10078 t.Fatalf("Expected meta.Baz to be %q, got %q", expected, meta.Baz) 10079 } 10080 } 10081 } 10082 10083 func TestContext2Apply_ProviderMeta_apply_unset(t *testing.T) { 10084 m := testModule(t, "provider-meta-unset") 10085 p := testProvider("test") 10086 p.PlanResourceChangeFn = testDiffFn 10087 schema := p.ProviderSchema() 10088 schema.ProviderMeta = &configschema.Block{ 10089 Attributes: map[string]*configschema.Attribute{ 10090 "baz": { 10091 Type: cty.String, 10092 Required: true, 10093 }, 10094 }, 10095 } 10096 var pmMu sync.Mutex 10097 arcPMs := map[string]cty.Value{} 10098 p.ApplyResourceChangeFn = func(req providers.ApplyResourceChangeRequest) providers.ApplyResourceChangeResponse { 10099 pmMu.Lock() 10100 defer pmMu.Unlock() 10101 arcPMs[req.TypeName] = req.ProviderMeta 10102 10103 s := req.PlannedState.AsValueMap() 10104 s["id"] = cty.StringVal("ID") 10105 if ty, ok := s["type"]; ok && !ty.IsKnown() { 10106 s["type"] = cty.StringVal(req.TypeName) 10107 } 10108 return providers.ApplyResourceChangeResponse{ 10109 NewState: cty.ObjectVal(s), 10110 } 10111 } 10112 p.GetProviderSchemaResponse = getProviderSchemaResponseFromProviderSchema(schema) 10113 ctx := testContext2(t, &ContextOpts{ 10114 Providers: map[addrs.Provider]providers.Factory{ 10115 addrs.NewDefaultProvider("test"): testProviderFuncFixed(p), 10116 }, 10117 }) 10118 10119 plan, diags := ctx.Plan(m, states.NewState(), DefaultPlanOpts) 10120 assertNoErrors(t, diags) 10121 10122 _, diags = ctx.Apply(plan, m) 10123 assertNoErrors(t, diags) 10124 10125 if !p.ApplyResourceChangeCalled { 10126 t.Fatalf("ApplyResourceChange not called") 10127 } 10128 10129 if pm, ok := arcPMs["test_resource"]; !ok { 10130 t.Fatalf("sub-module ApplyResourceChange not called") 10131 } else if !pm.IsNull() { 10132 t.Fatalf("non-null ProviderMeta in sub-module ApplyResourceChange: %+v", pm) 10133 } 10134 10135 if pm, ok := arcPMs["test_instance"]; !ok { 10136 t.Fatalf("root module ApplyResourceChange not called") 10137 } else if !pm.IsNull() { 10138 t.Fatalf("non-null ProviderMeta in root module ApplyResourceChange: %+v", pm) 10139 } 10140 } 10141 10142 func TestContext2Apply_ProviderMeta_plan_set(t *testing.T) { 10143 m := testModule(t, "provider-meta-set") 10144 p := testProvider("test") 10145 schema := p.ProviderSchema() 10146 schema.ProviderMeta = &configschema.Block{ 10147 Attributes: map[string]*configschema.Attribute{ 10148 "baz": { 10149 Type: cty.String, 10150 Required: true, 10151 }, 10152 }, 10153 } 10154 prcPMs := map[string]cty.Value{} 10155 p.PlanResourceChangeFn = func(req providers.PlanResourceChangeRequest) providers.PlanResourceChangeResponse { 10156 prcPMs[req.TypeName] = req.ProviderMeta 10157 return providers.PlanResourceChangeResponse{ 10158 PlannedState: req.ProposedNewState, 10159 } 10160 } 10161 p.GetProviderSchemaResponse = getProviderSchemaResponseFromProviderSchema(schema) 10162 ctx := testContext2(t, &ContextOpts{ 10163 Providers: map[addrs.Provider]providers.Factory{ 10164 addrs.NewDefaultProvider("test"): testProviderFuncFixed(p), 10165 }, 10166 }) 10167 10168 _, diags := ctx.Plan(m, states.NewState(), DefaultPlanOpts) 10169 assertNoErrors(t, diags) 10170 10171 if !p.PlanResourceChangeCalled { 10172 t.Fatalf("PlanResourceChange not called") 10173 } 10174 10175 expectations := map[string]cty.Value{} 10176 10177 if pm, ok := prcPMs["test_resource"]; !ok { 10178 t.Fatalf("sub-module PlanResourceChange not called") 10179 } else if pm.IsNull() { 10180 t.Fatalf("null ProviderMeta in sub-module PlanResourceChange") 10181 } else { 10182 expectations["quux-submodule"] = pm 10183 } 10184 10185 if pm, ok := prcPMs["test_instance"]; !ok { 10186 t.Fatalf("root module PlanResourceChange not called") 10187 } else if pm.IsNull() { 10188 t.Fatalf("null ProviderMeta in root module PlanResourceChange") 10189 } else { 10190 expectations["quux"] = pm 10191 } 10192 10193 type metaStruct struct { 10194 Baz string `cty:"baz"` 10195 } 10196 10197 for expected, v := range expectations { 10198 var meta metaStruct 10199 err := gocty.FromCtyValue(v, &meta) 10200 if err != nil { 10201 t.Fatalf("Error parsing cty value: %s", err) 10202 } 10203 if meta.Baz != expected { 10204 t.Fatalf("Expected meta.Baz to be %q, got %q", expected, meta.Baz) 10205 } 10206 } 10207 } 10208 10209 func TestContext2Apply_ProviderMeta_plan_unset(t *testing.T) { 10210 m := testModule(t, "provider-meta-unset") 10211 p := testProvider("test") 10212 schema := p.ProviderSchema() 10213 schema.ProviderMeta = &configschema.Block{ 10214 Attributes: map[string]*configschema.Attribute{ 10215 "baz": { 10216 Type: cty.String, 10217 Required: true, 10218 }, 10219 }, 10220 } 10221 prcPMs := map[string]cty.Value{} 10222 p.PlanResourceChangeFn = func(req providers.PlanResourceChangeRequest) providers.PlanResourceChangeResponse { 10223 prcPMs[req.TypeName] = req.ProviderMeta 10224 return providers.PlanResourceChangeResponse{ 10225 PlannedState: req.ProposedNewState, 10226 } 10227 } 10228 p.GetProviderSchemaResponse = getProviderSchemaResponseFromProviderSchema(schema) 10229 ctx := testContext2(t, &ContextOpts{ 10230 Providers: map[addrs.Provider]providers.Factory{ 10231 addrs.NewDefaultProvider("test"): testProviderFuncFixed(p), 10232 }, 10233 }) 10234 10235 _, diags := ctx.Plan(m, states.NewState(), DefaultPlanOpts) 10236 assertNoErrors(t, diags) 10237 10238 if !p.PlanResourceChangeCalled { 10239 t.Fatalf("PlanResourceChange not called") 10240 } 10241 10242 if pm, ok := prcPMs["test_resource"]; !ok { 10243 t.Fatalf("sub-module PlanResourceChange not called") 10244 } else if !pm.IsNull() { 10245 t.Fatalf("non-null ProviderMeta in sub-module PlanResourceChange: %+v", pm) 10246 } 10247 10248 if pm, ok := prcPMs["test_instance"]; !ok { 10249 t.Fatalf("root module PlanResourceChange not called") 10250 } else if !pm.IsNull() { 10251 t.Fatalf("non-null ProviderMeta in root module PlanResourceChange: %+v", pm) 10252 } 10253 } 10254 10255 func TestContext2Apply_ProviderMeta_plan_setNoSchema(t *testing.T) { 10256 m := testModule(t, "provider-meta-set") 10257 p := testProvider("test") 10258 p.PlanResourceChangeFn = testDiffFn 10259 ctx := testContext2(t, &ContextOpts{ 10260 Providers: map[addrs.Provider]providers.Factory{ 10261 addrs.NewDefaultProvider("test"): testProviderFuncFixed(p), 10262 }, 10263 }) 10264 10265 _, diags := ctx.Plan(m, states.NewState(), DefaultPlanOpts) 10266 if !diags.HasErrors() { 10267 t.Fatalf("plan supposed to error, has no errors") 10268 } 10269 10270 var rootErr, subErr bool 10271 errorSummary := "The resource test_%s.bar belongs to a provider that doesn't support provider_meta blocks" 10272 for _, diag := range diags { 10273 if diag.Description().Summary != "Provider registry.opentofu.org/hashicorp/test doesn't support provider_meta" { 10274 t.Errorf("Unexpected error: %+v", diag.Description()) 10275 } 10276 switch diag.Description().Detail { 10277 case fmt.Sprintf(errorSummary, "instance"): 10278 rootErr = true 10279 case fmt.Sprintf(errorSummary, "resource"): 10280 subErr = true 10281 default: 10282 t.Errorf("Unexpected error: %s", diag.Description()) 10283 } 10284 } 10285 if !rootErr { 10286 t.Errorf("Expected unsupported provider_meta block error for root module, none received") 10287 } 10288 if !subErr { 10289 t.Errorf("Expected unsupported provider_meta block error for sub-module, none received") 10290 } 10291 } 10292 10293 func TestContext2Apply_ProviderMeta_plan_setInvalid(t *testing.T) { 10294 m := testModule(t, "provider-meta-set") 10295 p := testProvider("test") 10296 p.PlanResourceChangeFn = testDiffFn 10297 schema := p.ProviderSchema() 10298 schema.ProviderMeta = &configschema.Block{ 10299 Attributes: map[string]*configschema.Attribute{ 10300 "quux": { 10301 Type: cty.String, 10302 Required: true, 10303 }, 10304 }, 10305 } 10306 p.GetProviderSchemaResponse = getProviderSchemaResponseFromProviderSchema(schema) 10307 ctx := testContext2(t, &ContextOpts{ 10308 Providers: map[addrs.Provider]providers.Factory{ 10309 addrs.NewDefaultProvider("test"): testProviderFuncFixed(p), 10310 }, 10311 }) 10312 10313 _, diags := ctx.Plan(m, states.NewState(), DefaultPlanOpts) 10314 if !diags.HasErrors() { 10315 t.Fatalf("plan supposed to error, has no errors") 10316 } 10317 10318 var reqErr, invalidErr bool 10319 for _, diag := range diags { 10320 switch diag.Description().Summary { 10321 case "Missing required argument": 10322 if diag.Description().Detail == `The argument "quux" is required, but no definition was found.` { 10323 reqErr = true 10324 } else { 10325 t.Errorf("Unexpected error %+v", diag.Description()) 10326 } 10327 case "Unsupported argument": 10328 if diag.Description().Detail == `An argument named "baz" is not expected here.` { 10329 invalidErr = true 10330 } else { 10331 t.Errorf("Unexpected error %+v", diag.Description()) 10332 } 10333 default: 10334 t.Errorf("Unexpected error %+v", diag.Description()) 10335 } 10336 } 10337 if !reqErr { 10338 t.Errorf("Expected missing required argument error, none received") 10339 } 10340 if !invalidErr { 10341 t.Errorf("Expected unsupported argument error, none received") 10342 } 10343 } 10344 10345 func TestContext2Apply_ProviderMeta_refresh_set(t *testing.T) { 10346 m := testModule(t, "provider-meta-set") 10347 p := testProvider("test") 10348 p.PlanResourceChangeFn = testDiffFn 10349 schema := p.ProviderSchema() 10350 schema.ProviderMeta = &configschema.Block{ 10351 Attributes: map[string]*configschema.Attribute{ 10352 "baz": { 10353 Type: cty.String, 10354 Required: true, 10355 }, 10356 }, 10357 } 10358 rrcPMs := map[string]cty.Value{} 10359 p.ReadResourceFn = func(req providers.ReadResourceRequest) (resp providers.ReadResourceResponse) { 10360 rrcPMs[req.TypeName] = req.ProviderMeta 10361 newState, err := p.GetProviderSchemaResponse.ResourceTypes[req.TypeName].Block.CoerceValue(req.PriorState) 10362 if err != nil { 10363 panic(err) 10364 } 10365 resp.NewState = newState 10366 return resp 10367 } 10368 p.GetProviderSchemaResponse = getProviderSchemaResponseFromProviderSchema(schema) 10369 ctx := testContext2(t, &ContextOpts{ 10370 Providers: map[addrs.Provider]providers.Factory{ 10371 addrs.NewDefaultProvider("test"): testProviderFuncFixed(p), 10372 }, 10373 }) 10374 10375 plan, diags := ctx.Plan(m, states.NewState(), DefaultPlanOpts) 10376 assertNoErrors(t, diags) 10377 10378 state, diags := ctx.Apply(plan, m) 10379 assertNoErrors(t, diags) 10380 10381 _, diags = ctx.Refresh(m, state, DefaultPlanOpts) 10382 assertNoErrors(t, diags) 10383 10384 if !p.ReadResourceCalled { 10385 t.Fatalf("ReadResource not called") 10386 } 10387 10388 expectations := map[string]cty.Value{} 10389 10390 if pm, ok := rrcPMs["test_resource"]; !ok { 10391 t.Fatalf("sub-module ReadResource not called") 10392 } else if pm.IsNull() { 10393 t.Fatalf("null ProviderMeta in sub-module ReadResource") 10394 } else { 10395 expectations["quux-submodule"] = pm 10396 } 10397 10398 if pm, ok := rrcPMs["test_instance"]; !ok { 10399 t.Fatalf("root module ReadResource not called") 10400 } else if pm.IsNull() { 10401 t.Fatalf("null ProviderMeta in root module ReadResource") 10402 } else { 10403 expectations["quux"] = pm 10404 } 10405 10406 type metaStruct struct { 10407 Baz string `cty:"baz"` 10408 } 10409 10410 for expected, v := range expectations { 10411 var meta metaStruct 10412 err := gocty.FromCtyValue(v, &meta) 10413 if err != nil { 10414 t.Fatalf("Error parsing cty value: %s", err) 10415 } 10416 if meta.Baz != expected { 10417 t.Fatalf("Expected meta.Baz to be %q, got %q", expected, meta.Baz) 10418 } 10419 } 10420 } 10421 10422 func TestContext2Apply_ProviderMeta_refresh_setNoSchema(t *testing.T) { 10423 m := testModule(t, "provider-meta-set") 10424 p := testProvider("test") 10425 p.PlanResourceChangeFn = testDiffFn 10426 10427 // we need a schema for plan/apply so they don't error 10428 schema := p.ProviderSchema() 10429 schema.ProviderMeta = &configschema.Block{ 10430 Attributes: map[string]*configschema.Attribute{ 10431 "baz": { 10432 Type: cty.String, 10433 Required: true, 10434 }, 10435 }, 10436 } 10437 p.GetProviderSchemaResponse = getProviderSchemaResponseFromProviderSchema(schema) 10438 ctx := testContext2(t, &ContextOpts{ 10439 Providers: map[addrs.Provider]providers.Factory{ 10440 addrs.NewDefaultProvider("test"): testProviderFuncFixed(p), 10441 }, 10442 }) 10443 10444 plan, diags := ctx.Plan(m, states.NewState(), DefaultPlanOpts) 10445 assertNoErrors(t, diags) 10446 10447 state, diags := ctx.Apply(plan, m) 10448 assertNoErrors(t, diags) 10449 10450 // drop the schema before refresh, to test that it errors 10451 schema.ProviderMeta = nil 10452 p.GetProviderSchemaResponse = getProviderSchemaResponseFromProviderSchema(schema) 10453 ctx = testContext2(t, &ContextOpts{ 10454 Providers: map[addrs.Provider]providers.Factory{ 10455 addrs.NewDefaultProvider("test"): testProviderFuncFixed(p), 10456 }, 10457 }) 10458 10459 _, diags = ctx.Refresh(m, state, DefaultPlanOpts) 10460 if !diags.HasErrors() { 10461 t.Fatalf("refresh supposed to error, has no errors") 10462 } 10463 10464 var rootErr, subErr bool 10465 errorSummary := "The resource test_%s.bar belongs to a provider that doesn't support provider_meta blocks" 10466 for _, diag := range diags { 10467 if diag.Description().Summary != "Provider registry.opentofu.org/hashicorp/test doesn't support provider_meta" { 10468 t.Errorf("Unexpected error: %+v", diag.Description()) 10469 } 10470 switch diag.Description().Detail { 10471 case fmt.Sprintf(errorSummary, "instance"): 10472 rootErr = true 10473 case fmt.Sprintf(errorSummary, "resource"): 10474 subErr = true 10475 default: 10476 t.Errorf("Unexpected error: %s", diag.Description()) 10477 } 10478 } 10479 if !rootErr { 10480 t.Errorf("Expected unsupported provider_meta block error for root module, none received") 10481 } 10482 if !subErr { 10483 t.Errorf("Expected unsupported provider_meta block error for sub-module, none received") 10484 } 10485 } 10486 10487 func TestContext2Apply_ProviderMeta_refresh_setInvalid(t *testing.T) { 10488 m := testModule(t, "provider-meta-set") 10489 p := testProvider("test") 10490 p.PlanResourceChangeFn = testDiffFn 10491 10492 // we need a matching schema for plan/apply so they don't error 10493 schema := p.ProviderSchema() 10494 schema.ProviderMeta = &configschema.Block{ 10495 Attributes: map[string]*configschema.Attribute{ 10496 "baz": { 10497 Type: cty.String, 10498 Required: true, 10499 }, 10500 }, 10501 } 10502 p.GetProviderSchemaResponse = getProviderSchemaResponseFromProviderSchema(schema) 10503 ctx := testContext2(t, &ContextOpts{ 10504 Providers: map[addrs.Provider]providers.Factory{ 10505 addrs.NewDefaultProvider("test"): testProviderFuncFixed(p), 10506 }, 10507 }) 10508 10509 plan, diags := ctx.Plan(m, states.NewState(), DefaultPlanOpts) 10510 assertNoErrors(t, diags) 10511 10512 state, diags := ctx.Apply(plan, m) 10513 assertNoErrors(t, diags) 10514 10515 // change the schema before refresh, to test that it errors 10516 schema.ProviderMeta = &configschema.Block{ 10517 Attributes: map[string]*configschema.Attribute{ 10518 "quux": { 10519 Type: cty.String, 10520 Required: true, 10521 }, 10522 }, 10523 } 10524 p.GetProviderSchemaResponse = getProviderSchemaResponseFromProviderSchema(schema) 10525 ctx = testContext2(t, &ContextOpts{ 10526 Providers: map[addrs.Provider]providers.Factory{ 10527 addrs.NewDefaultProvider("test"): testProviderFuncFixed(p), 10528 }, 10529 }) 10530 10531 _, diags = ctx.Refresh(m, state, DefaultPlanOpts) 10532 if !diags.HasErrors() { 10533 t.Fatalf("refresh supposed to error, has no errors") 10534 } 10535 10536 var reqErr, invalidErr bool 10537 for _, diag := range diags { 10538 switch diag.Description().Summary { 10539 case "Missing required argument": 10540 if diag.Description().Detail == `The argument "quux" is required, but no definition was found.` { 10541 reqErr = true 10542 } else { 10543 t.Errorf("Unexpected error %+v", diag.Description()) 10544 } 10545 case "Unsupported argument": 10546 if diag.Description().Detail == `An argument named "baz" is not expected here.` { 10547 invalidErr = true 10548 } else { 10549 t.Errorf("Unexpected error %+v", diag.Description()) 10550 } 10551 default: 10552 t.Errorf("Unexpected error %+v", diag.Description()) 10553 } 10554 } 10555 if !reqErr { 10556 t.Errorf("Expected missing required argument error, none received") 10557 } 10558 if !invalidErr { 10559 t.Errorf("Expected unsupported argument error, none received") 10560 } 10561 } 10562 10563 func TestContext2Apply_ProviderMeta_refreshdata_set(t *testing.T) { 10564 m := testModule(t, "provider-meta-data-set") 10565 p := testProvider("test") 10566 p.PlanResourceChangeFn = testDiffFn 10567 schema := p.ProviderSchema() 10568 schema.ProviderMeta = &configschema.Block{ 10569 Attributes: map[string]*configschema.Attribute{ 10570 "baz": { 10571 Type: cty.String, 10572 Required: true, 10573 }, 10574 }, 10575 } 10576 p.GetProviderSchemaResponse = getProviderSchemaResponseFromProviderSchema(schema) 10577 ctx := testContext2(t, &ContextOpts{ 10578 Providers: map[addrs.Provider]providers.Factory{ 10579 addrs.NewDefaultProvider("test"): testProviderFuncFixed(p), 10580 }, 10581 }) 10582 rdsPMs := map[string]cty.Value{} 10583 p.ReadDataSourceFn = func(req providers.ReadDataSourceRequest) providers.ReadDataSourceResponse { 10584 rdsPMs[req.TypeName] = req.ProviderMeta 10585 switch req.TypeName { 10586 case "test_data_source": 10587 log.Printf("[TRACE] test_data_source RDSR returning") 10588 return providers.ReadDataSourceResponse{ 10589 State: cty.ObjectVal(map[string]cty.Value{ 10590 "id": cty.StringVal("yo"), 10591 "foo": cty.StringVal("bar"), 10592 }), 10593 } 10594 case "test_file": 10595 log.Printf("[TRACE] test_file RDSR returning") 10596 return providers.ReadDataSourceResponse{ 10597 State: cty.ObjectVal(map[string]cty.Value{ 10598 "id": cty.StringVal("bar"), 10599 "rendered": cty.StringVal("baz"), 10600 "template": cty.StringVal(""), 10601 }), 10602 } 10603 default: 10604 // config drift, oops 10605 log.Printf("[TRACE] unknown request TypeName: %q", req.TypeName) 10606 return providers.ReadDataSourceResponse{} 10607 } 10608 } 10609 10610 plan, diags := ctx.Plan(m, states.NewState(), DefaultPlanOpts) 10611 assertNoErrors(t, diags) 10612 10613 state, diags := ctx.Apply(plan, m) 10614 assertNoErrors(t, diags) 10615 10616 _, diags = ctx.Refresh(m, state, DefaultPlanOpts) 10617 assertNoErrors(t, diags) 10618 10619 if !p.ReadDataSourceCalled { 10620 t.Fatalf("ReadDataSource not called") 10621 } 10622 10623 expectations := map[string]cty.Value{} 10624 10625 if pm, ok := rdsPMs["test_file"]; !ok { 10626 t.Fatalf("sub-module ReadDataSource not called") 10627 } else if pm.IsNull() { 10628 t.Fatalf("null ProviderMeta in sub-module ReadDataSource") 10629 } else { 10630 expectations["quux-submodule"] = pm 10631 } 10632 10633 if pm, ok := rdsPMs["test_data_source"]; !ok { 10634 t.Fatalf("root module ReadDataSource not called") 10635 } else if pm.IsNull() { 10636 t.Fatalf("null ProviderMeta in root module ReadDataSource") 10637 } else { 10638 expectations["quux"] = pm 10639 } 10640 10641 type metaStruct struct { 10642 Baz string `cty:"baz"` 10643 } 10644 10645 for expected, v := range expectations { 10646 var meta metaStruct 10647 err := gocty.FromCtyValue(v, &meta) 10648 if err != nil { 10649 t.Fatalf("Error parsing cty value: %s", err) 10650 } 10651 if meta.Baz != expected { 10652 t.Fatalf("Expected meta.Baz to be %q, got %q", expected, meta.Baz) 10653 } 10654 } 10655 } 10656 10657 func TestContext2Apply_ProviderMeta_refreshdata_unset(t *testing.T) { 10658 m := testModule(t, "provider-meta-data-unset") 10659 p := testProvider("test") 10660 p.PlanResourceChangeFn = testDiffFn 10661 schema := p.ProviderSchema() 10662 schema.ProviderMeta = &configschema.Block{ 10663 Attributes: map[string]*configschema.Attribute{ 10664 "baz": { 10665 Type: cty.String, 10666 Required: true, 10667 }, 10668 }, 10669 } 10670 p.GetProviderSchemaResponse = getProviderSchemaResponseFromProviderSchema(schema) 10671 ctx := testContext2(t, &ContextOpts{ 10672 Providers: map[addrs.Provider]providers.Factory{ 10673 addrs.NewDefaultProvider("test"): testProviderFuncFixed(p), 10674 }, 10675 }) 10676 rdsPMs := map[string]cty.Value{} 10677 p.ReadDataSourceFn = func(req providers.ReadDataSourceRequest) providers.ReadDataSourceResponse { 10678 rdsPMs[req.TypeName] = req.ProviderMeta 10679 switch req.TypeName { 10680 case "test_data_source": 10681 return providers.ReadDataSourceResponse{ 10682 State: cty.ObjectVal(map[string]cty.Value{ 10683 "id": cty.StringVal("yo"), 10684 "foo": cty.StringVal("bar"), 10685 }), 10686 } 10687 case "test_file": 10688 return providers.ReadDataSourceResponse{ 10689 State: cty.ObjectVal(map[string]cty.Value{ 10690 "id": cty.StringVal("bar"), 10691 "rendered": cty.StringVal("baz"), 10692 "template": cty.StringVal(""), 10693 }), 10694 } 10695 default: 10696 // config drift, oops 10697 return providers.ReadDataSourceResponse{} 10698 } 10699 } 10700 10701 plan, diags := ctx.Plan(m, states.NewState(), DefaultPlanOpts) 10702 assertNoErrors(t, diags) 10703 10704 _, diags = ctx.Apply(plan, m) 10705 assertNoErrors(t, diags) 10706 10707 if !p.ReadDataSourceCalled { 10708 t.Fatalf("ReadDataSource not called") 10709 } 10710 10711 if pm, ok := rdsPMs["test_file"]; !ok { 10712 t.Fatalf("sub-module ReadDataSource not called") 10713 } else if !pm.IsNull() { 10714 t.Fatalf("non-null ProviderMeta in sub-module ReadDataSource") 10715 } 10716 10717 if pm, ok := rdsPMs["test_data_source"]; !ok { 10718 t.Fatalf("root module ReadDataSource not called") 10719 } else if !pm.IsNull() { 10720 t.Fatalf("non-null ProviderMeta in root module ReadDataSource") 10721 } 10722 } 10723 10724 func TestContext2Apply_ProviderMeta_refreshdata_setNoSchema(t *testing.T) { 10725 m := testModule(t, "provider-meta-data-set") 10726 p := testProvider("test") 10727 p.PlanResourceChangeFn = testDiffFn 10728 ctx := testContext2(t, &ContextOpts{ 10729 Providers: map[addrs.Provider]providers.Factory{ 10730 addrs.NewDefaultProvider("test"): testProviderFuncFixed(p), 10731 }, 10732 }) 10733 p.ReadDataSourceResponse = &providers.ReadDataSourceResponse{ 10734 State: cty.ObjectVal(map[string]cty.Value{ 10735 "id": cty.StringVal("yo"), 10736 "foo": cty.StringVal("bar"), 10737 }), 10738 } 10739 10740 _, diags := ctx.Refresh(m, states.NewState(), DefaultPlanOpts) 10741 if !diags.HasErrors() { 10742 t.Fatalf("refresh supposed to error, has no errors") 10743 } 10744 10745 var rootErr, subErr bool 10746 errorSummary := "The resource data.test_%s.foo belongs to a provider that doesn't support provider_meta blocks" 10747 for _, diag := range diags { 10748 if diag.Description().Summary != "Provider registry.opentofu.org/hashicorp/test doesn't support provider_meta" { 10749 t.Errorf("Unexpected error: %+v", diag.Description()) 10750 } 10751 switch diag.Description().Detail { 10752 case fmt.Sprintf(errorSummary, "data_source"): 10753 rootErr = true 10754 case fmt.Sprintf(errorSummary, "file"): 10755 subErr = true 10756 default: 10757 t.Errorf("Unexpected error: %s", diag.Description()) 10758 } 10759 } 10760 if !rootErr { 10761 t.Errorf("Expected unsupported provider_meta block error for root module, none received") 10762 } 10763 if !subErr { 10764 t.Errorf("Expected unsupported provider_meta block error for sub-module, none received") 10765 } 10766 } 10767 10768 func TestContext2Apply_ProviderMeta_refreshdata_setInvalid(t *testing.T) { 10769 m := testModule(t, "provider-meta-data-set") 10770 p := testProvider("test") 10771 p.PlanResourceChangeFn = testDiffFn 10772 schema := p.ProviderSchema() 10773 schema.ProviderMeta = &configschema.Block{ 10774 Attributes: map[string]*configschema.Attribute{ 10775 "quux": { 10776 Type: cty.String, 10777 Required: true, 10778 }, 10779 }, 10780 } 10781 p.GetProviderSchemaResponse = getProviderSchemaResponseFromProviderSchema(schema) 10782 ctx := testContext2(t, &ContextOpts{ 10783 Providers: map[addrs.Provider]providers.Factory{ 10784 addrs.NewDefaultProvider("test"): testProviderFuncFixed(p), 10785 }, 10786 }) 10787 p.ReadDataSourceResponse = &providers.ReadDataSourceResponse{ 10788 State: cty.ObjectVal(map[string]cty.Value{ 10789 "id": cty.StringVal("yo"), 10790 "foo": cty.StringVal("bar"), 10791 }), 10792 } 10793 10794 _, diags := ctx.Refresh(m, states.NewState(), DefaultPlanOpts) 10795 if !diags.HasErrors() { 10796 t.Fatalf("refresh supposed to error, has no errors") 10797 } 10798 10799 var reqErr, invalidErr bool 10800 for _, diag := range diags { 10801 switch diag.Description().Summary { 10802 case "Missing required argument": 10803 if diag.Description().Detail == `The argument "quux" is required, but no definition was found.` { 10804 reqErr = true 10805 } else { 10806 t.Errorf("Unexpected error %+v", diag.Description()) 10807 } 10808 case "Unsupported argument": 10809 if diag.Description().Detail == `An argument named "baz" is not expected here.` { 10810 invalidErr = true 10811 } else { 10812 t.Errorf("Unexpected error %+v", diag.Description()) 10813 } 10814 default: 10815 t.Errorf("Unexpected error %+v", diag.Description()) 10816 } 10817 } 10818 if !reqErr { 10819 t.Errorf("Expected missing required argument error, none received") 10820 } 10821 if !invalidErr { 10822 t.Errorf("Expected unsupported argument error, none received") 10823 } 10824 } 10825 10826 func TestContext2Apply_expandModuleVariables(t *testing.T) { 10827 m := testModuleInline(t, map[string]string{ 10828 "main.tf": ` 10829 module "mod1" { 10830 for_each = toset(["a"]) 10831 source = "./mod" 10832 } 10833 10834 module "mod2" { 10835 source = "./mod" 10836 in = module.mod1["a"].out 10837 } 10838 `, 10839 "mod/main.tf": ` 10840 resource "aws_instance" "foo" { 10841 foo = var.in 10842 } 10843 10844 variable "in" { 10845 type = string 10846 default = "default" 10847 } 10848 10849 output "out" { 10850 value = aws_instance.foo.id 10851 } 10852 `, 10853 }) 10854 10855 p := testProvider("aws") 10856 p.PlanResourceChangeFn = testDiffFn 10857 p.ApplyResourceChangeFn = testApplyFn 10858 ctx := testContext2(t, &ContextOpts{ 10859 Providers: map[addrs.Provider]providers.Factory{ 10860 addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p), 10861 }, 10862 }) 10863 10864 plan, diags := ctx.Plan(m, states.NewState(), DefaultPlanOpts) 10865 if diags.HasErrors() { 10866 t.Fatal(diags.ErrWithWarnings()) 10867 } 10868 10869 state, diags := ctx.Apply(plan, m) 10870 if diags.HasErrors() { 10871 t.Fatal(diags.ErrWithWarnings()) 10872 } 10873 10874 expected := `<no state> 10875 module.mod1["a"]: 10876 aws_instance.foo: 10877 ID = foo 10878 provider = provider["registry.opentofu.org/hashicorp/aws"] 10879 foo = default 10880 type = aws_instance 10881 10882 Outputs: 10883 10884 out = foo 10885 module.mod2: 10886 aws_instance.foo: 10887 ID = foo 10888 provider = provider["registry.opentofu.org/hashicorp/aws"] 10889 foo = foo 10890 type = aws_instance 10891 10892 Dependencies: 10893 module.mod1.aws_instance.foo` 10894 10895 if state.String() != expected { 10896 t.Fatalf("expected:\n%s\ngot:\n%s\n", expected, state) 10897 } 10898 } 10899 10900 func TestContext2Apply_inheritAndStoreCBD(t *testing.T) { 10901 m := testModuleInline(t, map[string]string{ 10902 "main.tf": ` 10903 resource "aws_instance" "foo" { 10904 } 10905 10906 resource "aws_instance" "cbd" { 10907 foo = aws_instance.foo.id 10908 lifecycle { 10909 create_before_destroy = true 10910 } 10911 } 10912 `, 10913 }) 10914 10915 p := testProvider("aws") 10916 p.PlanResourceChangeFn = testDiffFn 10917 ctx := testContext2(t, &ContextOpts{ 10918 Providers: map[addrs.Provider]providers.Factory{ 10919 addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p), 10920 }, 10921 }) 10922 10923 plan, diags := ctx.Plan(m, states.NewState(), DefaultPlanOpts) 10924 if diags.HasErrors() { 10925 t.Fatal(diags.ErrWithWarnings()) 10926 } 10927 10928 state, diags := ctx.Apply(plan, m) 10929 if diags.HasErrors() { 10930 t.Fatal(diags.ErrWithWarnings()) 10931 } 10932 10933 foo := state.ResourceInstance(mustResourceInstanceAddr("aws_instance.foo")) 10934 if !foo.Current.CreateBeforeDestroy { 10935 t.Fatal("aws_instance.foo should also be create_before_destroy") 10936 } 10937 } 10938 10939 func TestContext2Apply_moduleDependsOn(t *testing.T) { 10940 m := testModule(t, "apply-module-depends-on") 10941 10942 p := testProvider("test") 10943 10944 // each instance being applied should happen in sequential order 10945 applied := int64(0) 10946 10947 p.ReadDataSourceFn = func(req providers.ReadDataSourceRequest) providers.ReadDataSourceResponse { 10948 cfg := req.Config.AsValueMap() 10949 foo := cfg["foo"].AsString() 10950 ord := atomic.LoadInt64(&applied) 10951 10952 resp := providers.ReadDataSourceResponse{ 10953 State: cty.ObjectVal(map[string]cty.Value{ 10954 "id": cty.StringVal("data"), 10955 "foo": cfg["foo"], 10956 }), 10957 } 10958 10959 if foo == "a" && ord < 4 { 10960 // due to data source "a"'s module depending on instance 4, this 10961 // should not be less than 4 10962 resp.Diagnostics = resp.Diagnostics.Append(fmt.Errorf("data source a read too early")) 10963 } 10964 if foo == "b" && ord < 1 { 10965 // due to data source "b"'s module depending on instance 1, this 10966 // should not be less than 1 10967 resp.Diagnostics = resp.Diagnostics.Append(fmt.Errorf("data source b read too early")) 10968 } 10969 return resp 10970 } 10971 p.PlanResourceChangeFn = testDiffFn 10972 10973 p.ApplyResourceChangeFn = func(req providers.ApplyResourceChangeRequest) (resp providers.ApplyResourceChangeResponse) { 10974 state := req.PlannedState.AsValueMap() 10975 num, _ := state["num"].AsBigFloat().Float64() 10976 ord := int64(num) 10977 if !atomic.CompareAndSwapInt64(&applied, ord-1, ord) { 10978 actual := atomic.LoadInt64(&applied) 10979 resp.Diagnostics = resp.Diagnostics.Append(fmt.Errorf("instance %d was applied after %d", ord, actual)) 10980 } 10981 10982 state["id"] = cty.StringVal(fmt.Sprintf("test_%d", ord)) 10983 state["type"] = cty.StringVal("test_instance") 10984 resp.NewState = cty.ObjectVal(state) 10985 10986 return resp 10987 } 10988 10989 ctx := testContext2(t, &ContextOpts{ 10990 Providers: map[addrs.Provider]providers.Factory{ 10991 addrs.NewDefaultProvider("test"): testProviderFuncFixed(p), 10992 }, 10993 }) 10994 10995 plan, diags := ctx.Plan(m, states.NewState(), DefaultPlanOpts) 10996 if diags.HasErrors() { 10997 t.Fatal(diags.ErrWithWarnings()) 10998 } 10999 11000 state, diags := ctx.Apply(plan, m) 11001 if diags.HasErrors() { 11002 t.Fatal(diags.ErrWithWarnings()) 11003 } 11004 11005 plan, diags = ctx.Plan(m, state, DefaultPlanOpts) 11006 if diags.HasErrors() { 11007 t.Fatal(diags.ErrWithWarnings()) 11008 } 11009 11010 for _, res := range plan.Changes.Resources { 11011 if res.Action != plans.NoOp { 11012 t.Fatalf("expected NoOp, got %s for %s", res.Action, res.Addr) 11013 } 11014 } 11015 } 11016 11017 func TestContext2Apply_moduleSelfReference(t *testing.T) { 11018 m := testModuleInline(t, map[string]string{ 11019 "main.tf": ` 11020 module "test" { 11021 source = "./test" 11022 11023 a = module.test.b 11024 } 11025 11026 output "c" { 11027 value = module.test.c 11028 } 11029 `, 11030 "test/main.tf": ` 11031 variable "a" {} 11032 11033 resource "test_instance" "test" { 11034 } 11035 11036 output "b" { 11037 value = test_instance.test.id 11038 } 11039 11040 output "c" { 11041 value = var.a 11042 }`}) 11043 11044 p := testProvider("test") 11045 p.PlanResourceChangeFn = testDiffFn 11046 ctx := testContext2(t, &ContextOpts{ 11047 Providers: map[addrs.Provider]providers.Factory{ 11048 addrs.NewDefaultProvider("test"): testProviderFuncFixed(p), 11049 }, 11050 }) 11051 11052 plan, diags := ctx.Plan(m, states.NewState(), DefaultPlanOpts) 11053 if diags.HasErrors() { 11054 t.Fatal(diags.ErrWithWarnings()) 11055 } 11056 11057 state, diags := ctx.Apply(plan, m) 11058 if diags.HasErrors() { 11059 t.Fatal(diags.ErrWithWarnings()) 11060 } 11061 11062 ctx = testContext2(t, &ContextOpts{ 11063 Providers: map[addrs.Provider]providers.Factory{ 11064 addrs.NewDefaultProvider("test"): testProviderFuncFixed(p), 11065 }, 11066 }) 11067 11068 plan, diags = ctx.Plan(m, state, &PlanOpts{ 11069 Mode: plans.DestroyMode, 11070 }) 11071 if diags.HasErrors() { 11072 t.Fatal(diags.ErrWithWarnings()) 11073 } 11074 11075 state, diags = ctx.Apply(plan, m) 11076 if diags.HasErrors() { 11077 t.Fatal(diags.ErrWithWarnings()) 11078 } 11079 11080 if !state.Empty() { 11081 t.Fatal("expected empty state, got:", state) 11082 } 11083 } 11084 11085 func TestContext2Apply_moduleExpandDependsOn(t *testing.T) { 11086 m := testModuleInline(t, map[string]string{ 11087 "main.tf": ` 11088 module "child" { 11089 count = 1 11090 source = "./child" 11091 11092 depends_on = [test_instance.a, test_instance.b] 11093 } 11094 11095 resource "test_instance" "a" { 11096 } 11097 11098 11099 resource "test_instance" "b" { 11100 } 11101 `, 11102 "child/main.tf": ` 11103 resource "test_instance" "foo" { 11104 } 11105 11106 output "myoutput" { 11107 value = "literal string" 11108 } 11109 `}) 11110 11111 p := testProvider("test") 11112 p.PlanResourceChangeFn = testDiffFn 11113 ctx := testContext2(t, &ContextOpts{ 11114 Providers: map[addrs.Provider]providers.Factory{ 11115 addrs.NewDefaultProvider("test"): testProviderFuncFixed(p), 11116 }, 11117 }) 11118 11119 plan, diags := ctx.Plan(m, states.NewState(), DefaultPlanOpts) 11120 if diags.HasErrors() { 11121 t.Fatal(diags.ErrWithWarnings()) 11122 } 11123 11124 state, diags := ctx.Apply(plan, m) 11125 if diags.HasErrors() { 11126 t.Fatal(diags.ErrWithWarnings()) 11127 } 11128 11129 ctx = testContext2(t, &ContextOpts{ 11130 Providers: map[addrs.Provider]providers.Factory{ 11131 addrs.NewDefaultProvider("test"): testProviderFuncFixed(p), 11132 }, 11133 }) 11134 11135 plan, diags = ctx.Plan(m, state, &PlanOpts{ 11136 Mode: plans.DestroyMode, 11137 }) 11138 if diags.HasErrors() { 11139 t.Fatal(diags.ErrWithWarnings()) 11140 } 11141 11142 state, diags = ctx.Apply(plan, m) 11143 if diags.HasErrors() { 11144 t.Fatal(diags.ErrWithWarnings()) 11145 } 11146 11147 if !state.Empty() { 11148 t.Fatal("expected empty state, got:", state) 11149 } 11150 } 11151 11152 func TestContext2Apply_scaleInCBD(t *testing.T) { 11153 m := testModuleInline(t, map[string]string{ 11154 "main.tf": ` 11155 variable "ct" { 11156 type = number 11157 } 11158 11159 resource "test_instance" "a" { 11160 count = var.ct 11161 } 11162 11163 resource "test_instance" "b" { 11164 require_new = local.removable 11165 lifecycle { 11166 create_before_destroy = true 11167 } 11168 } 11169 11170 resource "test_instance" "c" { 11171 require_new = test_instance.b.id 11172 lifecycle { 11173 create_before_destroy = true 11174 } 11175 } 11176 11177 output "out" { 11178 value = join(".", test_instance.a[*].id) 11179 } 11180 11181 locals { 11182 removable = join(".", test_instance.a[*].id) 11183 } 11184 `}) 11185 11186 state := states.NewState() 11187 root := state.EnsureModule(addrs.RootModuleInstance) 11188 root.SetResourceInstanceCurrent( 11189 mustResourceInstanceAddr("test_instance.a[0]").Resource, 11190 &states.ResourceInstanceObjectSrc{ 11191 Status: states.ObjectReady, 11192 AttrsJSON: []byte(`{"id":"a0"}`), 11193 Dependencies: []addrs.ConfigResource{}, 11194 CreateBeforeDestroy: true, 11195 }, 11196 mustProviderConfig(`provider["registry.opentofu.org/hashicorp/test"]`), 11197 ) 11198 root.SetResourceInstanceCurrent( 11199 mustResourceInstanceAddr("test_instance.a[1]").Resource, 11200 &states.ResourceInstanceObjectSrc{ 11201 Status: states.ObjectReady, 11202 AttrsJSON: []byte(`{"id":"a1"}`), 11203 Dependencies: []addrs.ConfigResource{}, 11204 CreateBeforeDestroy: true, 11205 }, 11206 mustProviderConfig(`provider["registry.opentofu.org/hashicorp/test"]`), 11207 ) 11208 root.SetResourceInstanceCurrent( 11209 mustResourceInstanceAddr("test_instance.b").Resource, 11210 &states.ResourceInstanceObjectSrc{ 11211 Status: states.ObjectReady, 11212 AttrsJSON: []byte(`{"id":"b", "require_new":"old.old"}`), 11213 Dependencies: []addrs.ConfigResource{mustConfigResourceAddr("test_instance.a")}, 11214 CreateBeforeDestroy: true, 11215 }, 11216 mustProviderConfig(`provider["registry.opentofu.org/hashicorp/test"]`), 11217 ) 11218 root.SetResourceInstanceCurrent( 11219 mustResourceInstanceAddr("test_instance.c").Resource, 11220 &states.ResourceInstanceObjectSrc{ 11221 Status: states.ObjectReady, 11222 AttrsJSON: []byte(`{"id":"c", "require_new":"b"}`), 11223 Dependencies: []addrs.ConfigResource{ 11224 mustConfigResourceAddr("test_instance.a"), 11225 mustConfigResourceAddr("test_instance.b"), 11226 }, 11227 CreateBeforeDestroy: true, 11228 }, 11229 mustProviderConfig(`provider["registry.opentofu.org/hashicorp/test"]`), 11230 ) 11231 11232 p := testProvider("test") 11233 11234 p.PlanResourceChangeFn = func(r providers.PlanResourceChangeRequest) (resp providers.PlanResourceChangeResponse) { 11235 // this is a destroy plan 11236 if r.ProposedNewState.IsNull() { 11237 resp.PlannedState = r.ProposedNewState 11238 resp.PlannedPrivate = r.PriorPrivate 11239 return resp 11240 } 11241 11242 n := r.ProposedNewState.AsValueMap() 11243 11244 if r.PriorState.IsNull() { 11245 n["id"] = cty.UnknownVal(cty.String) 11246 resp.PlannedState = cty.ObjectVal(n) 11247 return resp 11248 } 11249 11250 p := r.PriorState.AsValueMap() 11251 11252 priorRN := p["require_new"] 11253 newRN := n["require_new"] 11254 11255 if eq := priorRN.Equals(newRN); !eq.IsKnown() || eq.False() { 11256 resp.RequiresReplace = []cty.Path{{cty.GetAttrStep{Name: "require_new"}}} 11257 n["id"] = cty.UnknownVal(cty.String) 11258 } 11259 11260 resp.PlannedState = cty.ObjectVal(n) 11261 return resp 11262 } 11263 11264 // reduce the count to 1 11265 ctx := testContext2(t, &ContextOpts{ 11266 Providers: map[addrs.Provider]providers.Factory{ 11267 addrs.NewDefaultProvider("test"): testProviderFuncFixed(p), 11268 }, 11269 }) 11270 11271 plan, diags := ctx.Plan(m, state, &PlanOpts{ 11272 Mode: plans.NormalMode, 11273 SetVariables: InputValues{ 11274 "ct": &InputValue{ 11275 Value: cty.NumberIntVal(1), 11276 SourceType: ValueFromCaller, 11277 }, 11278 }, 11279 }) 11280 if diags.HasErrors() { 11281 t.Fatal(diags.ErrWithWarnings()) 11282 } 11283 { 11284 addr := mustResourceInstanceAddr("test_instance.a[0]") 11285 change := plan.Changes.ResourceInstance(addr) 11286 if change == nil { 11287 t.Fatalf("no planned change for %s", addr) 11288 } 11289 if got, want := change.PrevRunAddr, mustResourceInstanceAddr("test_instance.a[0]"); !want.Equal(got) { 11290 t.Errorf("wrong previous run address for %s %s; want %s", addr, got, want) 11291 } 11292 if got, want := change.Action, plans.NoOp; got != want { 11293 t.Errorf("wrong action for %s %s; want %s", addr, got, want) 11294 } 11295 if got, want := change.ActionReason, plans.ResourceInstanceChangeNoReason; got != want { 11296 t.Errorf("wrong action reason for %s %s; want %s", addr, got, want) 11297 } 11298 } 11299 { 11300 addr := mustResourceInstanceAddr("test_instance.a[1]") 11301 change := plan.Changes.ResourceInstance(addr) 11302 if change == nil { 11303 t.Fatalf("no planned change for %s", addr) 11304 } 11305 if got, want := change.PrevRunAddr, mustResourceInstanceAddr("test_instance.a[1]"); !want.Equal(got) { 11306 t.Errorf("wrong previous run address for %s %s; want %s", addr, got, want) 11307 } 11308 if got, want := change.Action, plans.Delete; got != want { 11309 t.Errorf("wrong action for %s %s; want %s", addr, got, want) 11310 } 11311 if got, want := change.ActionReason, plans.ResourceInstanceDeleteBecauseCountIndex; got != want { 11312 t.Errorf("wrong action reason for %s %s; want %s", addr, got, want) 11313 } 11314 } 11315 11316 state, diags = ctx.Apply(plan, m) 11317 if diags.HasErrors() { 11318 log.Fatal(diags.ErrWithWarnings()) 11319 } 11320 11321 // check the output, as those can't cause an error planning the value 11322 out := state.RootModule().OutputValues["out"].Value.AsString() 11323 if out != "a0" { 11324 t.Fatalf(`expected output "a0", got: %q`, out) 11325 } 11326 11327 // reduce the count to 0 11328 ctx = testContext2(t, &ContextOpts{ 11329 Providers: map[addrs.Provider]providers.Factory{ 11330 addrs.NewDefaultProvider("test"): testProviderFuncFixed(p), 11331 }, 11332 }) 11333 11334 plan, diags = ctx.Plan(m, state, &PlanOpts{ 11335 Mode: plans.NormalMode, 11336 SetVariables: InputValues{ 11337 "ct": &InputValue{ 11338 Value: cty.NumberIntVal(0), 11339 SourceType: ValueFromCaller, 11340 }, 11341 }, 11342 }) 11343 if diags.HasErrors() { 11344 t.Fatal(diags.ErrWithWarnings()) 11345 } 11346 { 11347 addr := mustResourceInstanceAddr("test_instance.a[0]") 11348 change := plan.Changes.ResourceInstance(addr) 11349 if change == nil { 11350 t.Fatalf("no planned change for %s", addr) 11351 } 11352 if got, want := change.PrevRunAddr, mustResourceInstanceAddr("test_instance.a[0]"); !want.Equal(got) { 11353 t.Errorf("wrong previous run address for %s %s; want %s", addr, got, want) 11354 } 11355 if got, want := change.Action, plans.Delete; got != want { 11356 t.Errorf("wrong action for %s %s; want %s", addr, got, want) 11357 } 11358 if got, want := change.ActionReason, plans.ResourceInstanceDeleteBecauseCountIndex; got != want { 11359 t.Errorf("wrong action reason for %s %s; want %s", addr, got, want) 11360 } 11361 } 11362 { 11363 addr := mustResourceInstanceAddr("test_instance.a[1]") 11364 change := plan.Changes.ResourceInstance(addr) 11365 if change != nil { 11366 // It was already removed in the previous plan/apply 11367 t.Errorf("unexpected planned change for %s", addr) 11368 } 11369 } 11370 11371 state, diags = ctx.Apply(plan, m) 11372 if diags.HasErrors() { 11373 t.Fatal(diags.ErrWithWarnings()) 11374 } 11375 11376 // check the output, as those can't cause an error planning the value 11377 out = state.RootModule().OutputValues["out"].Value.AsString() 11378 if out != "" { 11379 t.Fatalf(`expected output "", got: %q`, out) 11380 } 11381 } 11382 11383 // Ensure that we can destroy when a provider references a resource that will 11384 // also be destroyed 11385 func TestContext2Apply_destroyProviderReference(t *testing.T) { 11386 m, snap := testModuleWithSnapshot(t, "apply-destroy-provisider-refs") 11387 11388 schemaFn := func(name string) *ProviderSchema { 11389 return &ProviderSchema{ 11390 Provider: &configschema.Block{ 11391 Attributes: map[string]*configschema.Attribute{ 11392 "value": { 11393 Type: cty.String, 11394 Required: true, 11395 }, 11396 }, 11397 }, 11398 ResourceTypes: map[string]*configschema.Block{ 11399 name + "_instance": { 11400 Attributes: map[string]*configschema.Attribute{ 11401 "id": { 11402 Type: cty.String, 11403 Computed: true, 11404 }, 11405 "foo": { 11406 Type: cty.String, 11407 Optional: true, 11408 }, 11409 }, 11410 }, 11411 }, 11412 DataSources: map[string]*configschema.Block{ 11413 name + "_data_source": { 11414 Attributes: map[string]*configschema.Attribute{ 11415 "id": { 11416 Type: cty.String, 11417 Computed: true, 11418 }, 11419 "output": { 11420 Type: cty.String, 11421 Computed: true, 11422 }, 11423 }, 11424 }, 11425 }, 11426 } 11427 } 11428 11429 testP := new(MockProvider) 11430 testP.ReadResourceFn = func(req providers.ReadResourceRequest) providers.ReadResourceResponse { 11431 return providers.ReadResourceResponse{NewState: req.PriorState} 11432 } 11433 testP.GetProviderSchemaResponse = getProviderSchemaResponseFromProviderSchema(schemaFn("test")) 11434 11435 providerConfig := "" 11436 testP.ConfigureProviderFn = func(req providers.ConfigureProviderRequest) (resp providers.ConfigureProviderResponse) { 11437 value := req.Config.GetAttr("value") 11438 if value.IsKnown() && !value.IsNull() { 11439 providerConfig = value.AsString() 11440 } else { 11441 providerConfig = "" 11442 } 11443 return resp 11444 } 11445 testP.ApplyResourceChangeFn = func(req providers.ApplyResourceChangeRequest) (resp providers.ApplyResourceChangeResponse) { 11446 if providerConfig != "valid" { 11447 resp.Diagnostics = resp.Diagnostics.Append(fmt.Errorf("provider config is %q", providerConfig)) 11448 return 11449 } 11450 return testApplyFn(req) 11451 } 11452 testP.PlanResourceChangeFn = testDiffFn 11453 11454 nullP := new(MockProvider) 11455 nullP.ReadResourceFn = func(req providers.ReadResourceRequest) providers.ReadResourceResponse { 11456 return providers.ReadResourceResponse{NewState: req.PriorState} 11457 } 11458 nullP.GetProviderSchemaResponse = getProviderSchemaResponseFromProviderSchema(schemaFn("null")) 11459 11460 nullP.ApplyResourceChangeFn = testApplyFn 11461 nullP.PlanResourceChangeFn = testDiffFn 11462 11463 nullP.ReadDataSourceResponse = &providers.ReadDataSourceResponse{ 11464 State: cty.ObjectVal(map[string]cty.Value{ 11465 "id": cty.StringVal("ID"), 11466 "output": cty.StringVal("valid"), 11467 }), 11468 } 11469 11470 ctx := testContext2(t, &ContextOpts{ 11471 Providers: map[addrs.Provider]providers.Factory{ 11472 addrs.NewDefaultProvider("test"): testProviderFuncFixed(testP), 11473 addrs.NewDefaultProvider("null"): testProviderFuncFixed(nullP), 11474 }, 11475 }) 11476 11477 plan, diags := ctx.Plan(m, states.NewState(), DefaultPlanOpts) 11478 assertNoErrors(t, diags) 11479 11480 state, diags := ctx.Apply(plan, m) 11481 if diags.HasErrors() { 11482 t.Fatalf("apply errors: %s", diags.Err()) 11483 } 11484 11485 providers := map[addrs.Provider]providers.Factory{ 11486 addrs.NewDefaultProvider("test"): testProviderFuncFixed(testP), 11487 addrs.NewDefaultProvider("null"): testProviderFuncFixed(nullP), 11488 } 11489 ctx = testContext2(t, &ContextOpts{ 11490 Providers: providers, 11491 }) 11492 11493 plan, diags = ctx.Plan(m, state, &PlanOpts{ 11494 Mode: plans.DestroyMode, 11495 }) 11496 assertNoErrors(t, diags) 11497 11498 // We'll marshal and unmarshal the plan here, to ensure that we have 11499 // a clean new context as would be created if we separately ran 11500 // tofu plan -out=tfplan && tofu apply tfplan 11501 ctxOpts, m, plan, err := contextOptsForPlanViaFile(t, snap, plan) 11502 if err != nil { 11503 t.Fatal(err) 11504 } 11505 ctxOpts.Providers = providers 11506 ctx, diags = NewContext(ctxOpts) 11507 11508 if diags.HasErrors() { 11509 t.Fatalf("failed to create context for plan: %s", diags.Err()) 11510 } 11511 if _, diags := ctx.Apply(plan, m); diags.HasErrors() { 11512 t.Fatalf("destroy apply errors: %s", diags.Err()) 11513 } 11514 } 11515 11516 // Destroying properly requires pruning out all unneeded config nodes to 11517 // prevent incorrect expansion evaluation. 11518 func TestContext2Apply_destroyInterModuleExpansion(t *testing.T) { 11519 m := testModuleInline(t, map[string]string{ 11520 "main.tf": ` 11521 data "test_data_source" "a" { 11522 for_each = { 11523 one = "thing" 11524 } 11525 } 11526 11527 locals { 11528 module_input = { 11529 for k, v in data.test_data_source.a : k => v.id 11530 } 11531 } 11532 11533 module "mod1" { 11534 source = "./mod" 11535 input = local.module_input 11536 } 11537 11538 module "mod2" { 11539 source = "./mod" 11540 input = module.mod1.outputs 11541 } 11542 11543 resource "test_instance" "bar" { 11544 for_each = module.mod2.outputs 11545 } 11546 11547 output "module_output" { 11548 value = module.mod2.outputs 11549 } 11550 output "test_instances" { 11551 value = test_instance.bar 11552 } 11553 `, 11554 "mod/main.tf": ` 11555 variable "input" { 11556 } 11557 11558 data "test_data_source" "foo" { 11559 for_each = var.input 11560 } 11561 11562 output "outputs" { 11563 value = data.test_data_source.foo 11564 } 11565 `}) 11566 11567 p := testProvider("test") 11568 p.PlanResourceChangeFn = testDiffFn 11569 p.ReadDataSourceFn = func(req providers.ReadDataSourceRequest) providers.ReadDataSourceResponse { 11570 return providers.ReadDataSourceResponse{ 11571 State: cty.ObjectVal(map[string]cty.Value{ 11572 "id": cty.StringVal("data_source"), 11573 "foo": cty.StringVal("output"), 11574 }), 11575 } 11576 } 11577 11578 ctx := testContext2(t, &ContextOpts{ 11579 Providers: map[addrs.Provider]providers.Factory{ 11580 addrs.NewDefaultProvider("test"): testProviderFuncFixed(p), 11581 }, 11582 }) 11583 11584 plan, diags := ctx.Plan(m, states.NewState(), DefaultPlanOpts) 11585 assertNoErrors(t, diags) 11586 11587 state, diags := ctx.Apply(plan, m) 11588 if diags.HasErrors() { 11589 t.Fatalf("apply errors: %s", diags.Err()) 11590 } 11591 11592 destroy := func() { 11593 ctx = testContext2(t, &ContextOpts{ 11594 Providers: map[addrs.Provider]providers.Factory{ 11595 addrs.NewDefaultProvider("test"): testProviderFuncFixed(p), 11596 }, 11597 }) 11598 11599 plan, diags = ctx.Plan(m, state, &PlanOpts{ 11600 Mode: plans.DestroyMode, 11601 }) 11602 assertNoErrors(t, diags) 11603 11604 state, diags = ctx.Apply(plan, m) 11605 if diags.HasErrors() { 11606 t.Fatalf("destroy apply errors: %s", diags.Err()) 11607 } 11608 } 11609 11610 destroy() 11611 // Destroying again from the empty state should not cause any errors either 11612 destroy() 11613 } 11614 11615 func TestContext2Apply_createBeforeDestroyWithModule(t *testing.T) { 11616 m := testModuleInline(t, map[string]string{ 11617 "main.tf": ` 11618 variable "v" {} 11619 11620 module "mod" { 11621 source = "./mod" 11622 in = var.v 11623 } 11624 11625 resource "test_resource" "a" { 11626 value = var.v 11627 depends_on = [module.mod] 11628 lifecycle { 11629 create_before_destroy = true 11630 } 11631 } 11632 `, 11633 "mod/main.tf": ` 11634 variable "in" {} 11635 11636 resource "test_resource" "a" { 11637 value = var.in 11638 } 11639 `}) 11640 11641 p := testProvider("test") 11642 p.PlanResourceChangeFn = func(req providers.PlanResourceChangeRequest) (resp providers.PlanResourceChangeResponse) { 11643 // this is a destroy plan 11644 if req.ProposedNewState.IsNull() { 11645 resp.PlannedState = req.ProposedNewState 11646 resp.PlannedPrivate = req.PriorPrivate 11647 return resp 11648 } 11649 11650 proposed := req.ProposedNewState.AsValueMap() 11651 proposed["id"] = cty.UnknownVal(cty.String) 11652 11653 resp.PlannedState = cty.ObjectVal(proposed) 11654 resp.RequiresReplace = []cty.Path{{cty.GetAttrStep{Name: "value"}}} 11655 return resp 11656 } 11657 11658 ctx := testContext2(t, &ContextOpts{ 11659 Providers: map[addrs.Provider]providers.Factory{ 11660 addrs.NewDefaultProvider("test"): testProviderFuncFixed(p), 11661 }, 11662 }) 11663 11664 plan, diags := ctx.Plan(m, states.NewState(), &PlanOpts{ 11665 Mode: plans.NormalMode, 11666 SetVariables: InputValues{ 11667 "v": &InputValue{ 11668 Value: cty.StringVal("A"), 11669 }, 11670 }, 11671 }) 11672 assertNoErrors(t, diags) 11673 11674 state, diags := ctx.Apply(plan, m) 11675 if diags.HasErrors() { 11676 t.Fatalf("apply errors: %s", diags.Err()) 11677 } 11678 11679 ctx = testContext2(t, &ContextOpts{ 11680 Providers: map[addrs.Provider]providers.Factory{ 11681 addrs.NewDefaultProvider("test"): testProviderFuncFixed(p), 11682 }, 11683 }) 11684 11685 plan, diags = ctx.Plan(m, state, &PlanOpts{ 11686 Mode: plans.NormalMode, 11687 SetVariables: InputValues{ 11688 "v": &InputValue{ 11689 Value: cty.StringVal("B"), 11690 }, 11691 }, 11692 }) 11693 assertNoErrors(t, diags) 11694 11695 _, diags = ctx.Apply(plan, m) 11696 if diags.HasErrors() { 11697 t.Fatalf("apply errors: %s", diags.Err()) 11698 } 11699 } 11700 11701 func TestContext2Apply_forcedCBD(t *testing.T) { 11702 m := testModuleInline(t, map[string]string{ 11703 "main.tf": ` 11704 variable "v" {} 11705 11706 resource "test_instance" "a" { 11707 require_new = var.v 11708 } 11709 11710 resource "test_instance" "b" { 11711 depends_on = [test_instance.a] 11712 lifecycle { 11713 create_before_destroy = true 11714 } 11715 } 11716 `}) 11717 11718 p := testProvider("test") 11719 p.PlanResourceChangeFn = testDiffFn 11720 11721 ctx := testContext2(t, &ContextOpts{ 11722 Providers: map[addrs.Provider]providers.Factory{ 11723 addrs.NewDefaultProvider("test"): testProviderFuncFixed(p), 11724 }, 11725 }) 11726 11727 plan, diags := ctx.Plan(m, states.NewState(), &PlanOpts{ 11728 Mode: plans.NormalMode, 11729 SetVariables: InputValues{ 11730 "v": &InputValue{ 11731 Value: cty.StringVal("A"), 11732 }, 11733 }, 11734 }) 11735 assertNoErrors(t, diags) 11736 11737 state, diags := ctx.Apply(plan, m) 11738 if diags.HasErrors() { 11739 t.Fatalf("apply errors: %s", diags.Err()) 11740 } 11741 11742 ctx = testContext2(t, &ContextOpts{ 11743 Providers: map[addrs.Provider]providers.Factory{ 11744 addrs.NewDefaultProvider("test"): testProviderFuncFixed(p), 11745 }, 11746 }) 11747 11748 plan, diags = ctx.Plan(m, state, &PlanOpts{ 11749 Mode: plans.NormalMode, 11750 SetVariables: InputValues{ 11751 "v": &InputValue{ 11752 Value: cty.StringVal("B"), 11753 }, 11754 }, 11755 }) 11756 assertNoErrors(t, diags) 11757 11758 _, diags = ctx.Apply(plan, m) 11759 if diags.HasErrors() { 11760 t.Fatalf("apply errors: %s", diags.Err()) 11761 } 11762 } 11763 11764 func TestContext2Apply_removeReferencedResource(t *testing.T) { 11765 m := testModuleInline(t, map[string]string{ 11766 "main.tf": ` 11767 variable "ct" { 11768 } 11769 11770 resource "test_resource" "to_remove" { 11771 count = var.ct 11772 } 11773 11774 resource "test_resource" "c" { 11775 value = join("", test_resource.to_remove[*].id) 11776 } 11777 `}) 11778 11779 p := testProvider("test") 11780 p.PlanResourceChangeFn = testDiffFn 11781 11782 ctx := testContext2(t, &ContextOpts{ 11783 Providers: map[addrs.Provider]providers.Factory{ 11784 addrs.NewDefaultProvider("test"): testProviderFuncFixed(p), 11785 }, 11786 }) 11787 11788 plan, diags := ctx.Plan(m, states.NewState(), &PlanOpts{ 11789 Mode: plans.NormalMode, 11790 SetVariables: InputValues{ 11791 "ct": &InputValue{ 11792 Value: cty.NumberIntVal(1), 11793 }, 11794 }, 11795 }) 11796 assertNoErrors(t, diags) 11797 11798 state, diags := ctx.Apply(plan, m) 11799 if diags.HasErrors() { 11800 t.Fatalf("apply errors: %s", diags.Err()) 11801 } 11802 11803 ctx = testContext2(t, &ContextOpts{ 11804 Providers: map[addrs.Provider]providers.Factory{ 11805 addrs.NewDefaultProvider("test"): testProviderFuncFixed(p), 11806 }, 11807 }) 11808 11809 plan, diags = ctx.Plan(m, state, &PlanOpts{ 11810 Mode: plans.NormalMode, 11811 SetVariables: InputValues{ 11812 "ct": &InputValue{ 11813 Value: cty.NumberIntVal(0), 11814 }, 11815 }, 11816 }) 11817 assertNoErrors(t, diags) 11818 11819 _, diags = ctx.Apply(plan, m) 11820 if diags.HasErrors() { 11821 t.Fatalf("apply errors: %s", diags.Err()) 11822 } 11823 } 11824 11825 func TestContext2Apply_variableSensitivity(t *testing.T) { 11826 m := testModuleInline(t, map[string]string{ 11827 "main.tf": ` 11828 variable "sensitive_var" { 11829 default = "foo" 11830 sensitive = true 11831 } 11832 11833 variable "sensitive_id" { 11834 default = "secret id" 11835 sensitive = true 11836 } 11837 11838 resource "test_resource" "foo" { 11839 value = var.sensitive_var 11840 11841 network_interface { 11842 network_interface_id = var.sensitive_id 11843 } 11844 }`, 11845 }) 11846 11847 p := new(MockProvider) 11848 p.ReadResourceFn = func(req providers.ReadResourceRequest) providers.ReadResourceResponse { 11849 return providers.ReadResourceResponse{NewState: req.PriorState} 11850 } 11851 p.GetProviderSchemaResponse = getProviderSchemaResponseFromProviderSchema(&ProviderSchema{ 11852 Provider: &configschema.Block{}, 11853 ResourceTypes: map[string]*configschema.Block{ 11854 "test_resource": { 11855 Attributes: map[string]*configschema.Attribute{ 11856 "id": { 11857 Type: cty.String, 11858 Computed: true, 11859 }, 11860 "value": { 11861 Type: cty.String, 11862 Optional: true, 11863 Computed: true, 11864 }, 11865 }, 11866 BlockTypes: map[string]*configschema.NestedBlock{ 11867 "network_interface": { 11868 Block: configschema.Block{ 11869 Attributes: map[string]*configschema.Attribute{ 11870 "network_interface_id": {Type: cty.String, Optional: true}, 11871 "device_index": {Type: cty.Number, Optional: true}, 11872 }, 11873 }, 11874 Nesting: configschema.NestingSet, 11875 }, 11876 }, 11877 }, 11878 }, 11879 }) 11880 p.PlanResourceChangeFn = testDiffFn 11881 11882 ctx := testContext2(t, &ContextOpts{ 11883 Providers: map[addrs.Provider]providers.Factory{ 11884 addrs.NewDefaultProvider("test"): testProviderFuncFixed(p), 11885 }, 11886 }) 11887 11888 plan, diags := ctx.Plan(m, states.NewState(), SimplePlanOpts(plans.NormalMode, testInputValuesUnset(m.Module.Variables))) 11889 assertNoErrors(t, diags) 11890 11891 state, diags := ctx.Apply(plan, m) 11892 if diags.HasErrors() { 11893 t.Fatalf("apply errors: %s", diags.Err()) 11894 } 11895 11896 // Run a second apply with no changes 11897 ctx = testContext2(t, &ContextOpts{ 11898 Providers: map[addrs.Provider]providers.Factory{ 11899 addrs.NewDefaultProvider("test"): testProviderFuncFixed(p), 11900 }, 11901 }) 11902 11903 plan, diags = ctx.Plan(m, state, SimplePlanOpts(plans.NormalMode, testInputValuesUnset(m.Module.Variables))) 11904 assertNoErrors(t, diags) 11905 11906 state, diags = ctx.Apply(plan, m) 11907 if diags.HasErrors() { 11908 t.Fatalf("apply errors: %s", diags.Err()) 11909 } 11910 11911 // Now change the variable value for sensitive_var 11912 ctx = testContext2(t, &ContextOpts{ 11913 Providers: map[addrs.Provider]providers.Factory{ 11914 addrs.NewDefaultProvider("test"): testProviderFuncFixed(p), 11915 }, 11916 }) 11917 11918 plan, diags = ctx.Plan(m, state, &PlanOpts{ 11919 Mode: plans.NormalMode, 11920 SetVariables: InputValues{ 11921 "sensitive_id": &InputValue{Value: cty.NilVal}, 11922 "sensitive_var": &InputValue{ 11923 Value: cty.StringVal("bar"), 11924 }, 11925 }, 11926 }) 11927 assertNoErrors(t, diags) 11928 11929 _, diags = ctx.Apply(plan, m) 11930 if diags.HasErrors() { 11931 t.Fatalf("apply errors: %s", diags.Err()) 11932 } 11933 } 11934 11935 func TestContext2Apply_variableSensitivityPropagation(t *testing.T) { 11936 m := testModuleInline(t, map[string]string{ 11937 "main.tf": ` 11938 variable "sensitive_map" { 11939 type = map(string) 11940 default = { 11941 "x" = "foo" 11942 } 11943 sensitive = true 11944 } 11945 11946 resource "test_resource" "foo" { 11947 value = var.sensitive_map.x 11948 } 11949 `, 11950 }) 11951 11952 p := testProvider("test") 11953 p.PlanResourceChangeFn = testDiffFn 11954 11955 ctx := testContext2(t, &ContextOpts{ 11956 Providers: map[addrs.Provider]providers.Factory{ 11957 addrs.NewDefaultProvider("test"): testProviderFuncFixed(p), 11958 }, 11959 }) 11960 11961 plan, diags := ctx.Plan(m, states.NewState(), SimplePlanOpts(plans.NormalMode, testInputValuesUnset(m.Module.Variables))) 11962 if diags.HasErrors() { 11963 t.Fatalf("plan errors: %s", diags.Err()) 11964 } 11965 11966 verifySensitiveValue := func(pvms []cty.PathValueMarks) { 11967 if len(pvms) != 1 { 11968 t.Fatalf("expected 1 sensitive path, got %d", len(pvms)) 11969 } 11970 pvm := pvms[0] 11971 if gotPath, wantPath := pvm.Path, cty.GetAttrPath("value"); !gotPath.Equals(wantPath) { 11972 t.Errorf("wrong path\n got: %#v\nwant: %#v", gotPath, wantPath) 11973 } 11974 if gotMarks, wantMarks := pvm.Marks, cty.NewValueMarks(marks.Sensitive); !gotMarks.Equal(wantMarks) { 11975 t.Errorf("wrong marks\n got: %#v\nwant: %#v", gotMarks, wantMarks) 11976 } 11977 } 11978 11979 addr := mustResourceInstanceAddr("test_resource.foo") 11980 fooChangeSrc := plan.Changes.ResourceInstance(addr) 11981 verifySensitiveValue(fooChangeSrc.AfterValMarks) 11982 11983 state, diags := ctx.Apply(plan, m) 11984 if diags.HasErrors() { 11985 t.Fatalf("apply errors: %s", diags.Err()) 11986 } 11987 11988 fooState := state.ResourceInstance(addr) 11989 verifySensitiveValue(fooState.Current.AttrSensitivePaths) 11990 } 11991 11992 func TestContext2Apply_variableSensitivityProviders(t *testing.T) { 11993 m := testModuleInline(t, map[string]string{ 11994 "main.tf": ` 11995 resource "test_resource" "foo" { 11996 sensitive_value = "should get marked" 11997 } 11998 11999 resource "test_resource" "bar" { 12000 value = test_resource.foo.sensitive_value 12001 random = test_resource.foo.id # not sensitive 12002 12003 nesting_single { 12004 value = "abc" 12005 sensitive_value = "xyz" 12006 } 12007 } 12008 12009 resource "test_resource" "baz" { 12010 value = test_resource.bar.nesting_single.sensitive_value 12011 } 12012 `, 12013 }) 12014 12015 p := testProvider("test") 12016 p.PlanResourceChangeFn = testDiffFn 12017 12018 ctx := testContext2(t, &ContextOpts{ 12019 Providers: map[addrs.Provider]providers.Factory{ 12020 addrs.NewDefaultProvider("test"): testProviderFuncFixed(p), 12021 }, 12022 }) 12023 12024 plan, diags := ctx.Plan(m, states.NewState(), DefaultPlanOpts) 12025 if diags.HasErrors() { 12026 t.Fatalf("plan errors: %s", diags.Err()) 12027 } 12028 12029 verifySensitiveValue := func(pvms []cty.PathValueMarks) { 12030 if len(pvms) != 1 { 12031 t.Fatalf("expected 1 sensitive path, got %d", len(pvms)) 12032 } 12033 pvm := pvms[0] 12034 if gotPath, wantPath := pvm.Path, cty.GetAttrPath("value"); !gotPath.Equals(wantPath) { 12035 t.Errorf("wrong path\n got: %#v\nwant: %#v", gotPath, wantPath) 12036 } 12037 if gotMarks, wantMarks := pvm.Marks, cty.NewValueMarks(marks.Sensitive); !gotMarks.Equal(wantMarks) { 12038 t.Errorf("wrong marks\n got: %#v\nwant: %#v", gotMarks, wantMarks) 12039 } 12040 } 12041 12042 // Sensitive attributes (defined by the provider) are marked 12043 // as sensitive when referenced from another resource 12044 // "bar" references sensitive resources in "foo" 12045 barAddr := mustResourceInstanceAddr("test_resource.bar") 12046 barChangeSrc := plan.Changes.ResourceInstance(barAddr) 12047 verifySensitiveValue(barChangeSrc.AfterValMarks) 12048 12049 bazAddr := mustResourceInstanceAddr("test_resource.baz") 12050 bazChangeSrc := plan.Changes.ResourceInstance(bazAddr) 12051 verifySensitiveValue(bazChangeSrc.AfterValMarks) 12052 12053 state, diags := ctx.Apply(plan, m) 12054 if diags.HasErrors() { 12055 t.Fatalf("apply errors: %s", diags.Err()) 12056 } 12057 12058 barState := state.ResourceInstance(barAddr) 12059 verifySensitiveValue(barState.Current.AttrSensitivePaths) 12060 12061 bazState := state.ResourceInstance(bazAddr) 12062 verifySensitiveValue(bazState.Current.AttrSensitivePaths) 12063 } 12064 12065 func TestContext2Apply_variableSensitivityChange(t *testing.T) { 12066 m := testModuleInline(t, map[string]string{ 12067 "main.tf": ` 12068 variable "sensitive_var" { 12069 default = "hello" 12070 sensitive = true 12071 } 12072 12073 resource "test_resource" "foo" { 12074 value = var.sensitive_var 12075 }`, 12076 }) 12077 12078 p := testProvider("test") 12079 p.PlanResourceChangeFn = testDiffFn 12080 12081 ctx := testContext2(t, &ContextOpts{ 12082 Providers: map[addrs.Provider]providers.Factory{ 12083 addrs.NewDefaultProvider("test"): testProviderFuncFixed(p), 12084 }, 12085 }) 12086 12087 state := states.BuildState(func(s *states.SyncState) { 12088 s.SetResourceInstanceCurrent( 12089 addrs.Resource{ 12090 Mode: addrs.ManagedResourceMode, 12091 Type: "test_resource", 12092 Name: "foo", 12093 }.Instance(addrs.NoKey).Absolute(addrs.RootModuleInstance), 12094 &states.ResourceInstanceObjectSrc{ 12095 Status: states.ObjectReady, 12096 AttrsJSON: []byte(`{"id":"foo", "value":"hello"}`), 12097 // No AttrSensitivePaths present 12098 }, 12099 addrs.AbsProviderConfig{ 12100 Provider: addrs.NewDefaultProvider("test"), 12101 Module: addrs.RootModule, 12102 }, 12103 ) 12104 }) 12105 12106 plan, diags := ctx.Plan(m, state, SimplePlanOpts(plans.NormalMode, testInputValuesUnset(m.Module.Variables))) 12107 assertNoErrors(t, diags) 12108 12109 addr := mustResourceInstanceAddr("test_resource.foo") 12110 12111 state, diags = ctx.Apply(plan, m) 12112 assertNoErrors(t, diags) 12113 12114 fooState := state.ResourceInstance(addr) 12115 12116 if len(fooState.Current.AttrSensitivePaths) != 1 { 12117 t.Fatalf("wrong number of sensitive paths, expected 1, got, %v", len(fooState.Current.AttrSensitivePaths)) 12118 } 12119 got := fooState.Current.AttrSensitivePaths[0] 12120 want := cty.PathValueMarks{ 12121 Path: cty.GetAttrPath("value"), 12122 Marks: cty.NewValueMarks(marks.Sensitive), 12123 } 12124 12125 if !got.Equal(want) { 12126 t.Fatalf("wrong value marks; got:\n%#v\n\nwant:\n%#v\n", got, want) 12127 } 12128 12129 m2 := testModuleInline(t, map[string]string{ 12130 "main.tf": ` 12131 variable "sensitive_var" { 12132 default = "hello" 12133 sensitive = false 12134 } 12135 12136 resource "test_resource" "foo" { 12137 value = var.sensitive_var 12138 }`, 12139 }) 12140 12141 ctx2 := testContext2(t, &ContextOpts{ 12142 Providers: map[addrs.Provider]providers.Factory{ 12143 addrs.NewDefaultProvider("test"): testProviderFuncFixed(p), 12144 }, 12145 }) 12146 12147 // NOTE: Prior to our refactoring to make the state an explicit argument 12148 // of Plan, as opposed to hidden state inside Context, this test was 12149 // calling ctx.Apply instead of ctx2.Apply and thus using the previous 12150 // plan instead of this new plan. "Fixing" it to use the new plan seems 12151 // to break the test, so we've preserved that oddity here by saving the 12152 // old plan as oldPlan and essentially discarding the new plan entirely, 12153 // but this seems rather suspicious and we should ideally figure out what 12154 // this test was originally intending to do and make it do that. 12155 oldPlan := plan 12156 _, diags = ctx2.Plan(m2, state, SimplePlanOpts(plans.NormalMode, testInputValuesUnset(m.Module.Variables))) 12157 assertNoErrors(t, diags) 12158 stateWithoutSensitive, diags := ctx.Apply(oldPlan, m) 12159 assertNoErrors(t, diags) 12160 12161 fooState2 := stateWithoutSensitive.ResourceInstance(addr) 12162 if len(fooState2.Current.AttrSensitivePaths) > 0 { 12163 t.Fatalf( 12164 "wrong number of sensitive paths, expected 0, got, %v\n%s", 12165 len(fooState2.Current.AttrSensitivePaths), 12166 spew.Sdump(fooState2.Current.AttrSensitivePaths), 12167 ) 12168 } 12169 } 12170 12171 func TestContext2Apply_moduleVariableOptionalAttributes(t *testing.T) { 12172 m := testModuleInline(t, map[string]string{ 12173 "main.tf": ` 12174 variable "in" { 12175 type = object({ 12176 required = string 12177 optional = optional(string) 12178 default = optional(bool, true) 12179 nested = optional( 12180 map(object({ 12181 a = optional(string, "foo") 12182 b = optional(number, 5) 12183 })), { 12184 "boop": {} 12185 } 12186 ) 12187 }) 12188 } 12189 12190 output "out" { 12191 value = var.in 12192 } 12193 `}) 12194 12195 ctx := testContext2(t, &ContextOpts{}) 12196 12197 plan, diags := ctx.Plan(m, states.NewState(), &PlanOpts{ 12198 Mode: plans.NormalMode, 12199 SetVariables: InputValues{ 12200 "in": &InputValue{ 12201 Value: cty.MapVal(map[string]cty.Value{ 12202 "required": cty.StringVal("boop"), 12203 }), 12204 SourceType: ValueFromCaller, 12205 }, 12206 }, 12207 }) 12208 if diags.HasErrors() { 12209 t.Fatal(diags.ErrWithWarnings()) 12210 } 12211 12212 state, diags := ctx.Apply(plan, m) 12213 if diags.HasErrors() { 12214 t.Fatal(diags.ErrWithWarnings()) 12215 } 12216 12217 got := state.RootModule().OutputValues["out"].Value 12218 want := cty.ObjectVal(map[string]cty.Value{ 12219 "required": cty.StringVal("boop"), 12220 12221 // Because "optional" was marked as optional, it got silently filled 12222 // in as a null value of string type rather than returning an error. 12223 "optional": cty.NullVal(cty.String), 12224 12225 // Similarly, "default" was marked as optional with a default value, 12226 // and since it was omitted should be filled in with that default. 12227 "default": cty.True, 12228 12229 // Nested is a complex structure which has fully described defaults, 12230 // so again it should be filled with the default structure. 12231 "nested": cty.MapVal(map[string]cty.Value{ 12232 "boop": cty.ObjectVal(map[string]cty.Value{ 12233 "a": cty.StringVal("foo"), 12234 "b": cty.NumberIntVal(5), 12235 }), 12236 }), 12237 }) 12238 if !want.RawEquals(got) { 12239 t.Fatalf("wrong result\ngot: %#v\nwant: %#v", got, want) 12240 } 12241 } 12242 12243 func TestContext2Apply_moduleVariableOptionalAttributesDefault(t *testing.T) { 12244 m := testModuleInline(t, map[string]string{ 12245 "main.tf": ` 12246 variable "in" { 12247 type = object({ 12248 required = string 12249 optional = optional(string) 12250 default = optional(bool, true) 12251 }) 12252 default = { 12253 required = "boop" 12254 } 12255 } 12256 12257 output "out" { 12258 value = var.in 12259 } 12260 `}) 12261 12262 ctx := testContext2(t, &ContextOpts{}) 12263 12264 // We don't specify a value for the variable here, relying on its defined 12265 // default. 12266 plan, diags := ctx.Plan(m, states.NewState(), SimplePlanOpts(plans.NormalMode, testInputValuesUnset(m.Module.Variables))) 12267 if diags.HasErrors() { 12268 t.Fatal(diags.ErrWithWarnings()) 12269 } 12270 12271 state, diags := ctx.Apply(plan, m) 12272 if diags.HasErrors() { 12273 t.Fatal(diags.ErrWithWarnings()) 12274 } 12275 12276 got := state.RootModule().OutputValues["out"].Value 12277 want := cty.ObjectVal(map[string]cty.Value{ 12278 "required": cty.StringVal("boop"), 12279 12280 // "optional" is not present in the variable default, so it is filled 12281 // with null. 12282 "optional": cty.NullVal(cty.String), 12283 12284 // Similarly, "default" is not present in the variable default, so its 12285 // value is replaced with the type's specified default. 12286 "default": cty.True, 12287 }) 12288 if !want.RawEquals(got) { 12289 t.Fatalf("wrong result\ngot: %#v\nwant: %#v", got, want) 12290 } 12291 } 12292 12293 func TestContext2Apply_moduleVariableOptionalAttributesDefaultNull(t *testing.T) { 12294 m := testModuleInline(t, map[string]string{ 12295 "main.tf": ` 12296 variable "in" { 12297 type = object({ 12298 required = string 12299 optional = optional(string) 12300 default = optional(bool, true) 12301 }) 12302 default = null 12303 } 12304 12305 # Wrap the input variable in a tuple because a null output value is elided from 12306 # the plan, which prevents us from testing its type. 12307 output "out" { 12308 value = [var.in] 12309 } 12310 `}) 12311 12312 ctx := testContext2(t, &ContextOpts{}) 12313 12314 // We don't specify a value for the variable here, relying on its defined 12315 // default. 12316 plan, diags := ctx.Plan(m, states.NewState(), SimplePlanOpts(plans.NormalMode, testInputValuesUnset(m.Module.Variables))) 12317 if diags.HasErrors() { 12318 t.Fatal(diags.ErrWithWarnings()) 12319 } 12320 12321 state, diags := ctx.Apply(plan, m) 12322 if diags.HasErrors() { 12323 t.Fatal(diags.ErrWithWarnings()) 12324 } 12325 12326 got := state.RootModule().OutputValues["out"].Value 12327 // The null default value should be bound, after type converting to the 12328 // full object type 12329 want := cty.TupleVal([]cty.Value{cty.NullVal(cty.Object(map[string]cty.Type{ 12330 "required": cty.String, 12331 "optional": cty.String, 12332 "default": cty.Bool, 12333 }))}) 12334 if !want.RawEquals(got) { 12335 t.Fatalf("wrong result\ngot: %#v\nwant: %#v", got, want) 12336 } 12337 } 12338 12339 func TestContext2Apply_moduleVariableOptionalAttributesDefaultChild(t *testing.T) { 12340 m := testModuleInline(t, map[string]string{ 12341 "main.tf": ` 12342 variable "in" { 12343 type = list(object({ 12344 a = optional(set(string)) 12345 })) 12346 default = [ 12347 { a = [ "foo" ] }, 12348 { }, 12349 ] 12350 } 12351 12352 module "child" { 12353 source = "./child" 12354 in = var.in 12355 } 12356 12357 output "out" { 12358 value = module.child.out 12359 } 12360 `, 12361 "child/main.tf": ` 12362 variable "in" { 12363 type = list(object({ 12364 a = optional(set(string), []) 12365 })) 12366 default = [] 12367 } 12368 12369 output "out" { 12370 value = var.in 12371 } 12372 `, 12373 }) 12374 12375 ctx := testContext2(t, &ContextOpts{}) 12376 12377 // We don't specify a value for the variable here, relying on its defined 12378 // default. 12379 plan, diags := ctx.Plan(m, states.NewState(), SimplePlanOpts(plans.NormalMode, testInputValuesUnset(m.Module.Variables))) 12380 if diags.HasErrors() { 12381 t.Fatal(diags.ErrWithWarnings()) 12382 } 12383 12384 state, diags := ctx.Apply(plan, m) 12385 if diags.HasErrors() { 12386 t.Fatal(diags.ErrWithWarnings()) 12387 } 12388 12389 got := state.RootModule().OutputValues["out"].Value 12390 want := cty.ListVal([]cty.Value{ 12391 cty.ObjectVal(map[string]cty.Value{ 12392 "a": cty.SetVal([]cty.Value{cty.StringVal("foo")}), 12393 }), 12394 cty.ObjectVal(map[string]cty.Value{ 12395 "a": cty.SetValEmpty(cty.String), 12396 }), 12397 }) 12398 if !want.RawEquals(got) { 12399 t.Fatalf("wrong result\ngot: %#v\nwant: %#v", got, want) 12400 } 12401 } 12402 12403 func TestContext2Apply_provisionerSensitive(t *testing.T) { 12404 m := testModule(t, "apply-provisioner-sensitive") 12405 p := testProvider("aws") 12406 12407 pr := testProvisioner() 12408 pr.ProvisionResourceFn = func(req provisioners.ProvisionResourceRequest) (resp provisioners.ProvisionResourceResponse) { 12409 if req.Config.ContainsMarked() { 12410 t.Fatalf("unexpectedly marked config value: %#v", req.Config) 12411 } 12412 command := req.Config.GetAttr("command") 12413 if command.IsMarked() { 12414 t.Fatalf("unexpectedly marked command argument: %#v", command.Marks()) 12415 } 12416 req.UIOutput.Output(fmt.Sprintf("Executing: %q", command.AsString())) 12417 return 12418 } 12419 p.PlanResourceChangeFn = testDiffFn 12420 p.ApplyResourceChangeFn = testApplyFn 12421 12422 h := new(MockHook) 12423 ctx := testContext2(t, &ContextOpts{ 12424 Hooks: []Hook{h}, 12425 Providers: map[addrs.Provider]providers.Factory{ 12426 addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p), 12427 }, 12428 Provisioners: map[string]provisioners.Factory{ 12429 "shell": testProvisionerFuncFixed(pr), 12430 }, 12431 }) 12432 12433 plan, diags := ctx.Plan(m, states.NewState(), &PlanOpts{ 12434 Mode: plans.NormalMode, 12435 SetVariables: InputValues{ 12436 "password": &InputValue{ 12437 Value: cty.StringVal("secret"), 12438 SourceType: ValueFromCaller, 12439 }, 12440 }, 12441 }) 12442 assertNoErrors(t, diags) 12443 12444 // "restart" provisioner 12445 pr.CloseCalled = false 12446 12447 state, diags := ctx.Apply(plan, m) 12448 if diags.HasErrors() { 12449 logDiagnostics(t, diags) 12450 t.Fatal("apply failed") 12451 } 12452 12453 actual := strings.TrimSpace(state.String()) 12454 expected := strings.TrimSpace(testTofuApplyProvisionerSensitiveStr) 12455 if actual != expected { 12456 t.Fatalf("wrong result\n\ngot:\n%s\n\nwant:\n%s", actual, expected) 12457 } 12458 12459 // Verify apply was invoked 12460 if !pr.ProvisionResourceCalled { 12461 t.Fatalf("provisioner was not called on apply") 12462 } 12463 12464 // Verify output was suppressed 12465 if !h.ProvisionOutputCalled { 12466 t.Fatalf("ProvisionOutput hook not called") 12467 } 12468 if got, doNotWant := h.ProvisionOutputMessage, "secret"; strings.Contains(got, doNotWant) { 12469 t.Errorf("sensitive value %q included in output:\n%s", doNotWant, got) 12470 } 12471 if got, want := h.ProvisionOutputMessage, "output suppressed"; !strings.Contains(got, want) { 12472 t.Errorf("expected hook to be called with %q, but was:\n%s", want, got) 12473 } 12474 } 12475 12476 func TestContext2Apply_warnings(t *testing.T) { 12477 m := testModuleInline(t, map[string]string{ 12478 "main.tf": ` 12479 resource "test_resource" "foo" { 12480 }`, 12481 }) 12482 12483 p := testProvider("test") 12484 p.PlanResourceChangeFn = testDiffFn 12485 12486 p.ApplyResourceChangeFn = func(req providers.ApplyResourceChangeRequest) providers.ApplyResourceChangeResponse { 12487 resp := testApplyFn(req) 12488 12489 resp.Diagnostics = resp.Diagnostics.Append(tfdiags.SimpleWarning("warning")) 12490 return resp 12491 } 12492 12493 ctx := testContext2(t, &ContextOpts{ 12494 Providers: map[addrs.Provider]providers.Factory{ 12495 addrs.NewDefaultProvider("test"): testProviderFuncFixed(p), 12496 }, 12497 }) 12498 12499 plan, diags := ctx.Plan(m, states.NewState(), DefaultPlanOpts) 12500 assertNoErrors(t, diags) 12501 12502 state, diags := ctx.Apply(plan, m) 12503 if diags.HasErrors() { 12504 t.Fatalf("diags: %s", diags.Err()) 12505 } 12506 12507 inst := state.ResourceInstance(mustResourceInstanceAddr("test_resource.foo")) 12508 if inst == nil { 12509 t.Fatal("missing 'test_resource.foo' in state:", state) 12510 } 12511 } 12512 12513 func TestContext2Apply_rpcDiagnostics(t *testing.T) { 12514 m := testModuleInline(t, map[string]string{ 12515 "main.tf": ` 12516 resource "test_instance" "a" { 12517 } 12518 `, 12519 }) 12520 12521 p := testProvider("test") 12522 p.PlanResourceChangeFn = testDiffFn 12523 p.ApplyResourceChangeFn = func(req providers.ApplyResourceChangeRequest) (resp providers.ApplyResourceChangeResponse) { 12524 resp = testApplyFn(req) 12525 resp.Diagnostics = resp.Diagnostics.Append(tfdiags.SimpleWarning("don't frobble")) 12526 return resp 12527 } 12528 12529 p.GetProviderSchemaResponse = getProviderSchemaResponseFromProviderSchema(&ProviderSchema{ 12530 ResourceTypes: map[string]*configschema.Block{ 12531 "test_instance": { 12532 Attributes: map[string]*configschema.Attribute{ 12533 "id": {Type: cty.String, Computed: true}, 12534 }, 12535 }, 12536 }, 12537 }) 12538 12539 ctx := testContext2(t, &ContextOpts{ 12540 Providers: map[addrs.Provider]providers.Factory{ 12541 addrs.NewDefaultProvider("test"): testProviderFuncFixed(p), 12542 }, 12543 }) 12544 plan, diags := ctx.Plan(m, states.NewState(), DefaultPlanOpts) 12545 if diags.HasErrors() { 12546 t.Fatal(diags.Err()) 12547 } 12548 12549 _, diags = ctx.Apply(plan, m) 12550 if diags.HasErrors() { 12551 t.Fatal(diags.Err()) 12552 } 12553 12554 if len(diags) == 0 { 12555 t.Fatal("expected warnings") 12556 } 12557 12558 for _, d := range diags { 12559 des := d.Description().Summary 12560 if !strings.Contains(des, "frobble") { 12561 t.Fatalf(`expected frobble, got %q`, des) 12562 } 12563 } 12564 } 12565 12566 func TestContext2Apply_dataSensitive(t *testing.T) { 12567 m := testModule(t, "apply-data-sensitive") 12568 p := testProvider("null") 12569 p.PlanResourceChangeFn = testDiffFn 12570 p.ReadDataSourceFn = func(req providers.ReadDataSourceRequest) providers.ReadDataSourceResponse { 12571 // add the required id 12572 m := req.Config.AsValueMap() 12573 m["id"] = cty.StringVal("foo") 12574 12575 return providers.ReadDataSourceResponse{ 12576 State: cty.ObjectVal(m), 12577 } 12578 } 12579 12580 ctx := testContext2(t, &ContextOpts{ 12581 Providers: map[addrs.Provider]providers.Factory{ 12582 addrs.NewDefaultProvider("null"): testProviderFuncFixed(p), 12583 }, 12584 }) 12585 12586 plan, diags := ctx.Plan(m, states.NewState(), SimplePlanOpts(plans.NormalMode, testInputValuesUnset(m.Module.Variables))) 12587 if diags.HasErrors() { 12588 t.Fatalf("diags: %s", diags.Err()) 12589 } else { 12590 t.Logf(legacyDiffComparisonString(plan.Changes)) 12591 } 12592 12593 state, diags := ctx.Apply(plan, m) 12594 assertNoErrors(t, diags) 12595 12596 addr := mustResourceInstanceAddr("data.null_data_source.testing") 12597 12598 dataSourceState := state.ResourceInstance(addr) 12599 pvms := dataSourceState.Current.AttrSensitivePaths 12600 if len(pvms) != 1 { 12601 t.Fatalf("expected 1 sensitive path, got %d", len(pvms)) 12602 } 12603 pvm := pvms[0] 12604 if gotPath, wantPath := pvm.Path, cty.GetAttrPath("foo"); !gotPath.Equals(wantPath) { 12605 t.Errorf("wrong path\n got: %#v\nwant: %#v", gotPath, wantPath) 12606 } 12607 if gotMarks, wantMarks := pvm.Marks, cty.NewValueMarks(marks.Sensitive); !gotMarks.Equal(wantMarks) { 12608 t.Errorf("wrong marks\n got: %#v\nwant: %#v", gotMarks, wantMarks) 12609 } 12610 } 12611 12612 func TestContext2Apply_errorRestorePrivateData(t *testing.T) { 12613 // empty config to remove our resource 12614 m := testModuleInline(t, map[string]string{ 12615 "main.tf": "", 12616 }) 12617 12618 p := simpleMockProvider() 12619 p.ApplyResourceChangeResponse = &providers.ApplyResourceChangeResponse{ 12620 // we error during apply, which will trigger core to preserve the last 12621 // known state, including private data 12622 Diagnostics: tfdiags.Diagnostics(nil).Append(errors.New("oops")), 12623 } 12624 12625 addr := mustResourceInstanceAddr("test_object.a") 12626 12627 state := states.BuildState(func(s *states.SyncState) { 12628 s.SetResourceInstanceCurrent(addr, &states.ResourceInstanceObjectSrc{ 12629 Status: states.ObjectReady, 12630 AttrsJSON: []byte(`{"id":"foo"}`), 12631 Private: []byte("private"), 12632 }, mustProviderConfig(`provider["registry.opentofu.org/hashicorp/test"]`)) 12633 }) 12634 12635 ctx := testContext2(t, &ContextOpts{ 12636 Providers: map[addrs.Provider]providers.Factory{ 12637 addrs.NewDefaultProvider("test"): testProviderFuncFixed(p), 12638 }, 12639 }) 12640 12641 plan, diags := ctx.Plan(m, state, DefaultPlanOpts) 12642 if diags.HasErrors() { 12643 t.Fatal(diags.Err()) 12644 } 12645 12646 state, _ = ctx.Apply(plan, m) 12647 if string(state.ResourceInstance(addr).Current.Private) != "private" { 12648 t.Fatal("missing private data in state") 12649 } 12650 } 12651 12652 func TestContext2Apply_errorRestoreStatus(t *testing.T) { 12653 // empty config to remove our resource 12654 m := testModuleInline(t, map[string]string{ 12655 "main.tf": "", 12656 }) 12657 12658 p := simpleMockProvider() 12659 p.ApplyResourceChangeFn = func(req providers.ApplyResourceChangeRequest) (resp providers.ApplyResourceChangeResponse) { 12660 // We error during apply, but return the current object state. 12661 resp.Diagnostics = resp.Diagnostics.Append(errors.New("oops")) 12662 // return a warning too to make sure it isn't dropped 12663 resp.Diagnostics = resp.Diagnostics.Append(tfdiags.SimpleWarning("warned")) 12664 resp.NewState = req.PriorState 12665 resp.Private = req.PlannedPrivate 12666 return resp 12667 } 12668 12669 addr := mustResourceInstanceAddr("test_object.a") 12670 12671 state := states.BuildState(func(s *states.SyncState) { 12672 s.SetResourceInstanceCurrent(addr, &states.ResourceInstanceObjectSrc{ 12673 Status: states.ObjectTainted, 12674 AttrsJSON: []byte(`{"test_string":"foo"}`), 12675 Private: []byte("private"), 12676 Dependencies: []addrs.ConfigResource{mustConfigResourceAddr("test_object.b")}, 12677 }, mustProviderConfig(`provider["registry.opentofu.org/hashicorp/test"]`)) 12678 }) 12679 12680 ctx := testContext2(t, &ContextOpts{ 12681 Providers: map[addrs.Provider]providers.Factory{ 12682 addrs.NewDefaultProvider("test"): testProviderFuncFixed(p), 12683 }, 12684 }) 12685 12686 plan, diags := ctx.Plan(m, state, DefaultPlanOpts) 12687 if diags.HasErrors() { 12688 t.Fatal(diags.Err()) 12689 } 12690 12691 state, diags = ctx.Apply(plan, m) 12692 12693 errString := diags.ErrWithWarnings().Error() 12694 if !strings.Contains(errString, "oops") || !strings.Contains(errString, "warned") { 12695 t.Fatalf("error missing expected info: %q", errString) 12696 } 12697 12698 if len(diags) != 2 { 12699 t.Fatalf("expected 1 error and 1 warning, got: %q", errString) 12700 } 12701 12702 res := state.ResourceInstance(addr) 12703 if res == nil { 12704 t.Fatal("resource was removed from state") 12705 } 12706 12707 if res.Current.Status != states.ObjectTainted { 12708 t.Fatal("resource should still be tainted in the state") 12709 } 12710 12711 if len(res.Current.Dependencies) != 1 || !res.Current.Dependencies[0].Equal(mustConfigResourceAddr("test_object.b")) { 12712 t.Fatalf("incorrect dependencies, got %q", res.Current.Dependencies) 12713 } 12714 12715 if string(res.Current.Private) != "private" { 12716 t.Fatalf("incorrect private data, got %q", res.Current.Private) 12717 } 12718 } 12719 12720 func TestContext2Apply_nonConformingResponse(t *testing.T) { 12721 // empty config to remove our resource 12722 m := testModuleInline(t, map[string]string{ 12723 "main.tf": ` 12724 resource "test_object" "a" { 12725 test_string = "x" 12726 } 12727 `, 12728 }) 12729 12730 p := simpleMockProvider() 12731 respDiags := tfdiags.Diagnostics(nil).Append(tfdiags.SimpleWarning("warned")) 12732 respDiags = respDiags.Append(errors.New("oops")) 12733 p.ApplyResourceChangeResponse = &providers.ApplyResourceChangeResponse{ 12734 // Don't lose these diagnostics 12735 Diagnostics: respDiags, 12736 // This state is missing required attributes, and should produce an error 12737 NewState: cty.ObjectVal(map[string]cty.Value{ 12738 "test_string": cty.StringVal("x"), 12739 }), 12740 } 12741 12742 ctx := testContext2(t, &ContextOpts{ 12743 Providers: map[addrs.Provider]providers.Factory{ 12744 addrs.NewDefaultProvider("test"): testProviderFuncFixed(p), 12745 }, 12746 }) 12747 12748 plan, diags := ctx.Plan(m, states.NewState(), DefaultPlanOpts) 12749 if diags.HasErrors() { 12750 t.Fatal(diags.Err()) 12751 } 12752 12753 _, diags = ctx.Apply(plan, m) 12754 errString := diags.ErrWithWarnings().Error() 12755 if !strings.Contains(errString, "oops") || !strings.Contains(errString, "warned") { 12756 t.Fatalf("error missing expected info: %q", errString) 12757 } 12758 12759 // we should have more than the ones returned from the provider, and they 12760 // should not be coalesced into a single value 12761 if len(diags) < 3 { 12762 t.Fatalf("incorrect diagnostics, got %d values with %s", len(diags), diags.ErrWithWarnings()) 12763 } 12764 } 12765 12766 func TestContext2Apply_nilResponse(t *testing.T) { 12767 // empty config to remove our resource 12768 m := testModuleInline(t, map[string]string{ 12769 "main.tf": ` 12770 resource "test_object" "a" { 12771 } 12772 `, 12773 }) 12774 12775 p := simpleMockProvider() 12776 p.ApplyResourceChangeResponse = &providers.ApplyResourceChangeResponse{} 12777 12778 ctx := testContext2(t, &ContextOpts{ 12779 Providers: map[addrs.Provider]providers.Factory{ 12780 addrs.NewDefaultProvider("test"): testProviderFuncFixed(p), 12781 }, 12782 }) 12783 12784 plan, diags := ctx.Plan(m, states.NewState(), DefaultPlanOpts) 12785 if diags.HasErrors() { 12786 t.Fatal(diags.Err()) 12787 } 12788 12789 _, diags = ctx.Apply(plan, m) 12790 if !diags.HasErrors() { 12791 t.Fatal("expected and error") 12792 } 12793 12794 errString := diags.ErrWithWarnings().Error() 12795 if !strings.Contains(errString, "invalid nil value") { 12796 t.Fatalf("error missing expected info: %q", errString) 12797 } 12798 } 12799 12800 //////////////////////////////////////////////////////////////////////////////// 12801 // NOTE: Due to the size of this file, new tests should be added to 12802 // context_apply2_test.go. 12803 ////////////////////////////////////////////////////////////////////////////////