github.com/kanishk98/terraform@v1.3.0-dev.0.20220917174235-661ca8088a6a/internal/terraform/context_apply_test.go (about) 1 package terraform 2 3 import ( 4 "bytes" 5 "encoding/json" 6 "errors" 7 "fmt" 8 "log" 9 "reflect" 10 "runtime" 11 "sort" 12 "strings" 13 "sync" 14 "sync/atomic" 15 "testing" 16 "time" 17 18 "github.com/davecgh/go-spew/spew" 19 "github.com/go-test/deep" 20 "github.com/google/go-cmp/cmp" 21 "github.com/zclconf/go-cty/cty" 22 "github.com/zclconf/go-cty/cty/gocty" 23 24 "github.com/hashicorp/terraform/internal/addrs" 25 "github.com/hashicorp/terraform/internal/configs" 26 "github.com/hashicorp/terraform/internal/configs/configschema" 27 "github.com/hashicorp/terraform/internal/configs/hcl2shim" 28 "github.com/hashicorp/terraform/internal/lang/marks" 29 "github.com/hashicorp/terraform/internal/plans" 30 "github.com/hashicorp/terraform/internal/providers" 31 "github.com/hashicorp/terraform/internal/provisioners" 32 "github.com/hashicorp/terraform/internal/states" 33 "github.com/hashicorp/terraform/internal/tfdiags" 34 ) 35 36 func TestContext2Apply_basic(t *testing.T) { 37 m := testModule(t, "apply-good") 38 p := testProvider("aws") 39 p.PlanResourceChangeFn = testDiffFn 40 p.ApplyResourceChangeFn = testApplyFn 41 ctx := testContext2(t, &ContextOpts{ 42 Providers: map[addrs.Provider]providers.Factory{ 43 addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p), 44 }, 45 }) 46 47 plan, diags := ctx.Plan(m, states.NewState(), DefaultPlanOpts) 48 assertNoErrors(t, diags) 49 50 state, diags := ctx.Apply(plan, m) 51 if diags.HasErrors() { 52 t.Fatalf("diags: %s", diags.Err()) 53 } 54 55 mod := state.RootModule() 56 if len(mod.Resources) < 2 { 57 t.Fatalf("bad: %#v", mod.Resources) 58 } 59 60 actual := strings.TrimSpace(state.String()) 61 expected := strings.TrimSpace(testTerraformApplyStr) 62 if actual != expected { 63 t.Fatalf("wrong result\n\ngot:\n%s\n\nwant:\n%s", actual, expected) 64 } 65 } 66 67 func TestContext2Apply_unstable(t *testing.T) { 68 // This tests behavior when the configuration contains an unstable value, 69 // such as the result of uuid() or timestamp(), where each call produces 70 // a different result. 71 // 72 // This is an important case to test because we need to ensure that 73 // we don't re-call the function during the apply phase: the value should 74 // be fixed during plan 75 76 m := testModule(t, "apply-unstable") 77 p := testProvider("test") 78 p.PlanResourceChangeFn = testDiffFn 79 ctx := testContext2(t, &ContextOpts{ 80 Providers: map[addrs.Provider]providers.Factory{ 81 addrs.NewDefaultProvider("test"): testProviderFuncFixed(p), 82 }, 83 }) 84 85 plan, diags := ctx.Plan(m, states.NewState(), DefaultPlanOpts) 86 if diags.HasErrors() { 87 t.Fatalf("unexpected error during Plan: %s", diags.Err()) 88 } 89 90 addr := addrs.Resource{ 91 Mode: addrs.ManagedResourceMode, 92 Type: "test_resource", 93 Name: "foo", 94 }.Instance(addrs.NoKey).Absolute(addrs.RootModuleInstance) 95 schema := p.GetProviderSchemaResponse.ResourceTypes["test_resource"].Block 96 rds := plan.Changes.ResourceInstance(addr) 97 rd, err := rds.Decode(schema.ImpliedType()) 98 if err != nil { 99 t.Fatal(err) 100 } 101 if rd.After.GetAttr("random").IsKnown() { 102 t.Fatalf("Attribute 'random' has known value %#v; should be unknown in plan", rd.After.GetAttr("random")) 103 } 104 105 state, diags := ctx.Apply(plan, m) 106 if diags.HasErrors() { 107 t.Fatalf("unexpected error during Apply: %s", diags.Err()) 108 } 109 110 mod := state.Module(addr.Module) 111 rss := state.ResourceInstance(addr) 112 113 if len(mod.Resources) != 1 { 114 t.Fatalf("wrong number of resources %d; want 1", len(mod.Resources)) 115 } 116 117 rs, err := rss.Current.Decode(schema.ImpliedType()) 118 if err != nil { 119 t.Fatalf("decode error: %v", err) 120 } 121 got := rs.Value.GetAttr("random") 122 if !got.IsKnown() { 123 t.Fatalf("random is still unknown after apply") 124 } 125 if got, want := len(got.AsString()), 36; got != want { 126 t.Fatalf("random string has wrong length %d; want %d", got, want) 127 } 128 } 129 130 func TestContext2Apply_escape(t *testing.T) { 131 m := testModule(t, "apply-escape") 132 p := testProvider("aws") 133 p.PlanResourceChangeFn = testDiffFn 134 p.ApplyResourceChangeFn = testApplyFn 135 ctx := testContext2(t, &ContextOpts{ 136 Providers: map[addrs.Provider]providers.Factory{ 137 addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p), 138 }, 139 }) 140 141 plan, diags := ctx.Plan(m, states.NewState(), DefaultPlanOpts) 142 assertNoErrors(t, diags) 143 144 state, diags := ctx.Apply(plan, m) 145 if diags.HasErrors() { 146 t.Fatalf("diags: %s", diags.Err()) 147 } 148 149 checkStateString(t, state, ` 150 aws_instance.bar: 151 ID = foo 152 provider = provider["registry.terraform.io/hashicorp/aws"] 153 foo = "bar" 154 type = aws_instance 155 `) 156 } 157 158 func TestContext2Apply_resourceCountOneList(t *testing.T) { 159 m := testModule(t, "apply-resource-count-one-list") 160 p := testProvider("null") 161 p.PlanResourceChangeFn = testDiffFn 162 p.ApplyResourceChangeFn = testApplyFn 163 ctx := testContext2(t, &ContextOpts{ 164 Providers: map[addrs.Provider]providers.Factory{ 165 addrs.NewDefaultProvider("null"): testProviderFuncFixed(p), 166 }, 167 }) 168 169 plan, diags := ctx.Plan(m, states.NewState(), DefaultPlanOpts) 170 assertNoErrors(t, diags) 171 172 state, diags := ctx.Apply(plan, m) 173 assertNoDiagnostics(t, diags) 174 175 got := strings.TrimSpace(state.String()) 176 want := strings.TrimSpace(`null_resource.foo.0: 177 ID = foo 178 provider = provider["registry.terraform.io/hashicorp/null"] 179 180 Outputs: 181 182 test = [foo]`) 183 if got != want { 184 t.Fatalf("got:\n%s\n\nwant:\n%s\n", got, want) 185 } 186 } 187 func TestContext2Apply_resourceCountZeroList(t *testing.T) { 188 m := testModule(t, "apply-resource-count-zero-list") 189 p := testProvider("null") 190 p.PlanResourceChangeFn = testDiffFn 191 ctx := testContext2(t, &ContextOpts{ 192 Providers: map[addrs.Provider]providers.Factory{ 193 addrs.NewDefaultProvider("null"): testProviderFuncFixed(p), 194 }, 195 }) 196 197 plan, diags := ctx.Plan(m, states.NewState(), DefaultPlanOpts) 198 assertNoErrors(t, diags) 199 200 state, diags := ctx.Apply(plan, m) 201 if diags.HasErrors() { 202 t.Fatalf("diags: %s", diags.Err()) 203 } 204 205 got := strings.TrimSpace(state.String()) 206 want := strings.TrimSpace(`<no state> 207 Outputs: 208 209 test = []`) 210 if got != want { 211 t.Fatalf("wrong state\n\ngot:\n%s\n\nwant:\n%s\n", got, want) 212 } 213 } 214 215 func TestContext2Apply_resourceDependsOnModule(t *testing.T) { 216 m := testModule(t, "apply-resource-depends-on-module") 217 p := testProvider("aws") 218 p.PlanResourceChangeFn = testDiffFn 219 220 // verify the apply happens in the correct order 221 var mu sync.Mutex 222 var order []string 223 224 p.ApplyResourceChangeFn = func(req providers.ApplyResourceChangeRequest) (resp providers.ApplyResourceChangeResponse) { 225 ami := req.PlannedState.GetAttr("ami").AsString() 226 switch ami { 227 case "child": 228 229 // make the child slower than the parent 230 time.Sleep(50 * time.Millisecond) 231 232 mu.Lock() 233 order = append(order, "child") 234 mu.Unlock() 235 case "parent": 236 mu.Lock() 237 order = append(order, "parent") 238 mu.Unlock() 239 } 240 241 return testApplyFn(req) 242 } 243 244 ctx := testContext2(t, &ContextOpts{ 245 Providers: map[addrs.Provider]providers.Factory{ 246 addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p), 247 }, 248 }) 249 250 plan, diags := ctx.Plan(m, states.NewState(), DefaultPlanOpts) 251 assertNoErrors(t, diags) 252 253 state, diags := ctx.Apply(plan, m) 254 if diags.HasErrors() { 255 t.Fatalf("diags: %s", diags.Err()) 256 } 257 258 if !reflect.DeepEqual(order, []string{"child", "parent"}) { 259 t.Fatal("resources applied out of order") 260 } 261 262 checkStateString(t, state, testTerraformApplyResourceDependsOnModuleStr) 263 } 264 265 // Test that without a config, the Dependencies in the state are enough 266 // to maintain proper ordering. 267 func TestContext2Apply_resourceDependsOnModuleStateOnly(t *testing.T) { 268 m := testModule(t, "apply-resource-depends-on-module-empty") 269 p := testProvider("aws") 270 p.PlanResourceChangeFn = testDiffFn 271 272 state := states.NewState() 273 root := state.EnsureModule(addrs.RootModuleInstance) 274 root.SetResourceInstanceCurrent( 275 mustResourceInstanceAddr("aws_instance.a").Resource, 276 &states.ResourceInstanceObjectSrc{ 277 Status: states.ObjectReady, 278 AttrsJSON: []byte(`{"id":"parent"}`), 279 Dependencies: []addrs.ConfigResource{mustConfigResourceAddr("module.child.aws_instance.child")}, 280 }, 281 mustProviderConfig(`provider["registry.terraform.io/hashicorp/aws"]`), 282 ) 283 child := state.EnsureModule(addrs.RootModuleInstance.Child("child", addrs.NoKey)) 284 child.SetResourceInstanceCurrent( 285 mustResourceInstanceAddr("aws_instance.child").Resource, 286 &states.ResourceInstanceObjectSrc{ 287 Status: states.ObjectReady, 288 AttrsJSON: []byte(`{"id":"child"}`), 289 }, 290 mustProviderConfig(`provider["registry.terraform.io/hashicorp/aws"]`), 291 ) 292 293 { 294 // verify the apply happens in the correct order 295 var mu sync.Mutex 296 var order []string 297 298 p.ApplyResourceChangeFn = func(req providers.ApplyResourceChangeRequest) (resp providers.ApplyResourceChangeResponse) { 299 id := req.PriorState.GetAttr("id") 300 if id.IsKnown() && id.AsString() == "parent" { 301 // make the dep slower than the parent 302 time.Sleep(50 * time.Millisecond) 303 304 mu.Lock() 305 order = append(order, "child") 306 mu.Unlock() 307 } else { 308 mu.Lock() 309 order = append(order, "parent") 310 mu.Unlock() 311 } 312 313 return testApplyFn(req) 314 } 315 316 ctx := testContext2(t, &ContextOpts{ 317 Providers: map[addrs.Provider]providers.Factory{ 318 addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p), 319 }, 320 }) 321 322 plan, diags := ctx.Plan(m, state, DefaultPlanOpts) 323 assertNoErrors(t, diags) 324 325 state, diags := ctx.Apply(plan, m) 326 assertNoErrors(t, diags) 327 328 if !reflect.DeepEqual(order, []string{"child", "parent"}) { 329 t.Fatal("resources applied out of order") 330 } 331 332 checkStateString(t, state, "<no state>") 333 } 334 } 335 336 func TestContext2Apply_resourceDependsOnModuleDestroy(t *testing.T) { 337 m := testModule(t, "apply-resource-depends-on-module") 338 p := testProvider("aws") 339 p.PlanResourceChangeFn = testDiffFn 340 341 var globalState *states.State 342 { 343 ctx := testContext2(t, &ContextOpts{ 344 Providers: map[addrs.Provider]providers.Factory{ 345 addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p), 346 }, 347 }) 348 349 plan, diags := ctx.Plan(m, states.NewState(), DefaultPlanOpts) 350 assertNoErrors(t, diags) 351 352 state, diags := ctx.Apply(plan, m) 353 if diags.HasErrors() { 354 t.Fatalf("diags: %s", diags.Err()) 355 } 356 357 globalState = state 358 } 359 360 { 361 // Wait for the dependency, sleep, and verify the graph never 362 // called a child. 363 var called int32 364 var checked bool 365 p.ApplyResourceChangeFn = func(req providers.ApplyResourceChangeRequest) (resp providers.ApplyResourceChangeResponse) { 366 ami := req.PriorState.GetAttr("ami").AsString() 367 if ami == "parent" { 368 checked = true 369 370 // Sleep to allow parallel execution 371 time.Sleep(50 * time.Millisecond) 372 373 // Verify that called is 0 (dep not called) 374 if atomic.LoadInt32(&called) != 0 { 375 resp.Diagnostics = resp.Diagnostics.Append(fmt.Errorf("module child should not be called")) 376 return resp 377 } 378 } 379 380 atomic.AddInt32(&called, 1) 381 return testApplyFn(req) 382 } 383 384 ctx := testContext2(t, &ContextOpts{ 385 Providers: map[addrs.Provider]providers.Factory{ 386 addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p), 387 }, 388 }) 389 390 plan, diags := ctx.Plan(m, globalState, &PlanOpts{ 391 Mode: plans.DestroyMode, 392 }) 393 assertNoErrors(t, diags) 394 395 state, diags := ctx.Apply(plan, m) 396 if diags.HasErrors() { 397 t.Fatalf("diags: %s", diags.Err()) 398 } 399 400 if !checked { 401 t.Fatal("should check") 402 } 403 404 checkStateString(t, state, `<no state>`) 405 } 406 } 407 408 func TestContext2Apply_resourceDependsOnModuleGrandchild(t *testing.T) { 409 m := testModule(t, "apply-resource-depends-on-module-deep") 410 p := testProvider("aws") 411 p.PlanResourceChangeFn = testDiffFn 412 413 { 414 // Wait for the dependency, sleep, and verify the graph never 415 // called a child. 416 var called int32 417 var checked bool 418 p.ApplyResourceChangeFn = func(req providers.ApplyResourceChangeRequest) (resp providers.ApplyResourceChangeResponse) { 419 planned := req.PlannedState.AsValueMap() 420 if ami, ok := planned["ami"]; ok && ami.AsString() == "grandchild" { 421 checked = true 422 423 // Sleep to allow parallel execution 424 time.Sleep(50 * time.Millisecond) 425 426 // Verify that called is 0 (dep not called) 427 if atomic.LoadInt32(&called) != 0 { 428 resp.Diagnostics = resp.Diagnostics.Append(fmt.Errorf("aws_instance.a should not be called")) 429 return resp 430 } 431 } 432 433 atomic.AddInt32(&called, 1) 434 return testApplyFn(req) 435 } 436 437 ctx := testContext2(t, &ContextOpts{ 438 Providers: map[addrs.Provider]providers.Factory{ 439 addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p), 440 }, 441 }) 442 443 plan, diags := ctx.Plan(m, states.NewState(), DefaultPlanOpts) 444 assertNoErrors(t, diags) 445 446 state, diags := ctx.Apply(plan, m) 447 if diags.HasErrors() { 448 t.Fatalf("diags: %s", diags.Err()) 449 } 450 451 if !checked { 452 t.Fatal("should check") 453 } 454 455 checkStateString(t, state, testTerraformApplyResourceDependsOnModuleDeepStr) 456 } 457 } 458 459 func TestContext2Apply_resourceDependsOnModuleInModule(t *testing.T) { 460 m := testModule(t, "apply-resource-depends-on-module-in-module") 461 p := testProvider("aws") 462 p.PlanResourceChangeFn = testDiffFn 463 464 { 465 // Wait for the dependency, sleep, and verify the graph never 466 // called a child. 467 var called int32 468 var checked bool 469 p.ApplyResourceChangeFn = func(req providers.ApplyResourceChangeRequest) (resp providers.ApplyResourceChangeResponse) { 470 planned := req.PlannedState.AsValueMap() 471 if ami, ok := planned["ami"]; ok && ami.AsString() == "grandchild" { 472 checked = true 473 474 // Sleep to allow parallel execution 475 time.Sleep(50 * time.Millisecond) 476 477 // Verify that called is 0 (dep not called) 478 if atomic.LoadInt32(&called) != 0 { 479 resp.Diagnostics = resp.Diagnostics.Append(fmt.Errorf("something else was applied before grandchild; grandchild should be first")) 480 return resp 481 } 482 } 483 484 atomic.AddInt32(&called, 1) 485 return testApplyFn(req) 486 } 487 488 ctx := testContext2(t, &ContextOpts{ 489 Providers: map[addrs.Provider]providers.Factory{ 490 addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p), 491 }, 492 }) 493 494 plan, diags := ctx.Plan(m, states.NewState(), DefaultPlanOpts) 495 assertNoErrors(t, diags) 496 497 state, diags := ctx.Apply(plan, m) 498 if diags.HasErrors() { 499 t.Fatalf("diags: %s", diags.Err()) 500 } 501 502 if !checked { 503 t.Fatal("should check") 504 } 505 506 checkStateString(t, state, testTerraformApplyResourceDependsOnModuleInModuleStr) 507 } 508 } 509 510 func TestContext2Apply_mapVarBetweenModules(t *testing.T) { 511 m := testModule(t, "apply-map-var-through-module") 512 p := testProvider("null") 513 p.PlanResourceChangeFn = testDiffFn 514 p.ApplyResourceChangeFn = testApplyFn 515 ctx := testContext2(t, &ContextOpts{ 516 Providers: map[addrs.Provider]providers.Factory{ 517 addrs.NewDefaultProvider("null"): testProviderFuncFixed(p), 518 }, 519 }) 520 521 plan, diags := ctx.Plan(m, states.NewState(), SimplePlanOpts(plans.NormalMode, testInputValuesUnset(m.Module.Variables))) 522 assertNoErrors(t, diags) 523 524 state, diags := ctx.Apply(plan, m) 525 if diags.HasErrors() { 526 t.Fatalf("diags: %s", diags.Err()) 527 } 528 529 actual := strings.TrimSpace(state.String()) 530 expected := strings.TrimSpace(`<no state> 531 Outputs: 532 533 amis_from_module = {eu-west-1:ami-789012 eu-west-2:ami-989484 us-west-1:ami-123456 us-west-2:ami-456789 } 534 535 module.test: 536 null_resource.noop: 537 ID = foo 538 provider = provider["registry.terraform.io/hashicorp/null"] 539 540 Outputs: 541 542 amis_out = {eu-west-1:ami-789012 eu-west-2:ami-989484 us-west-1:ami-123456 us-west-2:ami-456789 }`) 543 if actual != expected { 544 t.Fatalf("expected: \n%s\n\ngot: \n%s\n", expected, actual) 545 } 546 } 547 548 func TestContext2Apply_refCount(t *testing.T) { 549 m := testModule(t, "apply-ref-count") 550 p := testProvider("aws") 551 p.PlanResourceChangeFn = testDiffFn 552 p.ApplyResourceChangeFn = testApplyFn 553 ctx := testContext2(t, &ContextOpts{ 554 Providers: map[addrs.Provider]providers.Factory{ 555 addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p), 556 }, 557 }) 558 559 plan, diags := ctx.Plan(m, states.NewState(), DefaultPlanOpts) 560 assertNoErrors(t, diags) 561 562 state, diags := ctx.Apply(plan, m) 563 if diags.HasErrors() { 564 t.Fatalf("diags: %s", diags.Err()) 565 } 566 567 mod := state.RootModule() 568 if len(mod.Resources) < 2 { 569 t.Fatalf("bad: %#v", mod.Resources) 570 } 571 572 actual := strings.TrimSpace(state.String()) 573 expected := strings.TrimSpace(testTerraformApplyRefCountStr) 574 if actual != expected { 575 t.Fatalf("wrong result\n\ngot:\n%s\n\nwant:\n%s", actual, expected) 576 } 577 } 578 579 func TestContext2Apply_providerAlias(t *testing.T) { 580 m := testModule(t, "apply-provider-alias") 581 582 // Each provider instance must be completely independent to ensure that we 583 // are verifying the correct state of each. 584 p := func() (providers.Interface, error) { 585 p := testProvider("aws") 586 p.PlanResourceChangeFn = testDiffFn 587 p.ApplyResourceChangeFn = testApplyFn 588 return p, nil 589 } 590 ctx := testContext2(t, &ContextOpts{ 591 Providers: map[addrs.Provider]providers.Factory{ 592 addrs.NewDefaultProvider("aws"): p, 593 }, 594 }) 595 596 plan, diags := ctx.Plan(m, states.NewState(), DefaultPlanOpts) 597 assertNoErrors(t, diags) 598 599 state, diags := ctx.Apply(plan, m) 600 if diags.HasErrors() { 601 t.Fatalf("diags: %s", diags.Err()) 602 } 603 604 mod := state.RootModule() 605 if len(mod.Resources) < 2 { 606 t.Fatalf("bad: %#v", mod.Resources) 607 } 608 609 actual := strings.TrimSpace(state.String()) 610 expected := strings.TrimSpace(testTerraformApplyProviderAliasStr) 611 if actual != expected { 612 t.Fatalf("wrong result\n\ngot:\n%s\n\nwant:\n%s", actual, expected) 613 } 614 } 615 616 // Two providers that are configured should both be configured prior to apply 617 func TestContext2Apply_providerAliasConfigure(t *testing.T) { 618 m := testModule(t, "apply-provider-alias-configure") 619 620 // Each provider instance must be completely independent to ensure that we 621 // are verifying the correct state of each. 622 p := func() (providers.Interface, error) { 623 p := testProvider("another") 624 p.ApplyResourceChangeFn = testApplyFn 625 p.PlanResourceChangeFn = testDiffFn 626 return p, nil 627 } 628 629 ctx := testContext2(t, &ContextOpts{ 630 Providers: map[addrs.Provider]providers.Factory{ 631 addrs.NewDefaultProvider("another"): p, 632 }, 633 }) 634 635 plan, diags := ctx.Plan(m, states.NewState(), DefaultPlanOpts) 636 if diags.HasErrors() { 637 t.Fatalf("diags: %s", diags.Err()) 638 } else { 639 t.Logf(legacyDiffComparisonString(plan.Changes)) 640 } 641 642 // Configure to record calls AFTER Plan above 643 var configCount int32 644 p = func() (providers.Interface, error) { 645 p := testProvider("another") 646 p.ApplyResourceChangeFn = testApplyFn 647 p.PlanResourceChangeFn = testDiffFn 648 p.ConfigureProviderFn = func(req providers.ConfigureProviderRequest) (resp providers.ConfigureProviderResponse) { 649 atomic.AddInt32(&configCount, 1) 650 651 foo := req.Config.GetAttr("foo").AsString() 652 if foo != "bar" { 653 resp.Diagnostics = resp.Diagnostics.Append(fmt.Errorf("foo: %#v", foo)) 654 } 655 656 return 657 } 658 return p, nil 659 } 660 661 ctx = testContext2(t, &ContextOpts{ 662 Providers: map[addrs.Provider]providers.Factory{ 663 addrs.NewDefaultProvider("another"): p, 664 }, 665 }) 666 667 state, diags := ctx.Apply(plan, m) 668 if diags.HasErrors() { 669 t.Fatalf("diags: %s", diags.Err()) 670 } 671 672 if configCount != 2 { 673 t.Fatalf("provider config expected 2 calls, got: %d", configCount) 674 } 675 676 actual := strings.TrimSpace(state.String()) 677 expected := strings.TrimSpace(testTerraformApplyProviderAliasConfigStr) 678 if actual != expected { 679 t.Fatalf("wrong result\n\ngot:\n%s\n\nwant:\n%s", actual, expected) 680 } 681 } 682 683 // GH-2870 684 func TestContext2Apply_providerWarning(t *testing.T) { 685 m := testModule(t, "apply-provider-warning") 686 p := testProvider("aws") 687 p.PlanResourceChangeFn = testDiffFn 688 p.ApplyResourceChangeFn = testApplyFn 689 p.ValidateResourceConfigFn = func(req providers.ValidateResourceConfigRequest) (resp providers.ValidateResourceConfigResponse) { 690 resp.Diagnostics = resp.Diagnostics.Append(tfdiags.SimpleWarning("just a warning")) 691 return 692 } 693 ctx := testContext2(t, &ContextOpts{ 694 Providers: map[addrs.Provider]providers.Factory{ 695 addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p), 696 }, 697 }) 698 699 plan, diags := ctx.Plan(m, states.NewState(), DefaultPlanOpts) 700 assertNoErrors(t, diags) 701 702 state, diags := ctx.Apply(plan, m) 703 if diags.HasErrors() { 704 t.Fatalf("diags: %s", diags.Err()) 705 } 706 707 actual := strings.TrimSpace(state.String()) 708 expected := strings.TrimSpace(` 709 aws_instance.foo: 710 ID = foo 711 provider = provider["registry.terraform.io/hashicorp/aws"] 712 type = aws_instance 713 `) 714 if actual != expected { 715 t.Fatalf("got: \n%s\n\nexpected:\n%s", actual, expected) 716 } 717 718 if !p.ConfigureProviderCalled { 719 t.Fatalf("provider Configure() was never called!") 720 } 721 } 722 723 func TestContext2Apply_emptyModule(t *testing.T) { 724 // A module with only outputs (no resources) 725 m := testModule(t, "apply-empty-module") 726 p := testProvider("aws") 727 p.PlanResourceChangeFn = testDiffFn 728 ctx := testContext2(t, &ContextOpts{ 729 Providers: map[addrs.Provider]providers.Factory{ 730 addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p), 731 }, 732 }) 733 734 plan, diags := ctx.Plan(m, states.NewState(), DefaultPlanOpts) 735 assertNoErrors(t, diags) 736 737 state, diags := ctx.Apply(plan, m) 738 if diags.HasErrors() { 739 t.Fatalf("diags: %s", diags.Err()) 740 } 741 742 actual := strings.TrimSpace(state.String()) 743 actual = strings.Replace(actual, " ", "", -1) 744 expected := strings.TrimSpace(testTerraformApplyEmptyModuleStr) 745 if actual != expected { 746 t.Fatalf("bad: \n%s\nexpect:\n%s", actual, expected) 747 } 748 } 749 750 func TestContext2Apply_createBeforeDestroy(t *testing.T) { 751 m := testModule(t, "apply-good-create-before") 752 p := testProvider("aws") 753 p.PlanResourceChangeFn = testDiffFn 754 p.ApplyResourceChangeFn = testApplyFn 755 state := states.NewState() 756 root := state.EnsureModule(addrs.RootModuleInstance) 757 root.SetResourceInstanceCurrent( 758 mustResourceInstanceAddr("aws_instance.bar").Resource, 759 &states.ResourceInstanceObjectSrc{ 760 Status: states.ObjectReady, 761 AttrsJSON: []byte(`{"id":"bar", "require_new": "abc"}`), 762 }, 763 mustProviderConfig(`provider["registry.terraform.io/hashicorp/aws"]`), 764 ) 765 ctx := testContext2(t, &ContextOpts{ 766 Providers: map[addrs.Provider]providers.Factory{ 767 addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p), 768 }, 769 }) 770 771 plan, diags := ctx.Plan(m, state, DefaultPlanOpts) 772 if diags.HasErrors() { 773 t.Fatalf("diags: %s", diags.Err()) 774 } else { 775 t.Logf(legacyDiffComparisonString(plan.Changes)) 776 } 777 778 state, diags = ctx.Apply(plan, m) 779 if diags.HasErrors() { 780 t.Fatalf("diags: %s", diags.Err()) 781 } 782 783 mod := state.RootModule() 784 if got, want := len(mod.Resources), 1; got != want { 785 t.Logf("state:\n%s", state) 786 t.Fatalf("wrong number of resources %d; want %d", got, want) 787 } 788 789 actual := strings.TrimSpace(state.String()) 790 expected := strings.TrimSpace(testTerraformApplyCreateBeforeStr) 791 if actual != expected { 792 t.Fatalf("expected:\n%s\ngot:\n%s", expected, actual) 793 } 794 } 795 796 func TestContext2Apply_createBeforeDestroyUpdate(t *testing.T) { 797 m := testModule(t, "apply-good-create-before-update") 798 p := testProvider("aws") 799 p.PlanResourceChangeFn = testDiffFn 800 801 // signal that resource foo has started applying 802 fooChan := make(chan struct{}) 803 804 p.ApplyResourceChangeFn = func(req providers.ApplyResourceChangeRequest) (resp providers.ApplyResourceChangeResponse) { 805 id := req.PriorState.GetAttr("id").AsString() 806 switch id { 807 case "bar": 808 select { 809 case <-fooChan: 810 resp.Diagnostics = resp.Diagnostics.Append(errors.New("bar must be updated before foo is destroyed")) 811 return resp 812 case <-time.After(100 * time.Millisecond): 813 // wait a moment to ensure that foo is not going to be destroyed first 814 } 815 case "foo": 816 close(fooChan) 817 } 818 819 return testApplyFn(req) 820 } 821 822 state := states.NewState() 823 root := state.EnsureModule(addrs.RootModuleInstance) 824 fooAddr := mustResourceInstanceAddr("aws_instance.foo") 825 root.SetResourceInstanceCurrent( 826 fooAddr.Resource, 827 &states.ResourceInstanceObjectSrc{ 828 Status: states.ObjectReady, 829 AttrsJSON: []byte(`{"id":"foo","foo":"bar"}`), 830 CreateBeforeDestroy: true, 831 }, 832 mustProviderConfig(`provider["registry.terraform.io/hashicorp/aws"]`), 833 ) 834 root.SetResourceInstanceCurrent( 835 mustResourceInstanceAddr("aws_instance.bar").Resource, 836 &states.ResourceInstanceObjectSrc{ 837 Status: states.ObjectReady, 838 AttrsJSON: []byte(`{"id":"bar","foo":"bar"}`), 839 CreateBeforeDestroy: true, 840 Dependencies: []addrs.ConfigResource{fooAddr.ContainingResource().Config()}, 841 }, 842 mustProviderConfig(`provider["registry.terraform.io/hashicorp/aws"]`), 843 ) 844 845 ctx := testContext2(t, &ContextOpts{ 846 Providers: map[addrs.Provider]providers.Factory{ 847 addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p), 848 }, 849 }) 850 851 plan, diags := ctx.Plan(m, state, DefaultPlanOpts) 852 if diags.HasErrors() { 853 t.Fatalf("diags: %s", diags.Err()) 854 } else { 855 t.Logf(legacyDiffComparisonString(plan.Changes)) 856 } 857 858 state, diags = ctx.Apply(plan, m) 859 if diags.HasErrors() { 860 t.Fatalf("diags: %s", diags.Err()) 861 } 862 863 mod := state.RootModule() 864 if len(mod.Resources) != 1 { 865 t.Fatalf("bad: %s", state) 866 } 867 868 actual := strings.TrimSpace(state.String()) 869 expected := strings.TrimSpace(testTerraformApplyCreateBeforeUpdateStr) 870 if actual != expected { 871 t.Fatalf("wrong result\n\ngot:\n%s\n\nwant:\n%s", actual, expected) 872 } 873 } 874 875 // This tests that when a CBD resource depends on a non-CBD resource, 876 // we can still properly apply changes that require new for both. 877 func TestContext2Apply_createBeforeDestroy_dependsNonCBD(t *testing.T) { 878 m := testModule(t, "apply-cbd-depends-non-cbd") 879 p := testProvider("aws") 880 p.PlanResourceChangeFn = testDiffFn 881 p.ApplyResourceChangeFn = testApplyFn 882 883 state := states.NewState() 884 root := state.EnsureModule(addrs.RootModuleInstance) 885 root.SetResourceInstanceCurrent( 886 mustResourceInstanceAddr("aws_instance.bar").Resource, 887 &states.ResourceInstanceObjectSrc{ 888 Status: states.ObjectReady, 889 AttrsJSON: []byte(`{"id":"bar", "require_new": "abc"}`), 890 }, 891 mustProviderConfig(`provider["registry.terraform.io/hashicorp/aws"]`), 892 ) 893 root.SetResourceInstanceCurrent( 894 mustResourceInstanceAddr("aws_instance.foo").Resource, 895 &states.ResourceInstanceObjectSrc{ 896 Status: states.ObjectReady, 897 AttrsJSON: []byte(`{"id":"foo", "require_new": "abc"}`), 898 }, 899 mustProviderConfig(`provider["registry.terraform.io/hashicorp/aws"]`), 900 ) 901 902 ctx := testContext2(t, &ContextOpts{ 903 Providers: map[addrs.Provider]providers.Factory{ 904 addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p), 905 }, 906 }) 907 908 plan, diags := ctx.Plan(m, state, DefaultPlanOpts) 909 if diags.HasErrors() { 910 t.Fatalf("diags: %s", diags.Err()) 911 } else { 912 t.Logf(legacyDiffComparisonString(plan.Changes)) 913 } 914 915 state, diags = ctx.Apply(plan, m) 916 if diags.HasErrors() { 917 t.Fatalf("diags: %s", diags.Err()) 918 } 919 920 checkStateString(t, state, ` 921 aws_instance.bar: 922 ID = foo 923 provider = provider["registry.terraform.io/hashicorp/aws"] 924 require_new = yes 925 type = aws_instance 926 value = foo 927 928 Dependencies: 929 aws_instance.foo 930 aws_instance.foo: 931 ID = foo 932 provider = provider["registry.terraform.io/hashicorp/aws"] 933 require_new = yes 934 type = aws_instance 935 `) 936 } 937 938 func TestContext2Apply_createBeforeDestroy_hook(t *testing.T) { 939 h := new(MockHook) 940 m := testModule(t, "apply-good-create-before") 941 p := testProvider("aws") 942 p.PlanResourceChangeFn = testDiffFn 943 p.ApplyResourceChangeFn = testApplyFn 944 state := states.NewState() 945 root := state.EnsureModule(addrs.RootModuleInstance) 946 root.SetResourceInstanceCurrent( 947 mustResourceInstanceAddr("aws_instance.bar").Resource, 948 &states.ResourceInstanceObjectSrc{ 949 Status: states.ObjectReady, 950 AttrsJSON: []byte(`{"id":"bar", "require_new": "abc"}`), 951 }, 952 mustProviderConfig(`provider["registry.terraform.io/hashicorp/aws"]`), 953 ) 954 955 var actual []cty.Value 956 var actualLock sync.Mutex 957 h.PostApplyFn = func(addr addrs.AbsResourceInstance, gen states.Generation, sv cty.Value, e error) (HookAction, error) { 958 actualLock.Lock() 959 960 defer actualLock.Unlock() 961 actual = append(actual, sv) 962 return HookActionContinue, nil 963 } 964 965 ctx := testContext2(t, &ContextOpts{ 966 Hooks: []Hook{h}, 967 Providers: map[addrs.Provider]providers.Factory{ 968 addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p), 969 }, 970 }) 971 972 plan, diags := ctx.Plan(m, state, DefaultPlanOpts) 973 if diags.HasErrors() { 974 t.Fatalf("diags: %s", diags.Err()) 975 } else { 976 t.Logf(legacyDiffComparisonString(plan.Changes)) 977 } 978 979 if _, diags := ctx.Apply(plan, m); diags.HasErrors() { 980 t.Fatalf("apply errors: %s", diags.Err()) 981 } 982 983 expected := []cty.Value{ 984 cty.ObjectVal(map[string]cty.Value{ 985 "id": cty.StringVal("foo"), 986 "require_new": cty.StringVal("xyz"), 987 "type": cty.StringVal("aws_instance"), 988 }), 989 cty.NullVal(cty.DynamicPseudoType), 990 } 991 992 cmpOpt := cmp.Transformer("ctyshim", hcl2shim.ConfigValueFromHCL2) 993 if !cmp.Equal(actual, expected, cmpOpt) { 994 t.Fatalf("wrong state snapshot sequence\n%s", cmp.Diff(expected, actual, cmpOpt)) 995 } 996 } 997 998 // Test that we can perform an apply with CBD in a count with deposed instances. 999 func TestContext2Apply_createBeforeDestroy_deposedCount(t *testing.T) { 1000 m := testModule(t, "apply-cbd-count") 1001 p := testProvider("aws") 1002 p.PlanResourceChangeFn = testDiffFn 1003 p.ApplyResourceChangeFn = testApplyFn 1004 1005 state := states.NewState() 1006 root := state.EnsureModule(addrs.RootModuleInstance) 1007 root.SetResourceInstanceCurrent( 1008 mustResourceInstanceAddr("aws_instance.bar[0]").Resource, 1009 &states.ResourceInstanceObjectSrc{ 1010 Status: states.ObjectTainted, 1011 AttrsJSON: []byte(`{"id":"bar"}`), 1012 }, 1013 mustProviderConfig(`provider["registry.terraform.io/hashicorp/aws"]`), 1014 ) 1015 root.SetResourceInstanceDeposed( 1016 mustResourceInstanceAddr("aws_instance.bar[0]").Resource, 1017 states.NewDeposedKey(), 1018 &states.ResourceInstanceObjectSrc{ 1019 Status: states.ObjectTainted, 1020 AttrsJSON: []byte(`{"id":"foo"}`), 1021 }, 1022 mustProviderConfig(`provider["registry.terraform.io/hashicorp/aws"]`), 1023 ) 1024 root.SetResourceInstanceCurrent( 1025 mustResourceInstanceAddr("aws_instance.bar[1]").Resource, 1026 &states.ResourceInstanceObjectSrc{ 1027 Status: states.ObjectTainted, 1028 AttrsJSON: []byte(`{"id":"bar"}`), 1029 }, 1030 mustProviderConfig(`provider["registry.terraform.io/hashicorp/aws"]`), 1031 ) 1032 root.SetResourceInstanceDeposed( 1033 mustResourceInstanceAddr("aws_instance.bar[1]").Resource, 1034 states.NewDeposedKey(), 1035 &states.ResourceInstanceObjectSrc{ 1036 Status: states.ObjectTainted, 1037 AttrsJSON: []byte(`{"id":"bar"}`), 1038 }, 1039 mustProviderConfig(`provider["registry.terraform.io/hashicorp/aws"]`), 1040 ) 1041 1042 ctx := testContext2(t, &ContextOpts{ 1043 Providers: map[addrs.Provider]providers.Factory{ 1044 addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p), 1045 }, 1046 }) 1047 1048 plan, diags := ctx.Plan(m, state, DefaultPlanOpts) 1049 if diags.HasErrors() { 1050 t.Fatalf("diags: %s", diags.Err()) 1051 } else { 1052 t.Logf(legacyDiffComparisonString(plan.Changes)) 1053 } 1054 1055 state, diags = ctx.Apply(plan, m) 1056 if diags.HasErrors() { 1057 t.Fatalf("diags: %s", diags.Err()) 1058 } 1059 1060 checkStateString(t, state, ` 1061 aws_instance.bar.0: 1062 ID = foo 1063 provider = provider["registry.terraform.io/hashicorp/aws"] 1064 foo = bar 1065 type = aws_instance 1066 aws_instance.bar.1: 1067 ID = foo 1068 provider = provider["registry.terraform.io/hashicorp/aws"] 1069 foo = bar 1070 type = aws_instance 1071 `) 1072 } 1073 1074 // Test that when we have a deposed instance but a good primary, we still 1075 // destroy the deposed instance. 1076 func TestContext2Apply_createBeforeDestroy_deposedOnly(t *testing.T) { 1077 m := testModule(t, "apply-cbd-deposed-only") 1078 p := testProvider("aws") 1079 p.PlanResourceChangeFn = testDiffFn 1080 p.ApplyResourceChangeFn = testApplyFn 1081 1082 state := states.NewState() 1083 root := state.EnsureModule(addrs.RootModuleInstance) 1084 root.SetResourceInstanceCurrent( 1085 mustResourceInstanceAddr("aws_instance.bar").Resource, 1086 &states.ResourceInstanceObjectSrc{ 1087 Status: states.ObjectReady, 1088 AttrsJSON: []byte(`{"id":"bar"}`), 1089 }, 1090 mustProviderConfig(`provider["registry.terraform.io/hashicorp/aws"]`), 1091 ) 1092 root.SetResourceInstanceDeposed( 1093 mustResourceInstanceAddr("aws_instance.bar").Resource, 1094 states.NewDeposedKey(), 1095 &states.ResourceInstanceObjectSrc{ 1096 Status: states.ObjectTainted, 1097 AttrsJSON: []byte(`{"id":"foo"}`), 1098 }, 1099 mustProviderConfig(`provider["registry.terraform.io/hashicorp/aws"]`), 1100 ) 1101 1102 ctx := testContext2(t, &ContextOpts{ 1103 Providers: map[addrs.Provider]providers.Factory{ 1104 addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p), 1105 }, 1106 }) 1107 1108 plan, diags := ctx.Plan(m, state, DefaultPlanOpts) 1109 if diags.HasErrors() { 1110 t.Fatalf("diags: %s", diags.Err()) 1111 } else { 1112 t.Logf(legacyDiffComparisonString(plan.Changes)) 1113 } 1114 1115 state, diags = ctx.Apply(plan, m) 1116 if diags.HasErrors() { 1117 t.Fatalf("diags: %s", diags.Err()) 1118 } 1119 1120 checkStateString(t, state, ` 1121 aws_instance.bar: 1122 ID = bar 1123 provider = provider["registry.terraform.io/hashicorp/aws"] 1124 type = aws_instance 1125 `) 1126 } 1127 1128 func TestContext2Apply_destroyComputed(t *testing.T) { 1129 m := testModule(t, "apply-destroy-computed") 1130 p := testProvider("aws") 1131 p.PlanResourceChangeFn = testDiffFn 1132 state := states.NewState() 1133 root := state.EnsureModule(addrs.RootModuleInstance) 1134 root.SetResourceInstanceCurrent( 1135 mustResourceInstanceAddr("aws_instance.foo").Resource, 1136 &states.ResourceInstanceObjectSrc{ 1137 Status: states.ObjectReady, 1138 AttrsJSON: []byte(`{"id":"foo", "output": "value"}`), 1139 }, 1140 mustProviderConfig(`provider["registry.terraform.io/hashicorp/aws"]`), 1141 ) 1142 ctx := testContext2(t, &ContextOpts{ 1143 Providers: map[addrs.Provider]providers.Factory{ 1144 addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p), 1145 }, 1146 }) 1147 1148 plan, diags := ctx.Plan(m, state, &PlanOpts{ 1149 Mode: plans.DestroyMode, 1150 }) 1151 if diags.HasErrors() { 1152 logDiagnostics(t, diags) 1153 t.Fatal("plan failed") 1154 } else { 1155 t.Logf("plan:\n\n%s", legacyDiffComparisonString(plan.Changes)) 1156 } 1157 1158 if _, diags := ctx.Apply(plan, m); diags.HasErrors() { 1159 logDiagnostics(t, diags) 1160 t.Fatal("apply failed") 1161 } 1162 } 1163 1164 // Test that the destroy operation uses depends_on as a source of ordering. 1165 func TestContext2Apply_destroyDependsOn(t *testing.T) { 1166 // It is possible for this to be racy, so we loop a number of times 1167 // just to check. 1168 for i := 0; i < 10; i++ { 1169 testContext2Apply_destroyDependsOn(t) 1170 } 1171 } 1172 1173 func testContext2Apply_destroyDependsOn(t *testing.T) { 1174 m := testModule(t, "apply-destroy-depends-on") 1175 p := testProvider("aws") 1176 p.PlanResourceChangeFn = testDiffFn 1177 1178 state := states.NewState() 1179 root := state.EnsureModule(addrs.RootModuleInstance) 1180 root.SetResourceInstanceCurrent( 1181 mustResourceInstanceAddr("aws_instance.bar").Resource, 1182 &states.ResourceInstanceObjectSrc{ 1183 Status: states.ObjectReady, 1184 AttrsJSON: []byte(`{"id":"bar"}`), 1185 }, 1186 mustProviderConfig(`provider["registry.terraform.io/hashicorp/aws"]`), 1187 ) 1188 root.SetResourceInstanceCurrent( 1189 mustResourceInstanceAddr("aws_instance.foo").Resource, 1190 &states.ResourceInstanceObjectSrc{ 1191 Status: states.ObjectReady, 1192 AttrsJSON: []byte(`{"id":"foo"}`), 1193 Dependencies: []addrs.ConfigResource{mustConfigResourceAddr("aws_instance.bar")}, 1194 }, 1195 mustProviderConfig(`provider["registry.terraform.io/hashicorp/aws"]`), 1196 ) 1197 1198 // Record the order we see Apply 1199 var actual []string 1200 var actualLock sync.Mutex 1201 p.ApplyResourceChangeFn = func(req providers.ApplyResourceChangeRequest) providers.ApplyResourceChangeResponse { 1202 actualLock.Lock() 1203 defer actualLock.Unlock() 1204 id := req.PriorState.GetAttr("id").AsString() 1205 actual = append(actual, id) 1206 1207 return testApplyFn(req) 1208 } 1209 1210 ctx := testContext2(t, &ContextOpts{ 1211 Providers: map[addrs.Provider]providers.Factory{ 1212 addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p), 1213 }, 1214 Parallelism: 1, // To check ordering 1215 }) 1216 1217 plan, diags := ctx.Plan(m, state, &PlanOpts{ 1218 Mode: plans.DestroyMode, 1219 }) 1220 assertNoErrors(t, diags) 1221 1222 if _, diags := ctx.Apply(plan, m); diags.HasErrors() { 1223 t.Fatalf("apply errors: %s", diags.Err()) 1224 } 1225 1226 expected := []string{"foo", "bar"} 1227 if !reflect.DeepEqual(actual, expected) { 1228 t.Fatalf("wrong order\ngot: %#v\nwant: %#v", actual, expected) 1229 } 1230 } 1231 1232 // Test that destroy ordering is correct with dependencies only 1233 // in the state. 1234 func TestContext2Apply_destroyDependsOnStateOnly(t *testing.T) { 1235 newState := states.NewState() 1236 root := newState.EnsureModule(addrs.RootModuleInstance) 1237 root.SetResourceInstanceCurrent( 1238 addrs.Resource{ 1239 Mode: addrs.ManagedResourceMode, 1240 Type: "aws_instance", 1241 Name: "foo", 1242 }.Instance(addrs.NoKey), 1243 &states.ResourceInstanceObjectSrc{ 1244 Status: states.ObjectReady, 1245 AttrsJSON: []byte(`{"id":"foo"}`), 1246 Dependencies: []addrs.ConfigResource{}, 1247 }, 1248 addrs.AbsProviderConfig{ 1249 Provider: addrs.NewDefaultProvider("aws"), 1250 Module: addrs.RootModule, 1251 }, 1252 ) 1253 root.SetResourceInstanceCurrent( 1254 addrs.Resource{ 1255 Mode: addrs.ManagedResourceMode, 1256 Type: "aws_instance", 1257 Name: "bar", 1258 }.Instance(addrs.NoKey), 1259 &states.ResourceInstanceObjectSrc{ 1260 Status: states.ObjectReady, 1261 AttrsJSON: []byte(`{"id":"bar"}`), 1262 Dependencies: []addrs.ConfigResource{ 1263 { 1264 Resource: addrs.Resource{ 1265 Mode: addrs.ManagedResourceMode, 1266 Type: "aws_instance", 1267 Name: "foo", 1268 }, 1269 Module: root.Addr.Module(), 1270 }, 1271 }, 1272 }, 1273 addrs.AbsProviderConfig{ 1274 Provider: addrs.NewDefaultProvider("aws"), 1275 Module: addrs.RootModule, 1276 }, 1277 ) 1278 1279 // It is possible for this to be racy, so we loop a number of times 1280 // just to check. 1281 for i := 0; i < 10; i++ { 1282 t.Run("new", func(t *testing.T) { 1283 testContext2Apply_destroyDependsOnStateOnly(t, newState) 1284 }) 1285 } 1286 } 1287 1288 func testContext2Apply_destroyDependsOnStateOnly(t *testing.T, state *states.State) { 1289 state = state.DeepCopy() 1290 m := testModule(t, "empty") 1291 p := testProvider("aws") 1292 p.PlanResourceChangeFn = testDiffFn 1293 // Record the order we see Apply 1294 var actual []string 1295 var actualLock sync.Mutex 1296 p.ApplyResourceChangeFn = func(req providers.ApplyResourceChangeRequest) providers.ApplyResourceChangeResponse { 1297 actualLock.Lock() 1298 defer actualLock.Unlock() 1299 id := req.PriorState.GetAttr("id").AsString() 1300 actual = append(actual, id) 1301 return testApplyFn(req) 1302 } 1303 1304 ctx := testContext2(t, &ContextOpts{ 1305 Providers: map[addrs.Provider]providers.Factory{ 1306 addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p), 1307 }, 1308 Parallelism: 1, // To check ordering 1309 }) 1310 1311 plan, diags := ctx.Plan(m, state, &PlanOpts{ 1312 Mode: plans.DestroyMode, 1313 }) 1314 assertNoErrors(t, diags) 1315 1316 if _, diags := ctx.Apply(plan, m); diags.HasErrors() { 1317 t.Fatalf("apply errors: %s", diags.Err()) 1318 } 1319 1320 expected := []string{"bar", "foo"} 1321 if !reflect.DeepEqual(actual, expected) { 1322 t.Fatalf("wrong order\ngot: %#v\nwant: %#v", actual, expected) 1323 } 1324 } 1325 1326 // Test that destroy ordering is correct with dependencies only 1327 // in the state within a module (GH-11749) 1328 func TestContext2Apply_destroyDependsOnStateOnlyModule(t *testing.T) { 1329 newState := states.NewState() 1330 child := newState.EnsureModule(addrs.RootModuleInstance.Child("child", addrs.NoKey)) 1331 child.SetResourceInstanceCurrent( 1332 addrs.Resource{ 1333 Mode: addrs.ManagedResourceMode, 1334 Type: "aws_instance", 1335 Name: "foo", 1336 }.Instance(addrs.NoKey), 1337 &states.ResourceInstanceObjectSrc{ 1338 Status: states.ObjectReady, 1339 AttrsJSON: []byte(`{"id":"foo"}`), 1340 Dependencies: []addrs.ConfigResource{}, 1341 }, 1342 addrs.AbsProviderConfig{ 1343 Provider: addrs.NewDefaultProvider("aws"), 1344 Module: addrs.RootModule, 1345 }, 1346 ) 1347 child.SetResourceInstanceCurrent( 1348 addrs.Resource{ 1349 Mode: addrs.ManagedResourceMode, 1350 Type: "aws_instance", 1351 Name: "bar", 1352 }.Instance(addrs.NoKey), 1353 &states.ResourceInstanceObjectSrc{ 1354 Status: states.ObjectReady, 1355 AttrsJSON: []byte(`{"id":"bar"}`), 1356 Dependencies: []addrs.ConfigResource{ 1357 { 1358 Resource: addrs.Resource{ 1359 Mode: addrs.ManagedResourceMode, 1360 Type: "aws_instance", 1361 Name: "foo", 1362 }, 1363 Module: child.Addr.Module(), 1364 }, 1365 }, 1366 }, 1367 addrs.AbsProviderConfig{ 1368 Provider: addrs.NewDefaultProvider("aws"), 1369 Module: addrs.RootModule, 1370 }, 1371 ) 1372 1373 // It is possible for this to be racy, so we loop a number of times 1374 // just to check. 1375 for i := 0; i < 10; i++ { 1376 t.Run("new", func(t *testing.T) { 1377 testContext2Apply_destroyDependsOnStateOnlyModule(t, newState) 1378 }) 1379 } 1380 } 1381 1382 func testContext2Apply_destroyDependsOnStateOnlyModule(t *testing.T, state *states.State) { 1383 state = state.DeepCopy() 1384 m := testModule(t, "empty") 1385 p := testProvider("aws") 1386 p.PlanResourceChangeFn = testDiffFn 1387 1388 // Record the order we see Apply 1389 var actual []string 1390 var actualLock sync.Mutex 1391 p.ApplyResourceChangeFn = func(req providers.ApplyResourceChangeRequest) providers.ApplyResourceChangeResponse { 1392 actualLock.Lock() 1393 defer actualLock.Unlock() 1394 id := req.PriorState.GetAttr("id").AsString() 1395 actual = append(actual, id) 1396 return testApplyFn(req) 1397 } 1398 1399 ctx := testContext2(t, &ContextOpts{ 1400 Providers: map[addrs.Provider]providers.Factory{ 1401 addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p), 1402 }, 1403 Parallelism: 1, // To check ordering 1404 }) 1405 1406 plan, diags := ctx.Plan(m, state, &PlanOpts{ 1407 Mode: plans.DestroyMode, 1408 }) 1409 assertNoErrors(t, diags) 1410 1411 if _, diags := ctx.Apply(plan, m); diags.HasErrors() { 1412 t.Fatalf("apply errors: %s", diags.Err()) 1413 } 1414 1415 expected := []string{"bar", "foo"} 1416 if !reflect.DeepEqual(actual, expected) { 1417 t.Fatalf("wrong order\ngot: %#v\nwant: %#v", actual, expected) 1418 } 1419 } 1420 1421 func TestContext2Apply_dataBasic(t *testing.T) { 1422 m := testModule(t, "apply-data-basic") 1423 p := testProvider("null") 1424 p.PlanResourceChangeFn = testDiffFn 1425 p.ReadDataSourceResponse = &providers.ReadDataSourceResponse{ 1426 State: cty.ObjectVal(map[string]cty.Value{ 1427 "id": cty.StringVal("yo"), 1428 "foo": cty.NullVal(cty.String), 1429 }), 1430 } 1431 1432 hook := new(MockHook) 1433 ctx := testContext2(t, &ContextOpts{ 1434 Hooks: []Hook{hook}, 1435 Providers: map[addrs.Provider]providers.Factory{ 1436 addrs.NewDefaultProvider("null"): testProviderFuncFixed(p), 1437 }, 1438 }) 1439 1440 plan, diags := ctx.Plan(m, states.NewState(), DefaultPlanOpts) 1441 if diags.HasErrors() { 1442 t.Fatalf("diags: %s", diags.Err()) 1443 } else { 1444 t.Logf(legacyDiffComparisonString(plan.Changes)) 1445 } 1446 1447 state, diags := ctx.Apply(plan, m) 1448 assertNoErrors(t, diags) 1449 1450 actual := strings.TrimSpace(state.String()) 1451 expected := strings.TrimSpace(testTerraformApplyDataBasicStr) 1452 if actual != expected { 1453 t.Fatalf("wrong result\n\ngot:\n%s\n\nwant:\n%s", actual, expected) 1454 } 1455 1456 if !hook.PreApplyCalled { 1457 t.Fatal("PreApply not called for data source read") 1458 } 1459 if !hook.PostApplyCalled { 1460 t.Fatal("PostApply not called for data source read") 1461 } 1462 } 1463 1464 func TestContext2Apply_destroyData(t *testing.T) { 1465 m := testModule(t, "apply-destroy-data-resource") 1466 p := testProvider("null") 1467 p.PlanResourceChangeFn = testDiffFn 1468 p.ReadDataSourceFn = func(req providers.ReadDataSourceRequest) providers.ReadDataSourceResponse { 1469 return providers.ReadDataSourceResponse{ 1470 State: req.Config, 1471 } 1472 } 1473 1474 state := states.NewState() 1475 root := state.EnsureModule(addrs.RootModuleInstance) 1476 root.SetResourceInstanceCurrent( 1477 mustResourceInstanceAddr("data.null_data_source.testing").Resource, 1478 &states.ResourceInstanceObjectSrc{ 1479 Status: states.ObjectReady, 1480 AttrsJSON: []byte(`{"id":"-"}`), 1481 }, 1482 mustProviderConfig(`provider["registry.terraform.io/hashicorp/null"]`), 1483 ) 1484 1485 hook := &testHook{} 1486 ctx := testContext2(t, &ContextOpts{ 1487 Providers: map[addrs.Provider]providers.Factory{ 1488 addrs.NewDefaultProvider("null"): testProviderFuncFixed(p), 1489 }, 1490 Hooks: []Hook{hook}, 1491 }) 1492 1493 plan, diags := ctx.Plan(m, state, &PlanOpts{ 1494 Mode: plans.DestroyMode, 1495 }) 1496 if diags.HasErrors() { 1497 t.Fatalf("diags: %s", diags.Err()) 1498 } else { 1499 t.Logf(legacyDiffComparisonString(plan.Changes)) 1500 } 1501 1502 newState, diags := ctx.Apply(plan, m) 1503 if diags.HasErrors() { 1504 t.Fatalf("diags: %s", diags.Err()) 1505 } 1506 1507 if got := len(newState.Modules); got != 1 { 1508 t.Fatalf("state has %d modules after destroy; want 1", got) 1509 } 1510 1511 if got := len(newState.RootModule().Resources); got != 0 { 1512 t.Fatalf("state has %d resources after destroy; want 0", got) 1513 } 1514 1515 wantHookCalls := []*testHookCall{ 1516 {"PreApply", "data.null_data_source.testing"}, 1517 {"PostApply", "data.null_data_source.testing"}, 1518 {"PostStateUpdate", ""}, 1519 } 1520 if !reflect.DeepEqual(hook.Calls, wantHookCalls) { 1521 t.Errorf("wrong hook calls\ngot: %swant: %s", spew.Sdump(hook.Calls), spew.Sdump(wantHookCalls)) 1522 } 1523 } 1524 1525 // https://github.com/hashicorp/terraform/pull/5096 1526 func TestContext2Apply_destroySkipsCBD(t *testing.T) { 1527 // Config contains CBD resource depending on non-CBD resource, which triggers 1528 // a cycle if they are both replaced, but should _not_ trigger a cycle when 1529 // just doing a `terraform destroy`. 1530 m := testModule(t, "apply-destroy-cbd") 1531 p := testProvider("aws") 1532 p.PlanResourceChangeFn = testDiffFn 1533 state := states.NewState() 1534 root := state.EnsureModule(addrs.RootModuleInstance) 1535 root.SetResourceInstanceCurrent( 1536 mustResourceInstanceAddr("aws_instance.foo").Resource, 1537 &states.ResourceInstanceObjectSrc{ 1538 Status: states.ObjectReady, 1539 AttrsJSON: []byte(`{"id":"foo"}`), 1540 }, 1541 mustProviderConfig(`provider["registry.terraform.io/hashicorp/aws"]`), 1542 ) 1543 root.SetResourceInstanceCurrent( 1544 mustResourceInstanceAddr("aws_instance.bar").Resource, 1545 &states.ResourceInstanceObjectSrc{ 1546 Status: states.ObjectReady, 1547 AttrsJSON: []byte(`{"id":"foo"}`), 1548 }, 1549 mustProviderConfig(`provider["registry.terraform.io/hashicorp/aws"]`), 1550 ) 1551 1552 ctx := testContext2(t, &ContextOpts{ 1553 Providers: map[addrs.Provider]providers.Factory{ 1554 addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p), 1555 }, 1556 }) 1557 1558 plan, diags := ctx.Plan(m, state, &PlanOpts{ 1559 Mode: plans.DestroyMode, 1560 }) 1561 if diags.HasErrors() { 1562 t.Fatalf("diags: %s", diags.Err()) 1563 } else { 1564 t.Logf(legacyDiffComparisonString(plan.Changes)) 1565 } 1566 1567 if _, diags := ctx.Apply(plan, m); diags.HasErrors() { 1568 t.Fatalf("apply errors: %s", diags.Err()) 1569 } 1570 } 1571 1572 func TestContext2Apply_destroyModuleVarProviderConfig(t *testing.T) { 1573 m := testModule(t, "apply-destroy-mod-var-provider-config") 1574 p := func() (providers.Interface, error) { 1575 p := testProvider("aws") 1576 p.PlanResourceChangeFn = testDiffFn 1577 return p, nil 1578 } 1579 state := states.NewState() 1580 child := state.EnsureModule(addrs.RootModuleInstance.Child("child", addrs.NoKey)) 1581 child.SetResourceInstanceCurrent( 1582 mustResourceInstanceAddr("aws_instance.foo").Resource, 1583 &states.ResourceInstanceObjectSrc{ 1584 Status: states.ObjectReady, 1585 AttrsJSON: []byte(`{"id":"foo"}`), 1586 }, 1587 mustProviderConfig(`provider["registry.terraform.io/hashicorp/aws"]`), 1588 ) 1589 ctx := testContext2(t, &ContextOpts{ 1590 Providers: map[addrs.Provider]providers.Factory{ 1591 addrs.NewDefaultProvider("aws"): p, 1592 }, 1593 }) 1594 1595 plan, diags := ctx.Plan(m, state, &PlanOpts{ 1596 Mode: plans.DestroyMode, 1597 }) 1598 assertNoErrors(t, diags) 1599 1600 _, diags = ctx.Apply(plan, m) 1601 if diags.HasErrors() { 1602 t.Fatalf("diags: %s", diags.Err()) 1603 } 1604 } 1605 1606 func TestContext2Apply_destroyCrossProviders(t *testing.T) { 1607 m := testModule(t, "apply-destroy-cross-providers") 1608 1609 p_aws := testProvider("aws") 1610 p_aws.ApplyResourceChangeFn = testApplyFn 1611 p_aws.PlanResourceChangeFn = testDiffFn 1612 p_aws.GetProviderSchemaResponse = getProviderSchemaResponseFromProviderSchema(&ProviderSchema{ 1613 ResourceTypes: map[string]*configschema.Block{ 1614 "aws_instance": { 1615 Attributes: map[string]*configschema.Attribute{ 1616 "id": { 1617 Type: cty.String, 1618 Computed: true, 1619 }, 1620 }, 1621 }, 1622 "aws_vpc": { 1623 Attributes: map[string]*configschema.Attribute{ 1624 "id": { 1625 Type: cty.String, 1626 Computed: true, 1627 }, 1628 "value": { 1629 Type: cty.String, 1630 Optional: true, 1631 }, 1632 }, 1633 }, 1634 }, 1635 }) 1636 1637 providers := map[addrs.Provider]providers.Factory{ 1638 addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p_aws), 1639 } 1640 1641 ctx, m, state := getContextForApply_destroyCrossProviders(t, m, providers) 1642 1643 plan, diags := ctx.Plan(m, state, &PlanOpts{ 1644 Mode: plans.DestroyMode, 1645 }) 1646 assertNoErrors(t, diags) 1647 1648 if _, diags := ctx.Apply(plan, m); diags.HasErrors() { 1649 logDiagnostics(t, diags) 1650 t.Fatal("apply failed") 1651 } 1652 } 1653 1654 func getContextForApply_destroyCrossProviders(t *testing.T, m *configs.Config, providerFactories map[addrs.Provider]providers.Factory) (*Context, *configs.Config, *states.State) { 1655 state := states.NewState() 1656 root := state.EnsureModule(addrs.RootModuleInstance) 1657 root.SetResourceInstanceCurrent( 1658 mustResourceInstanceAddr("aws_instance.shared").Resource, 1659 &states.ResourceInstanceObjectSrc{ 1660 Status: states.ObjectReady, 1661 AttrsJSON: []byte(`{"id":"test"}`), 1662 }, 1663 mustProviderConfig(`provider["registry.terraform.io/hashicorp/aws"]`), 1664 ) 1665 child := state.EnsureModule(addrs.RootModuleInstance.Child("child", addrs.NoKey)) 1666 child.SetResourceInstanceCurrent( 1667 mustResourceInstanceAddr("aws_vpc.bar").Resource, 1668 &states.ResourceInstanceObjectSrc{ 1669 Status: states.ObjectReady, 1670 AttrsJSON: []byte(`{"id": "vpc-aaabbb12", "value":"test"}`), 1671 }, 1672 mustProviderConfig(`provider["registry.terraform.io/hashicorp/aws"]`), 1673 ) 1674 1675 ctx := testContext2(t, &ContextOpts{ 1676 Providers: providerFactories, 1677 }) 1678 1679 return ctx, m, state 1680 } 1681 1682 func TestContext2Apply_minimal(t *testing.T) { 1683 m := testModule(t, "apply-minimal") 1684 p := testProvider("aws") 1685 p.PlanResourceChangeFn = testDiffFn 1686 p.ApplyResourceChangeFn = testApplyFn 1687 ctx := testContext2(t, &ContextOpts{ 1688 Providers: map[addrs.Provider]providers.Factory{ 1689 addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p), 1690 }, 1691 }) 1692 1693 plan, diags := ctx.Plan(m, states.NewState(), DefaultPlanOpts) 1694 assertNoErrors(t, diags) 1695 1696 state, diags := ctx.Apply(plan, m) 1697 if diags.HasErrors() { 1698 t.Fatalf("diags: %s", diags.Err()) 1699 } 1700 1701 actual := strings.TrimSpace(state.String()) 1702 expected := strings.TrimSpace(testTerraformApplyMinimalStr) 1703 if actual != expected { 1704 t.Fatalf("wrong result\n\ngot:\n%s\n\nwant:\n%s", actual, expected) 1705 } 1706 } 1707 1708 func TestContext2Apply_cancel(t *testing.T) { 1709 stopped := false 1710 1711 m := testModule(t, "apply-cancel") 1712 p := testProvider("aws") 1713 ctx := testContext2(t, &ContextOpts{ 1714 Providers: map[addrs.Provider]providers.Factory{ 1715 addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p), 1716 }, 1717 }) 1718 1719 p.ApplyResourceChangeFn = func(req providers.ApplyResourceChangeRequest) providers.ApplyResourceChangeResponse { 1720 if !stopped { 1721 stopped = true 1722 go ctx.Stop() 1723 1724 for { 1725 if ctx.sh.Stopped() { 1726 break 1727 } 1728 time.Sleep(10 * time.Millisecond) 1729 } 1730 } 1731 return testApplyFn(req) 1732 } 1733 p.PlanResourceChangeFn = testDiffFn 1734 1735 plan, diags := ctx.Plan(m, states.NewState(), DefaultPlanOpts) 1736 assertNoErrors(t, diags) 1737 1738 // Start the Apply in a goroutine 1739 var applyDiags tfdiags.Diagnostics 1740 stateCh := make(chan *states.State) 1741 go func() { 1742 state, diags := ctx.Apply(plan, m) 1743 applyDiags = diags 1744 1745 stateCh <- state 1746 }() 1747 1748 state := <-stateCh 1749 // only expecting an early exit error 1750 if !applyDiags.HasErrors() { 1751 t.Fatal("expected early exit error") 1752 } 1753 1754 for _, d := range applyDiags { 1755 desc := d.Description() 1756 if desc.Summary != "execution halted" { 1757 t.Fatalf("unexpected error: %v", applyDiags.Err()) 1758 } 1759 } 1760 1761 actual := strings.TrimSpace(state.String()) 1762 expected := strings.TrimSpace(testTerraformApplyCancelStr) 1763 if actual != expected { 1764 t.Fatalf("wrong result\n\ngot:\n%s\n\nwant:\n%s", actual, expected) 1765 } 1766 1767 if !p.StopCalled { 1768 t.Fatal("stop should be called") 1769 } 1770 } 1771 1772 func TestContext2Apply_cancelBlock(t *testing.T) { 1773 m := testModule(t, "apply-cancel-block") 1774 p := testProvider("aws") 1775 ctx := testContext2(t, &ContextOpts{ 1776 Providers: map[addrs.Provider]providers.Factory{ 1777 addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p), 1778 }, 1779 }) 1780 1781 applyCh := make(chan struct{}) 1782 p.PlanResourceChangeFn = testDiffFn 1783 p.ApplyResourceChangeFn = func(req providers.ApplyResourceChangeRequest) providers.ApplyResourceChangeResponse { 1784 close(applyCh) 1785 1786 for !ctx.sh.Stopped() { 1787 // Wait for stop to be called. We call Gosched here so that 1788 // the other goroutines can always be scheduled to set Stopped. 1789 runtime.Gosched() 1790 } 1791 1792 // Sleep 1793 time.Sleep(100 * time.Millisecond) 1794 return testApplyFn(req) 1795 } 1796 1797 plan, diags := ctx.Plan(m, states.NewState(), DefaultPlanOpts) 1798 assertNoErrors(t, diags) 1799 1800 // Start the Apply in a goroutine 1801 var applyDiags tfdiags.Diagnostics 1802 stateCh := make(chan *states.State) 1803 go func() { 1804 state, diags := ctx.Apply(plan, m) 1805 applyDiags = diags 1806 1807 stateCh <- state 1808 }() 1809 1810 stopDone := make(chan struct{}) 1811 go func() { 1812 defer close(stopDone) 1813 <-applyCh 1814 ctx.Stop() 1815 }() 1816 1817 // Make sure that stop blocks 1818 select { 1819 case <-stopDone: 1820 t.Fatal("stop should block") 1821 case <-time.After(10 * time.Millisecond): 1822 } 1823 1824 // Wait for stop 1825 select { 1826 case <-stopDone: 1827 case <-time.After(500 * time.Millisecond): 1828 t.Fatal("stop should be done") 1829 } 1830 1831 // Wait for apply to complete 1832 state := <-stateCh 1833 // only expecting an early exit error 1834 if !applyDiags.HasErrors() { 1835 t.Fatal("expected early exit error") 1836 } 1837 1838 for _, d := range applyDiags { 1839 desc := d.Description() 1840 if desc.Summary != "execution halted" { 1841 t.Fatalf("unexpected error: %v", applyDiags.Err()) 1842 } 1843 } 1844 1845 checkStateString(t, state, ` 1846 aws_instance.foo: 1847 ID = foo 1848 provider = provider["registry.terraform.io/hashicorp/aws"] 1849 num = 2 1850 type = aws_instance 1851 `) 1852 } 1853 1854 func TestContext2Apply_cancelProvisioner(t *testing.T) { 1855 m := testModule(t, "apply-cancel-provisioner") 1856 p := testProvider("aws") 1857 p.PlanResourceChangeFn = testDiffFn 1858 p.ApplyResourceChangeFn = testApplyFn 1859 1860 pr := testProvisioner() 1861 pr.GetSchemaResponse = provisioners.GetSchemaResponse{ 1862 Provisioner: &configschema.Block{ 1863 Attributes: map[string]*configschema.Attribute{ 1864 "foo": { 1865 Type: cty.String, 1866 Optional: true, 1867 }, 1868 }, 1869 }, 1870 } 1871 1872 ctx := testContext2(t, &ContextOpts{ 1873 Providers: map[addrs.Provider]providers.Factory{ 1874 addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p), 1875 }, 1876 Provisioners: map[string]provisioners.Factory{ 1877 "shell": testProvisionerFuncFixed(pr), 1878 }, 1879 }) 1880 1881 prStopped := make(chan struct{}) 1882 pr.ProvisionResourceFn = func(req provisioners.ProvisionResourceRequest) (resp provisioners.ProvisionResourceResponse) { 1883 // Start the stop process 1884 go ctx.Stop() 1885 1886 <-prStopped 1887 return 1888 } 1889 pr.StopFn = func() error { 1890 close(prStopped) 1891 return nil 1892 } 1893 1894 plan, diags := ctx.Plan(m, states.NewState(), DefaultPlanOpts) 1895 assertNoErrors(t, diags) 1896 1897 // Start the Apply in a goroutine 1898 var applyDiags tfdiags.Diagnostics 1899 stateCh := make(chan *states.State) 1900 go func() { 1901 state, diags := ctx.Apply(plan, m) 1902 applyDiags = diags 1903 1904 stateCh <- state 1905 }() 1906 1907 // Wait for completion 1908 state := <-stateCh 1909 1910 // we are expecting only an early exit error 1911 if !applyDiags.HasErrors() { 1912 t.Fatal("expected early exit error") 1913 } 1914 1915 for _, d := range applyDiags { 1916 desc := d.Description() 1917 if desc.Summary != "execution halted" { 1918 t.Fatalf("unexpected error: %v", applyDiags.Err()) 1919 } 1920 } 1921 1922 checkStateString(t, state, ` 1923 aws_instance.foo: (tainted) 1924 ID = foo 1925 provider = provider["registry.terraform.io/hashicorp/aws"] 1926 num = 2 1927 type = aws_instance 1928 `) 1929 1930 if !pr.StopCalled { 1931 t.Fatal("stop should be called") 1932 } 1933 } 1934 1935 func TestContext2Apply_compute(t *testing.T) { 1936 m := testModule(t, "apply-compute") 1937 p := testProvider("aws") 1938 p.PlanResourceChangeFn = testDiffFn 1939 p.ApplyResourceChangeFn = testApplyFn 1940 p.GetProviderSchemaResponse = getProviderSchemaResponseFromProviderSchema(&ProviderSchema{ 1941 ResourceTypes: map[string]*configschema.Block{ 1942 "aws_instance": { 1943 Attributes: map[string]*configschema.Attribute{ 1944 "num": { 1945 Type: cty.Number, 1946 Optional: true, 1947 }, 1948 "compute": { 1949 Type: cty.String, 1950 Optional: true, 1951 }, 1952 "compute_value": { 1953 Type: cty.String, 1954 Optional: true, 1955 }, 1956 "foo": { 1957 Type: cty.String, 1958 Optional: true, 1959 }, 1960 "id": { 1961 Type: cty.String, 1962 Computed: true, 1963 }, 1964 "type": { 1965 Type: cty.String, 1966 Computed: true, 1967 }, 1968 "value": { // Populated from compute_value because compute = "value" in the config fixture 1969 Type: cty.String, 1970 Computed: true, 1971 }, 1972 }, 1973 }, 1974 }, 1975 }) 1976 1977 ctx := testContext2(t, &ContextOpts{ 1978 Providers: map[addrs.Provider]providers.Factory{ 1979 addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p), 1980 }, 1981 }) 1982 1983 plan, diags := ctx.Plan(m, states.NewState(), &PlanOpts{ 1984 SetVariables: InputValues{ 1985 "value": &InputValue{ 1986 Value: cty.NumberIntVal(1), 1987 SourceType: ValueFromCaller, 1988 }, 1989 }, 1990 }) 1991 assertNoErrors(t, diags) 1992 1993 state, diags := ctx.Apply(plan, m) 1994 if diags.HasErrors() { 1995 t.Fatalf("unexpected errors: %s", diags.Err()) 1996 } 1997 1998 actual := strings.TrimSpace(state.String()) 1999 expected := strings.TrimSpace(testTerraformApplyComputeStr) 2000 if actual != expected { 2001 t.Fatalf("wrong result\n\ngot:\n%s\n\nwant:\n%s", actual, expected) 2002 } 2003 } 2004 2005 func TestContext2Apply_countDecrease(t *testing.T) { 2006 m := testModule(t, "apply-count-dec") 2007 p := testProvider("aws") 2008 p.PlanResourceChangeFn = testDiffFn 2009 p.ApplyResourceChangeFn = testApplyFn 2010 state := states.NewState() 2011 root := state.EnsureModule(addrs.RootModuleInstance) 2012 root.SetResourceInstanceCurrent( 2013 mustResourceInstanceAddr("aws_instance.foo[0]").Resource, 2014 &states.ResourceInstanceObjectSrc{ 2015 Status: states.ObjectReady, 2016 AttrsJSON: []byte(`{"id":"bar","foo": "foo","type": "aws_instance"}`), 2017 }, 2018 mustProviderConfig(`provider["registry.terraform.io/hashicorp/aws"]`), 2019 ) 2020 root.SetResourceInstanceCurrent( 2021 mustResourceInstanceAddr("aws_instance.foo[1]").Resource, 2022 &states.ResourceInstanceObjectSrc{ 2023 Status: states.ObjectReady, 2024 AttrsJSON: []byte(`{"id":"bar","foo": "foo","type": "aws_instance"}`), 2025 }, 2026 mustProviderConfig(`provider["registry.terraform.io/hashicorp/aws"]`), 2027 ) 2028 root.SetResourceInstanceCurrent( 2029 mustResourceInstanceAddr("aws_instance.foo[2]").Resource, 2030 &states.ResourceInstanceObjectSrc{ 2031 Status: states.ObjectReady, 2032 AttrsJSON: []byte(`{"id":"bar", "foo": "foo", "type": "aws_instance"}`), 2033 }, 2034 mustProviderConfig(`provider["registry.terraform.io/hashicorp/aws"]`), 2035 ) 2036 2037 ctx := testContext2(t, &ContextOpts{ 2038 Providers: map[addrs.Provider]providers.Factory{ 2039 addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p), 2040 }, 2041 }) 2042 2043 plan, diags := ctx.Plan(m, state, DefaultPlanOpts) 2044 assertNoErrors(t, diags) 2045 2046 s, diags := ctx.Apply(plan, m) 2047 assertNoErrors(t, diags) 2048 2049 actual := strings.TrimSpace(s.String()) 2050 expected := strings.TrimSpace(testTerraformApplyCountDecStr) 2051 if actual != expected { 2052 t.Fatalf("wrong result\n\ngot:\n%s\n\nwant:\n%s", actual, expected) 2053 } 2054 } 2055 2056 func TestContext2Apply_countDecreaseToOneX(t *testing.T) { 2057 m := testModule(t, "apply-count-dec-one") 2058 p := testProvider("aws") 2059 p.PlanResourceChangeFn = testDiffFn 2060 state := states.NewState() 2061 root := state.EnsureModule(addrs.RootModuleInstance) 2062 root.SetResourceInstanceCurrent( 2063 mustResourceInstanceAddr("aws_instance.foo[0]").Resource, 2064 &states.ResourceInstanceObjectSrc{ 2065 Status: states.ObjectReady, 2066 AttrsJSON: []byte(`{"id":"bar", "foo": "foo", "type": "aws_instance"}`), 2067 }, 2068 mustProviderConfig(`provider["registry.terraform.io/hashicorp/aws"]`), 2069 ) 2070 root.SetResourceInstanceCurrent( 2071 mustResourceInstanceAddr("aws_instance.foo[1]").Resource, 2072 &states.ResourceInstanceObjectSrc{ 2073 Status: states.ObjectReady, 2074 AttrsJSON: []byte(`{"id":"bar"}`), 2075 }, 2076 mustProviderConfig(`provider["registry.terraform.io/hashicorp/aws"]`), 2077 ) 2078 root.SetResourceInstanceCurrent( 2079 mustResourceInstanceAddr("aws_instance.foo[2]").Resource, 2080 &states.ResourceInstanceObjectSrc{ 2081 Status: states.ObjectReady, 2082 AttrsJSON: []byte(`{"id":"bar"}`), 2083 }, 2084 mustProviderConfig(`provider["registry.terraform.io/hashicorp/aws"]`), 2085 ) 2086 2087 ctx := testContext2(t, &ContextOpts{ 2088 Providers: map[addrs.Provider]providers.Factory{ 2089 addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p), 2090 }, 2091 }) 2092 2093 plan, diags := ctx.Plan(m, state, DefaultPlanOpts) 2094 assertNoErrors(t, diags) 2095 2096 s, diags := ctx.Apply(plan, m) 2097 if diags.HasErrors() { 2098 t.Fatalf("diags: %s", diags.Err()) 2099 } 2100 2101 actual := strings.TrimSpace(s.String()) 2102 expected := strings.TrimSpace(testTerraformApplyCountDecToOneStr) 2103 if actual != expected { 2104 t.Fatalf("wrong result\n\ngot:\n%s\n\nwant:\n%s", actual, expected) 2105 } 2106 } 2107 2108 // https://github.com/PeoplePerHour/terraform/pull/11 2109 // 2110 // This tests a rare but possible situation where we have both a no-key and 2111 // a zero-key instance of the same resource in the configuration when we 2112 // disable count. 2113 // 2114 // The main way to get here is for a provider to fail to destroy the zero-key 2115 // instance but succeed in creating the no-key instance, since those two 2116 // can typically happen concurrently. There are various other ways to get here 2117 // that might be considered user error, such as using "terraform state mv" 2118 // to create a strange combination of different key types on the same resource. 2119 // 2120 // This test indirectly exercises an intentional interaction between 2121 // refactoring.ImpliedMoveStatements and refactoring.ApplyMoves: we'll first 2122 // generate an implied move statement from aws_instance.foo[0] to 2123 // aws_instance.foo, but then refactoring.ApplyMoves should notice that and 2124 // ignore the statement, in the same way as it would if an explicit move 2125 // statement specified the same situation. 2126 func TestContext2Apply_countDecreaseToOneCorrupted(t *testing.T) { 2127 m := testModule(t, "apply-count-dec-one") 2128 p := testProvider("aws") 2129 p.PlanResourceChangeFn = testDiffFn 2130 state := states.NewState() 2131 root := state.EnsureModule(addrs.RootModuleInstance) 2132 root.SetResourceInstanceCurrent( 2133 mustResourceInstanceAddr("aws_instance.foo").Resource, 2134 &states.ResourceInstanceObjectSrc{ 2135 Status: states.ObjectReady, 2136 AttrsJSON: []byte(`{"id":"bar", "foo": "foo", "type": "aws_instance"}`), 2137 }, 2138 mustProviderConfig(`provider["registry.terraform.io/hashicorp/aws"]`), 2139 ) 2140 root.SetResourceInstanceCurrent( 2141 mustResourceInstanceAddr("aws_instance.foo[0]").Resource, 2142 &states.ResourceInstanceObjectSrc{ 2143 Status: states.ObjectReady, 2144 AttrsJSON: []byte(`{"id":"baz", "type": "aws_instance"}`), 2145 }, 2146 mustProviderConfig(`provider["registry.terraform.io/hashicorp/aws"]`), 2147 ) 2148 2149 ctx := testContext2(t, &ContextOpts{ 2150 Providers: map[addrs.Provider]providers.Factory{ 2151 addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p), 2152 }, 2153 }) 2154 2155 plan, diags := ctx.Plan(m, state, DefaultPlanOpts) 2156 assertNoErrors(t, diags) 2157 { 2158 got := strings.TrimSpace(legacyPlanComparisonString(state, plan.Changes)) 2159 want := strings.TrimSpace(testTerraformApplyCountDecToOneCorruptedPlanStr) 2160 if got != want { 2161 t.Fatalf("wrong plan result\ngot:\n%s\nwant:\n%s", got, want) 2162 } 2163 } 2164 { 2165 change := plan.Changes.ResourceInstance(mustResourceInstanceAddr("aws_instance.foo[0]")) 2166 if change == nil { 2167 t.Fatalf("no planned change for instance zero") 2168 } 2169 if got, want := change.Action, plans.Delete; got != want { 2170 t.Errorf("wrong action for instance zero %s; want %s", got, want) 2171 } 2172 if got, want := change.ActionReason, plans.ResourceInstanceDeleteBecauseWrongRepetition; got != want { 2173 t.Errorf("wrong action reason for instance zero %s; want %s", got, want) 2174 } 2175 } 2176 { 2177 change := plan.Changes.ResourceInstance(mustResourceInstanceAddr("aws_instance.foo")) 2178 if change == nil { 2179 t.Fatalf("no planned change for no-key instance") 2180 } 2181 if got, want := change.Action, plans.NoOp; got != want { 2182 t.Errorf("wrong action for no-key instance %s; want %s", got, want) 2183 } 2184 if got, want := change.ActionReason, plans.ResourceInstanceChangeNoReason; got != want { 2185 t.Errorf("wrong action reason for no-key instance %s; want %s", got, want) 2186 } 2187 } 2188 2189 s, diags := ctx.Apply(plan, m) 2190 if diags.HasErrors() { 2191 t.Fatalf("diags: %s", diags.Err()) 2192 } 2193 2194 actual := strings.TrimSpace(s.String()) 2195 expected := strings.TrimSpace(testTerraformApplyCountDecToOneCorruptedStr) 2196 if actual != expected { 2197 t.Fatalf("wrong final state\n\ngot:\n%s\n\nwant:\n%s", actual, expected) 2198 } 2199 } 2200 2201 func TestContext2Apply_countTainted(t *testing.T) { 2202 m := testModule(t, "apply-count-tainted") 2203 p := testProvider("aws") 2204 p.PlanResourceChangeFn = testDiffFn 2205 p.ApplyResourceChangeFn = testApplyFn 2206 state := states.NewState() 2207 root := state.EnsureModule(addrs.RootModuleInstance) 2208 root.SetResourceInstanceCurrent( 2209 mustResourceInstanceAddr("aws_instance.foo[0]").Resource, 2210 &states.ResourceInstanceObjectSrc{ 2211 Status: states.ObjectTainted, 2212 AttrsJSON: []byte(`{"id":"bar", "type": "aws_instance", "foo": "foo"}`), 2213 }, 2214 mustProviderConfig(`provider["registry.terraform.io/hashicorp/aws"]`), 2215 ) 2216 ctx := testContext2(t, &ContextOpts{ 2217 Providers: map[addrs.Provider]providers.Factory{ 2218 addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p), 2219 }, 2220 }) 2221 2222 plan, diags := ctx.Plan(m, state, DefaultPlanOpts) 2223 assertNoErrors(t, diags) 2224 { 2225 got := strings.TrimSpace(legacyDiffComparisonString(plan.Changes)) 2226 want := strings.TrimSpace(` 2227 DESTROY/CREATE: aws_instance.foo[0] 2228 foo: "foo" => "foo" 2229 id: "bar" => "<computed>" 2230 type: "aws_instance" => "<computed>" 2231 CREATE: aws_instance.foo[1] 2232 foo: "" => "foo" 2233 id: "" => "<computed>" 2234 type: "" => "<computed>" 2235 `) 2236 if got != want { 2237 t.Fatalf("wrong plan\n\ngot:\n%s\n\nwant:\n%s", got, want) 2238 } 2239 } 2240 2241 s, diags := ctx.Apply(plan, m) 2242 assertNoErrors(t, diags) 2243 2244 got := strings.TrimSpace(s.String()) 2245 want := strings.TrimSpace(` 2246 aws_instance.foo.0: 2247 ID = foo 2248 provider = provider["registry.terraform.io/hashicorp/aws"] 2249 foo = foo 2250 type = aws_instance 2251 aws_instance.foo.1: 2252 ID = foo 2253 provider = provider["registry.terraform.io/hashicorp/aws"] 2254 foo = foo 2255 type = aws_instance 2256 `) 2257 if got != want { 2258 t.Fatalf("wrong final state\n\ngot:\n%s\n\nwant:\n%s", got, want) 2259 } 2260 } 2261 2262 func TestContext2Apply_countVariable(t *testing.T) { 2263 m := testModule(t, "apply-count-variable") 2264 p := testProvider("aws") 2265 p.PlanResourceChangeFn = testDiffFn 2266 p.ApplyResourceChangeFn = testApplyFn 2267 ctx := testContext2(t, &ContextOpts{ 2268 Providers: map[addrs.Provider]providers.Factory{ 2269 addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p), 2270 }, 2271 }) 2272 2273 plan, diags := ctx.Plan(m, states.NewState(), SimplePlanOpts(plans.NormalMode, testInputValuesUnset(m.Module.Variables))) 2274 assertNoErrors(t, diags) 2275 2276 state, diags := ctx.Apply(plan, m) 2277 if diags.HasErrors() { 2278 t.Fatalf("diags: %s", diags.Err()) 2279 } 2280 2281 actual := strings.TrimSpace(state.String()) 2282 expected := strings.TrimSpace(testTerraformApplyCountVariableStr) 2283 if actual != expected { 2284 t.Fatalf("wrong result\n\ngot:\n%s\n\nwant:\n%s", actual, expected) 2285 } 2286 } 2287 2288 func TestContext2Apply_countVariableRef(t *testing.T) { 2289 m := testModule(t, "apply-count-variable-ref") 2290 p := testProvider("aws") 2291 p.PlanResourceChangeFn = testDiffFn 2292 p.ApplyResourceChangeFn = testApplyFn 2293 ctx := testContext2(t, &ContextOpts{ 2294 Providers: map[addrs.Provider]providers.Factory{ 2295 addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p), 2296 }, 2297 }) 2298 2299 plan, diags := ctx.Plan(m, states.NewState(), SimplePlanOpts(plans.NormalMode, testInputValuesUnset(m.Module.Variables))) 2300 assertNoErrors(t, diags) 2301 2302 state, diags := ctx.Apply(plan, m) 2303 if diags.HasErrors() { 2304 t.Fatalf("diags: %s", diags.Err()) 2305 } 2306 2307 actual := strings.TrimSpace(state.String()) 2308 expected := strings.TrimSpace(testTerraformApplyCountVariableRefStr) 2309 if actual != expected { 2310 t.Fatalf("wrong result\n\ngot:\n%s\n\nwant:\n%s", actual, expected) 2311 } 2312 } 2313 2314 func TestContext2Apply_provisionerInterpCount(t *testing.T) { 2315 // This test ensures that a provisioner can interpolate a resource count 2316 // even though the provisioner expression is evaluated during the plan 2317 // walk. https://github.com/hashicorp/terraform/issues/16840 2318 2319 m, snap := testModuleWithSnapshot(t, "apply-provisioner-interp-count") 2320 2321 p := testProvider("aws") 2322 p.PlanResourceChangeFn = testDiffFn 2323 2324 pr := testProvisioner() 2325 2326 Providers := map[addrs.Provider]providers.Factory{ 2327 addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p), 2328 } 2329 2330 provisioners := map[string]provisioners.Factory{ 2331 "local-exec": testProvisionerFuncFixed(pr), 2332 } 2333 ctx := testContext2(t, &ContextOpts{ 2334 Providers: Providers, 2335 Provisioners: provisioners, 2336 }) 2337 2338 plan, diags := ctx.Plan(m, states.NewState(), SimplePlanOpts(plans.NormalMode, testInputValuesUnset(m.Module.Variables))) 2339 assertNoErrors(t, diags) 2340 2341 // We'll marshal and unmarshal the plan here, to ensure that we have 2342 // a clean new context as would be created if we separately ran 2343 // terraform plan -out=tfplan && terraform apply tfplan 2344 ctxOpts, m, plan, err := contextOptsForPlanViaFile(t, snap, plan) 2345 if err != nil { 2346 t.Fatal(err) 2347 } 2348 ctxOpts.Providers = Providers 2349 ctxOpts.Provisioners = provisioners 2350 ctx, diags = NewContext(ctxOpts) 2351 if diags.HasErrors() { 2352 t.Fatalf("failed to create context for plan: %s", diags.Err()) 2353 } 2354 2355 // Applying the plan should now succeed 2356 _, diags = ctx.Apply(plan, m) 2357 if diags.HasErrors() { 2358 t.Fatalf("apply failed unexpectedly: %s", diags.Err()) 2359 } 2360 2361 // Verify apply was invoked 2362 if !pr.ProvisionResourceCalled { 2363 t.Fatalf("provisioner was not called") 2364 } 2365 } 2366 2367 func TestContext2Apply_foreachVariable(t *testing.T) { 2368 m := testModule(t, "plan-for-each-unknown-value") 2369 p := testProvider("aws") 2370 p.PlanResourceChangeFn = testDiffFn 2371 p.ApplyResourceChangeFn = testApplyFn 2372 ctx := testContext2(t, &ContextOpts{ 2373 Providers: map[addrs.Provider]providers.Factory{ 2374 addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p), 2375 }, 2376 }) 2377 2378 plan, diags := ctx.Plan(m, states.NewState(), &PlanOpts{ 2379 Mode: plans.NormalMode, 2380 SetVariables: InputValues{ 2381 "foo": &InputValue{ 2382 Value: cty.StringVal("hello"), 2383 }, 2384 }, 2385 }) 2386 assertNoErrors(t, diags) 2387 2388 state, diags := ctx.Apply(plan, m) 2389 if diags.HasErrors() { 2390 t.Fatalf("diags: %s", diags.Err()) 2391 } 2392 2393 actual := strings.TrimSpace(state.String()) 2394 expected := strings.TrimSpace(testTerraformApplyForEachVariableStr) 2395 if actual != expected { 2396 t.Fatalf("wrong result\n\ngot:\n%s\n\nwant:\n%s", actual, expected) 2397 } 2398 } 2399 2400 func TestContext2Apply_moduleBasic(t *testing.T) { 2401 m := testModule(t, "apply-module") 2402 p := testProvider("aws") 2403 p.PlanResourceChangeFn = testDiffFn 2404 p.ApplyResourceChangeFn = testApplyFn 2405 ctx := testContext2(t, &ContextOpts{ 2406 Providers: map[addrs.Provider]providers.Factory{ 2407 addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p), 2408 }, 2409 }) 2410 2411 plan, diags := ctx.Plan(m, states.NewState(), DefaultPlanOpts) 2412 assertNoErrors(t, diags) 2413 2414 state, diags := ctx.Apply(plan, m) 2415 if diags.HasErrors() { 2416 t.Fatalf("diags: %s", diags.Err()) 2417 } 2418 2419 actual := strings.TrimSpace(state.String()) 2420 expected := strings.TrimSpace(testTerraformApplyModuleStr) 2421 if actual != expected { 2422 t.Fatalf("bad, expected:\n%s\n\nactual:\n%s", expected, actual) 2423 } 2424 } 2425 2426 func TestContext2Apply_moduleDestroyOrder(t *testing.T) { 2427 m := testModule(t, "apply-module-destroy-order") 2428 p := testProvider("aws") 2429 p.PlanResourceChangeFn = testDiffFn 2430 2431 // Create a custom apply function to track the order they were destroyed 2432 var order []string 2433 var orderLock sync.Mutex 2434 p.ApplyResourceChangeFn = func(req providers.ApplyResourceChangeRequest) (resp providers.ApplyResourceChangeResponse) { 2435 id := req.PriorState.GetAttr("id").AsString() 2436 2437 if id == "b" { 2438 // Pause briefly to make any race conditions more visible, since 2439 // missing edges here can cause undeterministic ordering. 2440 time.Sleep(100 * time.Millisecond) 2441 } 2442 2443 orderLock.Lock() 2444 defer orderLock.Unlock() 2445 2446 order = append(order, id) 2447 resp.NewState = req.PlannedState 2448 return resp 2449 } 2450 2451 p.GetProviderSchemaResponse = getProviderSchemaResponseFromProviderSchema(&ProviderSchema{ 2452 ResourceTypes: map[string]*configschema.Block{ 2453 "aws_instance": { 2454 Attributes: map[string]*configschema.Attribute{ 2455 "id": {Type: cty.String, Required: true}, 2456 "blah": {Type: cty.String, Optional: true}, 2457 "value": {Type: cty.String, Optional: true}, 2458 }, 2459 }, 2460 }, 2461 }) 2462 2463 state := states.NewState() 2464 child := state.EnsureModule(addrs.RootModuleInstance.Child("child", addrs.NoKey)) 2465 child.SetResourceInstanceCurrent( 2466 mustResourceInstanceAddr("aws_instance.a").Resource, 2467 &states.ResourceInstanceObjectSrc{ 2468 Status: states.ObjectReady, 2469 AttrsJSON: []byte(`{"id":"a"}`), 2470 }, 2471 mustProviderConfig(`provider["registry.terraform.io/hashicorp/aws"]`), 2472 ) 2473 root := state.EnsureModule(addrs.RootModuleInstance) 2474 root.SetResourceInstanceCurrent( 2475 mustResourceInstanceAddr("aws_instance.b").Resource, 2476 &states.ResourceInstanceObjectSrc{ 2477 Status: states.ObjectReady, 2478 AttrsJSON: []byte(`{"id":"b"}`), 2479 Dependencies: []addrs.ConfigResource{mustConfigResourceAddr("module.child.aws_instance.a")}, 2480 }, 2481 mustProviderConfig(`provider["registry.terraform.io/hashicorp/aws"]`), 2482 ) 2483 2484 ctx := testContext2(t, &ContextOpts{ 2485 Providers: map[addrs.Provider]providers.Factory{ 2486 addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p), 2487 }, 2488 }) 2489 2490 plan, diags := ctx.Plan(m, state, &PlanOpts{ 2491 Mode: plans.DestroyMode, 2492 }) 2493 assertNoErrors(t, diags) 2494 2495 state, diags = ctx.Apply(plan, m) 2496 if diags.HasErrors() { 2497 t.Fatalf("diags: %s", diags.Err()) 2498 } 2499 2500 expected := []string{"b", "a"} 2501 if !reflect.DeepEqual(order, expected) { 2502 t.Errorf("wrong order\ngot: %#v\nwant: %#v", order, expected) 2503 } 2504 2505 { 2506 actual := strings.TrimSpace(state.String()) 2507 expected := strings.TrimSpace(testTerraformApplyModuleDestroyOrderStr) 2508 if actual != expected { 2509 t.Errorf("wrong final state\n\ngot:\n%s\n\nwant:\n%s", actual, expected) 2510 } 2511 } 2512 } 2513 2514 func TestContext2Apply_moduleInheritAlias(t *testing.T) { 2515 m := testModule(t, "apply-module-provider-inherit-alias") 2516 p := testProvider("aws") 2517 p.PlanResourceChangeFn = testDiffFn 2518 p.ApplyResourceChangeFn = testApplyFn 2519 2520 p.ConfigureProviderFn = func(req providers.ConfigureProviderRequest) (resp providers.ConfigureProviderResponse) { 2521 val := req.Config.GetAttr("value") 2522 if val.IsNull() { 2523 return 2524 } 2525 2526 root := req.Config.GetAttr("root") 2527 if !root.IsNull() { 2528 resp.Diagnostics = resp.Diagnostics.Append(fmt.Errorf("child should not get root")) 2529 } 2530 2531 return 2532 } 2533 2534 ctx := testContext2(t, &ContextOpts{ 2535 Providers: map[addrs.Provider]providers.Factory{ 2536 addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p), 2537 }, 2538 }) 2539 2540 plan, diags := ctx.Plan(m, states.NewState(), DefaultPlanOpts) 2541 assertNoErrors(t, diags) 2542 2543 state, diags := ctx.Apply(plan, m) 2544 if diags.HasErrors() { 2545 t.Fatalf("diags: %s", diags.Err()) 2546 } 2547 2548 checkStateString(t, state, ` 2549 <no state> 2550 module.child: 2551 aws_instance.foo: 2552 ID = foo 2553 provider = provider["registry.terraform.io/hashicorp/aws"].eu 2554 type = aws_instance 2555 `) 2556 } 2557 2558 func TestContext2Apply_orphanResource(t *testing.T) { 2559 // This is a two-step test: 2560 // 1. Apply a configuration with resources that have count set. 2561 // This should place the empty resource object in the state to record 2562 // that each exists, and record any instances. 2563 // 2. Apply an empty configuration against the same state, which should 2564 // then clean up both the instances and the containing resource objects. 2565 p := testProvider("test") 2566 p.PlanResourceChangeFn = testDiffFn 2567 p.ApplyResourceChangeFn = testApplyFn 2568 p.GetProviderSchemaResponse = getProviderSchemaResponseFromProviderSchema(&ProviderSchema{ 2569 ResourceTypes: map[string]*configschema.Block{ 2570 "test_thing": { 2571 Attributes: map[string]*configschema.Attribute{ 2572 "id": {Type: cty.String, Computed: true}, 2573 "foo": {Type: cty.String, Optional: true}, 2574 }, 2575 }, 2576 }, 2577 }) 2578 2579 // Step 1: create the resources and instances 2580 m := testModule(t, "apply-orphan-resource") 2581 ctx := testContext2(t, &ContextOpts{ 2582 Providers: map[addrs.Provider]providers.Factory{ 2583 addrs.NewDefaultProvider("test"): testProviderFuncFixed(p), 2584 }, 2585 }) 2586 plan, diags := ctx.Plan(m, states.NewState(), DefaultPlanOpts) 2587 assertNoErrors(t, diags) 2588 state, diags := ctx.Apply(plan, m) 2589 assertNoErrors(t, diags) 2590 2591 // At this point both resources should be recorded in the state, along 2592 // with the single instance associated with test_thing.one. 2593 want := states.BuildState(func(s *states.SyncState) { 2594 providerAddr := addrs.AbsProviderConfig{ 2595 Provider: addrs.NewDefaultProvider("test"), 2596 Module: addrs.RootModule, 2597 } 2598 oneAddr := addrs.Resource{ 2599 Mode: addrs.ManagedResourceMode, 2600 Type: "test_thing", 2601 Name: "one", 2602 }.Absolute(addrs.RootModuleInstance) 2603 s.SetResourceProvider(oneAddr, providerAddr) 2604 s.SetResourceInstanceCurrent(oneAddr.Instance(addrs.IntKey(0)), &states.ResourceInstanceObjectSrc{ 2605 Status: states.ObjectReady, 2606 AttrsJSON: []byte(`{"id":"foo"}`), 2607 }, providerAddr) 2608 }) 2609 2610 if state.String() != want.String() { 2611 t.Fatalf("wrong state after step 1\n%s", cmp.Diff(want, state)) 2612 } 2613 2614 // Step 2: update with an empty config, to destroy everything 2615 m = testModule(t, "empty") 2616 ctx = testContext2(t, &ContextOpts{ 2617 Providers: map[addrs.Provider]providers.Factory{ 2618 addrs.NewDefaultProvider("test"): testProviderFuncFixed(p), 2619 }, 2620 }) 2621 plan, diags = ctx.Plan(m, state, DefaultPlanOpts) 2622 assertNoErrors(t, diags) 2623 { 2624 addr := mustResourceInstanceAddr("test_thing.one[0]") 2625 change := plan.Changes.ResourceInstance(addr) 2626 if change == nil { 2627 t.Fatalf("no planned change for %s", addr) 2628 } 2629 if got, want := change.Action, plans.Delete; got != want { 2630 t.Errorf("wrong action for %s %s; want %s", addr, got, want) 2631 } 2632 if got, want := change.ActionReason, plans.ResourceInstanceDeleteBecauseNoResourceConfig; got != want { 2633 t.Errorf("wrong action for %s %s; want %s", addr, got, want) 2634 } 2635 } 2636 2637 state, diags = ctx.Apply(plan, m) 2638 assertNoErrors(t, diags) 2639 2640 // The state should now be _totally_ empty, with just an empty root module 2641 // (since that always exists) and no resources at all. 2642 want = states.NewState() 2643 want.CheckResults = &states.CheckResults{} 2644 if !cmp.Equal(state, want) { 2645 t.Fatalf("wrong state after step 2\ngot: %swant: %s", spew.Sdump(state), spew.Sdump(want)) 2646 } 2647 2648 } 2649 2650 func TestContext2Apply_moduleOrphanInheritAlias(t *testing.T) { 2651 m := testModule(t, "apply-module-provider-inherit-alias-orphan") 2652 p := testProvider("aws") 2653 p.PlanResourceChangeFn = testDiffFn 2654 2655 p.ConfigureProviderFn = func(req providers.ConfigureProviderRequest) (resp providers.ConfigureProviderResponse) { 2656 val := req.Config.GetAttr("value") 2657 if val.IsNull() { 2658 return 2659 } 2660 2661 root := req.Config.GetAttr("root") 2662 if !root.IsNull() { 2663 resp.Diagnostics = resp.Diagnostics.Append(fmt.Errorf("child should not get root")) 2664 } 2665 2666 return 2667 } 2668 2669 // Create a state with an orphan module 2670 state := states.NewState() 2671 child := state.EnsureModule(addrs.RootModuleInstance.Child("child", addrs.NoKey)) 2672 child.SetResourceInstanceCurrent( 2673 mustResourceInstanceAddr("aws_instance.bar").Resource, 2674 &states.ResourceInstanceObjectSrc{ 2675 Status: states.ObjectReady, 2676 AttrsJSON: []byte(`{"id":"bar"}`), 2677 }, 2678 mustProviderConfig(`provider["registry.terraform.io/hashicorp/aws"]`), 2679 ) 2680 2681 ctx := testContext2(t, &ContextOpts{ 2682 Providers: map[addrs.Provider]providers.Factory{ 2683 addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p), 2684 }, 2685 }) 2686 2687 plan, diags := ctx.Plan(m, state, DefaultPlanOpts) 2688 assertNoErrors(t, diags) 2689 { 2690 addr := mustResourceInstanceAddr("module.child.aws_instance.bar") 2691 change := plan.Changes.ResourceInstance(addr) 2692 if change == nil { 2693 t.Fatalf("no planned change for %s", addr) 2694 } 2695 if got, want := change.Action, plans.Delete; got != want { 2696 t.Errorf("wrong action for %s %s; want %s", addr, got, want) 2697 } 2698 // This should ideally be ResourceInstanceDeleteBecauseNoModule, but 2699 // the codepath deciding this doesn't currently have enough information 2700 // to differentiate, and so this is a compromise. 2701 if got, want := change.ActionReason, plans.ResourceInstanceDeleteBecauseNoResourceConfig; got != want { 2702 t.Errorf("wrong action for %s %s; want %s", addr, got, want) 2703 } 2704 } 2705 2706 state, diags = ctx.Apply(plan, m) 2707 if diags.HasErrors() { 2708 t.Fatalf("diags: %s", diags.Err()) 2709 } 2710 2711 if !p.ConfigureProviderCalled { 2712 t.Fatal("must call configure") 2713 } 2714 2715 checkStateString(t, state, "<no state>") 2716 } 2717 2718 func TestContext2Apply_moduleOrphanProvider(t *testing.T) { 2719 m := testModule(t, "apply-module-orphan-provider-inherit") 2720 p := testProvider("aws") 2721 p.PlanResourceChangeFn = testDiffFn 2722 2723 p.ConfigureProviderFn = func(req providers.ConfigureProviderRequest) (resp providers.ConfigureProviderResponse) { 2724 val := req.Config.GetAttr("value") 2725 if val.IsNull() { 2726 resp.Diagnostics = resp.Diagnostics.Append(fmt.Errorf("value is not found")) 2727 } 2728 2729 return 2730 } 2731 2732 // Create a state with an orphan module 2733 state := states.NewState() 2734 child := state.EnsureModule(addrs.RootModuleInstance.Child("child", addrs.NoKey)) 2735 child.SetResourceInstanceCurrent( 2736 mustResourceInstanceAddr("aws_instance.bar").Resource, 2737 &states.ResourceInstanceObjectSrc{ 2738 Status: states.ObjectReady, 2739 AttrsJSON: []byte(`{"id":"bar"}`), 2740 }, 2741 mustProviderConfig(`provider["registry.terraform.io/hashicorp/aws"]`), 2742 ) 2743 2744 ctx := testContext2(t, &ContextOpts{ 2745 Providers: map[addrs.Provider]providers.Factory{ 2746 addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p), 2747 }, 2748 }) 2749 2750 plan, diags := ctx.Plan(m, state, DefaultPlanOpts) 2751 assertNoErrors(t, diags) 2752 2753 if _, diags := ctx.Apply(plan, m); diags.HasErrors() { 2754 t.Fatalf("apply errors: %s", diags.Err()) 2755 } 2756 } 2757 2758 func TestContext2Apply_moduleOrphanGrandchildProvider(t *testing.T) { 2759 m := testModule(t, "apply-module-orphan-provider-inherit") 2760 p := testProvider("aws") 2761 p.PlanResourceChangeFn = testDiffFn 2762 2763 p.ConfigureProviderFn = func(req providers.ConfigureProviderRequest) (resp providers.ConfigureProviderResponse) { 2764 val := req.Config.GetAttr("value") 2765 if val.IsNull() { 2766 resp.Diagnostics = resp.Diagnostics.Append(fmt.Errorf("value is not found")) 2767 } 2768 2769 return 2770 } 2771 2772 // Create a state with an orphan module that is nested (grandchild) 2773 state := states.NewState() 2774 child := state.EnsureModule(addrs.RootModuleInstance.Child("parent", addrs.NoKey).Child("child", addrs.NoKey)) 2775 child.SetResourceInstanceCurrent( 2776 mustResourceInstanceAddr("aws_instance.bar").Resource, 2777 &states.ResourceInstanceObjectSrc{ 2778 Status: states.ObjectReady, 2779 AttrsJSON: []byte(`{"id":"bar"}`), 2780 }, 2781 mustProviderConfig(`provider["registry.terraform.io/hashicorp/aws"]`), 2782 ) 2783 2784 ctx := testContext2(t, &ContextOpts{ 2785 Providers: map[addrs.Provider]providers.Factory{ 2786 addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p), 2787 }, 2788 }) 2789 2790 plan, diags := ctx.Plan(m, state, DefaultPlanOpts) 2791 assertNoErrors(t, diags) 2792 2793 if _, diags := ctx.Apply(plan, m); diags.HasErrors() { 2794 t.Fatalf("apply errors: %s", diags.Err()) 2795 } 2796 } 2797 2798 func TestContext2Apply_moduleGrandchildProvider(t *testing.T) { 2799 m := testModule(t, "apply-module-grandchild-provider-inherit") 2800 p := testProvider("aws") 2801 p.PlanResourceChangeFn = testDiffFn 2802 2803 var callLock sync.Mutex 2804 called := false 2805 p.ConfigureProviderFn = func(req providers.ConfigureProviderRequest) (resp providers.ConfigureProviderResponse) { 2806 val := req.Config.GetAttr("value") 2807 if val.IsNull() { 2808 resp.Diagnostics = resp.Diagnostics.Append(fmt.Errorf("value is not found")) 2809 } 2810 2811 callLock.Lock() 2812 called = true 2813 callLock.Unlock() 2814 2815 return 2816 } 2817 2818 ctx := testContext2(t, &ContextOpts{ 2819 Providers: map[addrs.Provider]providers.Factory{ 2820 addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p), 2821 }, 2822 }) 2823 2824 plan, diags := ctx.Plan(m, states.NewState(), DefaultPlanOpts) 2825 assertNoErrors(t, diags) 2826 2827 if _, diags := ctx.Apply(plan, m); diags.HasErrors() { 2828 t.Fatalf("apply errors: %s", diags.Err()) 2829 } 2830 2831 callLock.Lock() 2832 defer callLock.Unlock() 2833 if called != true { 2834 t.Fatalf("err: configure never called") 2835 } 2836 } 2837 2838 // This tests an issue where all the providers in a module but not 2839 // in the root weren't being added to the root properly. In this test 2840 // case: aws is explicitly added to root, but "test" should be added to. 2841 // With the bug, it wasn't. 2842 func TestContext2Apply_moduleOnlyProvider(t *testing.T) { 2843 m := testModule(t, "apply-module-only-provider") 2844 p := testProvider("aws") 2845 p.PlanResourceChangeFn = testDiffFn 2846 p.ApplyResourceChangeFn = testApplyFn 2847 pTest := testProvider("test") 2848 pTest.ApplyResourceChangeFn = testApplyFn 2849 pTest.PlanResourceChangeFn = testDiffFn 2850 2851 ctx := testContext2(t, &ContextOpts{ 2852 Providers: map[addrs.Provider]providers.Factory{ 2853 addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p), 2854 addrs.NewDefaultProvider("test"): testProviderFuncFixed(pTest), 2855 }, 2856 }) 2857 2858 plan, diags := ctx.Plan(m, states.NewState(), DefaultPlanOpts) 2859 assertNoErrors(t, diags) 2860 2861 state, diags := ctx.Apply(plan, m) 2862 if diags.HasErrors() { 2863 t.Fatalf("diags: %s", diags.Err()) 2864 } 2865 2866 actual := strings.TrimSpace(state.String()) 2867 expected := strings.TrimSpace(testTerraformApplyModuleOnlyProviderStr) 2868 if actual != expected { 2869 t.Fatalf("wrong result\n\ngot:\n%s\n\nwant:\n%s", actual, expected) 2870 } 2871 } 2872 2873 func TestContext2Apply_moduleProviderAlias(t *testing.T) { 2874 m := testModule(t, "apply-module-provider-alias") 2875 p := testProvider("aws") 2876 p.PlanResourceChangeFn = testDiffFn 2877 p.ApplyResourceChangeFn = testApplyFn 2878 ctx := testContext2(t, &ContextOpts{ 2879 Providers: map[addrs.Provider]providers.Factory{ 2880 addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p), 2881 }, 2882 }) 2883 2884 plan, diags := ctx.Plan(m, states.NewState(), DefaultPlanOpts) 2885 assertNoErrors(t, diags) 2886 2887 state, diags := ctx.Apply(plan, m) 2888 if diags.HasErrors() { 2889 t.Fatalf("diags: %s", diags.Err()) 2890 } 2891 2892 actual := strings.TrimSpace(state.String()) 2893 expected := strings.TrimSpace(testTerraformApplyModuleProviderAliasStr) 2894 if actual != expected { 2895 t.Fatalf("wrong result\n\ngot:\n%s\n\nwant:\n%s", actual, expected) 2896 } 2897 } 2898 2899 func TestContext2Apply_moduleProviderAliasTargets(t *testing.T) { 2900 m := testModule(t, "apply-module-provider-alias") 2901 p := testProvider("aws") 2902 p.PlanResourceChangeFn = testDiffFn 2903 ctx := testContext2(t, &ContextOpts{ 2904 Providers: map[addrs.Provider]providers.Factory{ 2905 addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p), 2906 }, 2907 }) 2908 2909 plan, diags := ctx.Plan(m, states.NewState(), &PlanOpts{ 2910 Mode: plans.NormalMode, 2911 Targets: []addrs.Targetable{ 2912 addrs.ConfigResource{ 2913 Module: addrs.RootModule, 2914 Resource: addrs.Resource{ 2915 Mode: addrs.ManagedResourceMode, 2916 Type: "nonexistent", 2917 Name: "thing", 2918 }, 2919 }, 2920 }, 2921 }) 2922 assertNoErrors(t, diags) 2923 2924 state, diags := ctx.Apply(plan, m) 2925 if diags.HasErrors() { 2926 t.Fatalf("diags: %s", diags.Err()) 2927 } 2928 2929 actual := strings.TrimSpace(state.String()) 2930 expected := strings.TrimSpace(` 2931 <no state> 2932 `) 2933 if actual != expected { 2934 t.Fatalf("wrong result\n\ngot:\n%s\n\nwant:\n%s", actual, expected) 2935 } 2936 } 2937 2938 func TestContext2Apply_moduleProviderCloseNested(t *testing.T) { 2939 m := testModule(t, "apply-module-provider-close-nested") 2940 p := testProvider("aws") 2941 p.PlanResourceChangeFn = testDiffFn 2942 state := states.NewState() 2943 root := state.EnsureModule(addrs.RootModuleInstance) 2944 root.SetResourceInstanceCurrent( 2945 mustResourceInstanceAddr("aws_instance.foo").Resource, 2946 &states.ResourceInstanceObjectSrc{ 2947 Status: states.ObjectReady, 2948 AttrsJSON: []byte(`{"id":"bar"}`), 2949 }, 2950 mustProviderConfig(`provider["registry.terraform.io/hashicorp/aws"]`), 2951 ) 2952 2953 ctx := testContext2(t, &ContextOpts{ 2954 Providers: map[addrs.Provider]providers.Factory{ 2955 addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p), 2956 }, 2957 }) 2958 2959 plan, diags := ctx.Plan(m, state, &PlanOpts{ 2960 Mode: plans.DestroyMode, 2961 }) 2962 assertNoErrors(t, diags) 2963 2964 if _, diags := ctx.Apply(plan, m); diags.HasErrors() { 2965 t.Fatalf("apply errors: %s", diags.Err()) 2966 } 2967 } 2968 2969 // Tests that variables used as module vars that reference data that 2970 // already exists in the state and requires no diff works properly. This 2971 // fixes an issue faced where module variables were pruned because they were 2972 // accessing "non-existent" resources (they existed, just not in the graph 2973 // cause they weren't in the diff). 2974 func TestContext2Apply_moduleVarRefExisting(t *testing.T) { 2975 m := testModule(t, "apply-ref-existing") 2976 p := testProvider("aws") 2977 p.PlanResourceChangeFn = testDiffFn 2978 p.ApplyResourceChangeFn = testApplyFn 2979 state := states.NewState() 2980 root := state.EnsureModule(addrs.RootModuleInstance) 2981 root.SetResourceInstanceCurrent( 2982 mustResourceInstanceAddr("aws_instance.foo").Resource, 2983 &states.ResourceInstanceObjectSrc{ 2984 Status: states.ObjectReady, 2985 AttrsJSON: []byte(`{"id":"foo","foo":"bar"}`), 2986 }, 2987 mustProviderConfig(`provider["registry.terraform.io/hashicorp/aws"]`), 2988 ) 2989 2990 ctx := testContext2(t, &ContextOpts{ 2991 Providers: map[addrs.Provider]providers.Factory{ 2992 addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p), 2993 }, 2994 }) 2995 2996 plan, diags := ctx.Plan(m, state, DefaultPlanOpts) 2997 assertNoErrors(t, diags) 2998 2999 state, diags = ctx.Apply(plan, m) 3000 if diags.HasErrors() { 3001 t.Fatalf("diags: %s", diags.Err()) 3002 } 3003 3004 actual := strings.TrimSpace(state.String()) 3005 expected := strings.TrimSpace(testTerraformApplyModuleVarRefExistingStr) 3006 if actual != expected { 3007 t.Fatalf("wrong result\n\ngot:\n%s\n\nwant:\n%s", actual, expected) 3008 } 3009 } 3010 3011 func TestContext2Apply_moduleVarResourceCount(t *testing.T) { 3012 m := testModule(t, "apply-module-var-resource-count") 3013 p := testProvider("aws") 3014 p.PlanResourceChangeFn = testDiffFn 3015 ctx := testContext2(t, &ContextOpts{ 3016 Providers: map[addrs.Provider]providers.Factory{ 3017 addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p), 3018 }, 3019 }) 3020 3021 plan, diags := ctx.Plan(m, states.NewState(), &PlanOpts{ 3022 Mode: plans.DestroyMode, 3023 SetVariables: InputValues{ 3024 "num": &InputValue{ 3025 Value: cty.NumberIntVal(2), 3026 SourceType: ValueFromCaller, 3027 }, 3028 }, 3029 }) 3030 assertNoErrors(t, diags) 3031 3032 state, diags := ctx.Apply(plan, m) 3033 assertNoErrors(t, diags) 3034 3035 ctx = testContext2(t, &ContextOpts{ 3036 Providers: map[addrs.Provider]providers.Factory{ 3037 addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p), 3038 }, 3039 }) 3040 3041 plan, diags = ctx.Plan(m, state, &PlanOpts{ 3042 Mode: plans.NormalMode, 3043 SetVariables: InputValues{ 3044 "num": &InputValue{ 3045 Value: cty.NumberIntVal(5), 3046 SourceType: ValueFromCaller, 3047 }, 3048 }, 3049 }) 3050 assertNoErrors(t, diags) 3051 3052 if _, diags := ctx.Apply(plan, m); diags.HasErrors() { 3053 t.Fatalf("apply errors: %s", diags.Err()) 3054 } 3055 } 3056 3057 // GH-819 3058 func TestContext2Apply_moduleBool(t *testing.T) { 3059 m := testModule(t, "apply-module-bool") 3060 p := testProvider("aws") 3061 p.PlanResourceChangeFn = testDiffFn 3062 p.ApplyResourceChangeFn = testApplyFn 3063 ctx := testContext2(t, &ContextOpts{ 3064 Providers: map[addrs.Provider]providers.Factory{ 3065 addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p), 3066 }, 3067 }) 3068 3069 plan, diags := ctx.Plan(m, states.NewState(), DefaultPlanOpts) 3070 assertNoErrors(t, diags) 3071 3072 state, diags := ctx.Apply(plan, m) 3073 if diags.HasErrors() { 3074 t.Fatalf("diags: %s", diags.Err()) 3075 } 3076 3077 actual := strings.TrimSpace(state.String()) 3078 expected := strings.TrimSpace(testTerraformApplyModuleBoolStr) 3079 if actual != expected { 3080 t.Fatalf("wrong result\n\ngot:\n%s\n\nwant:\n%s", actual, expected) 3081 } 3082 } 3083 3084 // Tests that a module can be targeted and everything is properly created. 3085 // This adds to the plan test to also just verify that apply works. 3086 func TestContext2Apply_moduleTarget(t *testing.T) { 3087 m := testModule(t, "plan-targeted-cross-module") 3088 p := testProvider("aws") 3089 p.PlanResourceChangeFn = testDiffFn 3090 p.ApplyResourceChangeFn = testApplyFn 3091 ctx := testContext2(t, &ContextOpts{ 3092 Providers: map[addrs.Provider]providers.Factory{ 3093 addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p), 3094 }, 3095 }) 3096 3097 plan, diags := ctx.Plan(m, states.NewState(), &PlanOpts{ 3098 Mode: plans.NormalMode, 3099 Targets: []addrs.Targetable{ 3100 addrs.RootModuleInstance.Child("B", addrs.NoKey), 3101 }, 3102 }) 3103 assertNoErrors(t, diags) 3104 3105 state, diags := ctx.Apply(plan, m) 3106 if diags.HasErrors() { 3107 t.Fatalf("diags: %s", diags.Err()) 3108 } 3109 3110 checkStateString(t, state, ` 3111 <no state> 3112 module.A: 3113 aws_instance.foo: 3114 ID = foo 3115 provider = provider["registry.terraform.io/hashicorp/aws"] 3116 foo = bar 3117 type = aws_instance 3118 3119 Outputs: 3120 3121 value = foo 3122 module.B: 3123 aws_instance.bar: 3124 ID = foo 3125 provider = provider["registry.terraform.io/hashicorp/aws"] 3126 foo = foo 3127 type = aws_instance 3128 3129 Dependencies: 3130 module.A.aws_instance.foo 3131 `) 3132 } 3133 3134 func TestContext2Apply_multiProvider(t *testing.T) { 3135 m := testModule(t, "apply-multi-provider") 3136 p := testProvider("aws") 3137 p.PlanResourceChangeFn = testDiffFn 3138 p.ApplyResourceChangeFn = testApplyFn 3139 3140 pDO := testProvider("do") 3141 pDO.ApplyResourceChangeFn = testApplyFn 3142 pDO.PlanResourceChangeFn = testDiffFn 3143 3144 ctx := testContext2(t, &ContextOpts{ 3145 Providers: map[addrs.Provider]providers.Factory{ 3146 addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p), 3147 addrs.NewDefaultProvider("do"): testProviderFuncFixed(pDO), 3148 }, 3149 }) 3150 3151 plan, diags := ctx.Plan(m, states.NewState(), DefaultPlanOpts) 3152 assertNoErrors(t, diags) 3153 3154 state, diags := ctx.Apply(plan, m) 3155 if diags.HasErrors() { 3156 t.Fatalf("diags: %s", diags.Err()) 3157 } 3158 3159 mod := state.RootModule() 3160 if len(mod.Resources) < 2 { 3161 t.Fatalf("bad: %#v", mod.Resources) 3162 } 3163 3164 actual := strings.TrimSpace(state.String()) 3165 expected := strings.TrimSpace(testTerraformApplyMultiProviderStr) 3166 if actual != expected { 3167 t.Fatalf("wrong result\n\ngot:\n%s\n\nwant:\n%s", actual, expected) 3168 } 3169 } 3170 3171 func TestContext2Apply_multiProviderDestroy(t *testing.T) { 3172 m := testModule(t, "apply-multi-provider-destroy") 3173 p := testProvider("aws") 3174 p.PlanResourceChangeFn = testDiffFn 3175 p.GetProviderSchemaResponse = getProviderSchemaResponseFromProviderSchema(&ProviderSchema{ 3176 Provider: &configschema.Block{ 3177 Attributes: map[string]*configschema.Attribute{ 3178 "addr": {Type: cty.String, Optional: true}, 3179 }, 3180 }, 3181 ResourceTypes: map[string]*configschema.Block{ 3182 "aws_instance": { 3183 Attributes: map[string]*configschema.Attribute{ 3184 "id": {Type: cty.String, Computed: true}, 3185 "foo": {Type: cty.String, Optional: true}, 3186 }, 3187 }, 3188 }, 3189 }) 3190 3191 p2 := testProvider("vault") 3192 p2.ApplyResourceChangeFn = testApplyFn 3193 p2.PlanResourceChangeFn = testDiffFn 3194 p2.GetProviderSchemaResponse = getProviderSchemaResponseFromProviderSchema(&ProviderSchema{ 3195 ResourceTypes: map[string]*configschema.Block{ 3196 "vault_instance": { 3197 Attributes: map[string]*configschema.Attribute{ 3198 "id": {Type: cty.String, Computed: true}, 3199 }, 3200 }, 3201 }, 3202 }) 3203 3204 var state *states.State 3205 3206 // First, create the instances 3207 { 3208 ctx := testContext2(t, &ContextOpts{ 3209 Providers: map[addrs.Provider]providers.Factory{ 3210 addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p), 3211 addrs.NewDefaultProvider("vault"): testProviderFuncFixed(p2), 3212 }, 3213 }) 3214 3215 plan, diags := ctx.Plan(m, states.NewState(), DefaultPlanOpts) 3216 assertNoErrors(t, diags) 3217 3218 s, diags := ctx.Apply(plan, m) 3219 assertNoErrors(t, diags) 3220 3221 state = s 3222 } 3223 3224 // Destroy them 3225 { 3226 // Verify that aws_instance.bar is destroyed first 3227 var checked bool 3228 var called int32 3229 var lock sync.Mutex 3230 applyFn := func(req providers.ApplyResourceChangeRequest) (resp providers.ApplyResourceChangeResponse) { 3231 lock.Lock() 3232 defer lock.Unlock() 3233 3234 if req.TypeName == "aws_instance" { 3235 checked = true 3236 3237 // Sleep to allow parallel execution 3238 time.Sleep(50 * time.Millisecond) 3239 3240 // Verify that called is 0 (dep not called) 3241 if atomic.LoadInt32(&called) != 0 { 3242 resp.Diagnostics = resp.Diagnostics.Append(fmt.Errorf("nothing else should be called")) 3243 return resp 3244 } 3245 } 3246 3247 atomic.AddInt32(&called, 1) 3248 return testApplyFn(req) 3249 } 3250 3251 // Set the apply functions 3252 p.ApplyResourceChangeFn = applyFn 3253 p2.ApplyResourceChangeFn = applyFn 3254 3255 ctx := testContext2(t, &ContextOpts{ 3256 Providers: map[addrs.Provider]providers.Factory{ 3257 addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p), 3258 addrs.NewDefaultProvider("vault"): testProviderFuncFixed(p2), 3259 }, 3260 }) 3261 3262 plan, diags := ctx.Plan(m, state, &PlanOpts{ 3263 Mode: plans.DestroyMode, 3264 }) 3265 assertNoErrors(t, diags) 3266 3267 s, diags := ctx.Apply(plan, m) 3268 assertNoErrors(t, diags) 3269 3270 if !checked { 3271 t.Fatal("should be checked") 3272 } 3273 3274 state = s 3275 } 3276 3277 checkStateString(t, state, `<no state>`) 3278 } 3279 3280 // This is like the multiProviderDestroy test except it tests that 3281 // dependent resources within a child module that inherit provider 3282 // configuration are still destroyed first. 3283 func TestContext2Apply_multiProviderDestroyChild(t *testing.T) { 3284 m := testModule(t, "apply-multi-provider-destroy-child") 3285 p := testProvider("aws") 3286 p.PlanResourceChangeFn = testDiffFn 3287 p.GetProviderSchemaResponse = getProviderSchemaResponseFromProviderSchema(&ProviderSchema{ 3288 Provider: &configschema.Block{ 3289 Attributes: map[string]*configschema.Attribute{ 3290 "value": {Type: cty.String, Optional: true}, 3291 }, 3292 }, 3293 ResourceTypes: map[string]*configschema.Block{ 3294 "aws_instance": { 3295 Attributes: map[string]*configschema.Attribute{ 3296 "id": {Type: cty.String, Computed: true}, 3297 "foo": {Type: cty.String, Optional: true}, 3298 }, 3299 }, 3300 }, 3301 }) 3302 3303 p2 := testProvider("vault") 3304 p2.ApplyResourceChangeFn = testApplyFn 3305 p2.PlanResourceChangeFn = testDiffFn 3306 p2.GetProviderSchemaResponse = getProviderSchemaResponseFromProviderSchema(&ProviderSchema{ 3307 Provider: &configschema.Block{}, 3308 ResourceTypes: map[string]*configschema.Block{ 3309 "vault_instance": { 3310 Attributes: map[string]*configschema.Attribute{ 3311 "id": {Type: cty.String, Computed: true}, 3312 }, 3313 }, 3314 }, 3315 }) 3316 3317 var state *states.State 3318 3319 // First, create the instances 3320 { 3321 ctx := testContext2(t, &ContextOpts{ 3322 Providers: map[addrs.Provider]providers.Factory{ 3323 addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p), 3324 addrs.NewDefaultProvider("vault"): testProviderFuncFixed(p2), 3325 }, 3326 }) 3327 3328 plan, diags := ctx.Plan(m, states.NewState(), DefaultPlanOpts) 3329 assertNoErrors(t, diags) 3330 3331 s, diags := ctx.Apply(plan, m) 3332 if diags.HasErrors() { 3333 t.Fatalf("diags: %s", diags.Err()) 3334 } 3335 3336 state = s 3337 } 3338 3339 // Destroy them 3340 { 3341 // Verify that aws_instance.bar is destroyed first 3342 var checked bool 3343 var called int32 3344 var lock sync.Mutex 3345 applyFn := func(req providers.ApplyResourceChangeRequest) (resp providers.ApplyResourceChangeResponse) { 3346 lock.Lock() 3347 defer lock.Unlock() 3348 3349 if req.TypeName == "aws_instance" { 3350 checked = true 3351 3352 // Sleep to allow parallel execution 3353 time.Sleep(50 * time.Millisecond) 3354 3355 // Verify that called is 0 (dep not called) 3356 if atomic.LoadInt32(&called) != 0 { 3357 resp.Diagnostics = resp.Diagnostics.Append(fmt.Errorf("nothing else should be called")) 3358 return resp 3359 } 3360 } 3361 3362 atomic.AddInt32(&called, 1) 3363 return testApplyFn(req) 3364 } 3365 3366 // Set the apply functions 3367 p.ApplyResourceChangeFn = applyFn 3368 p2.ApplyResourceChangeFn = applyFn 3369 3370 ctx := testContext2(t, &ContextOpts{ 3371 Providers: map[addrs.Provider]providers.Factory{ 3372 addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p), 3373 addrs.NewDefaultProvider("vault"): testProviderFuncFixed(p2), 3374 }, 3375 }) 3376 3377 plan, diags := ctx.Plan(m, state, &PlanOpts{ 3378 Mode: plans.DestroyMode, 3379 }) 3380 assertNoErrors(t, diags) 3381 3382 s, diags := ctx.Apply(plan, m) 3383 if diags.HasErrors() { 3384 t.Fatalf("diags: %s", diags.Err()) 3385 } 3386 3387 if !checked { 3388 t.Fatal("should be checked") 3389 } 3390 3391 state = s 3392 } 3393 3394 checkStateString(t, state, ` 3395 <no state> 3396 `) 3397 } 3398 3399 func TestContext2Apply_multiVar(t *testing.T) { 3400 m := testModule(t, "apply-multi-var") 3401 p := testProvider("aws") 3402 p.PlanResourceChangeFn = testDiffFn 3403 3404 // First, apply with a count of 3 3405 ctx := testContext2(t, &ContextOpts{ 3406 Providers: map[addrs.Provider]providers.Factory{ 3407 addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p), 3408 }, 3409 }) 3410 3411 plan, diags := ctx.Plan(m, states.NewState(), &PlanOpts{ 3412 Mode: plans.NormalMode, 3413 SetVariables: InputValues{ 3414 "num": &InputValue{ 3415 Value: cty.NumberIntVal(3), 3416 SourceType: ValueFromCaller, 3417 }, 3418 }, 3419 }) 3420 assertNoErrors(t, diags) 3421 3422 state, diags := ctx.Apply(plan, m) 3423 if diags.HasErrors() { 3424 t.Fatalf("diags: %s", diags.Err()) 3425 } 3426 3427 actual := state.RootModule().OutputValues["output"] 3428 expected := cty.StringVal("bar0,bar1,bar2") 3429 if actual == nil || actual.Value != expected { 3430 t.Fatalf("wrong value\ngot: %#v\nwant: %#v", actual.Value, expected) 3431 } 3432 3433 t.Logf("Initial state: %s", state.String()) 3434 3435 // Apply again, reduce the count to 1 3436 { 3437 ctx := testContext2(t, &ContextOpts{ 3438 Providers: map[addrs.Provider]providers.Factory{ 3439 addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p), 3440 }, 3441 }) 3442 3443 plan, diags := ctx.Plan(m, state, &PlanOpts{ 3444 Mode: plans.NormalMode, 3445 SetVariables: InputValues{ 3446 "num": &InputValue{ 3447 Value: cty.NumberIntVal(1), 3448 SourceType: ValueFromCaller, 3449 }, 3450 }, 3451 }) 3452 assertNoErrors(t, diags) 3453 3454 state, diags := ctx.Apply(plan, m) 3455 if diags.HasErrors() { 3456 t.Fatalf("diags: %s", diags.Err()) 3457 } 3458 3459 t.Logf("End state: %s", state.String()) 3460 3461 actual := state.RootModule().OutputValues["output"] 3462 if actual == nil { 3463 t.Fatal("missing output") 3464 } 3465 3466 expected := cty.StringVal("bar0") 3467 if actual.Value != expected { 3468 t.Fatalf("wrong value\ngot: %#v\nwant: %#v", actual.Value, expected) 3469 } 3470 } 3471 } 3472 3473 // This is a holistic test of multi-var (aka "splat variable") handling 3474 // across several different Terraform subsystems. This is here because 3475 // historically there were quirky differences in handling across different 3476 // parts of Terraform and so here we want to assert the expected behavior and 3477 // ensure that it remains consistent in future. 3478 func TestContext2Apply_multiVarComprehensive(t *testing.T) { 3479 m := testModule(t, "apply-multi-var-comprehensive") 3480 p := testProvider("test") 3481 3482 configs := map[string]cty.Value{} 3483 var configsLock sync.Mutex 3484 3485 p.ApplyResourceChangeFn = testApplyFn 3486 p.PlanResourceChangeFn = func(req providers.PlanResourceChangeRequest) providers.PlanResourceChangeResponse { 3487 proposed := req.ProposedNewState 3488 configsLock.Lock() 3489 defer configsLock.Unlock() 3490 key := proposed.GetAttr("key").AsString() 3491 // This test was originally written using the legacy p.PlanResourceChangeFn interface, 3492 // and so the assertions below expect an old-style ResourceConfig, which 3493 // we'll construct via our shim for now to avoid rewriting all of the 3494 // assertions. 3495 configs[key] = req.ProposedNewState 3496 3497 retVals := make(map[string]cty.Value) 3498 for it := proposed.ElementIterator(); it.Next(); { 3499 idxVal, val := it.Element() 3500 idx := idxVal.AsString() 3501 3502 switch idx { 3503 case "id": 3504 retVals[idx] = cty.UnknownVal(cty.String) 3505 case "name": 3506 retVals[idx] = cty.StringVal(key) 3507 default: 3508 retVals[idx] = val 3509 } 3510 } 3511 3512 return providers.PlanResourceChangeResponse{ 3513 PlannedState: cty.ObjectVal(retVals), 3514 } 3515 } 3516 3517 p.GetProviderSchemaResponse = getProviderSchemaResponseFromProviderSchema(&ProviderSchema{ 3518 ResourceTypes: map[string]*configschema.Block{ 3519 "test_thing": { 3520 Attributes: map[string]*configschema.Attribute{ 3521 "key": {Type: cty.String, Required: true}, 3522 3523 "source_id": {Type: cty.String, Optional: true}, 3524 "source_name": {Type: cty.String, Optional: true}, 3525 "first_source_id": {Type: cty.String, Optional: true}, 3526 "first_source_name": {Type: cty.String, Optional: true}, 3527 "source_ids": {Type: cty.List(cty.String), Optional: true}, 3528 "source_names": {Type: cty.List(cty.String), Optional: true}, 3529 "source_ids_from_func": {Type: cty.List(cty.String), Optional: true}, 3530 "source_names_from_func": {Type: cty.List(cty.String), Optional: true}, 3531 "source_ids_wrapped": {Type: cty.List(cty.List(cty.String)), Optional: true}, 3532 "source_names_wrapped": {Type: cty.List(cty.List(cty.String)), Optional: true}, 3533 3534 "id": {Type: cty.String, Computed: true}, 3535 "name": {Type: cty.String, Computed: true}, 3536 }, 3537 }, 3538 }, 3539 }) 3540 3541 // First, apply with a count of 3 3542 ctx := testContext2(t, &ContextOpts{ 3543 Providers: map[addrs.Provider]providers.Factory{ 3544 addrs.NewDefaultProvider("test"): testProviderFuncFixed(p), 3545 }, 3546 }) 3547 3548 plan, diags := ctx.Plan(m, states.NewState(), &PlanOpts{ 3549 Mode: plans.NormalMode, 3550 SetVariables: InputValues{ 3551 "num": &InputValue{ 3552 Value: cty.NumberIntVal(3), 3553 SourceType: ValueFromCaller, 3554 }, 3555 }, 3556 }) 3557 assertNoErrors(t, diags) 3558 3559 checkConfig := func(key string, want cty.Value) { 3560 configsLock.Lock() 3561 defer configsLock.Unlock() 3562 3563 got, ok := configs[key] 3564 if !ok { 3565 t.Errorf("no config recorded for %s; expected a configuration", key) 3566 return 3567 } 3568 3569 t.Run("config for "+key, func(t *testing.T) { 3570 for _, problem := range deep.Equal(got, want) { 3571 t.Errorf(problem) 3572 } 3573 }) 3574 } 3575 3576 checkConfig("multi_count_var.0", cty.ObjectVal(map[string]cty.Value{ 3577 "source_id": cty.UnknownVal(cty.String), 3578 "source_name": cty.StringVal("source.0"), 3579 })) 3580 checkConfig("multi_count_var.2", cty.ObjectVal(map[string]cty.Value{ 3581 "source_id": cty.UnknownVal(cty.String), 3582 "source_name": cty.StringVal("source.2"), 3583 })) 3584 checkConfig("multi_count_derived.0", cty.ObjectVal(map[string]cty.Value{ 3585 "source_id": cty.UnknownVal(cty.String), 3586 "source_name": cty.StringVal("source.0"), 3587 })) 3588 checkConfig("multi_count_derived.2", cty.ObjectVal(map[string]cty.Value{ 3589 "source_id": cty.UnknownVal(cty.String), 3590 "source_name": cty.StringVal("source.2"), 3591 })) 3592 checkConfig("whole_splat", cty.ObjectVal(map[string]cty.Value{ 3593 "source_ids": cty.ListVal([]cty.Value{ 3594 cty.UnknownVal(cty.String), 3595 cty.UnknownVal(cty.String), 3596 cty.UnknownVal(cty.String), 3597 }), 3598 "source_names": cty.ListVal([]cty.Value{ 3599 cty.StringVal("source.0"), 3600 cty.StringVal("source.1"), 3601 cty.StringVal("source.2"), 3602 }), 3603 "source_ids_from_func": cty.UnknownVal(cty.String), 3604 "source_names_from_func": cty.ListVal([]cty.Value{ 3605 cty.StringVal("source.0"), 3606 cty.StringVal("source.1"), 3607 cty.StringVal("source.2"), 3608 }), 3609 "source_ids_wrapped": cty.ListVal([]cty.Value{ 3610 cty.ListVal([]cty.Value{ 3611 cty.UnknownVal(cty.String), 3612 cty.UnknownVal(cty.String), 3613 cty.UnknownVal(cty.String), 3614 }), 3615 }), 3616 "source_names_wrapped": cty.ListVal([]cty.Value{ 3617 cty.ListVal([]cty.Value{ 3618 cty.StringVal("source.0"), 3619 cty.StringVal("source.1"), 3620 cty.StringVal("source.2"), 3621 }), 3622 }), 3623 "first_source_id": cty.UnknownVal(cty.String), 3624 "first_source_name": cty.StringVal("source.0"), 3625 })) 3626 checkConfig("child.whole_splat", cty.ObjectVal(map[string]cty.Value{ 3627 "source_ids": cty.ListVal([]cty.Value{ 3628 cty.UnknownVal(cty.String), 3629 cty.UnknownVal(cty.String), 3630 cty.UnknownVal(cty.String), 3631 }), 3632 "source_names": cty.ListVal([]cty.Value{ 3633 cty.StringVal("source.0"), 3634 cty.StringVal("source.1"), 3635 cty.StringVal("source.2"), 3636 }), 3637 "source_ids_wrapped": cty.ListVal([]cty.Value{ 3638 cty.ListVal([]cty.Value{ 3639 cty.UnknownVal(cty.String), 3640 cty.UnknownVal(cty.String), 3641 cty.UnknownVal(cty.String), 3642 }), 3643 }), 3644 "source_names_wrapped": cty.ListVal([]cty.Value{ 3645 cty.ListVal([]cty.Value{ 3646 cty.StringVal("source.0"), 3647 cty.StringVal("source.1"), 3648 cty.StringVal("source.2"), 3649 }), 3650 }), 3651 })) 3652 3653 t.Run("apply", func(t *testing.T) { 3654 state, diags := ctx.Apply(plan, m) 3655 if diags.HasErrors() { 3656 t.Fatalf("error during apply: %s", diags.Err()) 3657 } 3658 3659 want := map[string]interface{}{ 3660 "source_ids": []interface{}{"foo", "foo", "foo"}, 3661 "source_names": []interface{}{ 3662 "source.0", 3663 "source.1", 3664 "source.2", 3665 }, 3666 } 3667 got := map[string]interface{}{} 3668 for k, s := range state.RootModule().OutputValues { 3669 got[k] = hcl2shim.ConfigValueFromHCL2(s.Value) 3670 } 3671 if !reflect.DeepEqual(got, want) { 3672 t.Errorf( 3673 "wrong outputs\ngot: %s\nwant: %s", 3674 spew.Sdump(got), spew.Sdump(want), 3675 ) 3676 } 3677 }) 3678 } 3679 3680 // Test that multi-var (splat) access is ordered by count, not by 3681 // value. 3682 func TestContext2Apply_multiVarOrder(t *testing.T) { 3683 m := testModule(t, "apply-multi-var-order") 3684 p := testProvider("aws") 3685 p.PlanResourceChangeFn = testDiffFn 3686 3687 // First, apply with a count of 3 3688 ctx := testContext2(t, &ContextOpts{ 3689 Providers: map[addrs.Provider]providers.Factory{ 3690 addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p), 3691 }, 3692 }) 3693 3694 plan, diags := ctx.Plan(m, states.NewState(), SimplePlanOpts(plans.NormalMode, testInputValuesUnset(m.Module.Variables))) 3695 assertNoErrors(t, diags) 3696 3697 state, diags := ctx.Apply(plan, m) 3698 if diags.HasErrors() { 3699 t.Fatalf("diags: %s", diags.Err()) 3700 } 3701 3702 t.Logf("State: %s", state.String()) 3703 3704 actual := state.RootModule().OutputValues["should-be-11"] 3705 expected := cty.StringVal("index-11") 3706 if actual == nil || actual.Value != expected { 3707 t.Fatalf("wrong value\ngot: %#v\nwant: %#v", actual.Value, expected) 3708 } 3709 } 3710 3711 // Test that multi-var (splat) access is ordered by count, not by 3712 // value, through interpolations. 3713 func TestContext2Apply_multiVarOrderInterp(t *testing.T) { 3714 m := testModule(t, "apply-multi-var-order-interp") 3715 p := testProvider("aws") 3716 p.PlanResourceChangeFn = testDiffFn 3717 3718 // First, apply with a count of 3 3719 ctx := testContext2(t, &ContextOpts{ 3720 Providers: map[addrs.Provider]providers.Factory{ 3721 addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p), 3722 }, 3723 }) 3724 3725 plan, diags := ctx.Plan(m, states.NewState(), SimplePlanOpts(plans.NormalMode, testInputValuesUnset(m.Module.Variables))) 3726 assertNoErrors(t, diags) 3727 3728 state, diags := ctx.Apply(plan, m) 3729 if diags.HasErrors() { 3730 t.Fatalf("diags: %s", diags.Err()) 3731 } 3732 3733 t.Logf("State: %s", state.String()) 3734 3735 actual := state.RootModule().OutputValues["should-be-11"] 3736 expected := cty.StringVal("baz-index-11") 3737 if actual == nil || actual.Value != expected { 3738 t.Fatalf("wrong value\ngot: %#v\nwant: %#v", actual.Value, expected) 3739 } 3740 } 3741 3742 // Based on GH-10440 where a graph edge wasn't properly being created 3743 // between a modified resource and a count instance being destroyed. 3744 func TestContext2Apply_multiVarCountDec(t *testing.T) { 3745 var s *states.State 3746 3747 // First create resources. Nothing sneaky here. 3748 { 3749 m := testModule(t, "apply-multi-var-count-dec") 3750 p := testProvider("aws") 3751 p.PlanResourceChangeFn = testDiffFn 3752 p.ApplyResourceChangeFn = testApplyFn 3753 ctx := testContext2(t, &ContextOpts{ 3754 Providers: map[addrs.Provider]providers.Factory{ 3755 addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p), 3756 }, 3757 }) 3758 3759 log.Print("\n========\nStep 1 Plan\n========") 3760 plan, diags := ctx.Plan(m, states.NewState(), &PlanOpts{ 3761 Mode: plans.NormalMode, 3762 SetVariables: InputValues{ 3763 "num": &InputValue{ 3764 Value: cty.NumberIntVal(2), 3765 SourceType: ValueFromCaller, 3766 }, 3767 }, 3768 }) 3769 assertNoErrors(t, diags) 3770 3771 log.Print("\n========\nStep 1 Apply\n========") 3772 state, diags := ctx.Apply(plan, m) 3773 if diags.HasErrors() { 3774 t.Fatalf("diags: %s", diags.Err()) 3775 } 3776 3777 t.Logf("Step 1 state:\n%s", state) 3778 3779 s = state 3780 } 3781 3782 // Decrease the count by 1 and verify that everything happens in the 3783 // right order. 3784 m := testModule(t, "apply-multi-var-count-dec") 3785 p := testProvider("aws") 3786 p.PlanResourceChangeFn = testDiffFn 3787 3788 // Verify that aws_instance.bar is modified first and nothing 3789 // else happens at the same time. 3790 { 3791 var checked bool 3792 var called int32 3793 var lock sync.Mutex 3794 p.ApplyResourceChangeFn = func(req providers.ApplyResourceChangeRequest) (resp providers.ApplyResourceChangeResponse) { 3795 lock.Lock() 3796 defer lock.Unlock() 3797 3798 if !req.PlannedState.IsNull() { 3799 s := req.PlannedState.AsValueMap() 3800 if ami, ok := s["ami"]; ok && !ami.IsNull() && ami.AsString() == "special" { 3801 checked = true 3802 3803 // Sleep to allow parallel execution 3804 time.Sleep(50 * time.Millisecond) 3805 3806 // Verify that called is 0 (dep not called) 3807 if atomic.LoadInt32(&called) != 1 { 3808 resp.Diagnostics = resp.Diagnostics.Append(fmt.Errorf("nothing else should be called")) 3809 return 3810 } 3811 } 3812 } 3813 atomic.AddInt32(&called, 1) 3814 return testApplyFn(req) 3815 } 3816 3817 ctx := testContext2(t, &ContextOpts{ 3818 Providers: map[addrs.Provider]providers.Factory{ 3819 addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p), 3820 }, 3821 }) 3822 3823 log.Print("\n========\nStep 2 Plan\n========") 3824 plan, diags := ctx.Plan(m, s, &PlanOpts{ 3825 Mode: plans.NormalMode, 3826 SetVariables: InputValues{ 3827 "num": &InputValue{ 3828 Value: cty.NumberIntVal(1), 3829 SourceType: ValueFromCaller, 3830 }, 3831 }, 3832 }) 3833 assertNoErrors(t, diags) 3834 3835 t.Logf("Step 2 plan:\n%s", legacyDiffComparisonString(plan.Changes)) 3836 3837 log.Print("\n========\nStep 2 Apply\n========") 3838 _, diags = ctx.Apply(plan, m) 3839 if diags.HasErrors() { 3840 t.Fatalf("apply errors: %s", diags.Err()) 3841 } 3842 3843 if !checked { 3844 t.Error("apply never called") 3845 } 3846 } 3847 } 3848 3849 // Test that we can resolve a multi-var (splat) for the first resource 3850 // created in a non-root module, which happens when the module state doesn't 3851 // exist yet. 3852 // https://github.com/hashicorp/terraform/issues/14438 3853 func TestContext2Apply_multiVarMissingState(t *testing.T) { 3854 m := testModule(t, "apply-multi-var-missing-state") 3855 p := testProvider("test") 3856 p.PlanResourceChangeFn = testDiffFn 3857 p.GetProviderSchemaResponse = getProviderSchemaResponseFromProviderSchema(&ProviderSchema{ 3858 ResourceTypes: map[string]*configschema.Block{ 3859 "test_thing": { 3860 Attributes: map[string]*configschema.Attribute{ 3861 "a_ids": {Type: cty.String, Optional: true}, 3862 "id": {Type: cty.String, Computed: true}, 3863 }, 3864 }, 3865 }, 3866 }) 3867 3868 // First, apply with a count of 3 3869 ctx := testContext2(t, &ContextOpts{ 3870 Providers: map[addrs.Provider]providers.Factory{ 3871 addrs.NewDefaultProvider("test"): testProviderFuncFixed(p), 3872 }, 3873 }) 3874 3875 plan, diags := ctx.Plan(m, states.NewState(), DefaultPlanOpts) 3876 assertNoErrors(t, diags) 3877 3878 // Before the relevant bug was fixed, Terraform would panic during apply. 3879 if _, diags := ctx.Apply(plan, m); diags.HasErrors() { 3880 t.Fatalf("apply failed: %s", diags.Err()) 3881 } 3882 3883 // If we get here with no errors or panics then our test was successful. 3884 } 3885 3886 func TestContext2Apply_outputOrphan(t *testing.T) { 3887 m := testModule(t, "apply-output-orphan") 3888 p := testProvider("aws") 3889 p.PlanResourceChangeFn = testDiffFn 3890 3891 state := states.NewState() 3892 root := state.EnsureModule(addrs.RootModuleInstance) 3893 root.SetOutputValue("foo", cty.StringVal("bar"), false) 3894 root.SetOutputValue("bar", cty.StringVal("baz"), false) 3895 3896 ctx := testContext2(t, &ContextOpts{ 3897 Providers: map[addrs.Provider]providers.Factory{ 3898 addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p), 3899 }, 3900 }) 3901 3902 plan, diags := ctx.Plan(m, state, DefaultPlanOpts) 3903 assertNoErrors(t, diags) 3904 3905 state, diags = ctx.Apply(plan, m) 3906 if diags.HasErrors() { 3907 t.Fatalf("diags: %s", diags.Err()) 3908 } 3909 3910 actual := strings.TrimSpace(state.String()) 3911 expected := strings.TrimSpace(testTerraformApplyOutputOrphanStr) 3912 if actual != expected { 3913 t.Fatalf("wrong result\n\ngot:\n%s\n\nwant:\n%s", actual, expected) 3914 } 3915 } 3916 3917 func TestContext2Apply_outputOrphanModule(t *testing.T) { 3918 m := testModule(t, "apply-output-orphan-module") 3919 p := testProvider("aws") 3920 p.PlanResourceChangeFn = testDiffFn 3921 3922 state := states.NewState() 3923 3924 ctx := testContext2(t, &ContextOpts{ 3925 Providers: map[addrs.Provider]providers.Factory{ 3926 addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p), 3927 }, 3928 }) 3929 3930 plan, diags := ctx.Plan(m, state, DefaultPlanOpts) 3931 assertNoErrors(t, diags) 3932 3933 s, diags := ctx.Apply(plan, m) 3934 if diags.HasErrors() { 3935 t.Fatalf("diags: %s", diags.Err()) 3936 } 3937 3938 actual := strings.TrimSpace(s.String()) 3939 expected := strings.TrimSpace(testTerraformApplyOutputOrphanModuleStr) 3940 if actual != expected { 3941 t.Fatalf("expected:\n%s\n\ngot:\n%s", expected, actual) 3942 } 3943 3944 // now apply with no module in the config, which should remove the 3945 // remaining output 3946 ctx = testContext2(t, &ContextOpts{ 3947 Providers: map[addrs.Provider]providers.Factory{ 3948 addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p), 3949 }, 3950 }) 3951 3952 emptyConfig := configs.NewEmptyConfig() 3953 3954 // NOTE: While updating this test to pass the state in as a Plan argument, 3955 // rather than into the testContext2 call above, it previously said 3956 // State: state.DeepCopy(), which is a little weird since we just 3957 // created "s" above as the result of the previous apply, but I've preserved 3958 // it to avoid changing the flow of this test in case that's important 3959 // for some reason. 3960 plan, diags = ctx.Plan(emptyConfig, state.DeepCopy(), DefaultPlanOpts) 3961 assertNoErrors(t, diags) 3962 3963 state, diags = ctx.Apply(plan, emptyConfig) 3964 if diags.HasErrors() { 3965 t.Fatalf("diags: %s", diags.Err()) 3966 } 3967 3968 if !state.Empty() { 3969 t.Fatalf("wrong final state %s\nwant empty state", spew.Sdump(state)) 3970 } 3971 } 3972 3973 func TestContext2Apply_providerComputedVar(t *testing.T) { 3974 m := testModule(t, "apply-provider-computed") 3975 p := testProvider("aws") 3976 p.PlanResourceChangeFn = testDiffFn 3977 3978 pTest := testProvider("test") 3979 pTest.ApplyResourceChangeFn = testApplyFn 3980 pTest.PlanResourceChangeFn = testDiffFn 3981 3982 ctx := testContext2(t, &ContextOpts{ 3983 Providers: map[addrs.Provider]providers.Factory{ 3984 addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p), 3985 addrs.NewDefaultProvider("test"): testProviderFuncFixed(pTest), 3986 }, 3987 }) 3988 3989 p.ConfigureProviderFn = func(req providers.ConfigureProviderRequest) (resp providers.ConfigureProviderResponse) { 3990 val := req.Config.GetAttr("value") 3991 if val.IsNull() { 3992 resp.Diagnostics = resp.Diagnostics.Append(fmt.Errorf("value is not found")) 3993 return 3994 } 3995 return 3996 } 3997 3998 plan, diags := ctx.Plan(m, states.NewState(), DefaultPlanOpts) 3999 assertNoErrors(t, diags) 4000 4001 if _, diags := ctx.Apply(plan, m); diags.HasErrors() { 4002 t.Fatalf("apply errors: %s", diags.Err()) 4003 } 4004 } 4005 4006 func TestContext2Apply_providerConfigureDisabled(t *testing.T) { 4007 m := testModule(t, "apply-provider-configure-disabled") 4008 p := testProvider("aws") 4009 p.PlanResourceChangeFn = testDiffFn 4010 4011 p.ConfigureProviderFn = func(req providers.ConfigureProviderRequest) (resp providers.ConfigureProviderResponse) { 4012 val := req.Config.GetAttr("value") 4013 if val.IsNull() { 4014 resp.Diagnostics = resp.Diagnostics.Append(fmt.Errorf("value is not found")) 4015 } 4016 4017 return 4018 } 4019 4020 ctx := testContext2(t, &ContextOpts{ 4021 Providers: map[addrs.Provider]providers.Factory{ 4022 addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p), 4023 }, 4024 }) 4025 4026 plan, diags := ctx.Plan(m, states.NewState(), DefaultPlanOpts) 4027 assertNoErrors(t, diags) 4028 4029 if _, diags := ctx.Apply(plan, m); diags.HasErrors() { 4030 t.Fatalf("apply errors: %s", diags.Err()) 4031 } 4032 4033 if !p.ConfigureProviderCalled { 4034 t.Fatal("configure never called") 4035 } 4036 } 4037 4038 func TestContext2Apply_provisionerModule(t *testing.T) { 4039 m := testModule(t, "apply-provisioner-module") 4040 4041 p := testProvider("aws") 4042 p.PlanResourceChangeFn = testDiffFn 4043 p.ApplyResourceChangeFn = testApplyFn 4044 4045 pr := testProvisioner() 4046 pr.GetSchemaResponse = provisioners.GetSchemaResponse{ 4047 Provisioner: &configschema.Block{ 4048 Attributes: map[string]*configschema.Attribute{ 4049 "foo": {Type: cty.String, Optional: true}, 4050 }, 4051 }, 4052 } 4053 4054 ctx := testContext2(t, &ContextOpts{ 4055 Providers: map[addrs.Provider]providers.Factory{ 4056 addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p), 4057 }, 4058 Provisioners: map[string]provisioners.Factory{ 4059 "shell": testProvisionerFuncFixed(pr), 4060 }, 4061 }) 4062 4063 plan, diags := ctx.Plan(m, states.NewState(), DefaultPlanOpts) 4064 assertNoErrors(t, diags) 4065 4066 state, diags := ctx.Apply(plan, m) 4067 if diags.HasErrors() { 4068 t.Fatalf("diags: %s", diags.Err()) 4069 } 4070 4071 actual := strings.TrimSpace(state.String()) 4072 expected := strings.TrimSpace(testTerraformApplyProvisionerModuleStr) 4073 if actual != expected { 4074 t.Fatalf("wrong result\n\ngot:\n%s\n\nwant:\n%s", actual, expected) 4075 } 4076 4077 // Verify apply was invoked 4078 if !pr.ProvisionResourceCalled { 4079 t.Fatalf("provisioner not invoked") 4080 } 4081 } 4082 4083 func TestContext2Apply_Provisioner_compute(t *testing.T) { 4084 m := testModule(t, "apply-provisioner-compute") 4085 p := testProvider("aws") 4086 pr := testProvisioner() 4087 p.PlanResourceChangeFn = testDiffFn 4088 p.ApplyResourceChangeFn = testApplyFn 4089 pr.ProvisionResourceFn = func(req provisioners.ProvisionResourceRequest) (resp provisioners.ProvisionResourceResponse) { 4090 4091 val := req.Config.GetAttr("command").AsString() 4092 if val != "computed_value" { 4093 t.Fatalf("bad value for foo: %q", val) 4094 } 4095 req.UIOutput.Output(fmt.Sprintf("Executing: %q", val)) 4096 4097 return 4098 } 4099 h := new(MockHook) 4100 ctx := testContext2(t, &ContextOpts{ 4101 Hooks: []Hook{h}, 4102 Providers: map[addrs.Provider]providers.Factory{ 4103 addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p), 4104 }, 4105 Provisioners: map[string]provisioners.Factory{ 4106 "shell": testProvisionerFuncFixed(pr), 4107 }, 4108 }) 4109 4110 plan, diags := ctx.Plan(m, states.NewState(), &PlanOpts{ 4111 Mode: plans.NormalMode, 4112 SetVariables: InputValues{ 4113 "value": &InputValue{ 4114 Value: cty.NumberIntVal(1), 4115 SourceType: ValueFromCaller, 4116 }, 4117 }, 4118 }) 4119 assertNoErrors(t, diags) 4120 4121 state, diags := ctx.Apply(plan, m) 4122 if diags.HasErrors() { 4123 t.Fatalf("diags: %s", diags.Err()) 4124 } 4125 4126 actual := strings.TrimSpace(state.String()) 4127 expected := strings.TrimSpace(testTerraformApplyProvisionerStr) 4128 if actual != expected { 4129 t.Fatalf("wrong result\n\ngot:\n%s\n\nwant:\n%s", actual, expected) 4130 } 4131 4132 // Verify apply was invoked 4133 if !pr.ProvisionResourceCalled { 4134 t.Fatalf("provisioner not invoked") 4135 } 4136 4137 // Verify output was rendered 4138 if !h.ProvisionOutputCalled { 4139 t.Fatalf("ProvisionOutput hook not called") 4140 } 4141 if got, want := h.ProvisionOutputMessage, `Executing: "computed_value"`; got != want { 4142 t.Errorf("expected output to be %q, but was %q", want, got) 4143 } 4144 } 4145 4146 func TestContext2Apply_provisionerCreateFail(t *testing.T) { 4147 m := testModule(t, "apply-provisioner-fail-create") 4148 p := testProvider("aws") 4149 pr := testProvisioner() 4150 p.PlanResourceChangeFn = testDiffFn 4151 4152 p.ApplyResourceChangeFn = func(req providers.ApplyResourceChangeRequest) providers.ApplyResourceChangeResponse { 4153 resp := testApplyFn(req) 4154 resp.Diagnostics = resp.Diagnostics.Append(fmt.Errorf("error")) 4155 4156 return resp 4157 } 4158 4159 ctx := testContext2(t, &ContextOpts{ 4160 Providers: map[addrs.Provider]providers.Factory{ 4161 addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p), 4162 }, 4163 Provisioners: map[string]provisioners.Factory{ 4164 "shell": testProvisionerFuncFixed(pr), 4165 }, 4166 }) 4167 4168 plan, diags := ctx.Plan(m, states.NewState(), DefaultPlanOpts) 4169 assertNoErrors(t, diags) 4170 4171 state, diags := ctx.Apply(plan, m) 4172 if diags == nil { 4173 t.Fatal("should error") 4174 } 4175 4176 got := strings.TrimSpace(state.String()) 4177 want := strings.TrimSpace(testTerraformApplyProvisionerFailCreateStr) 4178 if got != want { 4179 t.Fatalf("wrong result\n\ngot:\n%s\n\nwant:\n%s", got, want) 4180 } 4181 } 4182 4183 func TestContext2Apply_provisionerCreateFailNoId(t *testing.T) { 4184 m := testModule(t, "apply-provisioner-fail-create") 4185 p := testProvider("aws") 4186 pr := testProvisioner() 4187 p.PlanResourceChangeFn = testDiffFn 4188 4189 p.ApplyResourceChangeFn = func(req providers.ApplyResourceChangeRequest) (resp providers.ApplyResourceChangeResponse) { 4190 resp.Diagnostics = resp.Diagnostics.Append(fmt.Errorf("error")) 4191 return 4192 } 4193 4194 ctx := testContext2(t, &ContextOpts{ 4195 Providers: map[addrs.Provider]providers.Factory{ 4196 addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p), 4197 }, 4198 Provisioners: map[string]provisioners.Factory{ 4199 "shell": testProvisionerFuncFixed(pr), 4200 }, 4201 }) 4202 4203 plan, diags := ctx.Plan(m, states.NewState(), DefaultPlanOpts) 4204 assertNoErrors(t, diags) 4205 4206 state, diags := ctx.Apply(plan, m) 4207 if diags == nil { 4208 t.Fatal("should error") 4209 } 4210 4211 actual := strings.TrimSpace(state.String()) 4212 expected := strings.TrimSpace(testTerraformApplyProvisionerFailCreateNoIdStr) 4213 if actual != expected { 4214 t.Fatalf("wrong result\n\ngot:\n%s\n\nwant:\n%s", actual, expected) 4215 } 4216 } 4217 4218 func TestContext2Apply_provisionerFail(t *testing.T) { 4219 m := testModule(t, "apply-provisioner-fail") 4220 p := testProvider("aws") 4221 p.PlanResourceChangeFn = testDiffFn 4222 p.ApplyResourceChangeFn = testApplyFn 4223 pr := testProvisioner() 4224 pr.ProvisionResourceFn = func(req provisioners.ProvisionResourceRequest) (resp provisioners.ProvisionResourceResponse) { 4225 resp.Diagnostics = resp.Diagnostics.Append(fmt.Errorf("EXPLOSION")) 4226 return 4227 } 4228 4229 ctx := testContext2(t, &ContextOpts{ 4230 Providers: map[addrs.Provider]providers.Factory{ 4231 addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p), 4232 }, 4233 Provisioners: map[string]provisioners.Factory{ 4234 "shell": testProvisionerFuncFixed(pr), 4235 }, 4236 }) 4237 4238 plan, diags := ctx.Plan(m, states.NewState(), DefaultPlanOpts) 4239 assertNoErrors(t, diags) 4240 4241 state, diags := ctx.Apply(plan, m) 4242 if diags == nil { 4243 t.Fatal("should error") 4244 } 4245 4246 actual := strings.TrimSpace(state.String()) 4247 expected := strings.TrimSpace(testTerraformApplyProvisionerFailStr) 4248 if actual != expected { 4249 t.Fatalf("wrong result\n\ngot:\n%s\n\nwant:\n%s", actual, expected) 4250 } 4251 } 4252 4253 func TestContext2Apply_provisionerFail_createBeforeDestroy(t *testing.T) { 4254 m := testModule(t, "apply-provisioner-fail-create-before") 4255 p := testProvider("aws") 4256 pr := testProvisioner() 4257 p.PlanResourceChangeFn = testDiffFn 4258 p.ApplyResourceChangeFn = testApplyFn 4259 pr.ProvisionResourceFn = func(req provisioners.ProvisionResourceRequest) (resp provisioners.ProvisionResourceResponse) { 4260 resp.Diagnostics = resp.Diagnostics.Append(fmt.Errorf("EXPLOSION")) 4261 return 4262 } 4263 4264 state := states.NewState() 4265 root := state.EnsureModule(addrs.RootModuleInstance) 4266 root.SetResourceInstanceCurrent( 4267 mustResourceInstanceAddr("aws_instance.bar").Resource, 4268 &states.ResourceInstanceObjectSrc{ 4269 Status: states.ObjectReady, 4270 AttrsJSON: []byte(`{"id":"bar","require_new":"abc"}`), 4271 }, 4272 mustProviderConfig(`provider["registry.terraform.io/hashicorp/aws"]`), 4273 ) 4274 4275 ctx := testContext2(t, &ContextOpts{ 4276 Providers: map[addrs.Provider]providers.Factory{ 4277 addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p), 4278 }, 4279 Provisioners: map[string]provisioners.Factory{ 4280 "shell": testProvisionerFuncFixed(pr), 4281 }, 4282 }) 4283 4284 plan, diags := ctx.Plan(m, state, DefaultPlanOpts) 4285 assertNoErrors(t, diags) 4286 4287 state, diags = ctx.Apply(plan, m) 4288 if !diags.HasErrors() { 4289 t.Fatal("should error") 4290 } 4291 4292 actual := strings.TrimSpace(state.String()) 4293 expected := strings.TrimSpace(testTerraformApplyProvisionerFailCreateBeforeDestroyStr) 4294 if actual != expected { 4295 t.Fatalf("expected:\n%s\n:got\n%s", expected, actual) 4296 } 4297 } 4298 4299 func TestContext2Apply_error_createBeforeDestroy(t *testing.T) { 4300 m := testModule(t, "apply-error-create-before") 4301 p := testProvider("aws") 4302 4303 state := states.NewState() 4304 root := state.EnsureModule(addrs.RootModuleInstance) 4305 root.SetResourceInstanceCurrent( 4306 mustResourceInstanceAddr("aws_instance.bar").Resource, 4307 &states.ResourceInstanceObjectSrc{ 4308 Status: states.ObjectReady, 4309 AttrsJSON: []byte(`{"id":"bar", "require_new": "abc","type":"aws_instance"}`), 4310 }, 4311 mustProviderConfig(`provider["registry.terraform.io/hashicorp/aws"]`), 4312 ) 4313 4314 ctx := testContext2(t, &ContextOpts{ 4315 Providers: map[addrs.Provider]providers.Factory{ 4316 addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p), 4317 }, 4318 }) 4319 p.ApplyResourceChangeFn = func(req providers.ApplyResourceChangeRequest) (resp providers.ApplyResourceChangeResponse) { 4320 resp.Diagnostics = resp.Diagnostics.Append(fmt.Errorf("placeholder error from ApplyFn")) 4321 return 4322 } 4323 p.PlanResourceChangeFn = testDiffFn 4324 4325 plan, diags := ctx.Plan(m, state, DefaultPlanOpts) 4326 assertNoErrors(t, diags) 4327 4328 state, diags = ctx.Apply(plan, m) 4329 if !diags.HasErrors() { 4330 t.Fatal("should have error") 4331 } 4332 if got, want := diags.Err().Error(), "placeholder error from ApplyFn"; got != want { 4333 // We're looking for our artificial error from ApplyFn above, whose 4334 // message is literally "placeholder error from ApplyFn". 4335 t.Fatalf("wrong error\ngot: %s\nwant: %s", got, want) 4336 } 4337 4338 actual := strings.TrimSpace(state.String()) 4339 expected := strings.TrimSpace(testTerraformApplyErrorCreateBeforeDestroyStr) 4340 if actual != expected { 4341 t.Fatalf("wrong final state\ngot:\n%s\n\nwant:\n%s", actual, expected) 4342 } 4343 } 4344 4345 func TestContext2Apply_errorDestroy_createBeforeDestroy(t *testing.T) { 4346 m := testModule(t, "apply-error-create-before") 4347 p := testProvider("aws") 4348 4349 state := states.NewState() 4350 root := state.EnsureModule(addrs.RootModuleInstance) 4351 root.SetResourceInstanceCurrent( 4352 mustResourceInstanceAddr("aws_instance.bar").Resource, 4353 &states.ResourceInstanceObjectSrc{ 4354 Status: states.ObjectReady, 4355 AttrsJSON: []byte(`{"id":"bar", "require_new": "abc"}`), 4356 }, 4357 mustProviderConfig(`provider["registry.terraform.io/hashicorp/aws"]`), 4358 ) 4359 4360 ctx := testContext2(t, &ContextOpts{ 4361 Providers: map[addrs.Provider]providers.Factory{ 4362 addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p), 4363 }, 4364 }) 4365 p.ApplyResourceChangeFn = func(req providers.ApplyResourceChangeRequest) (resp providers.ApplyResourceChangeResponse) { 4366 // Fail the destroy! 4367 if req.PlannedState.IsNull() { 4368 resp.NewState = req.PriorState 4369 resp.Diagnostics = resp.Diagnostics.Append(fmt.Errorf("error")) 4370 return 4371 } 4372 4373 return testApplyFn(req) 4374 } 4375 p.PlanResourceChangeFn = testDiffFn 4376 4377 plan, diags := ctx.Plan(m, state, DefaultPlanOpts) 4378 assertNoErrors(t, diags) 4379 4380 state, diags = ctx.Apply(plan, m) 4381 if !diags.HasErrors() { 4382 t.Fatal("should have error") 4383 } 4384 4385 actual := strings.TrimSpace(state.String()) 4386 expected := strings.TrimSpace(testTerraformApplyErrorDestroyCreateBeforeDestroyStr) 4387 if actual != expected { 4388 t.Fatalf("bad: actual:\n%s\n\nexpected:\n%s", actual, expected) 4389 } 4390 } 4391 4392 func TestContext2Apply_multiDepose_createBeforeDestroy(t *testing.T) { 4393 m := testModule(t, "apply-multi-depose-create-before-destroy") 4394 p := testProvider("aws") 4395 ps := map[addrs.Provider]providers.Factory{addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p)} 4396 p.GetProviderSchemaResponse = getProviderSchemaResponseFromProviderSchema(&ProviderSchema{ 4397 ResourceTypes: map[string]*configschema.Block{ 4398 "aws_instance": { 4399 Attributes: map[string]*configschema.Attribute{ 4400 "require_new": {Type: cty.String, Optional: true}, 4401 "id": {Type: cty.String, Computed: true}, 4402 }, 4403 }, 4404 }, 4405 }) 4406 4407 state := states.NewState() 4408 root := state.EnsureModule(addrs.RootModuleInstance) 4409 root.SetResourceInstanceCurrent( 4410 mustResourceInstanceAddr("aws_instance.web").Resource, 4411 &states.ResourceInstanceObjectSrc{ 4412 Status: states.ObjectReady, 4413 AttrsJSON: []byte(`{"id":"foo"}`), 4414 }, 4415 mustProviderConfig(`provider["registry.terraform.io/hashicorp/aws"]`), 4416 ) 4417 4418 p.PlanResourceChangeFn = testDiffFn 4419 4420 ctx := testContext2(t, &ContextOpts{ 4421 Providers: ps, 4422 }) 4423 createdInstanceId := "bar" 4424 // Create works 4425 createFunc := func(req providers.ApplyResourceChangeRequest) (resp providers.ApplyResourceChangeResponse) { 4426 s := req.PlannedState.AsValueMap() 4427 s["id"] = cty.StringVal(createdInstanceId) 4428 resp.NewState = cty.ObjectVal(s) 4429 return 4430 } 4431 4432 // Destroy starts broken 4433 destroyFunc := func(req providers.ApplyResourceChangeRequest) (resp providers.ApplyResourceChangeResponse) { 4434 resp.NewState = req.PriorState 4435 resp.Diagnostics = resp.Diagnostics.Append(fmt.Errorf("destroy failed")) 4436 return 4437 } 4438 p.ApplyResourceChangeFn = func(req providers.ApplyResourceChangeRequest) (resp providers.ApplyResourceChangeResponse) { 4439 if req.PlannedState.IsNull() { 4440 return destroyFunc(req) 4441 } else { 4442 return createFunc(req) 4443 } 4444 } 4445 4446 plan, diags := ctx.Plan(m, state, &PlanOpts{ 4447 Mode: plans.NormalMode, 4448 SetVariables: InputValues{ 4449 "require_new": &InputValue{ 4450 Value: cty.StringVal("yes"), 4451 }, 4452 }, 4453 }) 4454 assertNoErrors(t, diags) 4455 4456 // Destroy is broken, so even though CBD successfully replaces the instance, 4457 // we'll have to save the Deposed instance to destroy later 4458 state, diags = ctx.Apply(plan, m) 4459 if !diags.HasErrors() { 4460 t.Fatal("should have error") 4461 } 4462 4463 checkStateString(t, state, ` 4464 aws_instance.web: (1 deposed) 4465 ID = bar 4466 provider = provider["registry.terraform.io/hashicorp/aws"] 4467 require_new = yes 4468 Deposed ID 1 = foo 4469 `) 4470 4471 createdInstanceId = "baz" 4472 ctx = testContext2(t, &ContextOpts{ 4473 Providers: ps, 4474 }) 4475 4476 plan, diags = ctx.Plan(m, state, &PlanOpts{ 4477 Mode: plans.NormalMode, 4478 SetVariables: InputValues{ 4479 "require_new": &InputValue{ 4480 Value: cty.StringVal("baz"), 4481 }, 4482 }, 4483 }) 4484 assertNoErrors(t, diags) 4485 4486 // We're replacing the primary instance once again. Destroy is _still_ 4487 // broken, so the Deposed list gets longer 4488 state, diags = ctx.Apply(plan, m) 4489 if !diags.HasErrors() { 4490 t.Fatal("should have error") 4491 } 4492 4493 // For this one we can't rely on checkStateString because its result is 4494 // not deterministic when multiple deposed objects are present. Instead, 4495 // we will probe the state object directly. 4496 { 4497 is := state.RootModule().Resources["aws_instance.web"].Instances[addrs.NoKey] 4498 if is.Current == nil { 4499 t.Fatalf("no current object for aws_instance web; should have one") 4500 } 4501 if !bytes.Contains(is.Current.AttrsJSON, []byte("baz")) { 4502 t.Fatalf("incorrect current object attrs %s; want id=baz", is.Current.AttrsJSON) 4503 } 4504 if got, want := len(is.Deposed), 2; got != want { 4505 t.Fatalf("wrong number of deposed instances %d; want %d", got, want) 4506 } 4507 var foos, bars int 4508 for _, obj := range is.Deposed { 4509 if bytes.Contains(obj.AttrsJSON, []byte("foo")) { 4510 foos++ 4511 } 4512 if bytes.Contains(obj.AttrsJSON, []byte("bar")) { 4513 bars++ 4514 } 4515 } 4516 if got, want := foos, 1; got != want { 4517 t.Fatalf("wrong number of deposed instances with id=foo %d; want %d", got, want) 4518 } 4519 if got, want := bars, 1; got != want { 4520 t.Fatalf("wrong number of deposed instances with id=bar %d; want %d", got, want) 4521 } 4522 } 4523 4524 // Destroy partially fixed! 4525 destroyFunc = func(req providers.ApplyResourceChangeRequest) (resp providers.ApplyResourceChangeResponse) { 4526 s := req.PriorState.AsValueMap() 4527 id := s["id"].AsString() 4528 if id == "foo" || id == "baz" { 4529 resp.NewState = cty.NullVal(req.PriorState.Type()) 4530 } else { 4531 resp.NewState = req.PriorState 4532 resp.Diagnostics = resp.Diagnostics.Append(fmt.Errorf("destroy partially failed")) 4533 } 4534 return 4535 } 4536 4537 createdInstanceId = "qux" 4538 ctx = testContext2(t, &ContextOpts{ 4539 Providers: ps, 4540 }) 4541 plan, diags = ctx.Plan(m, state, &PlanOpts{ 4542 Mode: plans.NormalMode, 4543 SetVariables: InputValues{ 4544 "require_new": &InputValue{ 4545 Value: cty.StringVal("qux"), 4546 }, 4547 }, 4548 }) 4549 assertNoErrors(t, diags) 4550 4551 state, diags = ctx.Apply(plan, m) 4552 // Expect error because 1/2 of Deposed destroys failed 4553 if !diags.HasErrors() { 4554 t.Fatal("should have error") 4555 } 4556 4557 // foo and baz are now gone, bar sticks around 4558 checkStateString(t, state, ` 4559 aws_instance.web: (1 deposed) 4560 ID = qux 4561 provider = provider["registry.terraform.io/hashicorp/aws"] 4562 require_new = qux 4563 Deposed ID 1 = bar 4564 `) 4565 4566 // Destroy working fully! 4567 destroyFunc = func(req providers.ApplyResourceChangeRequest) (resp providers.ApplyResourceChangeResponse) { 4568 resp.NewState = cty.NullVal(req.PriorState.Type()) 4569 return 4570 } 4571 4572 createdInstanceId = "quux" 4573 ctx = testContext2(t, &ContextOpts{ 4574 Providers: ps, 4575 }) 4576 plan, diags = ctx.Plan(m, state, &PlanOpts{ 4577 Mode: plans.NormalMode, 4578 SetVariables: InputValues{ 4579 "require_new": &InputValue{ 4580 Value: cty.StringVal("quux"), 4581 }, 4582 }, 4583 }) 4584 assertNoErrors(t, diags) 4585 state, diags = ctx.Apply(plan, m) 4586 if diags.HasErrors() { 4587 t.Fatal("should not have error:", diags.Err()) 4588 } 4589 4590 // And finally the state is clean 4591 checkStateString(t, state, ` 4592 aws_instance.web: 4593 ID = quux 4594 provider = provider["registry.terraform.io/hashicorp/aws"] 4595 require_new = quux 4596 `) 4597 } 4598 4599 // Verify that a normal provisioner with on_failure "continue" set won't 4600 // taint the resource and continues executing. 4601 func TestContext2Apply_provisionerFailContinue(t *testing.T) { 4602 m := testModule(t, "apply-provisioner-fail-continue") 4603 p := testProvider("aws") 4604 pr := testProvisioner() 4605 p.PlanResourceChangeFn = testDiffFn 4606 p.ApplyResourceChangeFn = testApplyFn 4607 4608 pr.ProvisionResourceFn = func(req provisioners.ProvisionResourceRequest) (resp provisioners.ProvisionResourceResponse) { 4609 resp.Diagnostics = resp.Diagnostics.Append(fmt.Errorf("provisioner error")) 4610 return 4611 } 4612 4613 ctx := testContext2(t, &ContextOpts{ 4614 Providers: map[addrs.Provider]providers.Factory{ 4615 addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p), 4616 }, 4617 Provisioners: map[string]provisioners.Factory{ 4618 "shell": testProvisionerFuncFixed(pr), 4619 }, 4620 }) 4621 4622 plan, diags := ctx.Plan(m, states.NewState(), DefaultPlanOpts) 4623 assertNoErrors(t, diags) 4624 4625 state, diags := ctx.Apply(plan, m) 4626 if diags.HasErrors() { 4627 t.Fatalf("diags: %s", diags.Err()) 4628 } 4629 4630 checkStateString(t, state, ` 4631 aws_instance.foo: 4632 ID = foo 4633 provider = provider["registry.terraform.io/hashicorp/aws"] 4634 foo = bar 4635 type = aws_instance 4636 `) 4637 4638 // Verify apply was invoked 4639 if !pr.ProvisionResourceCalled { 4640 t.Fatalf("provisioner not invoked") 4641 } 4642 } 4643 4644 // Verify that a normal provisioner with on_failure "continue" records 4645 // the error with the hook. 4646 func TestContext2Apply_provisionerFailContinueHook(t *testing.T) { 4647 h := new(MockHook) 4648 m := testModule(t, "apply-provisioner-fail-continue") 4649 p := testProvider("aws") 4650 pr := testProvisioner() 4651 p.PlanResourceChangeFn = testDiffFn 4652 pr.ProvisionResourceFn = func(req provisioners.ProvisionResourceRequest) (resp provisioners.ProvisionResourceResponse) { 4653 resp.Diagnostics = resp.Diagnostics.Append(fmt.Errorf("provisioner error")) 4654 return 4655 } 4656 4657 ctx := testContext2(t, &ContextOpts{ 4658 Hooks: []Hook{h}, 4659 Providers: map[addrs.Provider]providers.Factory{ 4660 addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p), 4661 }, 4662 Provisioners: map[string]provisioners.Factory{ 4663 "shell": testProvisionerFuncFixed(pr), 4664 }, 4665 }) 4666 4667 plan, diags := ctx.Plan(m, states.NewState(), DefaultPlanOpts) 4668 assertNoErrors(t, diags) 4669 4670 if _, diags := ctx.Apply(plan, m); diags.HasErrors() { 4671 t.Fatalf("apply errors: %s", diags.Err()) 4672 } 4673 4674 if !h.PostProvisionInstanceStepCalled { 4675 t.Fatal("PostProvisionInstanceStep not called") 4676 } 4677 if h.PostProvisionInstanceStepErrorArg == nil { 4678 t.Fatal("should have error") 4679 } 4680 } 4681 4682 func TestContext2Apply_provisionerDestroy(t *testing.T) { 4683 m := testModule(t, "apply-provisioner-destroy") 4684 p := testProvider("aws") 4685 pr := testProvisioner() 4686 p.PlanResourceChangeFn = testDiffFn 4687 pr.ProvisionResourceFn = func(req provisioners.ProvisionResourceRequest) (resp provisioners.ProvisionResourceResponse) { 4688 val := req.Config.GetAttr("command").AsString() 4689 if val != "destroy a bar" { 4690 t.Fatalf("bad value for foo: %q", val) 4691 } 4692 4693 return 4694 } 4695 4696 state := states.NewState() 4697 root := state.RootModule() 4698 root.SetResourceInstanceCurrent( 4699 mustResourceInstanceAddr(`aws_instance.foo["a"]`).Resource, 4700 &states.ResourceInstanceObjectSrc{ 4701 Status: states.ObjectReady, 4702 AttrsJSON: []byte(`{"id":"bar","foo":"bar"}`), 4703 }, 4704 mustProviderConfig(`provider["registry.terraform.io/hashicorp/aws"]`), 4705 ) 4706 4707 ctx := testContext2(t, &ContextOpts{ 4708 Providers: map[addrs.Provider]providers.Factory{ 4709 addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p), 4710 }, 4711 Provisioners: map[string]provisioners.Factory{ 4712 "shell": testProvisionerFuncFixed(pr), 4713 }, 4714 }) 4715 4716 plan, diags := ctx.Plan(m, state, SimplePlanOpts(plans.DestroyMode, testInputValuesUnset(m.Module.Variables))) 4717 assertNoErrors(t, diags) 4718 4719 state, diags = ctx.Apply(plan, m) 4720 if diags.HasErrors() { 4721 t.Fatalf("diags: %s", diags.Err()) 4722 } 4723 4724 checkStateString(t, state, `<no state>`) 4725 4726 // Verify apply was invoked 4727 if !pr.ProvisionResourceCalled { 4728 t.Fatalf("provisioner not invoked") 4729 } 4730 } 4731 4732 // Verify that on destroy provisioner failure, nothing happens to the instance 4733 func TestContext2Apply_provisionerDestroyFail(t *testing.T) { 4734 m := testModule(t, "apply-provisioner-destroy") 4735 p := testProvider("aws") 4736 pr := testProvisioner() 4737 p.PlanResourceChangeFn = testDiffFn 4738 pr.ProvisionResourceFn = func(req provisioners.ProvisionResourceRequest) (resp provisioners.ProvisionResourceResponse) { 4739 resp.Diagnostics = resp.Diagnostics.Append(fmt.Errorf("provisioner error")) 4740 return 4741 } 4742 4743 state := states.NewState() 4744 root := state.RootModule() 4745 root.SetResourceInstanceCurrent( 4746 mustResourceInstanceAddr(`aws_instance.foo["a"]`).Resource, 4747 &states.ResourceInstanceObjectSrc{ 4748 Status: states.ObjectReady, 4749 AttrsJSON: []byte(`{"id":"bar","foo":"bar"}`), 4750 }, 4751 mustProviderConfig(`provider["registry.terraform.io/hashicorp/aws"]`), 4752 ) 4753 4754 ctx := testContext2(t, &ContextOpts{ 4755 Providers: map[addrs.Provider]providers.Factory{ 4756 addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p), 4757 }, 4758 Provisioners: map[string]provisioners.Factory{ 4759 "shell": testProvisionerFuncFixed(pr), 4760 }, 4761 }) 4762 4763 plan, diags := ctx.Plan(m, state, SimplePlanOpts(plans.DestroyMode, testInputValuesUnset(m.Module.Variables))) 4764 assertNoErrors(t, diags) 4765 4766 state, diags = ctx.Apply(plan, m) 4767 if diags == nil { 4768 t.Fatal("should error") 4769 } 4770 4771 checkStateString(t, state, ` 4772 aws_instance.foo["a"]: 4773 ID = bar 4774 provider = provider["registry.terraform.io/hashicorp/aws"] 4775 foo = bar 4776 `) 4777 4778 // Verify apply was invoked 4779 if !pr.ProvisionResourceCalled { 4780 t.Fatalf("provisioner not invoked") 4781 } 4782 } 4783 4784 // Verify that on destroy provisioner failure with "continue" that 4785 // we continue to the next provisioner. 4786 func TestContext2Apply_provisionerDestroyFailContinue(t *testing.T) { 4787 m := testModule(t, "apply-provisioner-destroy-continue") 4788 p := testProvider("aws") 4789 pr := testProvisioner() 4790 p.PlanResourceChangeFn = testDiffFn 4791 4792 var l sync.Mutex 4793 var calls []string 4794 pr.ProvisionResourceFn = func(req provisioners.ProvisionResourceRequest) (resp provisioners.ProvisionResourceResponse) { 4795 val := req.Config.GetAttr("command") 4796 if val.IsNull() { 4797 t.Fatalf("bad value for foo: %#v", val) 4798 } 4799 4800 l.Lock() 4801 defer l.Unlock() 4802 calls = append(calls, val.AsString()) 4803 resp.Diagnostics = resp.Diagnostics.Append(fmt.Errorf("provisioner error")) 4804 return 4805 } 4806 4807 state := states.NewState() 4808 root := state.RootModule() 4809 root.SetResourceInstanceCurrent( 4810 mustResourceInstanceAddr(`aws_instance.foo["a"]`).Resource, 4811 &states.ResourceInstanceObjectSrc{ 4812 Status: states.ObjectReady, 4813 AttrsJSON: []byte(`{"id":"bar"}`), 4814 }, 4815 mustProviderConfig(`provider["registry.terraform.io/hashicorp/aws"]`), 4816 ) 4817 4818 ctx := testContext2(t, &ContextOpts{ 4819 Providers: map[addrs.Provider]providers.Factory{ 4820 addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p), 4821 }, 4822 Provisioners: map[string]provisioners.Factory{ 4823 "shell": testProvisionerFuncFixed(pr), 4824 }, 4825 }) 4826 4827 plan, diags := ctx.Plan(m, state, &PlanOpts{ 4828 Mode: plans.DestroyMode, 4829 }) 4830 assertNoErrors(t, diags) 4831 4832 state, diags = ctx.Apply(plan, m) 4833 if diags.HasErrors() { 4834 t.Fatalf("diags: %s", diags.Err()) 4835 } 4836 4837 checkStateString(t, state, `<no state>`) 4838 4839 // Verify apply was invoked 4840 if !pr.ProvisionResourceCalled { 4841 t.Fatalf("provisioner not invoked") 4842 } 4843 4844 expected := []string{"one", "two"} 4845 if !reflect.DeepEqual(calls, expected) { 4846 t.Fatalf("wrong commands\ngot: %#v\nwant: %#v", calls, expected) 4847 } 4848 } 4849 4850 // Verify that on destroy provisioner failure with "continue" that 4851 // we continue to the next provisioner. But if the next provisioner defines 4852 // to fail, then we fail after running it. 4853 func TestContext2Apply_provisionerDestroyFailContinueFail(t *testing.T) { 4854 m := testModule(t, "apply-provisioner-destroy-fail") 4855 p := testProvider("aws") 4856 pr := testProvisioner() 4857 p.PlanResourceChangeFn = testDiffFn 4858 4859 var l sync.Mutex 4860 var calls []string 4861 pr.ProvisionResourceFn = func(req provisioners.ProvisionResourceRequest) (resp provisioners.ProvisionResourceResponse) { 4862 val := req.Config.GetAttr("command") 4863 if val.IsNull() { 4864 t.Fatalf("bad value for foo: %#v", val) 4865 } 4866 4867 l.Lock() 4868 defer l.Unlock() 4869 calls = append(calls, val.AsString()) 4870 resp.Diagnostics = resp.Diagnostics.Append(fmt.Errorf("provisioner error")) 4871 return 4872 } 4873 4874 state := states.NewState() 4875 root := state.EnsureModule(addrs.RootModuleInstance) 4876 root.SetResourceInstanceCurrent( 4877 mustResourceInstanceAddr("aws_instance.foo").Resource, 4878 &states.ResourceInstanceObjectSrc{ 4879 Status: states.ObjectReady, 4880 AttrsJSON: []byte(`{"id":"bar"}`), 4881 }, 4882 mustProviderConfig(`provider["registry.terraform.io/hashicorp/aws"]`), 4883 ) 4884 4885 ctx := testContext2(t, &ContextOpts{ 4886 Providers: map[addrs.Provider]providers.Factory{ 4887 addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p), 4888 }, 4889 Provisioners: map[string]provisioners.Factory{ 4890 "shell": testProvisionerFuncFixed(pr), 4891 }, 4892 }) 4893 4894 plan, diags := ctx.Plan(m, state, &PlanOpts{ 4895 Mode: plans.DestroyMode, 4896 }) 4897 assertNoErrors(t, diags) 4898 4899 state, diags = ctx.Apply(plan, m) 4900 if diags == nil { 4901 t.Fatal("apply succeeded; wanted error from second provisioner") 4902 } 4903 4904 checkStateString(t, state, ` 4905 aws_instance.foo: 4906 ID = bar 4907 provider = provider["registry.terraform.io/hashicorp/aws"] 4908 `) 4909 4910 // Verify apply was invoked 4911 if !pr.ProvisionResourceCalled { 4912 t.Fatalf("provisioner not invoked") 4913 } 4914 4915 expected := []string{"one", "two"} 4916 if !reflect.DeepEqual(calls, expected) { 4917 t.Fatalf("bad: %#v", calls) 4918 } 4919 } 4920 4921 // Verify destroy provisioners are not run for tainted instances. 4922 func TestContext2Apply_provisionerDestroyTainted(t *testing.T) { 4923 m := testModule(t, "apply-provisioner-destroy") 4924 p := testProvider("aws") 4925 pr := testProvisioner() 4926 p.PlanResourceChangeFn = testDiffFn 4927 p.ApplyResourceChangeFn = testApplyFn 4928 4929 destroyCalled := false 4930 pr.ProvisionResourceFn = func(req provisioners.ProvisionResourceRequest) (resp provisioners.ProvisionResourceResponse) { 4931 expected := "create a b" 4932 val := req.Config.GetAttr("command") 4933 if val.AsString() != expected { 4934 t.Fatalf("bad value for command: %#v", val) 4935 } 4936 4937 return 4938 } 4939 4940 state := states.NewState() 4941 root := state.RootModule() 4942 root.SetResourceInstanceCurrent( 4943 mustResourceInstanceAddr(`aws_instance.foo["a"]`).Resource, 4944 &states.ResourceInstanceObjectSrc{ 4945 Status: states.ObjectTainted, 4946 AttrsJSON: []byte(`{"id":"bar"}`), 4947 }, 4948 mustProviderConfig(`provider["registry.terraform.io/hashicorp/aws"]`), 4949 ) 4950 4951 ctx := testContext2(t, &ContextOpts{ 4952 Providers: map[addrs.Provider]providers.Factory{ 4953 addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p), 4954 }, 4955 Provisioners: map[string]provisioners.Factory{ 4956 "shell": testProvisionerFuncFixed(pr), 4957 }, 4958 }) 4959 4960 plan, diags := ctx.Plan(m, state, &PlanOpts{ 4961 Mode: plans.NormalMode, 4962 SetVariables: InputValues{ 4963 "input": &InputValue{ 4964 Value: cty.MapVal(map[string]cty.Value{ 4965 "a": cty.StringVal("b"), 4966 }), 4967 SourceType: ValueFromInput, 4968 }, 4969 }, 4970 }) 4971 assertNoErrors(t, diags) 4972 4973 state, diags = ctx.Apply(plan, m) 4974 if diags.HasErrors() { 4975 t.Fatalf("diags: %s", diags.Err()) 4976 } 4977 4978 checkStateString(t, state, ` 4979 aws_instance.foo["a"]: 4980 ID = foo 4981 provider = provider["registry.terraform.io/hashicorp/aws"] 4982 foo = bar 4983 type = aws_instance 4984 `) 4985 4986 // Verify apply was invoked 4987 if !pr.ProvisionResourceCalled { 4988 t.Fatalf("provisioner not invoked") 4989 } 4990 4991 if destroyCalled { 4992 t.Fatal("destroy should not be called") 4993 } 4994 } 4995 4996 func TestContext2Apply_provisionerResourceRef(t *testing.T) { 4997 m := testModule(t, "apply-provisioner-resource-ref") 4998 p := testProvider("aws") 4999 p.PlanResourceChangeFn = testDiffFn 5000 p.ApplyResourceChangeFn = testApplyFn 5001 5002 pr := testProvisioner() 5003 pr.ProvisionResourceFn = func(req provisioners.ProvisionResourceRequest) (resp provisioners.ProvisionResourceResponse) { 5004 val := req.Config.GetAttr("command") 5005 if val.AsString() != "2" { 5006 t.Fatalf("bad value for command: %#v", val) 5007 } 5008 5009 return 5010 } 5011 5012 ctx := testContext2(t, &ContextOpts{ 5013 Providers: map[addrs.Provider]providers.Factory{ 5014 addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p), 5015 }, 5016 Provisioners: map[string]provisioners.Factory{ 5017 "shell": testProvisionerFuncFixed(pr), 5018 }, 5019 }) 5020 5021 plan, diags := ctx.Plan(m, states.NewState(), DefaultPlanOpts) 5022 assertNoErrors(t, diags) 5023 5024 state, diags := ctx.Apply(plan, m) 5025 if diags.HasErrors() { 5026 t.Fatalf("diags: %s", diags.Err()) 5027 } 5028 5029 actual := strings.TrimSpace(state.String()) 5030 expected := strings.TrimSpace(testTerraformApplyProvisionerResourceRefStr) 5031 if actual != expected { 5032 t.Fatalf("wrong result\n\ngot:\n%s\n\nwant:\n%s", actual, expected) 5033 } 5034 5035 // Verify apply was invoked 5036 if !pr.ProvisionResourceCalled { 5037 t.Fatalf("provisioner not invoked") 5038 } 5039 } 5040 5041 func TestContext2Apply_provisionerSelfRef(t *testing.T) { 5042 m := testModule(t, "apply-provisioner-self-ref") 5043 p := testProvider("aws") 5044 pr := testProvisioner() 5045 p.PlanResourceChangeFn = testDiffFn 5046 p.ApplyResourceChangeFn = testApplyFn 5047 pr.ProvisionResourceFn = func(req provisioners.ProvisionResourceRequest) (resp provisioners.ProvisionResourceResponse) { 5048 val := req.Config.GetAttr("command") 5049 if val.AsString() != "bar" { 5050 t.Fatalf("bad value for command: %#v", val) 5051 } 5052 5053 return 5054 } 5055 5056 ctx := testContext2(t, &ContextOpts{ 5057 Providers: map[addrs.Provider]providers.Factory{ 5058 addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p), 5059 }, 5060 Provisioners: map[string]provisioners.Factory{ 5061 "shell": testProvisionerFuncFixed(pr), 5062 }, 5063 }) 5064 5065 plan, diags := ctx.Plan(m, states.NewState(), DefaultPlanOpts) 5066 assertNoErrors(t, diags) 5067 5068 state, diags := ctx.Apply(plan, m) 5069 if diags.HasErrors() { 5070 t.Fatalf("diags: %s", diags.Err()) 5071 } 5072 5073 actual := strings.TrimSpace(state.String()) 5074 expected := strings.TrimSpace(testTerraformApplyProvisionerSelfRefStr) 5075 if actual != expected { 5076 t.Fatalf("wrong result\n\ngot:\n%s\n\nwant:\n%s", actual, expected) 5077 } 5078 5079 // Verify apply was invoked 5080 if !pr.ProvisionResourceCalled { 5081 t.Fatalf("provisioner not invoked") 5082 } 5083 } 5084 5085 func TestContext2Apply_provisionerMultiSelfRef(t *testing.T) { 5086 var lock sync.Mutex 5087 commands := make([]string, 0, 5) 5088 5089 m := testModule(t, "apply-provisioner-multi-self-ref") 5090 p := testProvider("aws") 5091 pr := testProvisioner() 5092 p.PlanResourceChangeFn = testDiffFn 5093 p.ApplyResourceChangeFn = testApplyFn 5094 pr.ProvisionResourceFn = func(req provisioners.ProvisionResourceRequest) (resp provisioners.ProvisionResourceResponse) { 5095 lock.Lock() 5096 defer lock.Unlock() 5097 5098 val := req.Config.GetAttr("command") 5099 if val.IsNull() { 5100 t.Fatalf("bad value for command: %#v", val) 5101 } 5102 5103 commands = append(commands, val.AsString()) 5104 return 5105 } 5106 5107 ctx := testContext2(t, &ContextOpts{ 5108 Providers: map[addrs.Provider]providers.Factory{ 5109 addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p), 5110 }, 5111 Provisioners: map[string]provisioners.Factory{ 5112 "shell": testProvisionerFuncFixed(pr), 5113 }, 5114 }) 5115 5116 plan, diags := ctx.Plan(m, states.NewState(), DefaultPlanOpts) 5117 assertNoErrors(t, diags) 5118 5119 state, diags := ctx.Apply(plan, m) 5120 if diags.HasErrors() { 5121 t.Fatalf("diags: %s", diags.Err()) 5122 } 5123 5124 actual := strings.TrimSpace(state.String()) 5125 expected := strings.TrimSpace(testTerraformApplyProvisionerMultiSelfRefStr) 5126 if actual != expected { 5127 t.Fatalf("wrong result\n\ngot:\n%s\n\nwant:\n%s", actual, expected) 5128 } 5129 5130 // Verify apply was invoked 5131 if !pr.ProvisionResourceCalled { 5132 t.Fatalf("provisioner not invoked") 5133 } 5134 5135 // Verify our result 5136 sort.Strings(commands) 5137 expectedCommands := []string{"number 0", "number 1", "number 2"} 5138 if !reflect.DeepEqual(commands, expectedCommands) { 5139 t.Fatalf("bad: %#v", commands) 5140 } 5141 } 5142 5143 func TestContext2Apply_provisionerMultiSelfRefSingle(t *testing.T) { 5144 var lock sync.Mutex 5145 order := make([]string, 0, 5) 5146 5147 m := testModule(t, "apply-provisioner-multi-self-ref-single") 5148 p := testProvider("aws") 5149 pr := testProvisioner() 5150 p.PlanResourceChangeFn = testDiffFn 5151 p.ApplyResourceChangeFn = testApplyFn 5152 pr.ProvisionResourceFn = func(req provisioners.ProvisionResourceRequest) (resp provisioners.ProvisionResourceResponse) { 5153 lock.Lock() 5154 defer lock.Unlock() 5155 5156 val := req.Config.GetAttr("order") 5157 if val.IsNull() { 5158 t.Fatalf("no val for order") 5159 } 5160 5161 order = append(order, val.AsString()) 5162 return 5163 } 5164 5165 ctx := testContext2(t, &ContextOpts{ 5166 Providers: map[addrs.Provider]providers.Factory{ 5167 addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p), 5168 }, 5169 Provisioners: map[string]provisioners.Factory{ 5170 "shell": testProvisionerFuncFixed(pr), 5171 }, 5172 }) 5173 5174 plan, diags := ctx.Plan(m, states.NewState(), DefaultPlanOpts) 5175 assertNoErrors(t, diags) 5176 5177 state, diags := ctx.Apply(plan, m) 5178 if diags.HasErrors() { 5179 t.Fatalf("diags: %s", diags.Err()) 5180 } 5181 5182 actual := strings.TrimSpace(state.String()) 5183 expected := strings.TrimSpace(testTerraformApplyProvisionerMultiSelfRefSingleStr) 5184 if actual != expected { 5185 t.Fatalf("wrong result\n\ngot:\n%s\n\nwant:\n%s", actual, expected) 5186 } 5187 5188 // Verify apply was invoked 5189 if !pr.ProvisionResourceCalled { 5190 t.Fatalf("provisioner not invoked") 5191 } 5192 5193 // Verify our result 5194 sort.Strings(order) 5195 expectedOrder := []string{"0", "1", "2"} 5196 if !reflect.DeepEqual(order, expectedOrder) { 5197 t.Fatalf("bad: %#v", order) 5198 } 5199 } 5200 5201 func TestContext2Apply_provisionerExplicitSelfRef(t *testing.T) { 5202 m := testModule(t, "apply-provisioner-explicit-self-ref") 5203 p := testProvider("aws") 5204 pr := testProvisioner() 5205 p.PlanResourceChangeFn = testDiffFn 5206 pr.ProvisionResourceFn = func(req provisioners.ProvisionResourceRequest) (resp provisioners.ProvisionResourceResponse) { 5207 val := req.Config.GetAttr("command") 5208 if val.IsNull() || val.AsString() != "bar" { 5209 t.Fatalf("bad value for command: %#v", val) 5210 } 5211 5212 return 5213 } 5214 5215 var state *states.State 5216 { 5217 ctx := testContext2(t, &ContextOpts{ 5218 Providers: map[addrs.Provider]providers.Factory{ 5219 addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p), 5220 }, 5221 Provisioners: map[string]provisioners.Factory{ 5222 "shell": testProvisionerFuncFixed(pr), 5223 }, 5224 }) 5225 5226 plan, diags := ctx.Plan(m, states.NewState(), DefaultPlanOpts) 5227 if diags.HasErrors() { 5228 t.Fatalf("diags: %s", diags.Err()) 5229 } 5230 5231 state, diags = ctx.Apply(plan, m) 5232 if diags.HasErrors() { 5233 t.Fatalf("diags: %s", diags.Err()) 5234 } 5235 5236 // Verify apply was invoked 5237 if !pr.ProvisionResourceCalled { 5238 t.Fatalf("provisioner not invoked") 5239 } 5240 } 5241 5242 { 5243 ctx := testContext2(t, &ContextOpts{ 5244 Providers: map[addrs.Provider]providers.Factory{ 5245 addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p), 5246 }, 5247 Provisioners: map[string]provisioners.Factory{ 5248 "shell": testProvisionerFuncFixed(pr), 5249 }, 5250 }) 5251 5252 plan, diags := ctx.Plan(m, state, &PlanOpts{ 5253 Mode: plans.DestroyMode, 5254 }) 5255 if diags.HasErrors() { 5256 t.Fatalf("diags: %s", diags.Err()) 5257 } 5258 5259 state, diags = ctx.Apply(plan, m) 5260 if diags.HasErrors() { 5261 t.Fatalf("diags: %s", diags.Err()) 5262 } 5263 5264 checkStateString(t, state, `<no state>`) 5265 } 5266 } 5267 5268 func TestContext2Apply_provisionerForEachSelfRef(t *testing.T) { 5269 m := testModule(t, "apply-provisioner-for-each-self") 5270 p := testProvider("aws") 5271 pr := testProvisioner() 5272 p.PlanResourceChangeFn = testDiffFn 5273 5274 pr.ProvisionResourceFn = func(req provisioners.ProvisionResourceRequest) (resp provisioners.ProvisionResourceResponse) { 5275 val := req.Config.GetAttr("command") 5276 if val.IsNull() { 5277 t.Fatalf("bad value for command: %#v", val) 5278 } 5279 5280 return resp 5281 } 5282 5283 ctx := testContext2(t, &ContextOpts{ 5284 Providers: map[addrs.Provider]providers.Factory{ 5285 addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p), 5286 }, 5287 Provisioners: map[string]provisioners.Factory{ 5288 "shell": testProvisionerFuncFixed(pr), 5289 }, 5290 }) 5291 5292 plan, diags := ctx.Plan(m, states.NewState(), DefaultPlanOpts) 5293 assertNoErrors(t, diags) 5294 5295 _, diags = ctx.Apply(plan, m) 5296 if diags.HasErrors() { 5297 t.Fatalf("diags: %s", diags.Err()) 5298 } 5299 } 5300 5301 // Provisioner should NOT run on a diff, only create 5302 func TestContext2Apply_Provisioner_Diff(t *testing.T) { 5303 m := testModule(t, "apply-provisioner-diff") 5304 p := testProvider("aws") 5305 pr := testProvisioner() 5306 p.PlanResourceChangeFn = testDiffFn 5307 p.ApplyResourceChangeFn = testApplyFn 5308 ctx := testContext2(t, &ContextOpts{ 5309 Providers: map[addrs.Provider]providers.Factory{ 5310 addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p), 5311 }, 5312 Provisioners: map[string]provisioners.Factory{ 5313 "shell": testProvisionerFuncFixed(pr), 5314 }, 5315 }) 5316 5317 plan, diags := ctx.Plan(m, states.NewState(), DefaultPlanOpts) 5318 assertNoErrors(t, diags) 5319 5320 state, diags := ctx.Apply(plan, m) 5321 if diags.HasErrors() { 5322 logDiagnostics(t, diags) 5323 t.Fatal("apply failed") 5324 } 5325 5326 actual := strings.TrimSpace(state.String()) 5327 expected := strings.TrimSpace(testTerraformApplyProvisionerDiffStr) 5328 if actual != expected { 5329 t.Fatalf("wrong result\n\ngot:\n%s\n\nwant:\n%s", actual, expected) 5330 } 5331 5332 // Verify apply was invoked 5333 if !pr.ProvisionResourceCalled { 5334 t.Fatalf("provisioner was not called on first apply") 5335 } 5336 pr.ProvisionResourceCalled = false 5337 5338 // Change the state to force a diff 5339 mod := state.RootModule() 5340 obj := mod.Resources["aws_instance.bar"].Instances[addrs.NoKey].Current 5341 var attrs map[string]interface{} 5342 err := json.Unmarshal(obj.AttrsJSON, &attrs) 5343 if err != nil { 5344 t.Fatal(err) 5345 } 5346 attrs["foo"] = "baz" 5347 obj.AttrsJSON, err = json.Marshal(attrs) 5348 if err != nil { 5349 t.Fatal(err) 5350 } 5351 5352 // Re-create context with state 5353 ctx = testContext2(t, &ContextOpts{ 5354 Providers: map[addrs.Provider]providers.Factory{ 5355 addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p), 5356 }, 5357 Provisioners: map[string]provisioners.Factory{ 5358 "shell": testProvisionerFuncFixed(pr), 5359 }, 5360 }) 5361 5362 plan, diags = ctx.Plan(m, state, DefaultPlanOpts) 5363 assertNoErrors(t, diags) 5364 5365 state2, diags := ctx.Apply(plan, m) 5366 if diags.HasErrors() { 5367 logDiagnostics(t, diags) 5368 t.Fatal("apply failed") 5369 } 5370 5371 actual = strings.TrimSpace(state2.String()) 5372 if actual != expected { 5373 t.Fatalf("wrong result\n\ngot:\n%s\n\nwant:\n%s", actual, expected) 5374 } 5375 5376 // Verify apply was NOT invoked 5377 if pr.ProvisionResourceCalled { 5378 t.Fatalf("provisioner was called on second apply; should not have been") 5379 } 5380 } 5381 5382 func TestContext2Apply_outputDiffVars(t *testing.T) { 5383 m := testModule(t, "apply-good") 5384 p := testProvider("aws") 5385 5386 state := states.NewState() 5387 root := state.EnsureModule(addrs.RootModuleInstance) 5388 root.SetResourceInstanceCurrent( 5389 mustResourceInstanceAddr("aws_instance.baz").Resource, 5390 &states.ResourceInstanceObjectSrc{ 5391 Status: states.ObjectReady, 5392 AttrsJSON: []byte(`{"id":"bar"}`), 5393 }, 5394 mustProviderConfig(`provider["registry.terraform.io/hashicorp/aws"]`), 5395 ) 5396 5397 ctx := testContext2(t, &ContextOpts{ 5398 Providers: map[addrs.Provider]providers.Factory{ 5399 addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p), 5400 }, 5401 }) 5402 5403 p.PlanResourceChangeFn = testDiffFn 5404 //func(info *InstanceInfo, s *InstanceState, rc *ResourceConfig) (*InstanceDiff, error) { 5405 // d := &InstanceDiff{ 5406 // Attributes: map[string]*ResourceAttrDiff{}, 5407 // } 5408 // if new, ok := rc.Get("value"); ok { 5409 // d.Attributes["value"] = &ResourceAttrDiff{ 5410 // New: new.(string), 5411 // } 5412 // } 5413 // if new, ok := rc.Get("foo"); ok { 5414 // d.Attributes["foo"] = &ResourceAttrDiff{ 5415 // New: new.(string), 5416 // } 5417 // } else if rc.IsComputed("foo") { 5418 // d.Attributes["foo"] = &ResourceAttrDiff{ 5419 // NewComputed: true, 5420 // Type: DiffAttrOutput, // This doesn't actually really do anything anymore, but this test originally set it. 5421 // } 5422 // } 5423 // if new, ok := rc.Get("num"); ok { 5424 // d.Attributes["num"] = &ResourceAttrDiff{ 5425 // New: fmt.Sprintf("%#v", new), 5426 // } 5427 // } 5428 // return d, nil 5429 //} 5430 5431 plan, diags := ctx.Plan(m, state, DefaultPlanOpts) 5432 assertNoErrors(t, diags) 5433 5434 _, diags = ctx.Apply(plan, m) 5435 assertNoErrors(t, diags) 5436 } 5437 5438 func TestContext2Apply_destroyX(t *testing.T) { 5439 m := testModule(t, "apply-destroy") 5440 h := new(HookRecordApplyOrder) 5441 p := testProvider("aws") 5442 p.PlanResourceChangeFn = testDiffFn 5443 ctx := testContext2(t, &ContextOpts{ 5444 Hooks: []Hook{h}, 5445 Providers: map[addrs.Provider]providers.Factory{ 5446 addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p), 5447 }, 5448 }) 5449 5450 // First plan and apply a create operation 5451 plan, diags := ctx.Plan(m, states.NewState(), DefaultPlanOpts) 5452 assertNoErrors(t, diags) 5453 5454 state, diags := ctx.Apply(plan, m) 5455 if diags.HasErrors() { 5456 t.Fatalf("diags: %s", diags.Err()) 5457 } 5458 5459 // Next, plan and apply a destroy operation 5460 h.Active = true 5461 ctx = testContext2(t, &ContextOpts{ 5462 Hooks: []Hook{h}, 5463 Providers: map[addrs.Provider]providers.Factory{ 5464 addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p), 5465 }, 5466 }) 5467 5468 plan, diags = ctx.Plan(m, state, &PlanOpts{ 5469 Mode: plans.DestroyMode, 5470 }) 5471 assertNoErrors(t, diags) 5472 5473 state, diags = ctx.Apply(plan, m) 5474 if diags.HasErrors() { 5475 t.Fatalf("diags: %s", diags.Err()) 5476 } 5477 5478 // Test that things were destroyed 5479 actual := strings.TrimSpace(state.String()) 5480 expected := strings.TrimSpace(testTerraformApplyDestroyStr) 5481 if actual != expected { 5482 t.Fatalf("wrong result\n\ngot:\n%s\n\nwant:\n%s", actual, expected) 5483 } 5484 5485 // Test that things were destroyed _in the right order_ 5486 expected2 := []string{"aws_instance.bar", "aws_instance.foo"} 5487 actual2 := h.IDs 5488 if !reflect.DeepEqual(actual2, expected2) { 5489 t.Fatalf("expected: %#v\n\ngot:%#v", expected2, actual2) 5490 } 5491 } 5492 5493 func TestContext2Apply_destroyOrder(t *testing.T) { 5494 m := testModule(t, "apply-destroy") 5495 h := new(HookRecordApplyOrder) 5496 p := testProvider("aws") 5497 p.PlanResourceChangeFn = testDiffFn 5498 ctx := testContext2(t, &ContextOpts{ 5499 Hooks: []Hook{h}, 5500 Providers: map[addrs.Provider]providers.Factory{ 5501 addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p), 5502 }, 5503 }) 5504 5505 // First plan and apply a create operation 5506 plan, diags := ctx.Plan(m, states.NewState(), DefaultPlanOpts) 5507 assertNoErrors(t, diags) 5508 5509 state, diags := ctx.Apply(plan, m) 5510 if diags.HasErrors() { 5511 t.Fatalf("diags: %s", diags.Err()) 5512 } 5513 5514 t.Logf("State 1: %s", state) 5515 5516 // Next, plan and apply a destroy 5517 h.Active = true 5518 ctx = testContext2(t, &ContextOpts{ 5519 Hooks: []Hook{h}, 5520 Providers: map[addrs.Provider]providers.Factory{ 5521 addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p), 5522 }, 5523 }) 5524 5525 plan, diags = ctx.Plan(m, state, &PlanOpts{ 5526 Mode: plans.DestroyMode, 5527 }) 5528 assertNoErrors(t, diags) 5529 5530 state, diags = ctx.Apply(plan, m) 5531 if diags.HasErrors() { 5532 t.Fatalf("diags: %s", diags.Err()) 5533 } 5534 5535 // Test that things were destroyed 5536 actual := strings.TrimSpace(state.String()) 5537 expected := strings.TrimSpace(testTerraformApplyDestroyStr) 5538 if actual != expected { 5539 t.Fatalf("wrong result\n\ngot:\n%s\n\nwant:\n%s", actual, expected) 5540 } 5541 5542 // Test that things were destroyed _in the right order_ 5543 expected2 := []string{"aws_instance.bar", "aws_instance.foo"} 5544 actual2 := h.IDs 5545 if !reflect.DeepEqual(actual2, expected2) { 5546 t.Fatalf("expected: %#v\n\ngot:%#v", expected2, actual2) 5547 } 5548 } 5549 5550 // https://github.com/hashicorp/terraform/issues/2767 5551 func TestContext2Apply_destroyModulePrefix(t *testing.T) { 5552 m := testModule(t, "apply-destroy-module-resource-prefix") 5553 h := new(MockHook) 5554 p := testProvider("aws") 5555 p.PlanResourceChangeFn = testDiffFn 5556 ctx := testContext2(t, &ContextOpts{ 5557 Hooks: []Hook{h}, 5558 Providers: map[addrs.Provider]providers.Factory{ 5559 addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p), 5560 }, 5561 }) 5562 5563 // First plan and apply a create operation 5564 plan, diags := ctx.Plan(m, states.NewState(), DefaultPlanOpts) 5565 assertNoErrors(t, diags) 5566 5567 state, diags := ctx.Apply(plan, m) 5568 if diags.HasErrors() { 5569 t.Fatalf("diags: %s", diags.Err()) 5570 } 5571 5572 // Verify that we got the apply info correct 5573 if v := h.PreApplyAddr.String(); v != "module.child.aws_instance.foo" { 5574 t.Fatalf("bad: %s", v) 5575 } 5576 5577 // Next, plan and apply a destroy operation and reset the hook 5578 h = new(MockHook) 5579 ctx = testContext2(t, &ContextOpts{ 5580 Hooks: []Hook{h}, 5581 Providers: map[addrs.Provider]providers.Factory{ 5582 addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p), 5583 }, 5584 }) 5585 5586 plan, diags = ctx.Plan(m, state, &PlanOpts{ 5587 Mode: plans.DestroyMode, 5588 }) 5589 assertNoErrors(t, diags) 5590 5591 _, diags = ctx.Apply(plan, m) 5592 if diags.HasErrors() { 5593 t.Fatalf("diags: %s", diags.Err()) 5594 } 5595 5596 // Test that things were destroyed 5597 if v := h.PreApplyAddr.String(); v != "module.child.aws_instance.foo" { 5598 t.Fatalf("bad: %s", v) 5599 } 5600 } 5601 5602 func TestContext2Apply_destroyNestedModule(t *testing.T) { 5603 m := testModule(t, "apply-destroy-nested-module") 5604 p := testProvider("aws") 5605 p.PlanResourceChangeFn = testDiffFn 5606 5607 state := states.NewState() 5608 root := state.EnsureModule(addrs.RootModuleInstance) 5609 root.SetResourceInstanceCurrent( 5610 mustResourceInstanceAddr("aws_instance.bar").Resource, 5611 &states.ResourceInstanceObjectSrc{ 5612 Status: states.ObjectReady, 5613 AttrsJSON: []byte(`{"id":"bar"}`), 5614 }, 5615 mustProviderConfig(`provider["registry.terraform.io/hashicorp/aws"]`), 5616 ) 5617 5618 ctx := testContext2(t, &ContextOpts{ 5619 Providers: map[addrs.Provider]providers.Factory{ 5620 addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p), 5621 }, 5622 }) 5623 5624 // First plan and apply a create operation 5625 plan, diags := ctx.Plan(m, state, DefaultPlanOpts) 5626 assertNoErrors(t, diags) 5627 5628 s, diags := ctx.Apply(plan, m) 5629 if diags.HasErrors() { 5630 t.Fatalf("diags: %s", diags.Err()) 5631 } 5632 5633 // Test that things were destroyed 5634 actual := strings.TrimSpace(s.String()) 5635 if actual != "<no state>" { 5636 t.Fatalf("expected no state, got: %s", actual) 5637 } 5638 } 5639 5640 func TestContext2Apply_destroyDeeplyNestedModule(t *testing.T) { 5641 m := testModule(t, "apply-destroy-deeply-nested-module") 5642 p := testProvider("aws") 5643 p.PlanResourceChangeFn = testDiffFn 5644 5645 state := states.NewState() 5646 root := state.EnsureModule(addrs.RootModuleInstance) 5647 root.SetResourceInstanceCurrent( 5648 mustResourceInstanceAddr("aws_instance.bar").Resource, 5649 &states.ResourceInstanceObjectSrc{ 5650 Status: states.ObjectReady, 5651 AttrsJSON: []byte(`{"id":"bar"}`), 5652 }, 5653 mustProviderConfig(`provider["registry.terraform.io/hashicorp/aws"]`), 5654 ) 5655 5656 ctx := testContext2(t, &ContextOpts{ 5657 Providers: map[addrs.Provider]providers.Factory{ 5658 addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p), 5659 }, 5660 }) 5661 5662 // First plan and apply a create operation 5663 plan, diags := ctx.Plan(m, state, DefaultPlanOpts) 5664 assertNoErrors(t, diags) 5665 5666 s, diags := ctx.Apply(plan, m) 5667 if diags.HasErrors() { 5668 t.Fatalf("diags: %s", diags.Err()) 5669 } 5670 5671 // Test that things were destroyed 5672 if !s.Empty() { 5673 t.Fatalf("wrong final state %s\nwant empty state", spew.Sdump(s)) 5674 } 5675 } 5676 5677 // https://github.com/hashicorp/terraform/issues/5440 5678 func TestContext2Apply_destroyModuleWithAttrsReferencingResource(t *testing.T) { 5679 m, snap := testModuleWithSnapshot(t, "apply-destroy-module-with-attrs") 5680 p := testProvider("aws") 5681 p.PlanResourceChangeFn = testDiffFn 5682 5683 var state *states.State 5684 { 5685 ctx := testContext2(t, &ContextOpts{ 5686 Providers: map[addrs.Provider]providers.Factory{ 5687 addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p), 5688 }, 5689 }) 5690 5691 // First plan and apply a create operation 5692 plan, diags := ctx.Plan(m, states.NewState(), DefaultPlanOpts) 5693 if diags.HasErrors() { 5694 t.Fatalf("plan diags: %s", diags.Err()) 5695 } else { 5696 t.Logf("Step 1 plan: %s", legacyDiffComparisonString(plan.Changes)) 5697 } 5698 5699 state, diags = ctx.Apply(plan, m) 5700 if diags.HasErrors() { 5701 t.Fatalf("apply errs: %s", diags.Err()) 5702 } 5703 5704 t.Logf("Step 1 state: %s", state) 5705 } 5706 5707 h := new(HookRecordApplyOrder) 5708 h.Active = true 5709 5710 { 5711 ctx := testContext2(t, &ContextOpts{ 5712 Hooks: []Hook{h}, 5713 Providers: map[addrs.Provider]providers.Factory{ 5714 addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p), 5715 }, 5716 }) 5717 5718 // First plan and apply a create operation 5719 plan, diags := ctx.Plan(m, state, &PlanOpts{ 5720 Mode: plans.DestroyMode, 5721 }) 5722 if diags.HasErrors() { 5723 t.Fatalf("destroy plan err: %s", diags.Err()) 5724 } 5725 5726 t.Logf("Step 2 plan: %s", legacyDiffComparisonString(plan.Changes)) 5727 5728 ctxOpts, m, plan, err := contextOptsForPlanViaFile(t, snap, plan) 5729 if err != nil { 5730 t.Fatalf("failed to round-trip through planfile: %s", err) 5731 } 5732 5733 ctxOpts.Providers = map[addrs.Provider]providers.Factory{ 5734 addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p), 5735 } 5736 5737 ctx, diags = NewContext(ctxOpts) 5738 if diags.HasErrors() { 5739 t.Fatalf("err: %s", diags.Err()) 5740 } 5741 5742 state, diags = ctx.Apply(plan, m) 5743 if diags.HasErrors() { 5744 t.Fatalf("destroy apply err: %s", diags.Err()) 5745 } 5746 5747 t.Logf("Step 2 state: %s", state) 5748 } 5749 5750 //Test that things were destroyed 5751 if state.HasManagedResourceInstanceObjects() { 5752 t.Fatal("expected empty state, got:", state) 5753 } 5754 } 5755 5756 func TestContext2Apply_destroyWithModuleVariableAndCount(t *testing.T) { 5757 m, snap := testModuleWithSnapshot(t, "apply-destroy-mod-var-and-count") 5758 p := testProvider("aws") 5759 p.PlanResourceChangeFn = testDiffFn 5760 5761 var state *states.State 5762 { 5763 ctx := testContext2(t, &ContextOpts{ 5764 Providers: map[addrs.Provider]providers.Factory{ 5765 addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p), 5766 }, 5767 }) 5768 5769 // First plan and apply a create operation 5770 plan, diags := ctx.Plan(m, states.NewState(), DefaultPlanOpts) 5771 assertNoErrors(t, diags) 5772 5773 state, diags = ctx.Apply(plan, m) 5774 if diags.HasErrors() { 5775 t.Fatalf("apply err: %s", diags.Err()) 5776 } 5777 } 5778 5779 h := new(HookRecordApplyOrder) 5780 h.Active = true 5781 5782 { 5783 ctx := testContext2(t, &ContextOpts{ 5784 Hooks: []Hook{h}, 5785 Providers: map[addrs.Provider]providers.Factory{ 5786 addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p), 5787 }, 5788 }) 5789 5790 // First plan and apply a create operation 5791 plan, diags := ctx.Plan(m, state, &PlanOpts{ 5792 Mode: plans.DestroyMode, 5793 }) 5794 if diags.HasErrors() { 5795 t.Fatalf("destroy plan err: %s", diags.Err()) 5796 } 5797 5798 ctxOpts, m, plan, err := contextOptsForPlanViaFile(t, snap, plan) 5799 if err != nil { 5800 t.Fatalf("failed to round-trip through planfile: %s", err) 5801 } 5802 5803 ctxOpts.Providers = 5804 map[addrs.Provider]providers.Factory{ 5805 addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p), 5806 } 5807 5808 ctx, diags = NewContext(ctxOpts) 5809 if diags.HasErrors() { 5810 t.Fatalf("err: %s", diags.Err()) 5811 } 5812 5813 state, diags = ctx.Apply(plan, m) 5814 if diags.HasErrors() { 5815 t.Fatalf("destroy apply err: %s", diags.Err()) 5816 } 5817 } 5818 5819 //Test that things were destroyed 5820 actual := strings.TrimSpace(state.String()) 5821 expected := strings.TrimSpace(` 5822 <no state>`) 5823 if actual != expected { 5824 t.Fatalf("expected: \n%s\n\nbad: \n%s", expected, actual) 5825 } 5826 } 5827 5828 func TestContext2Apply_destroyTargetWithModuleVariableAndCount(t *testing.T) { 5829 m := testModule(t, "apply-destroy-mod-var-and-count") 5830 p := testProvider("aws") 5831 p.PlanResourceChangeFn = testDiffFn 5832 5833 var state *states.State 5834 { 5835 ctx := testContext2(t, &ContextOpts{ 5836 Providers: map[addrs.Provider]providers.Factory{ 5837 addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p), 5838 }, 5839 }) 5840 5841 // First plan and apply a create operation 5842 plan, diags := ctx.Plan(m, states.NewState(), DefaultPlanOpts) 5843 assertNoErrors(t, diags) 5844 5845 state, diags = ctx.Apply(plan, m) 5846 if diags.HasErrors() { 5847 t.Fatalf("apply err: %s", diags.Err()) 5848 } 5849 } 5850 5851 { 5852 ctx := testContext2(t, &ContextOpts{ 5853 Providers: map[addrs.Provider]providers.Factory{ 5854 addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p), 5855 }, 5856 }) 5857 5858 plan, diags := ctx.Plan(m, state, &PlanOpts{ 5859 Mode: plans.DestroyMode, 5860 Targets: []addrs.Targetable{ 5861 addrs.RootModuleInstance.Child("child", addrs.NoKey), 5862 }, 5863 }) 5864 if diags.HasErrors() { 5865 t.Fatalf("plan err: %s", diags) 5866 } 5867 if len(diags) != 1 { 5868 // Should have one warning that -target is in effect. 5869 t.Fatalf("got %d diagnostics in plan; want 1", len(diags)) 5870 } 5871 if got, want := diags[0].Severity(), tfdiags.Warning; got != want { 5872 t.Errorf("wrong diagnostic severity %#v; want %#v", got, want) 5873 } 5874 if got, want := diags[0].Description().Summary, "Resource targeting is in effect"; got != want { 5875 t.Errorf("wrong diagnostic summary %#v; want %#v", got, want) 5876 } 5877 5878 // Destroy, targeting the module explicitly 5879 state, diags = ctx.Apply(plan, m) 5880 if diags.HasErrors() { 5881 t.Fatalf("destroy apply err: %s", diags) 5882 } 5883 if len(diags) != 1 { 5884 t.Fatalf("got %d diagnostics; want 1", len(diags)) 5885 } 5886 if got, want := diags[0].Severity(), tfdiags.Warning; got != want { 5887 t.Errorf("wrong diagnostic severity %#v; want %#v", got, want) 5888 } 5889 if got, want := diags[0].Description().Summary, "Applied changes may be incomplete"; got != want { 5890 t.Errorf("wrong diagnostic summary %#v; want %#v", got, want) 5891 } 5892 } 5893 5894 //Test that things were destroyed 5895 actual := strings.TrimSpace(state.String()) 5896 expected := strings.TrimSpace(`<no state>`) 5897 if actual != expected { 5898 t.Fatalf("expected: \n%s\n\nbad: \n%s", expected, actual) 5899 } 5900 } 5901 5902 func TestContext2Apply_destroyWithModuleVariableAndCountNested(t *testing.T) { 5903 m, snap := testModuleWithSnapshot(t, "apply-destroy-mod-var-and-count-nested") 5904 p := testProvider("aws") 5905 p.PlanResourceChangeFn = testDiffFn 5906 5907 var state *states.State 5908 { 5909 ctx := testContext2(t, &ContextOpts{ 5910 Providers: map[addrs.Provider]providers.Factory{ 5911 addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p), 5912 }, 5913 }) 5914 5915 // First plan and apply a create operation 5916 plan, diags := ctx.Plan(m, states.NewState(), SimplePlanOpts(plans.NormalMode, testInputValuesUnset(m.Module.Variables))) 5917 assertNoErrors(t, diags) 5918 5919 state, diags = ctx.Apply(plan, m) 5920 if diags.HasErrors() { 5921 t.Fatalf("apply err: %s", diags.Err()) 5922 } 5923 } 5924 5925 h := new(HookRecordApplyOrder) 5926 h.Active = true 5927 5928 { 5929 ctx := testContext2(t, &ContextOpts{ 5930 Hooks: []Hook{h}, 5931 Providers: map[addrs.Provider]providers.Factory{ 5932 addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p), 5933 }, 5934 }) 5935 5936 // First plan and apply a create operation 5937 plan, diags := ctx.Plan(m, state, SimplePlanOpts(plans.DestroyMode, testInputValuesUnset(m.Module.Variables))) 5938 if diags.HasErrors() { 5939 t.Fatalf("destroy plan err: %s", diags.Err()) 5940 } 5941 5942 ctxOpts, m, plan, err := contextOptsForPlanViaFile(t, snap, plan) 5943 if err != nil { 5944 t.Fatalf("failed to round-trip through planfile: %s", err) 5945 } 5946 5947 ctxOpts.Providers = 5948 map[addrs.Provider]providers.Factory{ 5949 addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p), 5950 } 5951 5952 ctx, diags = NewContext(ctxOpts) 5953 if diags.HasErrors() { 5954 t.Fatalf("err: %s", diags.Err()) 5955 } 5956 5957 state, diags = ctx.Apply(plan, m) 5958 if diags.HasErrors() { 5959 t.Fatalf("destroy apply err: %s", diags.Err()) 5960 } 5961 } 5962 5963 //Test that things were destroyed 5964 actual := strings.TrimSpace(state.String()) 5965 expected := strings.TrimSpace(` 5966 <no state>`) 5967 if actual != expected { 5968 t.Fatalf("expected: \n%s\n\nbad: \n%s", expected, actual) 5969 } 5970 } 5971 5972 func TestContext2Apply_destroyOutputs(t *testing.T) { 5973 m := testModule(t, "apply-destroy-outputs") 5974 p := testProvider("test") 5975 p.PlanResourceChangeFn = testDiffFn 5976 5977 p.ReadDataSourceFn = func(req providers.ReadDataSourceRequest) providers.ReadDataSourceResponse { 5978 // add the required id 5979 m := req.Config.AsValueMap() 5980 m["id"] = cty.StringVal("foo") 5981 5982 return providers.ReadDataSourceResponse{ 5983 State: cty.ObjectVal(m), 5984 } 5985 } 5986 5987 ctx := testContext2(t, &ContextOpts{ 5988 Providers: map[addrs.Provider]providers.Factory{ 5989 addrs.NewDefaultProvider("test"): testProviderFuncFixed(p), 5990 }, 5991 }) 5992 5993 // First plan and apply a create operation 5994 plan, diags := ctx.Plan(m, states.NewState(), DefaultPlanOpts) 5995 assertNoErrors(t, diags) 5996 5997 state, diags := ctx.Apply(plan, m) 5998 5999 if diags.HasErrors() { 6000 t.Fatalf("diags: %s", diags.Err()) 6001 } 6002 6003 // Next, plan and apply a destroy operation 6004 ctx = testContext2(t, &ContextOpts{ 6005 Providers: map[addrs.Provider]providers.Factory{ 6006 addrs.NewDefaultProvider("test"): testProviderFuncFixed(p), 6007 }, 6008 }) 6009 6010 plan, diags = ctx.Plan(m, state, &PlanOpts{ 6011 Mode: plans.DestroyMode, 6012 }) 6013 assertNoErrors(t, diags) 6014 6015 state, diags = ctx.Apply(plan, m) 6016 if diags.HasErrors() { 6017 t.Fatalf("diags: %s", diags.Err()) 6018 } 6019 6020 mod := state.RootModule() 6021 if len(mod.Resources) > 0 { 6022 t.Fatalf("expected no resources, got: %#v", mod) 6023 } 6024 6025 // destroying again should produce no errors 6026 ctx = testContext2(t, &ContextOpts{ 6027 Providers: map[addrs.Provider]providers.Factory{ 6028 addrs.NewDefaultProvider("test"): testProviderFuncFixed(p), 6029 }, 6030 }) 6031 plan, diags = ctx.Plan(m, state, &PlanOpts{ 6032 Mode: plans.DestroyMode, 6033 }) 6034 assertNoErrors(t, diags) 6035 6036 if _, diags := ctx.Apply(plan, m); diags.HasErrors() { 6037 t.Fatal(diags.Err()) 6038 } 6039 } 6040 6041 func TestContext2Apply_destroyOrphan(t *testing.T) { 6042 m := testModule(t, "apply-error") 6043 p := testProvider("aws") 6044 state := states.NewState() 6045 root := state.EnsureModule(addrs.RootModuleInstance) 6046 root.SetResourceInstanceCurrent( 6047 mustResourceInstanceAddr("aws_instance.baz").Resource, 6048 &states.ResourceInstanceObjectSrc{ 6049 Status: states.ObjectReady, 6050 AttrsJSON: []byte(`{"id":"bar"}`), 6051 }, 6052 mustProviderConfig(`provider["registry.terraform.io/hashicorp/aws"]`), 6053 ) 6054 ctx := testContext2(t, &ContextOpts{ 6055 Providers: map[addrs.Provider]providers.Factory{ 6056 addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p), 6057 }, 6058 }) 6059 6060 p.PlanResourceChangeFn = testDiffFn 6061 6062 plan, diags := ctx.Plan(m, state, DefaultPlanOpts) 6063 assertNoErrors(t, diags) 6064 6065 s, diags := ctx.Apply(plan, m) 6066 if diags.HasErrors() { 6067 t.Fatalf("diags: %s", diags.Err()) 6068 } 6069 6070 mod := s.RootModule() 6071 if _, ok := mod.Resources["aws_instance.baz"]; ok { 6072 t.Fatalf("bad: %#v", mod.Resources) 6073 } 6074 } 6075 6076 func TestContext2Apply_destroyTaintedProvisioner(t *testing.T) { 6077 m := testModule(t, "apply-destroy-provisioner") 6078 p := testProvider("aws") 6079 pr := testProvisioner() 6080 p.PlanResourceChangeFn = testDiffFn 6081 6082 state := states.NewState() 6083 root := state.EnsureModule(addrs.RootModuleInstance) 6084 root.SetResourceInstanceCurrent( 6085 mustResourceInstanceAddr("aws_instance.foo").Resource, 6086 &states.ResourceInstanceObjectSrc{ 6087 Status: states.ObjectReady, 6088 AttrsJSON: []byte(`{"id":"bar"}`), 6089 }, 6090 mustProviderConfig(`provider["registry.terraform.io/hashicorp/aws"]`), 6091 ) 6092 6093 ctx := testContext2(t, &ContextOpts{ 6094 Providers: map[addrs.Provider]providers.Factory{ 6095 addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p), 6096 }, 6097 Provisioners: map[string]provisioners.Factory{ 6098 "shell": testProvisionerFuncFixed(pr), 6099 }, 6100 }) 6101 6102 plan, diags := ctx.Plan(m, state, &PlanOpts{ 6103 Mode: plans.DestroyMode, 6104 }) 6105 assertNoErrors(t, diags) 6106 6107 s, diags := ctx.Apply(plan, m) 6108 if diags.HasErrors() { 6109 t.Fatalf("diags: %s", diags.Err()) 6110 } 6111 6112 if pr.ProvisionResourceCalled { 6113 t.Fatal("provisioner should not be called") 6114 } 6115 6116 actual := strings.TrimSpace(s.String()) 6117 expected := strings.TrimSpace("<no state>") 6118 if actual != expected { 6119 t.Fatalf("wrong result\n\ngot:\n%s\n\nwant:\n%s", actual, expected) 6120 } 6121 } 6122 6123 func TestContext2Apply_error(t *testing.T) { 6124 errored := false 6125 6126 m := testModule(t, "apply-error") 6127 p := testProvider("aws") 6128 ctx := testContext2(t, &ContextOpts{ 6129 Providers: map[addrs.Provider]providers.Factory{ 6130 addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p), 6131 }, 6132 }) 6133 6134 p.ApplyResourceChangeFn = func(req providers.ApplyResourceChangeRequest) (resp providers.ApplyResourceChangeResponse) { 6135 if errored { 6136 resp.NewState = req.PlannedState 6137 resp.Diagnostics = resp.Diagnostics.Append(fmt.Errorf("error")) 6138 return 6139 } 6140 errored = true 6141 6142 return testApplyFn(req) 6143 } 6144 p.PlanResourceChangeFn = testDiffFn 6145 6146 plan, diags := ctx.Plan(m, states.NewState(), DefaultPlanOpts) 6147 assertNoErrors(t, diags) 6148 6149 state, diags := ctx.Apply(plan, m) 6150 if diags == nil { 6151 t.Fatal("should have error") 6152 } 6153 6154 actual := strings.TrimSpace(state.String()) 6155 expected := strings.TrimSpace(testTerraformApplyErrorStr) 6156 if actual != expected { 6157 t.Fatalf("expected:\n%s\n\ngot:\n%s", expected, actual) 6158 } 6159 } 6160 6161 func TestContext2Apply_errorDestroy(t *testing.T) { 6162 m := testModule(t, "empty") 6163 p := testProvider("test") 6164 6165 p.GetProviderSchemaResponse = getProviderSchemaResponseFromProviderSchema(&ProviderSchema{ 6166 ResourceTypes: map[string]*configschema.Block{ 6167 "test_thing": { 6168 Attributes: map[string]*configschema.Attribute{ 6169 "id": {Type: cty.String, Optional: true}, 6170 }, 6171 }, 6172 }, 6173 }) 6174 p.PlanResourceChangeFn = func(req providers.PlanResourceChangeRequest) providers.PlanResourceChangeResponse { 6175 // Should actually be called for this test, because Terraform Core 6176 // constructs the plan for a destroy operation itself. 6177 return providers.PlanResourceChangeResponse{ 6178 PlannedState: req.ProposedNewState, 6179 } 6180 } 6181 p.ApplyResourceChangeFn = func(req providers.ApplyResourceChangeRequest) providers.ApplyResourceChangeResponse { 6182 // The apply (in this case, a destroy) always fails, so we can verify 6183 // that the object stays in the state after a destroy fails even though 6184 // we aren't returning a new state object here. 6185 return providers.ApplyResourceChangeResponse{ 6186 Diagnostics: tfdiags.Diagnostics(nil).Append(fmt.Errorf("failed")), 6187 } 6188 } 6189 6190 ctx := testContext2(t, &ContextOpts{ 6191 Providers: map[addrs.Provider]providers.Factory{ 6192 addrs.NewDefaultProvider("test"): testProviderFuncFixed(p), 6193 }, 6194 }) 6195 6196 state := states.BuildState(func(ss *states.SyncState) { 6197 ss.SetResourceInstanceCurrent( 6198 addrs.Resource{ 6199 Mode: addrs.ManagedResourceMode, 6200 Type: "test_thing", 6201 Name: "foo", 6202 }.Instance(addrs.NoKey).Absolute(addrs.RootModuleInstance), 6203 &states.ResourceInstanceObjectSrc{ 6204 Status: states.ObjectReady, 6205 AttrsJSON: []byte(`{"id":"baz"}`), 6206 }, 6207 addrs.AbsProviderConfig{ 6208 Provider: addrs.NewDefaultProvider("test"), 6209 Module: addrs.RootModule, 6210 }, 6211 ) 6212 }) 6213 plan, diags := ctx.Plan(m, state, DefaultPlanOpts) 6214 assertNoErrors(t, diags) 6215 6216 state, diags = ctx.Apply(plan, m) 6217 if !diags.HasErrors() { 6218 t.Fatal("should have error") 6219 } 6220 6221 actual := strings.TrimSpace(state.String()) 6222 expected := strings.TrimSpace(` 6223 test_thing.foo: 6224 ID = baz 6225 provider = provider["registry.terraform.io/hashicorp/test"] 6226 `) // test_thing.foo is still here, even though provider returned no new state along with its error 6227 if actual != expected { 6228 t.Fatalf("expected:\n%s\n\ngot:\n%s", expected, actual) 6229 } 6230 } 6231 6232 func TestContext2Apply_errorCreateInvalidNew(t *testing.T) { 6233 m := testModule(t, "apply-error") 6234 6235 p := testProvider("aws") 6236 p.GetProviderSchemaResponse = getProviderSchemaResponseFromProviderSchema(&ProviderSchema{ 6237 ResourceTypes: map[string]*configschema.Block{ 6238 "aws_instance": { 6239 Attributes: map[string]*configschema.Attribute{ 6240 "value": {Type: cty.String, Optional: true}, 6241 "foo": {Type: cty.String, Optional: true}, 6242 }, 6243 }, 6244 }, 6245 }) 6246 p.PlanResourceChangeFn = func(req providers.PlanResourceChangeRequest) providers.PlanResourceChangeResponse { 6247 return providers.PlanResourceChangeResponse{ 6248 PlannedState: req.ProposedNewState, 6249 } 6250 } 6251 p.ApplyResourceChangeFn = func(req providers.ApplyResourceChangeRequest) providers.ApplyResourceChangeResponse { 6252 // We're intentionally returning an inconsistent new state here 6253 // because we want to test that Terraform ignores the inconsistency 6254 // when accompanied by another error. 6255 return providers.ApplyResourceChangeResponse{ 6256 NewState: cty.ObjectVal(map[string]cty.Value{ 6257 "value": cty.StringVal("wrong wrong wrong wrong"), 6258 "foo": cty.StringVal("absolutely brimming over with wrongability"), 6259 }), 6260 Diagnostics: tfdiags.Diagnostics(nil).Append(fmt.Errorf("forced error")), 6261 } 6262 } 6263 6264 ctx := testContext2(t, &ContextOpts{ 6265 Providers: map[addrs.Provider]providers.Factory{ 6266 addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p), 6267 }, 6268 }) 6269 6270 plan, diags := ctx.Plan(m, states.NewState(), DefaultPlanOpts) 6271 assertNoErrors(t, diags) 6272 6273 state, diags := ctx.Apply(plan, m) 6274 if diags == nil { 6275 t.Fatal("should have error") 6276 } 6277 if got, want := len(diags), 1; got != want { 6278 // There should be no additional diagnostics generated by Terraform's own eval logic, 6279 // because the provider's own error supersedes them. 6280 t.Errorf("wrong number of diagnostics %d; want %d\n%s", got, want, diags.Err()) 6281 } 6282 if got, want := diags.Err().Error(), "forced error"; !strings.Contains(got, want) { 6283 t.Errorf("returned error does not contain %q, but it should\n%s", want, diags.Err()) 6284 } 6285 if got, want := len(state.RootModule().Resources), 2; got != want { 6286 t.Errorf("%d resources in state before prune; should have %d\n%s", got, want, spew.Sdump(state)) 6287 } 6288 state.PruneResourceHusks() // aws_instance.bar with no instances gets left behind when we bail out, but that's okay 6289 if got, want := len(state.RootModule().Resources), 1; got != want { 6290 t.Errorf("%d resources in state after prune; should have only one (aws_instance.foo, tainted)\n%s", got, spew.Sdump(state)) 6291 } 6292 } 6293 6294 func TestContext2Apply_errorUpdateNullNew(t *testing.T) { 6295 m := testModule(t, "apply-error") 6296 6297 p := testProvider("aws") 6298 p.GetProviderSchemaResponse = getProviderSchemaResponseFromProviderSchema(&ProviderSchema{ 6299 ResourceTypes: map[string]*configschema.Block{ 6300 "aws_instance": { 6301 Attributes: map[string]*configschema.Attribute{ 6302 "value": {Type: cty.String, Optional: true}, 6303 "foo": {Type: cty.String, Optional: true}, 6304 }, 6305 }, 6306 }, 6307 }) 6308 p.PlanResourceChangeFn = func(req providers.PlanResourceChangeRequest) providers.PlanResourceChangeResponse { 6309 return providers.PlanResourceChangeResponse{ 6310 PlannedState: req.ProposedNewState, 6311 } 6312 } 6313 p.ApplyResourceChangeFn = func(req providers.ApplyResourceChangeRequest) providers.ApplyResourceChangeResponse { 6314 // We're intentionally returning no NewState here because we want to 6315 // test that Terraform retains the prior state, rather than treating 6316 // the returned null as "no state" (object deleted). 6317 return providers.ApplyResourceChangeResponse{ 6318 Diagnostics: tfdiags.Diagnostics(nil).Append(fmt.Errorf("forced error")), 6319 } 6320 } 6321 6322 ctx := testContext2(t, &ContextOpts{ 6323 Providers: map[addrs.Provider]providers.Factory{ 6324 addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p), 6325 }, 6326 }) 6327 6328 state := states.BuildState(func(ss *states.SyncState) { 6329 ss.SetResourceInstanceCurrent( 6330 addrs.Resource{ 6331 Mode: addrs.ManagedResourceMode, 6332 Type: "aws_instance", 6333 Name: "foo", 6334 }.Instance(addrs.NoKey).Absolute(addrs.RootModuleInstance), 6335 &states.ResourceInstanceObjectSrc{ 6336 Status: states.ObjectReady, 6337 AttrsJSON: []byte(`{"value":"old"}`), 6338 }, 6339 addrs.AbsProviderConfig{ 6340 Provider: addrs.NewDefaultProvider("aws"), 6341 Module: addrs.RootModule, 6342 }, 6343 ) 6344 }) 6345 plan, diags := ctx.Plan(m, state, DefaultPlanOpts) 6346 assertNoErrors(t, diags) 6347 6348 state, diags = ctx.Apply(plan, m) 6349 if !diags.HasErrors() { 6350 t.Fatal("should have error") 6351 } 6352 if got, want := len(diags), 1; got != want { 6353 // There should be no additional diagnostics generated by Terraform's own eval logic, 6354 // because the provider's own error supersedes them. 6355 t.Errorf("wrong number of diagnostics %d; want %d\n%s", got, want, diags.Err()) 6356 } 6357 if got, want := diags.Err().Error(), "forced error"; !strings.Contains(got, want) { 6358 t.Errorf("returned error does not contain %q, but it should\n%s", want, diags.Err()) 6359 } 6360 state.PruneResourceHusks() 6361 if got, want := len(state.RootModule().Resources), 1; got != want { 6362 t.Fatalf("%d resources in state; should have only one (aws_instance.foo, unmodified)\n%s", got, spew.Sdump(state)) 6363 } 6364 6365 is := state.ResourceInstance(addrs.Resource{ 6366 Mode: addrs.ManagedResourceMode, 6367 Type: "aws_instance", 6368 Name: "foo", 6369 }.Instance(addrs.NoKey).Absolute(addrs.RootModuleInstance)) 6370 if is == nil { 6371 t.Fatalf("aws_instance.foo is not in the state after apply") 6372 } 6373 if got, want := is.Current.AttrsJSON, []byte(`"old"`); !bytes.Contains(got, want) { 6374 t.Fatalf("incorrect attributes for aws_instance.foo\ngot: %s\nwant: JSON containing %s\n\n%s", got, want, spew.Sdump(is)) 6375 } 6376 } 6377 6378 func TestContext2Apply_errorPartial(t *testing.T) { 6379 errored := false 6380 6381 m := testModule(t, "apply-error") 6382 p := testProvider("aws") 6383 6384 state := states.NewState() 6385 root := state.EnsureModule(addrs.RootModuleInstance) 6386 root.SetResourceInstanceCurrent( 6387 mustResourceInstanceAddr("aws_instance.bar").Resource, 6388 &states.ResourceInstanceObjectSrc{ 6389 Status: states.ObjectReady, 6390 AttrsJSON: []byte(`{"id":"bar","type":"aws_instance"}`), 6391 }, 6392 mustProviderConfig(`provider["registry.terraform.io/hashicorp/aws"]`), 6393 ) 6394 6395 ctx := testContext2(t, &ContextOpts{ 6396 Providers: map[addrs.Provider]providers.Factory{ 6397 addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p), 6398 }, 6399 }) 6400 6401 p.ApplyResourceChangeFn = func(req providers.ApplyResourceChangeRequest) (resp providers.ApplyResourceChangeResponse) { 6402 if errored { 6403 resp.Diagnostics = resp.Diagnostics.Append(fmt.Errorf("error")) 6404 return 6405 } 6406 errored = true 6407 6408 return testApplyFn(req) 6409 } 6410 p.PlanResourceChangeFn = testDiffFn 6411 6412 plan, diags := ctx.Plan(m, state, DefaultPlanOpts) 6413 assertNoErrors(t, diags) 6414 6415 s, diags := ctx.Apply(plan, m) 6416 if diags == nil { 6417 t.Fatal("should have error") 6418 } 6419 6420 mod := s.RootModule() 6421 if len(mod.Resources) != 2 { 6422 t.Fatalf("bad: %#v", mod.Resources) 6423 } 6424 6425 actual := strings.TrimSpace(s.String()) 6426 expected := strings.TrimSpace(testTerraformApplyErrorPartialStr) 6427 if actual != expected { 6428 t.Fatalf("expected:\n%s\n\ngot:\n%s", expected, actual) 6429 } 6430 } 6431 6432 func TestContext2Apply_hook(t *testing.T) { 6433 m := testModule(t, "apply-good") 6434 h := new(MockHook) 6435 p := testProvider("aws") 6436 p.PlanResourceChangeFn = testDiffFn 6437 ctx := testContext2(t, &ContextOpts{ 6438 Hooks: []Hook{h}, 6439 Providers: map[addrs.Provider]providers.Factory{ 6440 addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p), 6441 }, 6442 }) 6443 6444 plan, diags := ctx.Plan(m, states.NewState(), DefaultPlanOpts) 6445 assertNoErrors(t, diags) 6446 6447 if _, diags := ctx.Apply(plan, m); diags.HasErrors() { 6448 t.Fatalf("apply errors: %s", diags.Err()) 6449 } 6450 6451 if !h.PreApplyCalled { 6452 t.Fatal("should be called") 6453 } 6454 if !h.PostApplyCalled { 6455 t.Fatal("should be called") 6456 } 6457 if !h.PostStateUpdateCalled { 6458 t.Fatalf("should call post state update") 6459 } 6460 } 6461 6462 func TestContext2Apply_hookOrphan(t *testing.T) { 6463 m := testModule(t, "apply-blank") 6464 h := new(MockHook) 6465 p := testProvider("aws") 6466 p.PlanResourceChangeFn = testDiffFn 6467 6468 state := states.NewState() 6469 root := state.EnsureModule(addrs.RootModuleInstance) 6470 root.SetResourceInstanceCurrent( 6471 mustResourceInstanceAddr("aws_instance.bar").Resource, 6472 &states.ResourceInstanceObjectSrc{ 6473 Status: states.ObjectReady, 6474 AttrsJSON: []byte(`{"id":"bar"}`), 6475 }, 6476 mustProviderConfig(`provider["registry.terraform.io/hashicorp/aws"]`), 6477 ) 6478 6479 ctx := testContext2(t, &ContextOpts{ 6480 Hooks: []Hook{h}, 6481 Providers: map[addrs.Provider]providers.Factory{ 6482 addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p), 6483 }, 6484 }) 6485 6486 plan, diags := ctx.Plan(m, state, DefaultPlanOpts) 6487 assertNoErrors(t, diags) 6488 6489 if _, diags := ctx.Apply(plan, m); diags.HasErrors() { 6490 t.Fatalf("apply errors: %s", diags.Err()) 6491 } 6492 6493 if !h.PreApplyCalled { 6494 t.Fatal("should be called") 6495 } 6496 if !h.PostApplyCalled { 6497 t.Fatal("should be called") 6498 } 6499 if !h.PostStateUpdateCalled { 6500 t.Fatalf("should call post state update") 6501 } 6502 } 6503 6504 func TestContext2Apply_idAttr(t *testing.T) { 6505 m := testModule(t, "apply-idattr") 6506 p := testProvider("aws") 6507 ctx := testContext2(t, &ContextOpts{ 6508 Providers: map[addrs.Provider]providers.Factory{ 6509 addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p), 6510 }, 6511 }) 6512 6513 p.PlanResourceChangeFn = testDiffFn 6514 p.ApplyResourceChangeFn = testApplyFn 6515 6516 plan, diags := ctx.Plan(m, states.NewState(), DefaultPlanOpts) 6517 assertNoErrors(t, diags) 6518 6519 state, diags := ctx.Apply(plan, m) 6520 if diags.HasErrors() { 6521 t.Fatalf("apply errors: %s", diags.Err()) 6522 } 6523 6524 mod := state.RootModule() 6525 rs, ok := mod.Resources["aws_instance.foo"] 6526 if !ok { 6527 t.Fatal("not in state") 6528 } 6529 var attrs map[string]interface{} 6530 err := json.Unmarshal(rs.Instances[addrs.NoKey].Current.AttrsJSON, &attrs) 6531 if err != nil { 6532 t.Fatal(err) 6533 } 6534 if got, want := attrs["id"], "foo"; got != want { 6535 t.Fatalf("wrong id\ngot: %#v\nwant: %#v", got, want) 6536 } 6537 } 6538 6539 func TestContext2Apply_outputBasic(t *testing.T) { 6540 m := testModule(t, "apply-output") 6541 p := testProvider("aws") 6542 p.PlanResourceChangeFn = testDiffFn 6543 p.ApplyResourceChangeFn = testApplyFn 6544 ctx := testContext2(t, &ContextOpts{ 6545 Providers: map[addrs.Provider]providers.Factory{ 6546 addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p), 6547 }, 6548 }) 6549 6550 plan, diags := ctx.Plan(m, states.NewState(), DefaultPlanOpts) 6551 assertNoErrors(t, diags) 6552 6553 state, diags := ctx.Apply(plan, m) 6554 if diags.HasErrors() { 6555 t.Fatalf("diags: %s", diags.Err()) 6556 } 6557 6558 actual := strings.TrimSpace(state.String()) 6559 expected := strings.TrimSpace(testTerraformApplyOutputStr) 6560 if actual != expected { 6561 t.Fatalf("wrong result\n\ngot:\n%s\n\nwant:\n%s", actual, expected) 6562 } 6563 } 6564 6565 func TestContext2Apply_outputAdd(t *testing.T) { 6566 m1 := testModule(t, "apply-output-add-before") 6567 p1 := testProvider("aws") 6568 p1.ApplyResourceChangeFn = testApplyFn 6569 p1.PlanResourceChangeFn = testDiffFn 6570 ctx1 := testContext2(t, &ContextOpts{ 6571 Providers: map[addrs.Provider]providers.Factory{ 6572 addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p1), 6573 }, 6574 }) 6575 6576 plan1, diags := ctx1.Plan(m1, states.NewState(), DefaultPlanOpts) 6577 assertNoErrors(t, diags) 6578 6579 state1, diags := ctx1.Apply(plan1, m1) 6580 if diags.HasErrors() { 6581 t.Fatalf("diags: %s", diags.Err()) 6582 } 6583 6584 m2 := testModule(t, "apply-output-add-after") 6585 p2 := testProvider("aws") 6586 p2.ApplyResourceChangeFn = testApplyFn 6587 p2.PlanResourceChangeFn = testDiffFn 6588 ctx2 := testContext2(t, &ContextOpts{ 6589 Providers: map[addrs.Provider]providers.Factory{ 6590 addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p2), 6591 }, 6592 }) 6593 6594 plan2, diags := ctx1.Plan(m2, state1, DefaultPlanOpts) 6595 assertNoErrors(t, diags) 6596 6597 state2, diags := ctx2.Apply(plan2, m2) 6598 if diags.HasErrors() { 6599 t.Fatalf("diags: %s", diags.Err()) 6600 } 6601 6602 actual := strings.TrimSpace(state2.String()) 6603 expected := strings.TrimSpace(testTerraformApplyOutputAddStr) 6604 if actual != expected { 6605 t.Fatalf("wrong result\n\ngot:\n%s\n\nwant:\n%s", actual, expected) 6606 } 6607 } 6608 6609 func TestContext2Apply_outputList(t *testing.T) { 6610 m := testModule(t, "apply-output-list") 6611 p := testProvider("aws") 6612 p.PlanResourceChangeFn = testDiffFn 6613 p.ApplyResourceChangeFn = testApplyFn 6614 ctx := testContext2(t, &ContextOpts{ 6615 Providers: map[addrs.Provider]providers.Factory{ 6616 addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p), 6617 }, 6618 }) 6619 6620 plan, diags := ctx.Plan(m, states.NewState(), DefaultPlanOpts) 6621 assertNoErrors(t, diags) 6622 6623 state, diags := ctx.Apply(plan, m) 6624 if diags.HasErrors() { 6625 t.Fatalf("diags: %s", diags.Err()) 6626 } 6627 6628 actual := strings.TrimSpace(state.String()) 6629 expected := strings.TrimSpace(testTerraformApplyOutputListStr) 6630 if actual != expected { 6631 t.Fatalf("expected: \n%s\n\nbad: \n%s", expected, actual) 6632 } 6633 } 6634 6635 func TestContext2Apply_outputMulti(t *testing.T) { 6636 m := testModule(t, "apply-output-multi") 6637 p := testProvider("aws") 6638 p.PlanResourceChangeFn = testDiffFn 6639 p.ApplyResourceChangeFn = testApplyFn 6640 ctx := testContext2(t, &ContextOpts{ 6641 Providers: map[addrs.Provider]providers.Factory{ 6642 addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p), 6643 }, 6644 }) 6645 6646 plan, diags := ctx.Plan(m, states.NewState(), DefaultPlanOpts) 6647 assertNoErrors(t, diags) 6648 6649 state, diags := ctx.Apply(plan, m) 6650 if diags.HasErrors() { 6651 t.Fatalf("diags: %s", diags.Err()) 6652 } 6653 6654 actual := strings.TrimSpace(state.String()) 6655 expected := strings.TrimSpace(testTerraformApplyOutputMultiStr) 6656 if actual != expected { 6657 t.Fatalf("wrong result\n\ngot:\n%s\n\nwant:\n%s", actual, expected) 6658 } 6659 } 6660 6661 func TestContext2Apply_outputMultiIndex(t *testing.T) { 6662 m := testModule(t, "apply-output-multi-index") 6663 p := testProvider("aws") 6664 p.PlanResourceChangeFn = testDiffFn 6665 p.ApplyResourceChangeFn = testApplyFn 6666 ctx := testContext2(t, &ContextOpts{ 6667 Providers: map[addrs.Provider]providers.Factory{ 6668 addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p), 6669 }, 6670 }) 6671 6672 plan, diags := ctx.Plan(m, states.NewState(), DefaultPlanOpts) 6673 assertNoErrors(t, diags) 6674 6675 state, diags := ctx.Apply(plan, m) 6676 if diags.HasErrors() { 6677 t.Fatalf("diags: %s", diags.Err()) 6678 } 6679 6680 actual := strings.TrimSpace(state.String()) 6681 expected := strings.TrimSpace(testTerraformApplyOutputMultiIndexStr) 6682 if actual != expected { 6683 t.Fatalf("wrong result\n\ngot:\n%s\n\nwant:\n%s", actual, expected) 6684 } 6685 } 6686 6687 func TestContext2Apply_taintX(t *testing.T) { 6688 m := testModule(t, "apply-taint") 6689 p := testProvider("aws") 6690 // destroyCount tests against regression of 6691 // https://github.com/hashicorp/terraform/issues/1056 6692 var destroyCount = int32(0) 6693 var once sync.Once 6694 simulateProviderDelay := func() { 6695 time.Sleep(10 * time.Millisecond) 6696 } 6697 6698 p.ApplyResourceChangeFn = func(req providers.ApplyResourceChangeRequest) providers.ApplyResourceChangeResponse { 6699 once.Do(simulateProviderDelay) 6700 if req.PlannedState.IsNull() { 6701 atomic.AddInt32(&destroyCount, 1) 6702 } 6703 return testApplyFn(req) 6704 } 6705 p.PlanResourceChangeFn = testDiffFn 6706 6707 state := states.NewState() 6708 root := state.EnsureModule(addrs.RootModuleInstance) 6709 root.SetResourceInstanceCurrent( 6710 mustResourceInstanceAddr("aws_instance.bar").Resource, 6711 &states.ResourceInstanceObjectSrc{ 6712 Status: states.ObjectTainted, 6713 AttrsJSON: []byte(`{"id":"baz","num": "2", "type": "aws_instance"}`), 6714 }, 6715 mustProviderConfig(`provider["registry.terraform.io/hashicorp/aws"]`), 6716 ) 6717 6718 ctx := testContext2(t, &ContextOpts{ 6719 Providers: map[addrs.Provider]providers.Factory{ 6720 addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p), 6721 }, 6722 }) 6723 6724 plan, diags := ctx.Plan(m, state, DefaultPlanOpts) 6725 if diags.HasErrors() { 6726 t.Fatalf("diags: %s", diags.Err()) 6727 } else { 6728 t.Logf("plan: %s", legacyDiffComparisonString(plan.Changes)) 6729 } 6730 6731 s, diags := ctx.Apply(plan, m) 6732 if diags.HasErrors() { 6733 t.Fatalf("diags: %s", diags.Err()) 6734 } 6735 6736 actual := strings.TrimSpace(s.String()) 6737 expected := strings.TrimSpace(testTerraformApplyTaintStr) 6738 if actual != expected { 6739 t.Fatalf("bad:\n%s", actual) 6740 } 6741 6742 if destroyCount != 1 { 6743 t.Fatalf("Expected 1 destroy, got %d", destroyCount) 6744 } 6745 } 6746 6747 func TestContext2Apply_taintDep(t *testing.T) { 6748 m := testModule(t, "apply-taint-dep") 6749 p := testProvider("aws") 6750 p.PlanResourceChangeFn = testDiffFn 6751 p.ApplyResourceChangeFn = testApplyFn 6752 6753 state := states.NewState() 6754 root := state.EnsureModule(addrs.RootModuleInstance) 6755 root.SetResourceInstanceCurrent( 6756 mustResourceInstanceAddr("aws_instance.foo").Resource, 6757 &states.ResourceInstanceObjectSrc{ 6758 Status: states.ObjectTainted, 6759 AttrsJSON: []byte(`{"id":"baz","num": "2", "type": "aws_instance"}`), 6760 }, 6761 mustProviderConfig(`provider["registry.terraform.io/hashicorp/aws"]`), 6762 ) 6763 root.SetResourceInstanceCurrent( 6764 mustResourceInstanceAddr("aws_instance.bar").Resource, 6765 &states.ResourceInstanceObjectSrc{ 6766 Status: states.ObjectReady, 6767 AttrsJSON: []byte(`{"id":"bar","num": "2", "type": "aws_instance", "foo": "baz"}`), 6768 Dependencies: []addrs.ConfigResource{mustConfigResourceAddr("aws_instance.foo")}, 6769 }, 6770 mustProviderConfig(`provider["registry.terraform.io/hashicorp/aws"]`), 6771 ) 6772 6773 ctx := testContext2(t, &ContextOpts{ 6774 Providers: map[addrs.Provider]providers.Factory{ 6775 addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p), 6776 }, 6777 }) 6778 6779 plan, diags := ctx.Plan(m, state, DefaultPlanOpts) 6780 if diags.HasErrors() { 6781 t.Fatalf("diags: %s", diags.Err()) 6782 } else { 6783 t.Logf("plan: %s", legacyDiffComparisonString(plan.Changes)) 6784 } 6785 6786 s, diags := ctx.Apply(plan, m) 6787 if diags.HasErrors() { 6788 t.Fatalf("diags: %s", diags.Err()) 6789 } 6790 6791 actual := strings.TrimSpace(s.String()) 6792 expected := strings.TrimSpace(testTerraformApplyTaintDepStr) 6793 if actual != expected { 6794 t.Fatalf("bad:\n%s", actual) 6795 } 6796 } 6797 6798 func TestContext2Apply_taintDepRequiresNew(t *testing.T) { 6799 m := testModule(t, "apply-taint-dep-requires-new") 6800 p := testProvider("aws") 6801 p.PlanResourceChangeFn = testDiffFn 6802 p.ApplyResourceChangeFn = testApplyFn 6803 6804 state := states.NewState() 6805 root := state.EnsureModule(addrs.RootModuleInstance) 6806 root.SetResourceInstanceCurrent( 6807 mustResourceInstanceAddr("aws_instance.foo").Resource, 6808 &states.ResourceInstanceObjectSrc{ 6809 Status: states.ObjectTainted, 6810 AttrsJSON: []byte(`{"id":"baz","num": "2", "type": "aws_instance"}`), 6811 }, 6812 mustProviderConfig(`provider["registry.terraform.io/hashicorp/aws"]`), 6813 ) 6814 root.SetResourceInstanceCurrent( 6815 mustResourceInstanceAddr("aws_instance.bar").Resource, 6816 &states.ResourceInstanceObjectSrc{ 6817 Status: states.ObjectReady, 6818 AttrsJSON: []byte(`{"id":"bar","num": "2", "type": "aws_instance", "foo": "baz"}`), 6819 Dependencies: []addrs.ConfigResource{mustConfigResourceAddr("aws_instance.foo")}, 6820 }, 6821 mustProviderConfig(`provider["registry.terraform.io/hashicorp/aws"]`), 6822 ) 6823 6824 ctx := testContext2(t, &ContextOpts{ 6825 Providers: map[addrs.Provider]providers.Factory{ 6826 addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p), 6827 }, 6828 }) 6829 6830 plan, diags := ctx.Plan(m, state, DefaultPlanOpts) 6831 if diags.HasErrors() { 6832 t.Fatalf("diags: %s", diags.Err()) 6833 } else { 6834 t.Logf("plan: %s", legacyDiffComparisonString(plan.Changes)) 6835 } 6836 6837 s, diags := ctx.Apply(plan, m) 6838 if diags.HasErrors() { 6839 t.Fatalf("diags: %s", diags.Err()) 6840 } 6841 6842 actual := strings.TrimSpace(s.String()) 6843 expected := strings.TrimSpace(testTerraformApplyTaintDepRequireNewStr) 6844 if actual != expected { 6845 t.Fatalf("bad:\n%s", actual) 6846 } 6847 } 6848 6849 func TestContext2Apply_targeted(t *testing.T) { 6850 m := testModule(t, "apply-targeted") 6851 p := testProvider("aws") 6852 p.PlanResourceChangeFn = testDiffFn 6853 p.ApplyResourceChangeFn = testApplyFn 6854 ctx := testContext2(t, &ContextOpts{ 6855 Providers: map[addrs.Provider]providers.Factory{ 6856 addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p), 6857 }, 6858 }) 6859 6860 plan, diags := ctx.Plan(m, states.NewState(), &PlanOpts{ 6861 Mode: plans.NormalMode, 6862 Targets: []addrs.Targetable{ 6863 addrs.RootModuleInstance.Resource( 6864 addrs.ManagedResourceMode, "aws_instance", "foo", 6865 ), 6866 }, 6867 }) 6868 assertNoErrors(t, diags) 6869 6870 state, diags := ctx.Apply(plan, m) 6871 if diags.HasErrors() { 6872 t.Fatalf("diags: %s", diags.Err()) 6873 } 6874 6875 mod := state.RootModule() 6876 if len(mod.Resources) != 1 { 6877 t.Fatalf("expected 1 resource, got: %#v", mod.Resources) 6878 } 6879 6880 checkStateString(t, state, ` 6881 aws_instance.foo: 6882 ID = foo 6883 provider = provider["registry.terraform.io/hashicorp/aws"] 6884 num = 2 6885 type = aws_instance 6886 `) 6887 } 6888 6889 func TestContext2Apply_targetedCount(t *testing.T) { 6890 m := testModule(t, "apply-targeted-count") 6891 p := testProvider("aws") 6892 p.PlanResourceChangeFn = testDiffFn 6893 p.ApplyResourceChangeFn = testApplyFn 6894 ctx := testContext2(t, &ContextOpts{ 6895 Providers: map[addrs.Provider]providers.Factory{ 6896 addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p), 6897 }, 6898 }) 6899 6900 plan, diags := ctx.Plan(m, states.NewState(), &PlanOpts{ 6901 Mode: plans.NormalMode, 6902 Targets: []addrs.Targetable{ 6903 addrs.RootModuleInstance.Resource( 6904 addrs.ManagedResourceMode, "aws_instance", "foo", 6905 ), 6906 }, 6907 }) 6908 assertNoErrors(t, diags) 6909 6910 state, diags := ctx.Apply(plan, m) 6911 if diags.HasErrors() { 6912 t.Fatalf("diags: %s", diags.Err()) 6913 } 6914 6915 checkStateString(t, state, ` 6916 aws_instance.foo.0: 6917 ID = foo 6918 provider = provider["registry.terraform.io/hashicorp/aws"] 6919 type = aws_instance 6920 aws_instance.foo.1: 6921 ID = foo 6922 provider = provider["registry.terraform.io/hashicorp/aws"] 6923 type = aws_instance 6924 aws_instance.foo.2: 6925 ID = foo 6926 provider = provider["registry.terraform.io/hashicorp/aws"] 6927 type = aws_instance 6928 `) 6929 } 6930 6931 func TestContext2Apply_targetedCountIndex(t *testing.T) { 6932 m := testModule(t, "apply-targeted-count") 6933 p := testProvider("aws") 6934 p.PlanResourceChangeFn = testDiffFn 6935 p.ApplyResourceChangeFn = testApplyFn 6936 ctx := testContext2(t, &ContextOpts{ 6937 Providers: map[addrs.Provider]providers.Factory{ 6938 addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p), 6939 }, 6940 }) 6941 6942 plan, diags := ctx.Plan(m, states.NewState(), &PlanOpts{ 6943 Mode: plans.NormalMode, 6944 Targets: []addrs.Targetable{ 6945 addrs.RootModuleInstance.ResourceInstance( 6946 addrs.ManagedResourceMode, "aws_instance", "foo", addrs.IntKey(1), 6947 ), 6948 }, 6949 }) 6950 assertNoErrors(t, diags) 6951 6952 state, diags := ctx.Apply(plan, m) 6953 if diags.HasErrors() { 6954 t.Fatalf("diags: %s", diags.Err()) 6955 } 6956 6957 checkStateString(t, state, ` 6958 aws_instance.foo.1: 6959 ID = foo 6960 provider = provider["registry.terraform.io/hashicorp/aws"] 6961 type = aws_instance 6962 `) 6963 } 6964 6965 func TestContext2Apply_targetedDestroy(t *testing.T) { 6966 m := testModule(t, "destroy-targeted") 6967 p := testProvider("aws") 6968 p.PlanResourceChangeFn = testDiffFn 6969 6970 state := states.NewState() 6971 root := state.EnsureModule(addrs.RootModuleInstance) 6972 root.SetResourceInstanceCurrent( 6973 mustResourceInstanceAddr("aws_instance.a").Resource, 6974 &states.ResourceInstanceObjectSrc{ 6975 Status: states.ObjectReady, 6976 AttrsJSON: []byte(`{"id":"bar"}`), 6977 }, 6978 mustProviderConfig(`provider["registry.terraform.io/hashicorp/aws"]`), 6979 ) 6980 root.SetOutputValue("out", cty.StringVal("bar"), false) 6981 6982 child := state.EnsureModule(addrs.RootModuleInstance.Child("child", addrs.NoKey)) 6983 child.SetResourceInstanceCurrent( 6984 mustResourceInstanceAddr("aws_instance.b").Resource, 6985 &states.ResourceInstanceObjectSrc{ 6986 Status: states.ObjectReady, 6987 AttrsJSON: []byte(`{"id":"i-bcd345"}`), 6988 }, 6989 mustProviderConfig(`provider["registry.terraform.io/hashicorp/aws"]`), 6990 ) 6991 6992 ctx := testContext2(t, &ContextOpts{ 6993 Providers: map[addrs.Provider]providers.Factory{ 6994 addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p), 6995 }, 6996 }) 6997 6998 if diags := ctx.Validate(m); diags.HasErrors() { 6999 t.Fatalf("validate errors: %s", diags.Err()) 7000 } 7001 7002 plan, diags := ctx.Plan(m, state, &PlanOpts{ 7003 Mode: plans.DestroyMode, 7004 Targets: []addrs.Targetable{ 7005 addrs.RootModuleInstance.Resource( 7006 addrs.ManagedResourceMode, "aws_instance", "a", 7007 ), 7008 }, 7009 }) 7010 assertNoErrors(t, diags) 7011 7012 state, diags = ctx.Apply(plan, m) 7013 if diags.HasErrors() { 7014 t.Fatalf("diags: %s", diags.Err()) 7015 } 7016 7017 mod := state.RootModule() 7018 if len(mod.Resources) != 0 { 7019 t.Fatalf("expected 0 resources, got: %#v", mod.Resources) 7020 } 7021 7022 // the root output should not get removed; only the targeted resource. 7023 // 7024 // Note: earlier versions of this test expected 0 outputs, but it turns out 7025 // that was because Validate - not apply or destroy - removed the output 7026 // (which depends on the targeted resource) from state. That version of this 7027 // test did not match actual terraform behavior: the output remains in 7028 // state. 7029 // 7030 // The reason it remains in the state is that we prune out the root module 7031 // output values from the destroy graph as part of pruning out the "update" 7032 // nodes for the resources, because otherwise the root module output values 7033 // force the resources to stay in the graph and can therefore cause 7034 // unwanted dependency cycles. 7035 // 7036 // TODO: Future refactoring may enable us to remove the output from state in 7037 // this case, and that would be Just Fine - this test can be modified to 7038 // expect 0 outputs. 7039 if len(mod.OutputValues) != 1 { 7040 t.Fatalf("expected 1 outputs, got: %#v", mod.OutputValues) 7041 } 7042 7043 // the module instance should remain 7044 mod = state.Module(addrs.RootModuleInstance.Child("child", addrs.NoKey)) 7045 if len(mod.Resources) != 1 { 7046 t.Fatalf("expected 1 resources, got: %#v", mod.Resources) 7047 } 7048 } 7049 7050 func TestContext2Apply_targetedDestroyCountDeps(t *testing.T) { 7051 m := testModule(t, "apply-destroy-targeted-count") 7052 p := testProvider("aws") 7053 p.PlanResourceChangeFn = testDiffFn 7054 7055 state := states.NewState() 7056 root := state.EnsureModule(addrs.RootModuleInstance) 7057 root.SetResourceInstanceCurrent( 7058 mustResourceInstanceAddr("aws_instance.foo").Resource, 7059 &states.ResourceInstanceObjectSrc{ 7060 Status: states.ObjectReady, 7061 AttrsJSON: []byte(`{"id":"i-bcd345"}`), 7062 }, 7063 mustProviderConfig(`provider["registry.terraform.io/hashicorp/aws"]`), 7064 ) 7065 root.SetResourceInstanceCurrent( 7066 mustResourceInstanceAddr("aws_instance.bar").Resource, 7067 &states.ResourceInstanceObjectSrc{ 7068 Status: states.ObjectReady, 7069 AttrsJSON: []byte(`{"id":"i-abc123"}`), 7070 Dependencies: []addrs.ConfigResource{mustConfigResourceAddr("aws_instance.foo")}, 7071 }, 7072 mustProviderConfig(`provider["registry.terraform.io/hashicorp/aws"]`), 7073 ) 7074 7075 ctx := testContext2(t, &ContextOpts{ 7076 Providers: map[addrs.Provider]providers.Factory{ 7077 addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p), 7078 }, 7079 }) 7080 7081 plan, diags := ctx.Plan(m, state, &PlanOpts{ 7082 Mode: plans.DestroyMode, 7083 Targets: []addrs.Targetable{ 7084 addrs.RootModuleInstance.Resource( 7085 addrs.ManagedResourceMode, "aws_instance", "foo", 7086 ), 7087 }, 7088 }) 7089 assertNoErrors(t, diags) 7090 7091 state, diags = ctx.Apply(plan, m) 7092 if diags.HasErrors() { 7093 t.Fatalf("diags: %s", diags.Err()) 7094 } 7095 7096 checkStateString(t, state, `<no state>`) 7097 } 7098 7099 // https://github.com/hashicorp/terraform/issues/4462 7100 func TestContext2Apply_targetedDestroyModule(t *testing.T) { 7101 m := testModule(t, "apply-targeted-module") 7102 p := testProvider("aws") 7103 p.PlanResourceChangeFn = testDiffFn 7104 7105 state := states.NewState() 7106 root := state.EnsureModule(addrs.RootModuleInstance) 7107 root.SetResourceInstanceCurrent( 7108 mustResourceInstanceAddr("aws_instance.foo").Resource, 7109 &states.ResourceInstanceObjectSrc{ 7110 Status: states.ObjectReady, 7111 AttrsJSON: []byte(`{"id":"i-bcd345"}`), 7112 }, 7113 mustProviderConfig(`provider["registry.terraform.io/hashicorp/aws"]`), 7114 ) 7115 root.SetResourceInstanceCurrent( 7116 mustResourceInstanceAddr("aws_instance.bar").Resource, 7117 &states.ResourceInstanceObjectSrc{ 7118 Status: states.ObjectReady, 7119 AttrsJSON: []byte(`{"id":"i-abc123"}`), 7120 }, 7121 mustProviderConfig(`provider["registry.terraform.io/hashicorp/aws"]`), 7122 ) 7123 child := state.EnsureModule(addrs.RootModuleInstance.Child("child", addrs.NoKey)) 7124 child.SetResourceInstanceCurrent( 7125 mustResourceInstanceAddr("aws_instance.foo").Resource, 7126 &states.ResourceInstanceObjectSrc{ 7127 Status: states.ObjectReady, 7128 AttrsJSON: []byte(`{"id":"i-bcd345"}`), 7129 }, 7130 mustProviderConfig(`provider["registry.terraform.io/hashicorp/aws"]`), 7131 ) 7132 child.SetResourceInstanceCurrent( 7133 mustResourceInstanceAddr("aws_instance.bar").Resource, 7134 &states.ResourceInstanceObjectSrc{ 7135 Status: states.ObjectReady, 7136 AttrsJSON: []byte(`{"id":"i-abc123"}`), 7137 }, 7138 mustProviderConfig(`provider["registry.terraform.io/hashicorp/aws"]`), 7139 ) 7140 7141 ctx := testContext2(t, &ContextOpts{ 7142 Providers: map[addrs.Provider]providers.Factory{ 7143 addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p), 7144 }, 7145 }) 7146 7147 plan, diags := ctx.Plan(m, state, &PlanOpts{ 7148 Mode: plans.DestroyMode, 7149 Targets: []addrs.Targetable{ 7150 addrs.RootModuleInstance.Child("child", addrs.NoKey).Resource( 7151 addrs.ManagedResourceMode, "aws_instance", "foo", 7152 ), 7153 }, 7154 }) 7155 assertNoErrors(t, diags) 7156 7157 state, diags = ctx.Apply(plan, m) 7158 if diags.HasErrors() { 7159 t.Fatalf("diags: %s", diags.Err()) 7160 } 7161 7162 checkStateString(t, state, ` 7163 aws_instance.bar: 7164 ID = i-abc123 7165 provider = provider["registry.terraform.io/hashicorp/aws"] 7166 aws_instance.foo: 7167 ID = i-bcd345 7168 provider = provider["registry.terraform.io/hashicorp/aws"] 7169 7170 module.child: 7171 aws_instance.bar: 7172 ID = i-abc123 7173 provider = provider["registry.terraform.io/hashicorp/aws"] 7174 `) 7175 } 7176 7177 func TestContext2Apply_targetedDestroyCountIndex(t *testing.T) { 7178 m := testModule(t, "apply-targeted-count") 7179 p := testProvider("aws") 7180 p.PlanResourceChangeFn = testDiffFn 7181 7182 foo := &states.ResourceInstanceObjectSrc{ 7183 Status: states.ObjectReady, 7184 AttrsJSON: []byte(`{"id":"i-bcd345"}`), 7185 } 7186 bar := &states.ResourceInstanceObjectSrc{ 7187 Status: states.ObjectReady, 7188 AttrsJSON: []byte(`{"id":"i-abc123"}`), 7189 } 7190 7191 state := states.NewState() 7192 root := state.EnsureModule(addrs.RootModuleInstance) 7193 root.SetResourceInstanceCurrent( 7194 mustResourceInstanceAddr("aws_instance.foo[0]").Resource, 7195 foo, 7196 mustProviderConfig(`provider["registry.terraform.io/hashicorp/aws"]`), 7197 ) 7198 root.SetResourceInstanceCurrent( 7199 mustResourceInstanceAddr("aws_instance.foo[1]").Resource, 7200 foo, 7201 mustProviderConfig(`provider["registry.terraform.io/hashicorp/aws"]`), 7202 ) 7203 root.SetResourceInstanceCurrent( 7204 mustResourceInstanceAddr("aws_instance.foo[2]").Resource, 7205 foo, 7206 mustProviderConfig(`provider["registry.terraform.io/hashicorp/aws"]`), 7207 ) 7208 root.SetResourceInstanceCurrent( 7209 mustResourceInstanceAddr("aws_instance.bar[0]").Resource, 7210 bar, 7211 mustProviderConfig(`provider["registry.terraform.io/hashicorp/aws"]`), 7212 ) 7213 root.SetResourceInstanceCurrent( 7214 mustResourceInstanceAddr("aws_instance.bar[1]").Resource, 7215 bar, 7216 mustProviderConfig(`provider["registry.terraform.io/hashicorp/aws"]`), 7217 ) 7218 root.SetResourceInstanceCurrent( 7219 mustResourceInstanceAddr("aws_instance.bar[2]").Resource, 7220 bar, 7221 mustProviderConfig(`provider["registry.terraform.io/hashicorp/aws"]`), 7222 ) 7223 7224 ctx := testContext2(t, &ContextOpts{ 7225 Providers: map[addrs.Provider]providers.Factory{ 7226 addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p), 7227 }, 7228 }) 7229 7230 plan, diags := ctx.Plan(m, state, &PlanOpts{ 7231 Mode: plans.DestroyMode, 7232 Targets: []addrs.Targetable{ 7233 addrs.RootModuleInstance.ResourceInstance( 7234 addrs.ManagedResourceMode, "aws_instance", "foo", addrs.IntKey(2), 7235 ), 7236 addrs.RootModuleInstance.ResourceInstance( 7237 addrs.ManagedResourceMode, "aws_instance", "bar", addrs.IntKey(1), 7238 ), 7239 }, 7240 }) 7241 assertNoErrors(t, diags) 7242 7243 state, diags = ctx.Apply(plan, m) 7244 if diags.HasErrors() { 7245 t.Fatalf("diags: %s", diags.Err()) 7246 } 7247 7248 checkStateString(t, state, ` 7249 aws_instance.bar.0: 7250 ID = i-abc123 7251 provider = provider["registry.terraform.io/hashicorp/aws"] 7252 aws_instance.bar.2: 7253 ID = i-abc123 7254 provider = provider["registry.terraform.io/hashicorp/aws"] 7255 aws_instance.foo.0: 7256 ID = i-bcd345 7257 provider = provider["registry.terraform.io/hashicorp/aws"] 7258 aws_instance.foo.1: 7259 ID = i-bcd345 7260 provider = provider["registry.terraform.io/hashicorp/aws"] 7261 `) 7262 } 7263 7264 func TestContext2Apply_targetedModule(t *testing.T) { 7265 m := testModule(t, "apply-targeted-module") 7266 p := testProvider("aws") 7267 p.PlanResourceChangeFn = testDiffFn 7268 p.ApplyResourceChangeFn = testApplyFn 7269 ctx := testContext2(t, &ContextOpts{ 7270 Providers: map[addrs.Provider]providers.Factory{ 7271 addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p), 7272 }, 7273 }) 7274 7275 plan, diags := ctx.Plan(m, states.NewState(), &PlanOpts{ 7276 Mode: plans.NormalMode, 7277 Targets: []addrs.Targetable{ 7278 addrs.RootModuleInstance.Child("child", addrs.NoKey), 7279 }, 7280 }) 7281 assertNoErrors(t, diags) 7282 7283 state, diags := ctx.Apply(plan, m) 7284 if diags.HasErrors() { 7285 t.Fatalf("diags: %s", diags.Err()) 7286 } 7287 7288 mod := state.Module(addrs.RootModuleInstance.Child("child", addrs.NoKey)) 7289 if mod == nil { 7290 t.Fatalf("no child module found in the state!\n\n%#v", state) 7291 } 7292 if len(mod.Resources) != 2 { 7293 t.Fatalf("expected 2 resources, got: %#v", mod.Resources) 7294 } 7295 7296 checkStateString(t, state, ` 7297 <no state> 7298 module.child: 7299 aws_instance.bar: 7300 ID = foo 7301 provider = provider["registry.terraform.io/hashicorp/aws"] 7302 num = 2 7303 type = aws_instance 7304 aws_instance.foo: 7305 ID = foo 7306 provider = provider["registry.terraform.io/hashicorp/aws"] 7307 num = 2 7308 type = aws_instance 7309 `) 7310 } 7311 7312 // GH-1858 7313 func TestContext2Apply_targetedModuleDep(t *testing.T) { 7314 m := testModule(t, "apply-targeted-module-dep") 7315 p := testProvider("aws") 7316 p.PlanResourceChangeFn = testDiffFn 7317 p.ApplyResourceChangeFn = testApplyFn 7318 ctx := testContext2(t, &ContextOpts{ 7319 Providers: map[addrs.Provider]providers.Factory{ 7320 addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p), 7321 }, 7322 }) 7323 7324 plan, diags := ctx.Plan(m, states.NewState(), &PlanOpts{ 7325 Mode: plans.NormalMode, 7326 Targets: []addrs.Targetable{ 7327 addrs.RootModuleInstance.Resource( 7328 addrs.ManagedResourceMode, "aws_instance", "foo", 7329 ), 7330 }, 7331 }) 7332 if diags.HasErrors() { 7333 t.Fatalf("diags: %s", diags.Err()) 7334 } else { 7335 t.Logf("Diff: %s", legacyDiffComparisonString(plan.Changes)) 7336 } 7337 7338 state, diags := ctx.Apply(plan, m) 7339 if diags.HasErrors() { 7340 t.Fatalf("diags: %s", diags.Err()) 7341 } 7342 7343 checkStateString(t, state, ` 7344 aws_instance.foo: 7345 ID = foo 7346 provider = provider["registry.terraform.io/hashicorp/aws"] 7347 foo = foo 7348 type = aws_instance 7349 7350 Dependencies: 7351 module.child.aws_instance.mod 7352 7353 module.child: 7354 aws_instance.mod: 7355 ID = foo 7356 provider = provider["registry.terraform.io/hashicorp/aws"] 7357 type = aws_instance 7358 7359 Outputs: 7360 7361 output = foo 7362 `) 7363 } 7364 7365 // GH-10911 untargeted outputs should not be in the graph, and therefore 7366 // not execute. 7367 func TestContext2Apply_targetedModuleUnrelatedOutputs(t *testing.T) { 7368 m := testModule(t, "apply-targeted-module-unrelated-outputs") 7369 p := testProvider("aws") 7370 p.PlanResourceChangeFn = testDiffFn 7371 p.ApplyResourceChangeFn = testApplyFn 7372 7373 state := states.NewState() 7374 _ = state.EnsureModule(addrs.RootModuleInstance.Child("child2", addrs.NoKey)) 7375 7376 ctx := testContext2(t, &ContextOpts{ 7377 Providers: map[addrs.Provider]providers.Factory{ 7378 addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p), 7379 }, 7380 }) 7381 7382 plan, diags := ctx.Plan(m, state, &PlanOpts{ 7383 Mode: plans.NormalMode, 7384 Targets: []addrs.Targetable{ 7385 addrs.RootModuleInstance.Child("child2", addrs.NoKey), 7386 }, 7387 }) 7388 assertNoErrors(t, diags) 7389 7390 s, diags := ctx.Apply(plan, m) 7391 if diags.HasErrors() { 7392 t.Fatalf("diags: %s", diags.Err()) 7393 } 7394 7395 // - module.child1's instance_id output is dropped because we don't preserve 7396 // non-root module outputs between runs (they can be recalculated from config) 7397 // - module.child2's instance_id is updated because its dependency is updated 7398 // - child2_id is updated because if its transitive dependency via module.child2 7399 checkStateString(t, s, ` 7400 <no state> 7401 Outputs: 7402 7403 child2_id = foo 7404 7405 module.child2: 7406 aws_instance.foo: 7407 ID = foo 7408 provider = provider["registry.terraform.io/hashicorp/aws"] 7409 type = aws_instance 7410 7411 Outputs: 7412 7413 instance_id = foo 7414 `) 7415 } 7416 7417 func TestContext2Apply_targetedModuleResource(t *testing.T) { 7418 m := testModule(t, "apply-targeted-module-resource") 7419 p := testProvider("aws") 7420 p.PlanResourceChangeFn = testDiffFn 7421 p.ApplyResourceChangeFn = testApplyFn 7422 ctx := testContext2(t, &ContextOpts{ 7423 Providers: map[addrs.Provider]providers.Factory{ 7424 addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p), 7425 }, 7426 }) 7427 7428 plan, diags := ctx.Plan(m, states.NewState(), &PlanOpts{ 7429 Mode: plans.NormalMode, 7430 Targets: []addrs.Targetable{ 7431 addrs.RootModuleInstance.Child("child", addrs.NoKey).Resource( 7432 addrs.ManagedResourceMode, "aws_instance", "foo", 7433 ), 7434 }, 7435 }) 7436 assertNoErrors(t, diags) 7437 7438 state, diags := ctx.Apply(plan, m) 7439 if diags.HasErrors() { 7440 t.Fatalf("diags: %s", diags.Err()) 7441 } 7442 7443 mod := state.Module(addrs.RootModuleInstance.Child("child", addrs.NoKey)) 7444 if mod == nil || len(mod.Resources) != 1 { 7445 t.Fatalf("expected 1 resource, got: %#v", mod) 7446 } 7447 7448 checkStateString(t, state, ` 7449 <no state> 7450 module.child: 7451 aws_instance.foo: 7452 ID = foo 7453 provider = provider["registry.terraform.io/hashicorp/aws"] 7454 num = 2 7455 type = aws_instance 7456 `) 7457 } 7458 7459 func TestContext2Apply_targetedResourceOrphanModule(t *testing.T) { 7460 m := testModule(t, "apply-targeted-resource-orphan-module") 7461 p := testProvider("aws") 7462 p.PlanResourceChangeFn = testDiffFn 7463 7464 state := states.NewState() 7465 child := state.EnsureModule(addrs.RootModuleInstance.Child("parent", addrs.NoKey)) 7466 child.SetResourceInstanceCurrent( 7467 mustResourceInstanceAddr("aws_instance.bar").Resource, 7468 &states.ResourceInstanceObjectSrc{ 7469 Status: states.ObjectReady, 7470 AttrsJSON: []byte(`{"type":"aws_instance"}`), 7471 }, 7472 mustProviderConfig(`provider["registry.terraform.io/hashicorp/aws"]`), 7473 ) 7474 7475 ctx := testContext2(t, &ContextOpts{ 7476 Providers: map[addrs.Provider]providers.Factory{ 7477 addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p), 7478 }, 7479 }) 7480 7481 plan, diags := ctx.Plan(m, state, &PlanOpts{ 7482 Mode: plans.NormalMode, 7483 Targets: []addrs.Targetable{ 7484 addrs.RootModuleInstance.Resource( 7485 addrs.ManagedResourceMode, "aws_instance", "foo", 7486 ), 7487 }, 7488 }) 7489 assertNoErrors(t, diags) 7490 7491 if _, diags := ctx.Apply(plan, m); diags.HasErrors() { 7492 t.Fatalf("apply errors: %s", diags.Err()) 7493 } 7494 } 7495 7496 func TestContext2Apply_unknownAttribute(t *testing.T) { 7497 m := testModule(t, "apply-unknown") 7498 p := testProvider("aws") 7499 p.PlanResourceChangeFn = func(req providers.PlanResourceChangeRequest) (resp providers.PlanResourceChangeResponse) { 7500 resp = testDiffFn(req) 7501 planned := resp.PlannedState.AsValueMap() 7502 planned["unknown"] = cty.UnknownVal(cty.String) 7503 resp.PlannedState = cty.ObjectVal(planned) 7504 return resp 7505 } 7506 p.ApplyResourceChangeFn = testApplyFn 7507 7508 p.GetProviderSchemaResponse = getProviderSchemaResponseFromProviderSchema(&ProviderSchema{ 7509 ResourceTypes: map[string]*configschema.Block{ 7510 "aws_instance": { 7511 Attributes: map[string]*configschema.Attribute{ 7512 "id": {Type: cty.String, Computed: true}, 7513 "num": {Type: cty.Number, Optional: true}, 7514 "unknown": {Type: cty.String, Computed: true}, 7515 "type": {Type: cty.String, Computed: true}, 7516 }, 7517 }, 7518 }, 7519 }) 7520 7521 ctx := testContext2(t, &ContextOpts{ 7522 Providers: map[addrs.Provider]providers.Factory{ 7523 addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p), 7524 }, 7525 }) 7526 7527 plan, diags := ctx.Plan(m, states.NewState(), DefaultPlanOpts) 7528 assertNoErrors(t, diags) 7529 7530 state, diags := ctx.Apply(plan, m) 7531 if !diags.HasErrors() { 7532 t.Error("should error, because attribute 'unknown' is still unknown after apply") 7533 } 7534 7535 actual := strings.TrimSpace(state.String()) 7536 expected := strings.TrimSpace(testTerraformApplyUnknownAttrStr) 7537 if actual != expected { 7538 t.Fatalf("wrong result\n\ngot:\n%s\n\nwant:\n%s", actual, expected) 7539 } 7540 } 7541 7542 func TestContext2Apply_unknownAttributeInterpolate(t *testing.T) { 7543 m := testModule(t, "apply-unknown-interpolate") 7544 p := testProvider("aws") 7545 p.PlanResourceChangeFn = testDiffFn 7546 ctx := testContext2(t, &ContextOpts{ 7547 Providers: map[addrs.Provider]providers.Factory{ 7548 addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p), 7549 }, 7550 }) 7551 7552 if _, diags := ctx.Plan(m, states.NewState(), DefaultPlanOpts); diags == nil { 7553 t.Fatal("should error") 7554 } 7555 } 7556 7557 func TestContext2Apply_vars(t *testing.T) { 7558 fixture := contextFixtureApplyVars(t) 7559 opts := fixture.ContextOpts() 7560 ctx := testContext2(t, opts) 7561 m := fixture.Config 7562 7563 diags := ctx.Validate(m) 7564 if len(diags) != 0 { 7565 t.Fatalf("bad: %s", diags.ErrWithWarnings()) 7566 } 7567 7568 variables := InputValues{ 7569 "foo": &InputValue{ 7570 Value: cty.StringVal("us-east-1"), 7571 SourceType: ValueFromCaller, 7572 }, 7573 "bar": &InputValue{ 7574 // This one is not explicitly set but that's okay because it 7575 // has a declared default, which Terraform Core will use instead. 7576 Value: cty.NilVal, 7577 SourceType: ValueFromCaller, 7578 }, 7579 "test_list": &InputValue{ 7580 Value: cty.ListVal([]cty.Value{ 7581 cty.StringVal("Hello"), 7582 cty.StringVal("World"), 7583 }), 7584 SourceType: ValueFromCaller, 7585 }, 7586 "test_map": &InputValue{ 7587 Value: cty.MapVal(map[string]cty.Value{ 7588 "Hello": cty.StringVal("World"), 7589 "Foo": cty.StringVal("Bar"), 7590 "Baz": cty.StringVal("Foo"), 7591 }), 7592 SourceType: ValueFromCaller, 7593 }, 7594 "amis": &InputValue{ 7595 Value: cty.MapVal(map[string]cty.Value{ 7596 "us-east-1": cty.StringVal("override"), 7597 }), 7598 SourceType: ValueFromCaller, 7599 }, 7600 } 7601 7602 plan, diags := ctx.Plan(m, states.NewState(), &PlanOpts{ 7603 Mode: plans.NormalMode, 7604 SetVariables: variables, 7605 }) 7606 assertNoErrors(t, diags) 7607 7608 state, diags := ctx.Apply(plan, m) 7609 if diags.HasErrors() { 7610 t.Fatalf("err: %s", diags.Err()) 7611 } 7612 7613 got := strings.TrimSpace(state.String()) 7614 want := strings.TrimSpace(testTerraformApplyVarsStr) 7615 if got != want { 7616 t.Errorf("wrong result\n\ngot:\n%s\n\nwant:\n%s", got, want) 7617 } 7618 } 7619 7620 func TestContext2Apply_varsEnv(t *testing.T) { 7621 fixture := contextFixtureApplyVarsEnv(t) 7622 opts := fixture.ContextOpts() 7623 ctx := testContext2(t, opts) 7624 m := fixture.Config 7625 7626 diags := ctx.Validate(m) 7627 if len(diags) != 0 { 7628 t.Fatalf("bad: %s", diags.ErrWithWarnings()) 7629 } 7630 7631 variables := InputValues{ 7632 "string": &InputValue{ 7633 Value: cty.StringVal("baz"), 7634 SourceType: ValueFromEnvVar, 7635 }, 7636 "list": &InputValue{ 7637 Value: cty.ListVal([]cty.Value{ 7638 cty.StringVal("Hello"), 7639 cty.StringVal("World"), 7640 }), 7641 SourceType: ValueFromEnvVar, 7642 }, 7643 "map": &InputValue{ 7644 Value: cty.MapVal(map[string]cty.Value{ 7645 "Hello": cty.StringVal("World"), 7646 "Foo": cty.StringVal("Bar"), 7647 "Baz": cty.StringVal("Foo"), 7648 }), 7649 SourceType: ValueFromEnvVar, 7650 }, 7651 } 7652 7653 plan, diags := ctx.Plan(m, states.NewState(), &PlanOpts{ 7654 Mode: plans.NormalMode, 7655 SetVariables: variables, 7656 }) 7657 assertNoErrors(t, diags) 7658 7659 state, diags := ctx.Apply(plan, m) 7660 if diags.HasErrors() { 7661 t.Fatalf("err: %s", diags.Err()) 7662 } 7663 7664 actual := strings.TrimSpace(state.String()) 7665 expected := strings.TrimSpace(testTerraformApplyVarsEnvStr) 7666 if actual != expected { 7667 t.Errorf("wrong result\n\ngot:\n%s\n\nwant:\n%s", actual, expected) 7668 } 7669 } 7670 7671 func TestContext2Apply_createBefore_depends(t *testing.T) { 7672 m := testModule(t, "apply-depends-create-before") 7673 h := new(HookRecordApplyOrder) 7674 p := testProvider("aws") 7675 p.PlanResourceChangeFn = testDiffFn 7676 p.ApplyResourceChangeFn = testApplyFn 7677 state := states.NewState() 7678 root := state.EnsureModule(addrs.RootModuleInstance) 7679 root.SetResourceInstanceCurrent( 7680 addrs.Resource{ 7681 Mode: addrs.ManagedResourceMode, 7682 Type: "aws_instance", 7683 Name: "web", 7684 }.Instance(addrs.NoKey), 7685 &states.ResourceInstanceObjectSrc{ 7686 Status: states.ObjectReady, 7687 AttrsJSON: []byte(`{"id":"bar","require_new":"ami-old"}`), 7688 }, 7689 addrs.AbsProviderConfig{ 7690 Provider: addrs.NewDefaultProvider("aws"), 7691 Module: addrs.RootModule, 7692 }, 7693 ) 7694 7695 root.SetResourceInstanceCurrent( 7696 addrs.Resource{ 7697 Mode: addrs.ManagedResourceMode, 7698 Type: "aws_instance", 7699 Name: "lb", 7700 }.Instance(addrs.NoKey), 7701 &states.ResourceInstanceObjectSrc{ 7702 Status: states.ObjectReady, 7703 AttrsJSON: []byte(`{"id":"baz","instance":"bar"}`), 7704 Dependencies: []addrs.ConfigResource{ 7705 { 7706 Resource: addrs.Resource{ 7707 Mode: addrs.ManagedResourceMode, 7708 Type: "aws_instance", 7709 Name: "web", 7710 }, 7711 Module: addrs.RootModule, 7712 }, 7713 }, 7714 }, 7715 addrs.AbsProviderConfig{ 7716 Provider: addrs.NewDefaultProvider("aws"), 7717 Module: addrs.RootModule, 7718 }, 7719 ) 7720 7721 ctx := testContext2(t, &ContextOpts{ 7722 Hooks: []Hook{h}, 7723 Providers: map[addrs.Provider]providers.Factory{ 7724 addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p), 7725 }, 7726 }) 7727 7728 plan, diags := ctx.Plan(m, state, DefaultPlanOpts) 7729 if diags.HasErrors() { 7730 logDiagnostics(t, diags) 7731 t.Fatal("plan failed") 7732 } else { 7733 t.Logf("plan:\n%s", legacyDiffComparisonString(plan.Changes)) 7734 } 7735 7736 h.Active = true 7737 state, diags = ctx.Apply(plan, m) 7738 if diags.HasErrors() { 7739 logDiagnostics(t, diags) 7740 t.Fatal("apply failed") 7741 } 7742 7743 mod := state.RootModule() 7744 if len(mod.Resources) < 2 { 7745 t.Logf("state after apply:\n%s", state.String()) 7746 t.Fatalf("only %d resources in root module; want at least 2", len(mod.Resources)) 7747 } 7748 7749 got := strings.TrimSpace(state.String()) 7750 want := strings.TrimSpace(testTerraformApplyDependsCreateBeforeStr) 7751 if got != want { 7752 t.Fatalf("wrong final state\ngot:\n%s\n\nwant:\n%s", got, want) 7753 } 7754 7755 // Test that things were managed _in the right order_ 7756 order := h.States 7757 7758 diffs := h.Diffs 7759 if !order[0].IsNull() || diffs[0].Action == plans.Delete { 7760 t.Fatalf("should create new instance first: %#v", order) 7761 } 7762 7763 if order[1].GetAttr("id").AsString() != "baz" { 7764 t.Fatalf("update must happen after create: %#v", order[1]) 7765 } 7766 7767 if order[2].GetAttr("id").AsString() != "bar" || diffs[2].Action != plans.Delete { 7768 t.Fatalf("destroy must happen after update: %#v", order[2]) 7769 } 7770 } 7771 7772 func TestContext2Apply_singleDestroy(t *testing.T) { 7773 m := testModule(t, "apply-depends-create-before") 7774 h := new(HookRecordApplyOrder) 7775 p := testProvider("aws") 7776 invokeCount := 0 7777 p.ApplyResourceChangeFn = func(req providers.ApplyResourceChangeRequest) providers.ApplyResourceChangeResponse { 7778 invokeCount++ 7779 switch invokeCount { 7780 case 1: 7781 if req.PlannedState.IsNull() { 7782 t.Fatalf("should not destroy") 7783 } 7784 if id := req.PlannedState.GetAttr("id"); id.IsKnown() { 7785 t.Fatalf("should not have ID") 7786 } 7787 case 2: 7788 if req.PlannedState.IsNull() { 7789 t.Fatalf("should not destroy") 7790 } 7791 if id := req.PlannedState.GetAttr("id"); id.AsString() != "baz" { 7792 t.Fatalf("should have id") 7793 } 7794 case 3: 7795 if !req.PlannedState.IsNull() { 7796 t.Fatalf("should destroy") 7797 } 7798 default: 7799 t.Fatalf("bad invoke count %d", invokeCount) 7800 } 7801 return testApplyFn(req) 7802 } 7803 7804 p.PlanResourceChangeFn = testDiffFn 7805 state := states.NewState() 7806 root := state.EnsureModule(addrs.RootModuleInstance) 7807 root.SetResourceInstanceCurrent( 7808 addrs.Resource{ 7809 Mode: addrs.ManagedResourceMode, 7810 Type: "aws_instance", 7811 Name: "web", 7812 }.Instance(addrs.NoKey), 7813 &states.ResourceInstanceObjectSrc{ 7814 Status: states.ObjectReady, 7815 AttrsJSON: []byte(`{"id":"bar","require_new":"ami-old"}`), 7816 }, 7817 addrs.AbsProviderConfig{ 7818 Provider: addrs.NewDefaultProvider("aws"), 7819 Module: addrs.RootModule, 7820 }, 7821 ) 7822 7823 root.SetResourceInstanceCurrent( 7824 addrs.Resource{ 7825 Mode: addrs.ManagedResourceMode, 7826 Type: "aws_instance", 7827 Name: "lb", 7828 }.Instance(addrs.NoKey), 7829 &states.ResourceInstanceObjectSrc{ 7830 Status: states.ObjectReady, 7831 AttrsJSON: []byte(`{"id":"baz","instance":"bar"}`), 7832 Dependencies: []addrs.ConfigResource{ 7833 { 7834 Resource: addrs.Resource{ 7835 Mode: addrs.ManagedResourceMode, 7836 Type: "aws_instance", 7837 Name: "web", 7838 }, 7839 Module: addrs.RootModule, 7840 }, 7841 }, 7842 }, 7843 addrs.AbsProviderConfig{ 7844 Provider: addrs.NewDefaultProvider("aws"), 7845 Module: addrs.RootModule, 7846 }, 7847 ) 7848 7849 ctx := testContext2(t, &ContextOpts{ 7850 Hooks: []Hook{h}, 7851 Providers: map[addrs.Provider]providers.Factory{ 7852 addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p), 7853 }, 7854 }) 7855 7856 plan, diags := ctx.Plan(m, state, DefaultPlanOpts) 7857 assertNoErrors(t, diags) 7858 7859 h.Active = true 7860 _, diags = ctx.Apply(plan, m) 7861 if diags.HasErrors() { 7862 t.Fatalf("diags: %s", diags.Err()) 7863 } 7864 7865 if invokeCount != 3 { 7866 t.Fatalf("bad: %d", invokeCount) 7867 } 7868 } 7869 7870 // GH-7824 7871 func TestContext2Apply_issue7824(t *testing.T) { 7872 p := testProvider("template") 7873 p.PlanResourceChangeFn = testDiffFn 7874 p.GetProviderSchemaResponse = getProviderSchemaResponseFromProviderSchema(&ProviderSchema{ 7875 ResourceTypes: map[string]*configschema.Block{ 7876 "template_file": { 7877 Attributes: map[string]*configschema.Attribute{ 7878 "template": {Type: cty.String, Optional: true}, 7879 "__template_requires_new": {Type: cty.Bool, Optional: true}, 7880 }, 7881 }, 7882 }, 7883 }) 7884 7885 m, snap := testModuleWithSnapshot(t, "issue-7824") 7886 7887 // Apply cleanly step 0 7888 ctx := testContext2(t, &ContextOpts{ 7889 Providers: map[addrs.Provider]providers.Factory{ 7890 addrs.NewDefaultProvider("template"): testProviderFuncFixed(p), 7891 }, 7892 }) 7893 7894 plan, diags := ctx.Plan(m, states.NewState(), SimplePlanOpts(plans.NormalMode, testInputValuesUnset(m.Module.Variables))) 7895 if diags.HasErrors() { 7896 t.Fatalf("err: %s", diags.Err()) 7897 } 7898 7899 // Write / Read plan to simulate running it through a Plan file 7900 ctxOpts, m, plan, err := contextOptsForPlanViaFile(t, snap, plan) 7901 if err != nil { 7902 t.Fatalf("failed to round-trip through planfile: %s", err) 7903 } 7904 7905 ctxOpts.Providers = 7906 map[addrs.Provider]providers.Factory{ 7907 addrs.NewDefaultProvider("template"): testProviderFuncFixed(p), 7908 } 7909 7910 ctx, diags = NewContext(ctxOpts) 7911 if diags.HasErrors() { 7912 t.Fatalf("err: %s", diags.Err()) 7913 } 7914 7915 _, diags = ctx.Apply(plan, m) 7916 if diags.HasErrors() { 7917 t.Fatalf("err: %s", diags.Err()) 7918 } 7919 } 7920 7921 // This deals with the situation where a splat expression is used referring 7922 // to another resource whose count is non-constant. 7923 func TestContext2Apply_issue5254(t *testing.T) { 7924 // Create a provider. We use "template" here just to match the repro 7925 // we got from the issue itself. 7926 p := testProvider("template") 7927 p.PlanResourceChangeFn = testDiffFn 7928 p.ApplyResourceChangeFn = testApplyFn 7929 p.GetProviderSchemaResponse = getProviderSchemaResponseFromProviderSchema(&ProviderSchema{ 7930 ResourceTypes: map[string]*configschema.Block{ 7931 "template_file": { 7932 Attributes: map[string]*configschema.Attribute{ 7933 "template": {Type: cty.String, Optional: true}, 7934 "__template_requires_new": {Type: cty.Bool, Optional: true}, 7935 "id": {Type: cty.String, Computed: true}, 7936 "type": {Type: cty.String, Computed: true}, 7937 }, 7938 }, 7939 }, 7940 }) 7941 7942 // Apply cleanly step 0 7943 m := testModule(t, "issue-5254/step-0") 7944 ctx := testContext2(t, &ContextOpts{ 7945 Providers: map[addrs.Provider]providers.Factory{ 7946 addrs.NewDefaultProvider("template"): testProviderFuncFixed(p), 7947 }, 7948 }) 7949 7950 plan, diags := ctx.Plan(m, states.NewState(), SimplePlanOpts(plans.NormalMode, testInputValuesUnset(m.Module.Variables))) 7951 if diags.HasErrors() { 7952 t.Fatalf("err: %s", diags.Err()) 7953 } 7954 7955 state, diags := ctx.Apply(plan, m) 7956 if diags.HasErrors() { 7957 t.Fatalf("err: %s", diags.Err()) 7958 } 7959 7960 m, snap := testModuleWithSnapshot(t, "issue-5254/step-1") 7961 7962 // Application success. Now make the modification and store a plan 7963 ctx = testContext2(t, &ContextOpts{ 7964 Providers: map[addrs.Provider]providers.Factory{ 7965 addrs.NewDefaultProvider("template"): testProviderFuncFixed(p), 7966 }, 7967 }) 7968 7969 plan, diags = ctx.Plan(m, state, SimplePlanOpts(plans.NormalMode, testInputValuesUnset(m.Module.Variables))) 7970 if diags.HasErrors() { 7971 t.Fatalf("err: %s", diags.Err()) 7972 } 7973 7974 // Write / Read plan to simulate running it through a Plan file 7975 ctxOpts, m, plan, err := contextOptsForPlanViaFile(t, snap, plan) 7976 if err != nil { 7977 t.Fatalf("failed to round-trip through planfile: %s", err) 7978 } 7979 7980 ctxOpts.Providers = map[addrs.Provider]providers.Factory{ 7981 addrs.NewDefaultProvider("template"): testProviderFuncFixed(p), 7982 } 7983 7984 ctx, diags = NewContext(ctxOpts) 7985 if diags.HasErrors() { 7986 t.Fatalf("err: %s", diags.Err()) 7987 } 7988 7989 state, diags = ctx.Apply(plan, m) 7990 if diags.HasErrors() { 7991 t.Fatalf("err: %s", diags.Err()) 7992 } 7993 7994 actual := strings.TrimSpace(state.String()) 7995 expected := strings.TrimSpace(` 7996 template_file.child: 7997 ID = foo 7998 provider = provider["registry.terraform.io/hashicorp/template"] 7999 __template_requires_new = true 8000 template = Hi 8001 type = template_file 8002 8003 Dependencies: 8004 template_file.parent 8005 template_file.parent.0: 8006 ID = foo 8007 provider = provider["registry.terraform.io/hashicorp/template"] 8008 template = Hi 8009 type = template_file 8010 `) 8011 if actual != expected { 8012 t.Fatalf("wrong final state\ngot:\n%s\n\nwant:\n%s", actual, expected) 8013 } 8014 } 8015 8016 func TestContext2Apply_targetedWithTaintedInState(t *testing.T) { 8017 p := testProvider("aws") 8018 p.PlanResourceChangeFn = testDiffFn 8019 p.ApplyResourceChangeFn = testApplyFn 8020 m, snap := testModuleWithSnapshot(t, "apply-tainted-targets") 8021 8022 state := states.NewState() 8023 root := state.EnsureModule(addrs.RootModuleInstance) 8024 root.SetResourceInstanceCurrent( 8025 mustResourceInstanceAddr("aws_instance.ifailedprovisioners").Resource, 8026 &states.ResourceInstanceObjectSrc{ 8027 Status: states.ObjectTainted, 8028 AttrsJSON: []byte(`{"id":"ifailedprovisioners"}`), 8029 }, 8030 mustProviderConfig(`provider["registry.terraform.io/hashicorp/aws"]`), 8031 ) 8032 8033 ctx := testContext2(t, &ContextOpts{ 8034 Providers: map[addrs.Provider]providers.Factory{ 8035 addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p), 8036 }, 8037 }) 8038 8039 plan, diags := ctx.Plan(m, state, &PlanOpts{ 8040 Mode: plans.NormalMode, 8041 Targets: []addrs.Targetable{ 8042 addrs.RootModuleInstance.Resource( 8043 addrs.ManagedResourceMode, "aws_instance", "iambeingadded", 8044 ), 8045 }, 8046 }) 8047 if diags.HasErrors() { 8048 t.Fatalf("err: %s", diags.Err()) 8049 } 8050 8051 // Write / Read plan to simulate running it through a Plan file 8052 ctxOpts, m, plan, err := contextOptsForPlanViaFile(t, snap, plan) 8053 if err != nil { 8054 t.Fatalf("failed to round-trip through planfile: %s", err) 8055 } 8056 8057 ctxOpts.Providers = map[addrs.Provider]providers.Factory{ 8058 addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p), 8059 } 8060 8061 ctx, diags = NewContext(ctxOpts) 8062 if diags.HasErrors() { 8063 t.Fatalf("err: %s", diags.Err()) 8064 } 8065 8066 s, diags := ctx.Apply(plan, m) 8067 if diags.HasErrors() { 8068 t.Fatalf("err: %s", diags.Err()) 8069 } 8070 8071 actual := strings.TrimSpace(s.String()) 8072 expected := strings.TrimSpace(` 8073 aws_instance.iambeingadded: 8074 ID = foo 8075 provider = provider["registry.terraform.io/hashicorp/aws"] 8076 type = aws_instance 8077 aws_instance.ifailedprovisioners: (tainted) 8078 ID = ifailedprovisioners 8079 provider = provider["registry.terraform.io/hashicorp/aws"] 8080 `) 8081 if actual != expected { 8082 t.Fatalf("expected state: \n%s\ngot: \n%s", expected, actual) 8083 } 8084 } 8085 8086 // Higher level test exposing the bug this covers in 8087 // TestResource_ignoreChangesRequired 8088 func TestContext2Apply_ignoreChangesCreate(t *testing.T) { 8089 m := testModule(t, "apply-ignore-changes-create") 8090 p := testProvider("aws") 8091 p.PlanResourceChangeFn = testDiffFn 8092 p.ApplyResourceChangeFn = testApplyFn 8093 8094 instanceSchema := p.GetProviderSchemaResponse.ResourceTypes["aws_instance"].Block 8095 instanceSchema.Attributes["required_field"] = &configschema.Attribute{ 8096 Type: cty.String, 8097 Required: true, 8098 } 8099 8100 ctx := testContext2(t, &ContextOpts{ 8101 Providers: map[addrs.Provider]providers.Factory{ 8102 addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p), 8103 }, 8104 }) 8105 8106 plan, diags := ctx.Plan(m, states.NewState(), DefaultPlanOpts) 8107 if diags.HasErrors() { 8108 t.Fatalf("diags: %s", diags.Err()) 8109 } else { 8110 t.Logf(legacyDiffComparisonString(plan.Changes)) 8111 } 8112 8113 state, diags := ctx.Apply(plan, m) 8114 if diags.HasErrors() { 8115 t.Fatalf("diags: %s", diags.Err()) 8116 } 8117 8118 mod := state.RootModule() 8119 if len(mod.Resources) != 1 { 8120 t.Fatalf("bad: %s", state) 8121 } 8122 8123 actual := strings.TrimSpace(state.String()) 8124 // Expect no changes from original state 8125 expected := strings.TrimSpace(` 8126 aws_instance.foo: 8127 ID = foo 8128 provider = provider["registry.terraform.io/hashicorp/aws"] 8129 required_field = set 8130 type = aws_instance 8131 `) 8132 if actual != expected { 8133 t.Fatalf("expected:\n%s\ngot:\n%s", expected, actual) 8134 } 8135 } 8136 8137 func TestContext2Apply_ignoreChangesWithDep(t *testing.T) { 8138 m := testModule(t, "apply-ignore-changes-dep") 8139 p := testProvider("aws") 8140 8141 p.PlanResourceChangeFn = func(req providers.PlanResourceChangeRequest) (resp providers.PlanResourceChangeResponse) { 8142 resp.PlannedState = req.ProposedNewState 8143 8144 switch req.TypeName { 8145 case "aws_instance": 8146 resp.RequiresReplace = append(resp.RequiresReplace, cty.Path{cty.GetAttrStep{Name: "ami"}}) 8147 case "aws_eip": 8148 return testDiffFn(req) 8149 default: 8150 t.Fatalf("Unexpected type: %s", req.TypeName) 8151 } 8152 return 8153 } 8154 8155 state := states.NewState() 8156 root := state.EnsureModule(addrs.RootModuleInstance) 8157 root.SetResourceInstanceCurrent( 8158 mustResourceInstanceAddr("aws_instance.foo[0]").Resource, 8159 &states.ResourceInstanceObjectSrc{ 8160 Status: states.ObjectReady, 8161 AttrsJSON: []byte(`{"id":"i-abc123","ami":"ami-abcd1234"}`), 8162 }, 8163 mustProviderConfig(`provider["registry.terraform.io/hashicorp/aws"]`), 8164 ) 8165 root.SetResourceInstanceCurrent( 8166 mustResourceInstanceAddr("aws_instance.foo[1]").Resource, 8167 &states.ResourceInstanceObjectSrc{ 8168 Status: states.ObjectReady, 8169 AttrsJSON: []byte(`{"id":"i-bcd234","ami":"i-bcd234"}`), 8170 }, 8171 mustProviderConfig(`provider["registry.terraform.io/hashicorp/aws"]`), 8172 ) 8173 root.SetResourceInstanceCurrent( 8174 mustResourceInstanceAddr("aws_eip.foo[0]").Resource, 8175 &states.ResourceInstanceObjectSrc{ 8176 Status: states.ObjectReady, 8177 AttrsJSON: []byte(`{"id":"eip-abc123","instance":"i-abc123"}`), 8178 Dependencies: []addrs.ConfigResource{ 8179 { 8180 Resource: addrs.Resource{ 8181 Mode: addrs.ManagedResourceMode, 8182 Type: "aws_instance", 8183 Name: "foo", 8184 }, 8185 Module: addrs.RootModule, 8186 }, 8187 }, 8188 }, 8189 mustProviderConfig(`provider["registry.terraform.io/hashicorp/aws"]`), 8190 ) 8191 root.SetResourceInstanceCurrent( 8192 mustResourceInstanceAddr("aws_eip.foo[1]").Resource, 8193 &states.ResourceInstanceObjectSrc{ 8194 Status: states.ObjectReady, 8195 AttrsJSON: []byte(`{"id":"eip-bcd234","instance":"i-bcd234"}`), 8196 Dependencies: []addrs.ConfigResource{ 8197 { 8198 Resource: addrs.Resource{ 8199 Mode: addrs.ManagedResourceMode, 8200 Type: "aws_instance", 8201 Name: "foo", 8202 }, 8203 Module: addrs.RootModule, 8204 }, 8205 }, 8206 }, 8207 mustProviderConfig(`provider["registry.terraform.io/hashicorp/aws"]`), 8208 ) 8209 8210 ctx := testContext2(t, &ContextOpts{ 8211 Providers: map[addrs.Provider]providers.Factory{ 8212 addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p), 8213 }, 8214 }) 8215 8216 plan, diags := ctx.Plan(m, state.DeepCopy(), DefaultPlanOpts) 8217 assertNoErrors(t, diags) 8218 8219 s, diags := ctx.Apply(plan, m) 8220 assertNoErrors(t, diags) 8221 8222 actual := strings.TrimSpace(s.String()) 8223 expected := strings.TrimSpace(state.String()) 8224 if actual != expected { 8225 t.Fatalf("expected:\n%s\n\ngot:\n%s", expected, actual) 8226 } 8227 } 8228 8229 func TestContext2Apply_ignoreChangesAll(t *testing.T) { 8230 m := testModule(t, "apply-ignore-changes-all") 8231 p := testProvider("aws") 8232 p.PlanResourceChangeFn = testDiffFn 8233 p.ApplyResourceChangeFn = testApplyFn 8234 8235 instanceSchema := p.GetProviderSchemaResponse.ResourceTypes["aws_instance"].Block 8236 instanceSchema.Attributes["required_field"] = &configschema.Attribute{ 8237 Type: cty.String, 8238 Required: true, 8239 } 8240 8241 ctx := testContext2(t, &ContextOpts{ 8242 Providers: map[addrs.Provider]providers.Factory{ 8243 addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p), 8244 }, 8245 }) 8246 8247 plan, diags := ctx.Plan(m, states.NewState(), DefaultPlanOpts) 8248 if diags.HasErrors() { 8249 logDiagnostics(t, diags) 8250 t.Fatal("plan failed") 8251 } else { 8252 t.Logf(legacyDiffComparisonString(plan.Changes)) 8253 } 8254 8255 state, diags := ctx.Apply(plan, m) 8256 assertNoErrors(t, diags) 8257 8258 mod := state.RootModule() 8259 if len(mod.Resources) != 1 { 8260 t.Fatalf("bad: %s", state) 8261 } 8262 8263 actual := strings.TrimSpace(state.String()) 8264 // Expect no changes from original state 8265 expected := strings.TrimSpace(` 8266 aws_instance.foo: 8267 ID = foo 8268 provider = provider["registry.terraform.io/hashicorp/aws"] 8269 required_field = set 8270 type = aws_instance 8271 `) 8272 if actual != expected { 8273 t.Fatalf("expected:\n%s\ngot:\n%s", expected, actual) 8274 } 8275 } 8276 8277 // https://github.com/hashicorp/terraform/issues/7378 8278 func TestContext2Apply_destroyNestedModuleWithAttrsReferencingResource(t *testing.T) { 8279 m, snap := testModuleWithSnapshot(t, "apply-destroy-nested-module-with-attrs") 8280 p := testProvider("null") 8281 p.PlanResourceChangeFn = testDiffFn 8282 8283 var state *states.State 8284 { 8285 ctx := testContext2(t, &ContextOpts{ 8286 Providers: map[addrs.Provider]providers.Factory{ 8287 addrs.NewDefaultProvider("null"): testProviderFuncFixed(p), 8288 }, 8289 }) 8290 8291 // First plan and apply a create operation 8292 plan, diags := ctx.Plan(m, states.NewState(), DefaultPlanOpts) 8293 assertNoErrors(t, diags) 8294 8295 state, diags = ctx.Apply(plan, m) 8296 if diags.HasErrors() { 8297 t.Fatalf("apply err: %s", diags.Err()) 8298 } 8299 } 8300 8301 { 8302 ctx := testContext2(t, &ContextOpts{ 8303 Providers: map[addrs.Provider]providers.Factory{ 8304 addrs.NewDefaultProvider("null"): testProviderFuncFixed(p), 8305 }, 8306 }) 8307 8308 plan, diags := ctx.Plan(m, state, &PlanOpts{ 8309 Mode: plans.DestroyMode, 8310 }) 8311 if diags.HasErrors() { 8312 t.Fatalf("destroy plan err: %s", diags.Err()) 8313 } 8314 8315 ctxOpts, m, plan, err := contextOptsForPlanViaFile(t, snap, plan) 8316 if err != nil { 8317 t.Fatalf("failed to round-trip through planfile: %s", err) 8318 } 8319 8320 ctxOpts.Providers = map[addrs.Provider]providers.Factory{ 8321 addrs.NewDefaultProvider("null"): testProviderFuncFixed(p), 8322 } 8323 8324 ctx, diags = NewContext(ctxOpts) 8325 if diags.HasErrors() { 8326 t.Fatalf("err: %s", diags.Err()) 8327 } 8328 8329 state, diags = ctx.Apply(plan, m) 8330 if diags.HasErrors() { 8331 t.Fatalf("destroy apply err: %s", diags.Err()) 8332 } 8333 } 8334 8335 if !state.Empty() { 8336 t.Fatalf("state after apply: %s\nwant empty state", spew.Sdump(state)) 8337 } 8338 } 8339 8340 // If a data source explicitly depends on another resource, it's because we need 8341 // that resource to be applied first. 8342 func TestContext2Apply_dataDependsOn(t *testing.T) { 8343 p := testProvider("null") 8344 m := testModuleInline(t, map[string]string{ 8345 "main.tf": ` 8346 resource "null_instance" "write" { 8347 foo = "attribute" 8348 } 8349 8350 data "null_data_source" "read" { 8351 count = 1 8352 depends_on = ["null_instance.write"] 8353 } 8354 8355 resource "null_instance" "depends" { 8356 foo = data.null_data_source.read[0].foo 8357 } 8358 `}) 8359 8360 ctx := testContext2(t, &ContextOpts{ 8361 Providers: map[addrs.Provider]providers.Factory{ 8362 addrs.NewDefaultProvider("null"): testProviderFuncFixed(p), 8363 }, 8364 }) 8365 8366 // the "provisioner" here writes to this variable, because the intent is to 8367 // create a dependency which can't be viewed through the graph, and depends 8368 // solely on the configuration providing "depends_on" 8369 provisionerOutput := "" 8370 8371 p.ApplyResourceChangeFn = func(req providers.ApplyResourceChangeRequest) providers.ApplyResourceChangeResponse { 8372 // the side effect of the resource being applied 8373 provisionerOutput = "APPLIED" 8374 return testApplyFn(req) 8375 } 8376 8377 p.PlanResourceChangeFn = testDiffFn 8378 p.ReadDataSourceFn = func(req providers.ReadDataSourceRequest) providers.ReadDataSourceResponse { 8379 return providers.ReadDataSourceResponse{ 8380 State: cty.ObjectVal(map[string]cty.Value{ 8381 "id": cty.StringVal("boop"), 8382 "foo": cty.StringVal(provisionerOutput), 8383 }), 8384 } 8385 } 8386 8387 plan, diags := ctx.Plan(m, states.NewState(), DefaultPlanOpts) 8388 assertNoErrors(t, diags) 8389 8390 state, diags := ctx.Apply(plan, m) 8391 assertNoErrors(t, diags) 8392 8393 root := state.Module(addrs.RootModuleInstance) 8394 is := root.ResourceInstance(addrs.Resource{ 8395 Mode: addrs.DataResourceMode, 8396 Type: "null_data_source", 8397 Name: "read", 8398 }.Instance(addrs.IntKey(0))) 8399 if is == nil { 8400 t.Fatal("data resource instance is not present in state; should be") 8401 } 8402 var attrs map[string]interface{} 8403 err := json.Unmarshal(is.Current.AttrsJSON, &attrs) 8404 if err != nil { 8405 t.Fatal(err) 8406 } 8407 actual := attrs["foo"] 8408 expected := "APPLIED" 8409 if actual != expected { 8410 t.Fatalf("bad:\n%s", strings.TrimSpace(state.String())) 8411 } 8412 8413 // run another plan to make sure the data source doesn't show as a change 8414 plan, diags = ctx.Plan(m, state, DefaultPlanOpts) 8415 assertNoErrors(t, diags) 8416 8417 for _, c := range plan.Changes.Resources { 8418 if c.Action != plans.NoOp { 8419 t.Fatalf("unexpected change for %s", c.Addr) 8420 } 8421 } 8422 8423 // now we cause a change in the first resource, which should trigger a plan 8424 // in the data source, and the resource that depends on the data source 8425 // must plan a change as well. 8426 m = testModuleInline(t, map[string]string{ 8427 "main.tf": ` 8428 resource "null_instance" "write" { 8429 foo = "new" 8430 } 8431 8432 data "null_data_source" "read" { 8433 depends_on = ["null_instance.write"] 8434 } 8435 8436 resource "null_instance" "depends" { 8437 foo = data.null_data_source.read.foo 8438 } 8439 `}) 8440 8441 p.ApplyResourceChangeFn = func(req providers.ApplyResourceChangeRequest) providers.ApplyResourceChangeResponse { 8442 // the side effect of the resource being applied 8443 provisionerOutput = "APPLIED_AGAIN" 8444 return testApplyFn(req) 8445 } 8446 8447 ctx = testContext2(t, &ContextOpts{ 8448 Providers: map[addrs.Provider]providers.Factory{ 8449 addrs.NewDefaultProvider("null"): testProviderFuncFixed(p), 8450 }, 8451 }) 8452 8453 plan, diags = ctx.Plan(m, state, DefaultPlanOpts) 8454 assertNoErrors(t, diags) 8455 8456 expectedChanges := map[string]plans.Action{ 8457 "null_instance.write": plans.Update, 8458 "data.null_data_source.read": plans.Read, 8459 "null_instance.depends": plans.Update, 8460 } 8461 8462 for _, c := range plan.Changes.Resources { 8463 if c.Action != expectedChanges[c.Addr.String()] { 8464 t.Errorf("unexpected %s for %s", c.Action, c.Addr) 8465 } 8466 } 8467 } 8468 8469 func TestContext2Apply_terraformWorkspace(t *testing.T) { 8470 m := testModule(t, "apply-terraform-workspace") 8471 p := testProvider("aws") 8472 p.PlanResourceChangeFn = testDiffFn 8473 8474 ctx := testContext2(t, &ContextOpts{ 8475 Meta: &ContextMeta{Env: "foo"}, 8476 Providers: map[addrs.Provider]providers.Factory{ 8477 addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p), 8478 }, 8479 }) 8480 8481 plan, diags := ctx.Plan(m, states.NewState(), DefaultPlanOpts) 8482 assertNoErrors(t, diags) 8483 8484 state, diags := ctx.Apply(plan, m) 8485 if diags.HasErrors() { 8486 t.Fatalf("diags: %s", diags.Err()) 8487 } 8488 8489 actual := state.RootModule().OutputValues["output"] 8490 expected := cty.StringVal("foo") 8491 if actual == nil || actual.Value != expected { 8492 t.Fatalf("wrong value\ngot: %#v\nwant: %#v", actual.Value, expected) 8493 } 8494 } 8495 8496 // verify that multiple config references only create a single depends_on entry 8497 func TestContext2Apply_multiRef(t *testing.T) { 8498 m := testModule(t, "apply-multi-ref") 8499 p := testProvider("aws") 8500 p.PlanResourceChangeFn = testDiffFn 8501 ctx := testContext2(t, &ContextOpts{ 8502 Providers: map[addrs.Provider]providers.Factory{ 8503 addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p), 8504 }, 8505 }) 8506 8507 plan, diags := ctx.Plan(m, states.NewState(), DefaultPlanOpts) 8508 assertNoErrors(t, diags) 8509 8510 state, diags := ctx.Apply(plan, m) 8511 if diags.HasErrors() { 8512 t.Fatalf("err: %s", diags.Err()) 8513 } 8514 8515 deps := state.Modules[""].Resources["aws_instance.other"].Instances[addrs.NoKey].Current.Dependencies 8516 if len(deps) != 1 || deps[0].String() != "aws_instance.create" { 8517 t.Fatalf("expected 1 depends_on entry for aws_instance.create, got %q", deps) 8518 } 8519 } 8520 8521 func TestContext2Apply_targetedModuleRecursive(t *testing.T) { 8522 m := testModule(t, "apply-targeted-module-recursive") 8523 p := testProvider("aws") 8524 p.PlanResourceChangeFn = testDiffFn 8525 p.ApplyResourceChangeFn = testApplyFn 8526 ctx := testContext2(t, &ContextOpts{ 8527 Providers: map[addrs.Provider]providers.Factory{ 8528 addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p), 8529 }, 8530 }) 8531 8532 plan, diags := ctx.Plan(m, states.NewState(), &PlanOpts{ 8533 Mode: plans.NormalMode, 8534 Targets: []addrs.Targetable{ 8535 addrs.RootModuleInstance.Child("child", addrs.NoKey), 8536 }, 8537 }) 8538 assertNoErrors(t, diags) 8539 8540 state, diags := ctx.Apply(plan, m) 8541 if diags.HasErrors() { 8542 t.Fatalf("err: %s", diags.Err()) 8543 } 8544 8545 mod := state.Module( 8546 addrs.RootModuleInstance.Child("child", addrs.NoKey).Child("subchild", addrs.NoKey), 8547 ) 8548 if mod == nil { 8549 t.Fatalf("no subchild module found in the state!\n\n%#v", state) 8550 } 8551 if len(mod.Resources) != 1 { 8552 t.Fatalf("expected 1 resources, got: %#v", mod.Resources) 8553 } 8554 8555 checkStateString(t, state, ` 8556 <no state> 8557 module.child.subchild: 8558 aws_instance.foo: 8559 ID = foo 8560 provider = provider["registry.terraform.io/hashicorp/aws"] 8561 num = 2 8562 type = aws_instance 8563 `) 8564 } 8565 8566 func TestContext2Apply_localVal(t *testing.T) { 8567 m := testModule(t, "apply-local-val") 8568 ctx := testContext2(t, &ContextOpts{ 8569 Providers: map[addrs.Provider]providers.Factory{}, 8570 }) 8571 8572 plan, diags := ctx.Plan(m, states.NewState(), DefaultPlanOpts) 8573 assertNoErrors(t, diags) 8574 8575 state, diags := ctx.Apply(plan, m) 8576 if diags.HasErrors() { 8577 t.Fatalf("error during apply: %s", diags.Err()) 8578 } 8579 8580 got := strings.TrimSpace(state.String()) 8581 want := strings.TrimSpace(` 8582 <no state> 8583 Outputs: 8584 8585 result_1 = hello 8586 result_3 = hello world 8587 `) 8588 if got != want { 8589 t.Fatalf("wrong final state\ngot:\n%s\nwant:\n%s", got, want) 8590 } 8591 } 8592 8593 func TestContext2Apply_destroyWithLocals(t *testing.T) { 8594 m := testModule(t, "apply-destroy-with-locals") 8595 p := testProvider("aws") 8596 p.PlanResourceChangeFn = testDiffFn 8597 8598 state := states.NewState() 8599 root := state.EnsureModule(addrs.RootModuleInstance) 8600 root.SetResourceInstanceCurrent( 8601 mustResourceInstanceAddr("aws_instance.foo").Resource, 8602 &states.ResourceInstanceObjectSrc{ 8603 Status: states.ObjectReady, 8604 AttrsJSON: []byte(`{"id":"foo"}`), 8605 }, 8606 mustProviderConfig(`provider["registry.terraform.io/hashicorp/aws"]`), 8607 ) 8608 root.SetOutputValue("name", cty.StringVal("test-bar"), false) 8609 8610 ctx := testContext2(t, &ContextOpts{ 8611 Providers: map[addrs.Provider]providers.Factory{ 8612 addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p), 8613 }, 8614 }) 8615 8616 plan, diags := ctx.Plan(m, state, &PlanOpts{ 8617 Mode: plans.DestroyMode, 8618 }) 8619 assertNoErrors(t, diags) 8620 8621 s, diags := ctx.Apply(plan, m) 8622 if diags.HasErrors() { 8623 t.Fatalf("error during apply: %s", diags.Err()) 8624 } 8625 8626 got := strings.TrimSpace(s.String()) 8627 want := strings.TrimSpace(`<no state>`) 8628 if got != want { 8629 t.Fatalf("wrong final state\ngot:\n%s\nwant:\n%s", got, want) 8630 } 8631 } 8632 8633 func TestContext2Apply_providerWithLocals(t *testing.T) { 8634 m := testModule(t, "provider-with-locals") 8635 p := testProvider("aws") 8636 8637 providerRegion := "" 8638 // this should not be overridden during destroy 8639 p.ConfigureProviderFn = func(req providers.ConfigureProviderRequest) (resp providers.ConfigureProviderResponse) { 8640 val := req.Config.GetAttr("region") 8641 if !val.IsNull() { 8642 providerRegion = val.AsString() 8643 } 8644 8645 return 8646 } 8647 8648 p.PlanResourceChangeFn = testDiffFn 8649 ctx := testContext2(t, &ContextOpts{ 8650 Providers: map[addrs.Provider]providers.Factory{ 8651 addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p), 8652 }, 8653 }) 8654 8655 plan, diags := ctx.Plan(m, states.NewState(), DefaultPlanOpts) 8656 assertNoErrors(t, diags) 8657 8658 state, diags := ctx.Apply(plan, m) 8659 if diags.HasErrors() { 8660 t.Fatalf("err: %s", diags.Err()) 8661 } 8662 8663 ctx = testContext2(t, &ContextOpts{ 8664 Providers: map[addrs.Provider]providers.Factory{ 8665 addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p), 8666 }, 8667 }) 8668 8669 plan, diags = ctx.Plan(m, state, &PlanOpts{ 8670 Mode: plans.DestroyMode, 8671 }) 8672 assertNoErrors(t, diags) 8673 8674 state, diags = ctx.Apply(plan, m) 8675 if diags.HasErrors() { 8676 t.Fatalf("err: %s", diags.Err()) 8677 } 8678 8679 if state.HasManagedResourceInstanceObjects() { 8680 t.Fatal("expected no state, got:", state) 8681 } 8682 8683 if providerRegion != "bar" { 8684 t.Fatalf("expected region %q, got: %q", "bar", providerRegion) 8685 } 8686 } 8687 8688 func TestContext2Apply_destroyWithProviders(t *testing.T) { 8689 m := testModule(t, "destroy-module-with-provider") 8690 p := testProvider("aws") 8691 p.PlanResourceChangeFn = testDiffFn 8692 8693 state := states.NewState() 8694 removed := state.EnsureModule(addrs.RootModuleInstance.Child("mod", addrs.NoKey).Child("removed", addrs.NoKey)) 8695 removed.SetResourceInstanceCurrent( 8696 mustResourceInstanceAddr("aws_instance.child").Resource, 8697 &states.ResourceInstanceObjectSrc{ 8698 Status: states.ObjectReady, 8699 AttrsJSON: []byte(`{"id":"bar"}`), 8700 }, 8701 mustProviderConfig(`provider["registry.terraform.io/hashicorp/aws"].baz`), 8702 ) 8703 8704 ctx := testContext2(t, &ContextOpts{ 8705 Providers: map[addrs.Provider]providers.Factory{ 8706 addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p), 8707 }, 8708 }) 8709 8710 // test that we can't destroy if the provider is missing 8711 if _, diags := ctx.Plan(m, state, &PlanOpts{Mode: plans.DestroyMode}); diags == nil { 8712 t.Fatal("expected plan error, provider.aws.baz doesn't exist") 8713 } 8714 8715 // correct the state 8716 state.Modules["module.mod.module.removed"].Resources["aws_instance.child"].ProviderConfig = mustProviderConfig(`provider["registry.terraform.io/hashicorp/aws"].bar`) 8717 8718 ctx = testContext2(t, &ContextOpts{ 8719 Providers: map[addrs.Provider]providers.Factory{ 8720 addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p), 8721 }, 8722 }) 8723 8724 plan, diags := ctx.Plan(m, state, &PlanOpts{ 8725 Mode: plans.DestroyMode, 8726 }) 8727 assertNoErrors(t, diags) 8728 8729 state, diags = ctx.Apply(plan, m) 8730 if diags.HasErrors() { 8731 t.Fatalf("error during apply: %s", diags.Err()) 8732 } 8733 8734 got := strings.TrimSpace(state.String()) 8735 8736 want := strings.TrimSpace("<no state>") 8737 if got != want { 8738 t.Fatalf("wrong final state\ngot:\n%s\nwant:\n%s", got, want) 8739 } 8740 } 8741 8742 func TestContext2Apply_providersFromState(t *testing.T) { 8743 m := configs.NewEmptyConfig() 8744 p := testProvider("aws") 8745 p.PlanResourceChangeFn = testDiffFn 8746 8747 implicitProviderState := states.NewState() 8748 impRoot := implicitProviderState.EnsureModule(addrs.RootModuleInstance) 8749 impRoot.SetResourceInstanceCurrent( 8750 mustResourceInstanceAddr("aws_instance.a").Resource, 8751 &states.ResourceInstanceObjectSrc{ 8752 Status: states.ObjectReady, 8753 AttrsJSON: []byte(`{"id":"bar"}`), 8754 }, 8755 mustProviderConfig(`provider["registry.terraform.io/hashicorp/aws"]`), 8756 ) 8757 8758 aliasedProviderState := states.NewState() 8759 aliasRoot := aliasedProviderState.EnsureModule(addrs.RootModuleInstance) 8760 aliasRoot.SetResourceInstanceCurrent( 8761 mustResourceInstanceAddr("aws_instance.a").Resource, 8762 &states.ResourceInstanceObjectSrc{ 8763 Status: states.ObjectReady, 8764 AttrsJSON: []byte(`{"id":"bar"}`), 8765 }, 8766 mustProviderConfig(`provider["registry.terraform.io/hashicorp/aws"].bar`), 8767 ) 8768 8769 moduleProviderState := states.NewState() 8770 moduleProviderRoot := moduleProviderState.EnsureModule(addrs.RootModuleInstance) 8771 moduleProviderRoot.SetResourceInstanceCurrent( 8772 mustResourceInstanceAddr("aws_instance.a").Resource, 8773 &states.ResourceInstanceObjectSrc{ 8774 Status: states.ObjectReady, 8775 AttrsJSON: []byte(`{"id":"bar"}`), 8776 }, 8777 mustProviderConfig(`module.child.provider["registry.terraform.io/hashicorp/aws"]`), 8778 ) 8779 8780 for _, tc := range []struct { 8781 name string 8782 state *states.State 8783 output string 8784 err bool 8785 }{ 8786 { 8787 name: "add implicit provider", 8788 state: implicitProviderState, 8789 err: false, 8790 output: "<no state>", 8791 }, 8792 8793 // an aliased provider must be in the config to remove a resource 8794 { 8795 name: "add aliased provider", 8796 state: aliasedProviderState, 8797 err: true, 8798 }, 8799 8800 // a provider in a module implies some sort of config, so this isn't 8801 // allowed even without an alias 8802 { 8803 name: "add unaliased module provider", 8804 state: moduleProviderState, 8805 err: true, 8806 }, 8807 } { 8808 t.Run(tc.name, func(t *testing.T) { 8809 ctx := testContext2(t, &ContextOpts{ 8810 Providers: map[addrs.Provider]providers.Factory{ 8811 addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p), 8812 }, 8813 }) 8814 8815 plan, diags := ctx.Plan(m, tc.state, DefaultPlanOpts) 8816 if tc.err { 8817 if diags == nil { 8818 t.Fatal("expected error") 8819 } else { 8820 return 8821 } 8822 } 8823 if !tc.err && diags.HasErrors() { 8824 t.Fatal(diags.Err()) 8825 } 8826 8827 state, diags := ctx.Apply(plan, m) 8828 if diags.HasErrors() { 8829 t.Fatalf("diags: %s", diags.Err()) 8830 } 8831 8832 checkStateString(t, state, "<no state>") 8833 8834 }) 8835 } 8836 } 8837 8838 func TestContext2Apply_plannedInterpolatedCount(t *testing.T) { 8839 m, snap := testModuleWithSnapshot(t, "apply-interpolated-count") 8840 8841 p := testProvider("aws") 8842 p.PlanResourceChangeFn = testDiffFn 8843 8844 Providers := map[addrs.Provider]providers.Factory{ 8845 addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p), 8846 } 8847 8848 state := states.NewState() 8849 root := state.EnsureModule(addrs.RootModuleInstance) 8850 root.SetResourceInstanceCurrent( 8851 mustResourceInstanceAddr("aws_instance.test").Resource, 8852 &states.ResourceInstanceObjectSrc{ 8853 Status: states.ObjectReady, 8854 AttrsJSON: []byte(`{"id":"foo"}`), 8855 }, 8856 mustProviderConfig(`provider["registry.terraform.io/hashicorp/aws"]`), 8857 ) 8858 8859 ctx := testContext2(t, &ContextOpts{ 8860 Providers: Providers, 8861 }) 8862 8863 plan, diags := ctx.Plan(m, state, SimplePlanOpts(plans.NormalMode, testInputValuesUnset(m.Module.Variables))) 8864 if diags.HasErrors() { 8865 t.Fatalf("plan failed: %s", diags.Err()) 8866 } 8867 8868 // We'll marshal and unmarshal the plan here, to ensure that we have 8869 // a clean new context as would be created if we separately ran 8870 // terraform plan -out=tfplan && terraform apply tfplan 8871 ctxOpts, m, plan, err := contextOptsForPlanViaFile(t, snap, plan) 8872 if err != nil { 8873 t.Fatalf("failed to round-trip through planfile: %s", err) 8874 } 8875 8876 ctxOpts.Providers = Providers 8877 ctx, diags = NewContext(ctxOpts) 8878 if diags.HasErrors() { 8879 t.Fatalf("err: %s", diags.Err()) 8880 } 8881 8882 // Applying the plan should now succeed 8883 _, diags = ctx.Apply(plan, m) 8884 if diags.HasErrors() { 8885 t.Fatalf("apply failed: %s", diags.Err()) 8886 } 8887 } 8888 8889 func TestContext2Apply_plannedDestroyInterpolatedCount(t *testing.T) { 8890 m, snap := testModuleWithSnapshot(t, "plan-destroy-interpolated-count") 8891 8892 p := testProvider("aws") 8893 p.PlanResourceChangeFn = testDiffFn 8894 providers := map[addrs.Provider]providers.Factory{ 8895 addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p), 8896 } 8897 8898 state := states.NewState() 8899 root := state.EnsureModule(addrs.RootModuleInstance) 8900 root.SetResourceInstanceCurrent( 8901 mustResourceInstanceAddr("aws_instance.a[0]").Resource, 8902 &states.ResourceInstanceObjectSrc{ 8903 Status: states.ObjectReady, 8904 AttrsJSON: []byte(`{"id":"foo"}`), 8905 }, 8906 mustProviderConfig(`provider["registry.terraform.io/hashicorp/aws"]`), 8907 ) 8908 root.SetResourceInstanceCurrent( 8909 mustResourceInstanceAddr("aws_instance.a[1]").Resource, 8910 &states.ResourceInstanceObjectSrc{ 8911 Status: states.ObjectReady, 8912 AttrsJSON: []byte(`{"id":"foo"}`), 8913 }, 8914 mustProviderConfig(`provider["registry.terraform.io/hashicorp/aws"]`), 8915 ) 8916 root.SetOutputValue("out", cty.ListVal([]cty.Value{cty.StringVal("foo"), cty.StringVal("foo")}), false) 8917 8918 ctx := testContext2(t, &ContextOpts{ 8919 Providers: providers, 8920 }) 8921 8922 plan, diags := ctx.Plan(m, state, SimplePlanOpts(plans.DestroyMode, testInputValuesUnset(m.Module.Variables))) 8923 if diags.HasErrors() { 8924 t.Fatalf("plan failed: %s", diags.Err()) 8925 } 8926 8927 // We'll marshal and unmarshal the plan here, to ensure that we have 8928 // a clean new context as would be created if we separately ran 8929 // terraform plan -out=tfplan && terraform apply tfplan 8930 ctxOpts, m, plan, err := contextOptsForPlanViaFile(t, snap, plan) 8931 if err != nil { 8932 t.Fatalf("failed to round-trip through planfile: %s", err) 8933 } 8934 8935 ctxOpts.Providers = providers 8936 ctx, diags = NewContext(ctxOpts) 8937 if diags.HasErrors() { 8938 t.Fatalf("err: %s", diags.Err()) 8939 } 8940 8941 // Applying the plan should now succeed 8942 state, diags = ctx.Apply(plan, m) 8943 if diags.HasErrors() { 8944 t.Fatalf("apply failed: %s", diags.Err()) 8945 } 8946 if !state.Empty() { 8947 t.Fatalf("state not empty: %s\n", state) 8948 } 8949 } 8950 8951 func TestContext2Apply_scaleInMultivarRef(t *testing.T) { 8952 m := testModule(t, "apply-resource-scale-in") 8953 8954 p := testProvider("aws") 8955 p.PlanResourceChangeFn = testDiffFn 8956 8957 Providers := map[addrs.Provider]providers.Factory{ 8958 addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p), 8959 } 8960 8961 state := states.NewState() 8962 root := state.EnsureModule(addrs.RootModuleInstance) 8963 root.SetResourceInstanceCurrent( 8964 mustResourceInstanceAddr("aws_instance.one").Resource, 8965 &states.ResourceInstanceObjectSrc{ 8966 Status: states.ObjectReady, 8967 AttrsJSON: []byte(`{"id":"foo"}`), 8968 }, 8969 mustProviderConfig(`provider["registry.terraform.io/hashicorp/aws"]`), 8970 ) 8971 root.SetResourceInstanceCurrent( 8972 mustResourceInstanceAddr("aws_instance.two").Resource, 8973 &states.ResourceInstanceObjectSrc{ 8974 Status: states.ObjectReady, 8975 AttrsJSON: []byte(`{"id":"foo"}`), 8976 }, 8977 mustProviderConfig(`provider["registry.terraform.io/hashicorp/aws"]`), 8978 ) 8979 8980 ctx := testContext2(t, &ContextOpts{ 8981 Providers: Providers, 8982 }) 8983 8984 plan, diags := ctx.Plan(m, state, &PlanOpts{ 8985 Mode: plans.NormalMode, 8986 SetVariables: InputValues{ 8987 "instance_count": { 8988 Value: cty.NumberIntVal(0), 8989 SourceType: ValueFromCaller, 8990 }, 8991 }, 8992 }) 8993 assertNoErrors(t, diags) 8994 { 8995 addr := mustResourceInstanceAddr("aws_instance.one[0]") 8996 change := plan.Changes.ResourceInstance(addr) 8997 if change == nil { 8998 t.Fatalf("no planned change for %s", addr) 8999 } 9000 // This test was originally written with Terraform v0.11 and earlier 9001 // in mind, so it declares a no-key instance of aws_instance.one, 9002 // but its configuration sets count (to zero) and so we end up first 9003 // moving the no-key instance to the zero key and then planning to 9004 // destroy the zero key. 9005 if got, want := change.PrevRunAddr, mustResourceInstanceAddr("aws_instance.one"); !want.Equal(got) { 9006 t.Errorf("wrong previous run address for %s %s; want %s", addr, got, want) 9007 } 9008 if got, want := change.Action, plans.Delete; got != want { 9009 t.Errorf("wrong action for %s %s; want %s", addr, got, want) 9010 } 9011 if got, want := change.ActionReason, plans.ResourceInstanceDeleteBecauseCountIndex; got != want { 9012 t.Errorf("wrong action reason for %s %s; want %s", addr, got, want) 9013 } 9014 } 9015 { 9016 addr := mustResourceInstanceAddr("aws_instance.two") 9017 change := plan.Changes.ResourceInstance(addr) 9018 if change == nil { 9019 t.Fatalf("no planned change for %s", addr) 9020 } 9021 if got, want := change.PrevRunAddr, mustResourceInstanceAddr("aws_instance.two"); !want.Equal(got) { 9022 t.Errorf("wrong previous run address for %s %s; want %s", addr, got, want) 9023 } 9024 if got, want := change.Action, plans.Update; got != want { 9025 t.Errorf("wrong action for %s %s; want %s", addr, got, want) 9026 } 9027 if got, want := change.ActionReason, plans.ResourceInstanceChangeNoReason; got != want { 9028 t.Errorf("wrong action reason for %s %s; want %s", addr, got, want) 9029 } 9030 } 9031 9032 // Applying the plan should now succeed 9033 _, diags = ctx.Apply(plan, m) 9034 assertNoErrors(t, diags) 9035 } 9036 9037 func TestContext2Apply_inconsistentWithPlan(t *testing.T) { 9038 m := testModule(t, "apply-inconsistent-with-plan") 9039 p := testProvider("test") 9040 p.GetProviderSchemaResponse = getProviderSchemaResponseFromProviderSchema(&ProviderSchema{ 9041 ResourceTypes: map[string]*configschema.Block{ 9042 "test": { 9043 Attributes: map[string]*configschema.Attribute{ 9044 "id": {Type: cty.String, Computed: true}, 9045 }, 9046 }, 9047 }, 9048 }) 9049 p.PlanResourceChangeFn = func(req providers.PlanResourceChangeRequest) providers.PlanResourceChangeResponse { 9050 return providers.PlanResourceChangeResponse{ 9051 PlannedState: cty.ObjectVal(map[string]cty.Value{ 9052 "id": cty.StringVal("before"), 9053 }), 9054 } 9055 } 9056 p.ApplyResourceChangeFn = func(req providers.ApplyResourceChangeRequest) providers.ApplyResourceChangeResponse { 9057 return providers.ApplyResourceChangeResponse{ 9058 NewState: cty.ObjectVal(map[string]cty.Value{ 9059 // This is intentionally incorrect: because id was fixed at "before" 9060 // during plan, it must not change during apply. 9061 "id": cty.StringVal("after"), 9062 }), 9063 } 9064 } 9065 ctx := testContext2(t, &ContextOpts{ 9066 Providers: map[addrs.Provider]providers.Factory{ 9067 addrs.NewDefaultProvider("test"): testProviderFuncFixed(p), 9068 }, 9069 }) 9070 9071 plan, diags := ctx.Plan(m, states.NewState(), DefaultPlanOpts) 9072 assertNoErrors(t, diags) 9073 9074 _, diags = ctx.Apply(plan, m) 9075 if !diags.HasErrors() { 9076 t.Fatalf("apply succeeded; want error") 9077 } 9078 if got, want := diags.Err().Error(), "Provider produced inconsistent result after apply"; !strings.Contains(got, want) { 9079 t.Fatalf("wrong error\ngot: %s\nshould contain: %s", got, want) 9080 } 9081 } 9082 9083 // Issue 19908 was about retaining an existing object in the state when an 9084 // update to it fails and the provider does not return a partially-updated 9085 // value for it. Previously we were incorrectly removing it from the state 9086 // in that case, but instead it should be retained so the update can be 9087 // retried. 9088 func TestContext2Apply_issue19908(t *testing.T) { 9089 m := testModule(t, "apply-issue19908") 9090 p := testProvider("test") 9091 p.GetProviderSchemaResponse = getProviderSchemaResponseFromProviderSchema(&ProviderSchema{ 9092 ResourceTypes: map[string]*configschema.Block{ 9093 "test": { 9094 Attributes: map[string]*configschema.Attribute{ 9095 "baz": {Type: cty.String, Required: true}, 9096 }, 9097 }, 9098 }, 9099 }) 9100 p.PlanResourceChangeFn = func(req providers.PlanResourceChangeRequest) providers.PlanResourceChangeResponse { 9101 return providers.PlanResourceChangeResponse{ 9102 PlannedState: req.ProposedNewState, 9103 } 9104 } 9105 p.ApplyResourceChangeFn = func(req providers.ApplyResourceChangeRequest) providers.ApplyResourceChangeResponse { 9106 var diags tfdiags.Diagnostics 9107 diags = diags.Append(fmt.Errorf("update failed")) 9108 return providers.ApplyResourceChangeResponse{ 9109 Diagnostics: diags, 9110 } 9111 } 9112 ctx := testContext2(t, &ContextOpts{ 9113 Providers: map[addrs.Provider]providers.Factory{ 9114 addrs.NewDefaultProvider("test"): testProviderFuncFixed(p), 9115 }, 9116 }) 9117 9118 state := states.BuildState(func(s *states.SyncState) { 9119 s.SetResourceInstanceCurrent( 9120 addrs.Resource{ 9121 Mode: addrs.ManagedResourceMode, 9122 Type: "test", 9123 Name: "foo", 9124 }.Instance(addrs.NoKey).Absolute(addrs.RootModuleInstance), 9125 &states.ResourceInstanceObjectSrc{ 9126 AttrsJSON: []byte(`{"baz":"old"}`), 9127 Status: states.ObjectReady, 9128 }, 9129 addrs.AbsProviderConfig{ 9130 Provider: addrs.NewDefaultProvider("test"), 9131 Module: addrs.RootModule, 9132 }, 9133 ) 9134 }) 9135 9136 plan, diags := ctx.Plan(m, state, DefaultPlanOpts) 9137 assertNoErrors(t, diags) 9138 9139 state, diags = ctx.Apply(plan, m) 9140 if !diags.HasErrors() { 9141 t.Fatalf("apply succeeded; want error") 9142 } 9143 if got, want := diags.Err().Error(), "update failed"; !strings.Contains(got, want) { 9144 t.Fatalf("wrong error\ngot: %s\nshould contain: %s", got, want) 9145 } 9146 9147 mod := state.RootModule() 9148 rs := mod.Resources["test.foo"] 9149 if rs == nil { 9150 t.Fatalf("test.foo not in state after apply, but should be") 9151 } 9152 is := rs.Instances[addrs.NoKey] 9153 if is == nil { 9154 t.Fatalf("test.foo not in state after apply, but should be") 9155 } 9156 obj := is.Current 9157 if obj == nil { 9158 t.Fatalf("test.foo has no current object in state after apply, but should do") 9159 } 9160 9161 if got, want := obj.Status, states.ObjectReady; got != want { 9162 t.Errorf("test.foo has wrong status %s after apply; want %s", got, want) 9163 } 9164 if got, want := obj.AttrsJSON, []byte(`"old"`); !bytes.Contains(got, want) { 9165 t.Errorf("test.foo attributes JSON doesn't contain %s after apply\ngot: %s", want, got) 9166 } 9167 } 9168 9169 func TestContext2Apply_invalidIndexRef(t *testing.T) { 9170 p := testProvider("test") 9171 p.GetProviderSchemaResponse = getProviderSchemaResponseFromProviderSchema(&ProviderSchema{ 9172 ResourceTypes: map[string]*configschema.Block{ 9173 "test_instance": { 9174 Attributes: map[string]*configschema.Attribute{ 9175 "value": {Type: cty.String, Optional: true, Computed: true}, 9176 }, 9177 }, 9178 }, 9179 }) 9180 p.PlanResourceChangeFn = testDiffFn 9181 9182 m := testModule(t, "apply-invalid-index") 9183 c := testContext2(t, &ContextOpts{ 9184 Providers: map[addrs.Provider]providers.Factory{ 9185 addrs.NewDefaultProvider("test"): testProviderFuncFixed(p), 9186 }, 9187 }) 9188 diags := c.Validate(m) 9189 if diags.HasErrors() { 9190 t.Fatalf("unexpected validation failure: %s", diags.Err()) 9191 } 9192 9193 wantErr := `The given key does not identify an element in this collection value` 9194 _, diags = c.Plan(m, states.NewState(), DefaultPlanOpts) 9195 9196 if !diags.HasErrors() { 9197 t.Fatalf("plan succeeded; want error") 9198 } 9199 gotErr := diags.Err().Error() 9200 9201 if !strings.Contains(gotErr, wantErr) { 9202 t.Fatalf("missing expected error\ngot: %s\n\nwant: error containing %q", gotErr, wantErr) 9203 } 9204 } 9205 9206 func TestContext2Apply_moduleReplaceCycle(t *testing.T) { 9207 for _, mode := range []string{"normal", "cbd"} { 9208 var m *configs.Config 9209 9210 switch mode { 9211 case "normal": 9212 m = testModule(t, "apply-module-replace-cycle") 9213 case "cbd": 9214 m = testModule(t, "apply-module-replace-cycle-cbd") 9215 } 9216 9217 p := testProvider("aws") 9218 p.PlanResourceChangeFn = testDiffFn 9219 9220 instanceSchema := &configschema.Block{ 9221 Attributes: map[string]*configschema.Attribute{ 9222 "id": {Type: cty.String, Computed: true}, 9223 "require_new": {Type: cty.String, Optional: true}, 9224 }, 9225 } 9226 9227 p.GetProviderSchemaResponse = getProviderSchemaResponseFromProviderSchema(&ProviderSchema{ 9228 ResourceTypes: map[string]*configschema.Block{ 9229 "aws_instance": instanceSchema, 9230 }, 9231 }) 9232 9233 state := states.NewState() 9234 modA := state.EnsureModule(addrs.RootModuleInstance.Child("a", addrs.NoKey)) 9235 modA.SetResourceInstanceCurrent( 9236 addrs.Resource{ 9237 Mode: addrs.ManagedResourceMode, 9238 Type: "aws_instance", 9239 Name: "a", 9240 }.Instance(addrs.NoKey), 9241 &states.ResourceInstanceObjectSrc{ 9242 Status: states.ObjectReady, 9243 AttrsJSON: []byte(`{"id":"a","require_new":"old"}`), 9244 CreateBeforeDestroy: mode == "cbd", 9245 }, 9246 addrs.AbsProviderConfig{ 9247 Provider: addrs.NewDefaultProvider("aws"), 9248 Module: addrs.RootModule, 9249 }, 9250 ) 9251 9252 modB := state.EnsureModule(addrs.RootModuleInstance.Child("b", addrs.NoKey)) 9253 modB.SetResourceInstanceCurrent( 9254 addrs.Resource{ 9255 Mode: addrs.ManagedResourceMode, 9256 Type: "aws_instance", 9257 Name: "b", 9258 }.Instance(addrs.IntKey(0)), 9259 &states.ResourceInstanceObjectSrc{ 9260 Status: states.ObjectReady, 9261 AttrsJSON: []byte(`{"id":"b","require_new":"old"}`), 9262 }, 9263 addrs.AbsProviderConfig{ 9264 Provider: addrs.NewDefaultProvider("aws"), 9265 Module: addrs.RootModule, 9266 }, 9267 ) 9268 9269 aBefore, _ := plans.NewDynamicValue( 9270 cty.ObjectVal(map[string]cty.Value{ 9271 "id": cty.StringVal("a"), 9272 "require_new": cty.StringVal("old"), 9273 }), instanceSchema.ImpliedType()) 9274 aAfter, _ := plans.NewDynamicValue( 9275 cty.ObjectVal(map[string]cty.Value{ 9276 "id": cty.UnknownVal(cty.String), 9277 "require_new": cty.StringVal("new"), 9278 }), instanceSchema.ImpliedType()) 9279 bBefore, _ := plans.NewDynamicValue( 9280 cty.ObjectVal(map[string]cty.Value{ 9281 "id": cty.StringVal("b"), 9282 "require_new": cty.StringVal("old"), 9283 }), instanceSchema.ImpliedType()) 9284 bAfter, _ := plans.NewDynamicValue( 9285 cty.ObjectVal(map[string]cty.Value{ 9286 "id": cty.UnknownVal(cty.String), 9287 "require_new": cty.UnknownVal(cty.String), 9288 }), instanceSchema.ImpliedType()) 9289 9290 var aAction plans.Action 9291 switch mode { 9292 case "normal": 9293 aAction = plans.DeleteThenCreate 9294 case "cbd": 9295 aAction = plans.CreateThenDelete 9296 } 9297 9298 ctx := testContext2(t, &ContextOpts{ 9299 Providers: map[addrs.Provider]providers.Factory{ 9300 addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p), 9301 }, 9302 }) 9303 9304 changes := &plans.Changes{ 9305 Resources: []*plans.ResourceInstanceChangeSrc{ 9306 { 9307 Addr: addrs.Resource{ 9308 Mode: addrs.ManagedResourceMode, 9309 Type: "aws_instance", 9310 Name: "a", 9311 }.Instance(addrs.NoKey).Absolute(addrs.RootModuleInstance.Child("a", addrs.NoKey)), 9312 ProviderAddr: addrs.AbsProviderConfig{ 9313 Provider: addrs.NewDefaultProvider("aws"), 9314 Module: addrs.RootModule, 9315 }, 9316 ChangeSrc: plans.ChangeSrc{ 9317 Action: aAction, 9318 Before: aBefore, 9319 After: aAfter, 9320 }, 9321 }, 9322 { 9323 Addr: addrs.Resource{ 9324 Mode: addrs.ManagedResourceMode, 9325 Type: "aws_instance", 9326 Name: "b", 9327 }.Instance(addrs.IntKey(0)).Absolute(addrs.RootModuleInstance.Child("b", addrs.NoKey)), 9328 ProviderAddr: addrs.AbsProviderConfig{ 9329 Provider: addrs.NewDefaultProvider("aws"), 9330 Module: addrs.RootModule, 9331 }, 9332 ChangeSrc: plans.ChangeSrc{ 9333 Action: plans.DeleteThenCreate, 9334 Before: bBefore, 9335 After: bAfter, 9336 }, 9337 }, 9338 }, 9339 } 9340 9341 plan := &plans.Plan{ 9342 UIMode: plans.NormalMode, 9343 Changes: changes, 9344 PriorState: state.DeepCopy(), 9345 PrevRunState: state.DeepCopy(), 9346 } 9347 9348 t.Run(mode, func(t *testing.T) { 9349 _, diags := ctx.Apply(plan, m) 9350 if diags.HasErrors() { 9351 t.Fatal(diags.Err()) 9352 } 9353 }) 9354 } 9355 } 9356 9357 func TestContext2Apply_destroyDataCycle(t *testing.T) { 9358 m, snap := testModuleWithSnapshot(t, "apply-destroy-data-cycle") 9359 p := testProvider("null") 9360 p.PlanResourceChangeFn = testDiffFn 9361 p.ReadDataSourceFn = func(req providers.ReadDataSourceRequest) providers.ReadDataSourceResponse { 9362 return providers.ReadDataSourceResponse{ 9363 State: cty.ObjectVal(map[string]cty.Value{ 9364 "id": cty.StringVal("new"), 9365 "foo": cty.NullVal(cty.String), 9366 }), 9367 } 9368 } 9369 9370 tp := testProvider("test") 9371 tp.PlanResourceChangeFn = testDiffFn 9372 9373 state := states.NewState() 9374 root := state.EnsureModule(addrs.RootModuleInstance) 9375 root.SetResourceInstanceCurrent( 9376 addrs.Resource{ 9377 Mode: addrs.ManagedResourceMode, 9378 Type: "null_resource", 9379 Name: "a", 9380 }.Instance(addrs.IntKey(0)), 9381 &states.ResourceInstanceObjectSrc{ 9382 Status: states.ObjectReady, 9383 AttrsJSON: []byte(`{"id":"a"}`), 9384 }, 9385 addrs.AbsProviderConfig{ 9386 Provider: addrs.NewDefaultProvider("null"), 9387 Module: addrs.RootModule, 9388 }, 9389 ) 9390 root.SetResourceInstanceCurrent( 9391 addrs.Resource{ 9392 Mode: addrs.ManagedResourceMode, 9393 Type: "test_resource", 9394 Name: "a", 9395 }.Instance(addrs.IntKey(0)), 9396 &states.ResourceInstanceObjectSrc{ 9397 Status: states.ObjectReady, 9398 AttrsJSON: []byte(`{"id":"a"}`), 9399 Dependencies: []addrs.ConfigResource{ 9400 { 9401 Resource: addrs.Resource{ 9402 Mode: addrs.DataResourceMode, 9403 Type: "null_data_source", 9404 Name: "d", 9405 }, 9406 Module: addrs.RootModule, 9407 }, 9408 }, 9409 }, 9410 addrs.AbsProviderConfig{ 9411 Provider: addrs.NewDefaultProvider("test"), 9412 Module: addrs.RootModule, 9413 }, 9414 ) 9415 root.SetResourceInstanceCurrent( 9416 addrs.Resource{ 9417 Mode: addrs.DataResourceMode, 9418 Type: "null_data_source", 9419 Name: "d", 9420 }.Instance(addrs.NoKey), 9421 &states.ResourceInstanceObjectSrc{ 9422 Status: states.ObjectReady, 9423 AttrsJSON: []byte(`{"id":"old"}`), 9424 }, 9425 addrs.AbsProviderConfig{ 9426 Provider: addrs.NewDefaultProvider("null"), 9427 Module: addrs.RootModule, 9428 }, 9429 ) 9430 9431 Providers := map[addrs.Provider]providers.Factory{ 9432 addrs.NewDefaultProvider("null"): testProviderFuncFixed(p), 9433 addrs.NewDefaultProvider("test"): testProviderFuncFixed(tp), 9434 } 9435 9436 ctx := testContext2(t, &ContextOpts{ 9437 Providers: Providers, 9438 }) 9439 9440 plan, diags := ctx.Plan(m, state, &PlanOpts{ 9441 Mode: plans.DestroyMode, 9442 }) 9443 diags.HasErrors() 9444 if diags.HasErrors() { 9445 t.Fatalf("diags: %s", diags.Err()) 9446 } 9447 9448 // We'll marshal and unmarshal the plan here, to ensure that we have 9449 // a clean new context as would be created if we separately ran 9450 // terraform plan -out=tfplan && terraform apply tfplan 9451 ctxOpts, m, plan, err := contextOptsForPlanViaFile(t, snap, plan) 9452 if err != nil { 9453 t.Fatal(err) 9454 } 9455 ctxOpts.Providers = Providers 9456 ctx, diags = NewContext(ctxOpts) 9457 if diags.HasErrors() { 9458 t.Fatalf("failed to create context for plan: %s", diags.Err()) 9459 } 9460 9461 tp.ConfigureProviderFn = func(req providers.ConfigureProviderRequest) (resp providers.ConfigureProviderResponse) { 9462 foo := req.Config.GetAttr("foo") 9463 if !foo.IsKnown() { 9464 resp.Diagnostics = resp.Diagnostics.Append(fmt.Errorf("unknown config value foo")) 9465 return resp 9466 } 9467 9468 if foo.AsString() != "new" { 9469 resp.Diagnostics = resp.Diagnostics.Append(fmt.Errorf("wrong config value: %q", foo.AsString())) 9470 } 9471 return resp 9472 } 9473 9474 _, diags = ctx.Apply(plan, m) 9475 if diags.HasErrors() { 9476 t.Fatalf("diags: %s", diags.Err()) 9477 } 9478 } 9479 9480 func TestContext2Apply_taintedDestroyFailure(t *testing.T) { 9481 m := testModule(t, "apply-destroy-tainted") 9482 p := testProvider("test") 9483 p.PlanResourceChangeFn = testDiffFn 9484 p.ApplyResourceChangeFn = func(req providers.ApplyResourceChangeRequest) (resp providers.ApplyResourceChangeResponse) { 9485 // All destroys fail. 9486 if req.PlannedState.IsNull() { 9487 resp.Diagnostics = resp.Diagnostics.Append(errors.New("failure")) 9488 return 9489 } 9490 9491 // c will also fail to create, meaning the existing tainted instance 9492 // becomes deposed, ans is then promoted back to current. 9493 // only C has a foo attribute 9494 planned := req.PlannedState.AsValueMap() 9495 foo, ok := planned["foo"] 9496 if ok && !foo.IsNull() && foo.AsString() == "c" { 9497 resp.Diagnostics = resp.Diagnostics.Append(errors.New("failure")) 9498 return 9499 } 9500 9501 return testApplyFn(req) 9502 } 9503 p.GetProviderSchemaResponse = getProviderSchemaResponseFromProviderSchema(&ProviderSchema{ 9504 ResourceTypes: map[string]*configschema.Block{ 9505 "test_instance": { 9506 Attributes: map[string]*configschema.Attribute{ 9507 "id": { 9508 Type: cty.String, 9509 Computed: true, 9510 }, 9511 "foo": { 9512 Type: cty.String, 9513 Optional: true, 9514 }, 9515 }, 9516 }, 9517 }, 9518 }) 9519 9520 state := states.NewState() 9521 root := state.EnsureModule(addrs.RootModuleInstance) 9522 root.SetResourceInstanceCurrent( 9523 addrs.Resource{ 9524 Mode: addrs.ManagedResourceMode, 9525 Type: "test_instance", 9526 Name: "a", 9527 }.Instance(addrs.NoKey), 9528 &states.ResourceInstanceObjectSrc{ 9529 Status: states.ObjectTainted, 9530 AttrsJSON: []byte(`{"id":"a","foo":"a"}`), 9531 }, 9532 addrs.AbsProviderConfig{ 9533 Provider: addrs.NewDefaultProvider("test"), 9534 Module: addrs.RootModule, 9535 }, 9536 ) 9537 root.SetResourceInstanceCurrent( 9538 addrs.Resource{ 9539 Mode: addrs.ManagedResourceMode, 9540 Type: "test_instance", 9541 Name: "b", 9542 }.Instance(addrs.NoKey), 9543 &states.ResourceInstanceObjectSrc{ 9544 Status: states.ObjectTainted, 9545 AttrsJSON: []byte(`{"id":"b","foo":"b"}`), 9546 }, 9547 addrs.AbsProviderConfig{ 9548 Provider: addrs.NewDefaultProvider("test"), 9549 Module: addrs.RootModule, 9550 }, 9551 ) 9552 root.SetResourceInstanceCurrent( 9553 addrs.Resource{ 9554 Mode: addrs.ManagedResourceMode, 9555 Type: "test_instance", 9556 Name: "c", 9557 }.Instance(addrs.NoKey), 9558 &states.ResourceInstanceObjectSrc{ 9559 Status: states.ObjectTainted, 9560 AttrsJSON: []byte(`{"id":"c","foo":"old"}`), 9561 }, 9562 addrs.AbsProviderConfig{ 9563 Provider: addrs.NewDefaultProvider("test"), 9564 Module: addrs.RootModule, 9565 }, 9566 ) 9567 9568 Providers := map[addrs.Provider]providers.Factory{ 9569 addrs.NewDefaultProvider("test"): testProviderFuncFixed(p), 9570 } 9571 9572 ctx := testContext2(t, &ContextOpts{ 9573 Providers: Providers, 9574 Hooks: []Hook{&testHook{}}, 9575 }) 9576 9577 plan, diags := ctx.Plan(m, state, DefaultPlanOpts) 9578 diags.HasErrors() 9579 if diags.HasErrors() { 9580 t.Fatalf("diags: %s", diags.Err()) 9581 } 9582 9583 state, diags = ctx.Apply(plan, m) 9584 if !diags.HasErrors() { 9585 t.Fatal("expected error") 9586 } 9587 9588 root = state.Module(addrs.RootModuleInstance) 9589 9590 // the instance that failed to destroy should remain tainted 9591 a := root.ResourceInstance(addrs.Resource{ 9592 Mode: addrs.ManagedResourceMode, 9593 Type: "test_instance", 9594 Name: "a", 9595 }.Instance(addrs.NoKey)) 9596 9597 if a.Current.Status != states.ObjectTainted { 9598 t.Fatal("test_instance.a should be tainted") 9599 } 9600 9601 // b is create_before_destroy, and the destroy failed, so there should be 1 9602 // deposed instance. 9603 b := root.ResourceInstance(addrs.Resource{ 9604 Mode: addrs.ManagedResourceMode, 9605 Type: "test_instance", 9606 Name: "b", 9607 }.Instance(addrs.NoKey)) 9608 9609 if b.Current.Status != states.ObjectReady { 9610 t.Fatal("test_instance.b should be Ready") 9611 } 9612 9613 if len(b.Deposed) != 1 { 9614 t.Fatal("test_instance.b failed to keep deposed instance") 9615 } 9616 9617 // the desposed c instance should be promoted back to Current, and remain 9618 // tainted 9619 c := root.ResourceInstance(addrs.Resource{ 9620 Mode: addrs.ManagedResourceMode, 9621 Type: "test_instance", 9622 Name: "c", 9623 }.Instance(addrs.NoKey)) 9624 9625 if c.Current == nil { 9626 t.Fatal("test_instance.c has no current instance, but it should") 9627 } 9628 9629 if c.Current.Status != states.ObjectTainted { 9630 t.Fatal("test_instance.c should be tainted") 9631 } 9632 9633 if len(c.Deposed) != 0 { 9634 t.Fatal("test_instance.c should have no deposed instances") 9635 } 9636 9637 if string(c.Current.AttrsJSON) != `{"foo":"old","id":"c"}` { 9638 t.Fatalf("unexpected attrs for c: %q\n", c.Current.AttrsJSON) 9639 } 9640 } 9641 9642 func TestContext2Apply_plannedConnectionRefs(t *testing.T) { 9643 m := testModule(t, "apply-plan-connection-refs") 9644 p := testProvider("test") 9645 p.PlanResourceChangeFn = testDiffFn 9646 p.ApplyResourceChangeFn = func(req providers.ApplyResourceChangeRequest) (resp providers.ApplyResourceChangeResponse) { 9647 s := req.PlannedState.AsValueMap() 9648 // delay "a" slightly, so if the reference edge is missing the "b" 9649 // provisioner will see an unknown value. 9650 if s["foo"].AsString() == "a" { 9651 time.Sleep(500 * time.Millisecond) 9652 } 9653 9654 s["id"] = cty.StringVal("ID") 9655 if ty, ok := s["type"]; ok && !ty.IsKnown() { 9656 s["type"] = cty.StringVal(req.TypeName) 9657 } 9658 resp.NewState = cty.ObjectVal(s) 9659 return resp 9660 } 9661 9662 provisionerFactory := func() (provisioners.Interface, error) { 9663 pr := testProvisioner() 9664 pr.ProvisionResourceFn = func(req provisioners.ProvisionResourceRequest) (resp provisioners.ProvisionResourceResponse) { 9665 host := req.Connection.GetAttr("host") 9666 if host.IsNull() || !host.IsKnown() { 9667 resp.Diagnostics = resp.Diagnostics.Append(fmt.Errorf("invalid host value: %#v", host)) 9668 } 9669 9670 return resp 9671 } 9672 return pr, nil 9673 } 9674 9675 Providers := map[addrs.Provider]providers.Factory{ 9676 addrs.NewDefaultProvider("test"): testProviderFuncFixed(p), 9677 } 9678 9679 provisioners := map[string]provisioners.Factory{ 9680 "shell": provisionerFactory, 9681 } 9682 9683 hook := &testHook{} 9684 ctx := testContext2(t, &ContextOpts{ 9685 Providers: Providers, 9686 Provisioners: provisioners, 9687 Hooks: []Hook{hook}, 9688 }) 9689 9690 plan, diags := ctx.Plan(m, states.NewState(), SimplePlanOpts(plans.NormalMode, testInputValuesUnset(m.Module.Variables))) 9691 diags.HasErrors() 9692 if diags.HasErrors() { 9693 t.Fatalf("diags: %s", diags.Err()) 9694 } 9695 9696 _, diags = ctx.Apply(plan, m) 9697 if diags.HasErrors() { 9698 t.Fatalf("diags: %s", diags.Err()) 9699 } 9700 } 9701 9702 func TestContext2Apply_cbdCycle(t *testing.T) { 9703 m, snap := testModuleWithSnapshot(t, "apply-cbd-cycle") 9704 p := testProvider("test") 9705 p.PlanResourceChangeFn = testDiffFn 9706 9707 state := states.NewState() 9708 root := state.EnsureModule(addrs.RootModuleInstance) 9709 root.SetResourceInstanceCurrent( 9710 addrs.Resource{ 9711 Mode: addrs.ManagedResourceMode, 9712 Type: "test_instance", 9713 Name: "a", 9714 }.Instance(addrs.NoKey), 9715 &states.ResourceInstanceObjectSrc{ 9716 Status: states.ObjectReady, 9717 AttrsJSON: []byte(`{"id":"a","require_new":"old","foo":"b"}`), 9718 Dependencies: []addrs.ConfigResource{ 9719 { 9720 Resource: addrs.Resource{ 9721 Mode: addrs.ManagedResourceMode, 9722 Type: "test_instance", 9723 Name: "b", 9724 }, 9725 Module: addrs.RootModule, 9726 }, 9727 { 9728 Resource: addrs.Resource{ 9729 Mode: addrs.ManagedResourceMode, 9730 Type: "test_instance", 9731 Name: "c", 9732 }, 9733 Module: addrs.RootModule, 9734 }, 9735 }, 9736 }, 9737 addrs.AbsProviderConfig{ 9738 Provider: addrs.NewDefaultProvider("test"), 9739 Module: addrs.RootModule, 9740 }, 9741 ) 9742 root.SetResourceInstanceCurrent( 9743 addrs.Resource{ 9744 Mode: addrs.ManagedResourceMode, 9745 Type: "test_instance", 9746 Name: "b", 9747 }.Instance(addrs.NoKey), 9748 &states.ResourceInstanceObjectSrc{ 9749 Status: states.ObjectReady, 9750 AttrsJSON: []byte(`{"id":"b","require_new":"old","foo":"c"}`), 9751 Dependencies: []addrs.ConfigResource{ 9752 { 9753 Resource: addrs.Resource{ 9754 Mode: addrs.ManagedResourceMode, 9755 Type: "test_instance", 9756 Name: "c", 9757 }, 9758 Module: addrs.RootModule, 9759 }, 9760 }, 9761 }, 9762 addrs.AbsProviderConfig{ 9763 Provider: addrs.NewDefaultProvider("test"), 9764 Module: addrs.RootModule, 9765 }, 9766 ) 9767 root.SetResourceInstanceCurrent( 9768 addrs.Resource{ 9769 Mode: addrs.ManagedResourceMode, 9770 Type: "test_instance", 9771 Name: "c", 9772 }.Instance(addrs.NoKey), 9773 &states.ResourceInstanceObjectSrc{ 9774 Status: states.ObjectReady, 9775 AttrsJSON: []byte(`{"id":"c","require_new":"old"}`), 9776 }, 9777 addrs.AbsProviderConfig{ 9778 Provider: addrs.NewDefaultProvider("test"), 9779 Module: addrs.RootModule, 9780 }, 9781 ) 9782 9783 Providers := map[addrs.Provider]providers.Factory{ 9784 addrs.NewDefaultProvider("test"): testProviderFuncFixed(p), 9785 } 9786 9787 hook := &testHook{} 9788 ctx := testContext2(t, &ContextOpts{ 9789 Providers: Providers, 9790 Hooks: []Hook{hook}, 9791 }) 9792 9793 plan, diags := ctx.Plan(m, state, DefaultPlanOpts) 9794 diags.HasErrors() 9795 if diags.HasErrors() { 9796 t.Fatalf("diags: %s", diags.Err()) 9797 } 9798 9799 // We'll marshal and unmarshal the plan here, to ensure that we have 9800 // a clean new context as would be created if we separately ran 9801 // terraform plan -out=tfplan && terraform apply tfplan 9802 ctxOpts, m, plan, err := contextOptsForPlanViaFile(t, snap, plan) 9803 if err != nil { 9804 t.Fatal(err) 9805 } 9806 ctxOpts.Providers = Providers 9807 ctx, diags = NewContext(ctxOpts) 9808 if diags.HasErrors() { 9809 t.Fatalf("failed to create context for plan: %s", diags.Err()) 9810 } 9811 9812 _, diags = ctx.Apply(plan, m) 9813 if diags.HasErrors() { 9814 t.Fatalf("diags: %s", diags.Err()) 9815 } 9816 } 9817 9818 func TestContext2Apply_ProviderMeta_apply_set(t *testing.T) { 9819 m := testModule(t, "provider-meta-set") 9820 p := testProvider("test") 9821 p.PlanResourceChangeFn = testDiffFn 9822 schema := p.ProviderSchema() 9823 schema.ProviderMeta = &configschema.Block{ 9824 Attributes: map[string]*configschema.Attribute{ 9825 "baz": { 9826 Type: cty.String, 9827 Required: true, 9828 }, 9829 }, 9830 } 9831 9832 var pmMu sync.Mutex 9833 arcPMs := map[string]cty.Value{} 9834 9835 p.ApplyResourceChangeFn = func(req providers.ApplyResourceChangeRequest) providers.ApplyResourceChangeResponse { 9836 pmMu.Lock() 9837 defer pmMu.Unlock() 9838 arcPMs[req.TypeName] = req.ProviderMeta 9839 9840 s := req.PlannedState.AsValueMap() 9841 s["id"] = cty.StringVal("ID") 9842 if ty, ok := s["type"]; ok && !ty.IsKnown() { 9843 s["type"] = cty.StringVal(req.TypeName) 9844 } 9845 return providers.ApplyResourceChangeResponse{ 9846 NewState: cty.ObjectVal(s), 9847 } 9848 } 9849 p.GetProviderSchemaResponse = getProviderSchemaResponseFromProviderSchema(schema) 9850 ctx := testContext2(t, &ContextOpts{ 9851 Providers: map[addrs.Provider]providers.Factory{ 9852 addrs.NewDefaultProvider("test"): testProviderFuncFixed(p), 9853 }, 9854 }) 9855 9856 plan, diags := ctx.Plan(m, states.NewState(), DefaultPlanOpts) 9857 assertNoErrors(t, diags) 9858 9859 _, diags = ctx.Apply(plan, m) 9860 assertNoErrors(t, diags) 9861 9862 if !p.ApplyResourceChangeCalled { 9863 t.Fatalf("ApplyResourceChange not called") 9864 } 9865 9866 expectations := map[string]cty.Value{} 9867 9868 if pm, ok := arcPMs["test_resource"]; !ok { 9869 t.Fatalf("sub-module ApplyResourceChange not called") 9870 } else if pm.IsNull() { 9871 t.Fatalf("null ProviderMeta in sub-module ApplyResourceChange") 9872 } else { 9873 expectations["quux-submodule"] = pm 9874 } 9875 9876 if pm, ok := arcPMs["test_instance"]; !ok { 9877 t.Fatalf("root module ApplyResourceChange not called") 9878 } else if pm.IsNull() { 9879 t.Fatalf("null ProviderMeta in root module ApplyResourceChange") 9880 } else { 9881 expectations["quux"] = pm 9882 } 9883 9884 type metaStruct struct { 9885 Baz string `cty:"baz"` 9886 } 9887 9888 for expected, v := range expectations { 9889 var meta metaStruct 9890 err := gocty.FromCtyValue(v, &meta) 9891 if err != nil { 9892 t.Fatalf("Error parsing cty value: %s", err) 9893 } 9894 if meta.Baz != expected { 9895 t.Fatalf("Expected meta.Baz to be %q, got %q", expected, meta.Baz) 9896 } 9897 } 9898 } 9899 9900 func TestContext2Apply_ProviderMeta_apply_unset(t *testing.T) { 9901 m := testModule(t, "provider-meta-unset") 9902 p := testProvider("test") 9903 p.PlanResourceChangeFn = testDiffFn 9904 schema := p.ProviderSchema() 9905 schema.ProviderMeta = &configschema.Block{ 9906 Attributes: map[string]*configschema.Attribute{ 9907 "baz": { 9908 Type: cty.String, 9909 Required: true, 9910 }, 9911 }, 9912 } 9913 var pmMu sync.Mutex 9914 arcPMs := map[string]cty.Value{} 9915 p.ApplyResourceChangeFn = func(req providers.ApplyResourceChangeRequest) providers.ApplyResourceChangeResponse { 9916 pmMu.Lock() 9917 defer pmMu.Unlock() 9918 arcPMs[req.TypeName] = req.ProviderMeta 9919 9920 s := req.PlannedState.AsValueMap() 9921 s["id"] = cty.StringVal("ID") 9922 if ty, ok := s["type"]; ok && !ty.IsKnown() { 9923 s["type"] = cty.StringVal(req.TypeName) 9924 } 9925 return providers.ApplyResourceChangeResponse{ 9926 NewState: cty.ObjectVal(s), 9927 } 9928 } 9929 p.GetProviderSchemaResponse = getProviderSchemaResponseFromProviderSchema(schema) 9930 ctx := testContext2(t, &ContextOpts{ 9931 Providers: map[addrs.Provider]providers.Factory{ 9932 addrs.NewDefaultProvider("test"): testProviderFuncFixed(p), 9933 }, 9934 }) 9935 9936 plan, diags := ctx.Plan(m, states.NewState(), DefaultPlanOpts) 9937 assertNoErrors(t, diags) 9938 9939 _, diags = ctx.Apply(plan, m) 9940 assertNoErrors(t, diags) 9941 9942 if !p.ApplyResourceChangeCalled { 9943 t.Fatalf("ApplyResourceChange not called") 9944 } 9945 9946 if pm, ok := arcPMs["test_resource"]; !ok { 9947 t.Fatalf("sub-module ApplyResourceChange not called") 9948 } else if !pm.IsNull() { 9949 t.Fatalf("non-null ProviderMeta in sub-module ApplyResourceChange: %+v", pm) 9950 } 9951 9952 if pm, ok := arcPMs["test_instance"]; !ok { 9953 t.Fatalf("root module ApplyResourceChange not called") 9954 } else if !pm.IsNull() { 9955 t.Fatalf("non-null ProviderMeta in root module ApplyResourceChange: %+v", pm) 9956 } 9957 } 9958 9959 func TestContext2Apply_ProviderMeta_plan_set(t *testing.T) { 9960 m := testModule(t, "provider-meta-set") 9961 p := testProvider("test") 9962 schema := p.ProviderSchema() 9963 schema.ProviderMeta = &configschema.Block{ 9964 Attributes: map[string]*configschema.Attribute{ 9965 "baz": { 9966 Type: cty.String, 9967 Required: true, 9968 }, 9969 }, 9970 } 9971 prcPMs := map[string]cty.Value{} 9972 p.PlanResourceChangeFn = func(req providers.PlanResourceChangeRequest) providers.PlanResourceChangeResponse { 9973 prcPMs[req.TypeName] = req.ProviderMeta 9974 return providers.PlanResourceChangeResponse{ 9975 PlannedState: req.ProposedNewState, 9976 } 9977 } 9978 p.GetProviderSchemaResponse = getProviderSchemaResponseFromProviderSchema(schema) 9979 ctx := testContext2(t, &ContextOpts{ 9980 Providers: map[addrs.Provider]providers.Factory{ 9981 addrs.NewDefaultProvider("test"): testProviderFuncFixed(p), 9982 }, 9983 }) 9984 9985 _, diags := ctx.Plan(m, states.NewState(), DefaultPlanOpts) 9986 assertNoErrors(t, diags) 9987 9988 if !p.PlanResourceChangeCalled { 9989 t.Fatalf("PlanResourceChange not called") 9990 } 9991 9992 expectations := map[string]cty.Value{} 9993 9994 if pm, ok := prcPMs["test_resource"]; !ok { 9995 t.Fatalf("sub-module PlanResourceChange not called") 9996 } else if pm.IsNull() { 9997 t.Fatalf("null ProviderMeta in sub-module PlanResourceChange") 9998 } else { 9999 expectations["quux-submodule"] = pm 10000 } 10001 10002 if pm, ok := prcPMs["test_instance"]; !ok { 10003 t.Fatalf("root module PlanResourceChange not called") 10004 } else if pm.IsNull() { 10005 t.Fatalf("null ProviderMeta in root module PlanResourceChange") 10006 } else { 10007 expectations["quux"] = pm 10008 } 10009 10010 type metaStruct struct { 10011 Baz string `cty:"baz"` 10012 } 10013 10014 for expected, v := range expectations { 10015 var meta metaStruct 10016 err := gocty.FromCtyValue(v, &meta) 10017 if err != nil { 10018 t.Fatalf("Error parsing cty value: %s", err) 10019 } 10020 if meta.Baz != expected { 10021 t.Fatalf("Expected meta.Baz to be %q, got %q", expected, meta.Baz) 10022 } 10023 } 10024 } 10025 10026 func TestContext2Apply_ProviderMeta_plan_unset(t *testing.T) { 10027 m := testModule(t, "provider-meta-unset") 10028 p := testProvider("test") 10029 schema := p.ProviderSchema() 10030 schema.ProviderMeta = &configschema.Block{ 10031 Attributes: map[string]*configschema.Attribute{ 10032 "baz": { 10033 Type: cty.String, 10034 Required: true, 10035 }, 10036 }, 10037 } 10038 prcPMs := map[string]cty.Value{} 10039 p.PlanResourceChangeFn = func(req providers.PlanResourceChangeRequest) providers.PlanResourceChangeResponse { 10040 prcPMs[req.TypeName] = req.ProviderMeta 10041 return providers.PlanResourceChangeResponse{ 10042 PlannedState: req.ProposedNewState, 10043 } 10044 } 10045 p.GetProviderSchemaResponse = getProviderSchemaResponseFromProviderSchema(schema) 10046 ctx := testContext2(t, &ContextOpts{ 10047 Providers: map[addrs.Provider]providers.Factory{ 10048 addrs.NewDefaultProvider("test"): testProviderFuncFixed(p), 10049 }, 10050 }) 10051 10052 _, diags := ctx.Plan(m, states.NewState(), DefaultPlanOpts) 10053 assertNoErrors(t, diags) 10054 10055 if !p.PlanResourceChangeCalled { 10056 t.Fatalf("PlanResourceChange not called") 10057 } 10058 10059 if pm, ok := prcPMs["test_resource"]; !ok { 10060 t.Fatalf("sub-module PlanResourceChange not called") 10061 } else if !pm.IsNull() { 10062 t.Fatalf("non-null ProviderMeta in sub-module PlanResourceChange: %+v", pm) 10063 } 10064 10065 if pm, ok := prcPMs["test_instance"]; !ok { 10066 t.Fatalf("root module PlanResourceChange not called") 10067 } else if !pm.IsNull() { 10068 t.Fatalf("non-null ProviderMeta in root module PlanResourceChange: %+v", pm) 10069 } 10070 } 10071 10072 func TestContext2Apply_ProviderMeta_plan_setNoSchema(t *testing.T) { 10073 m := testModule(t, "provider-meta-set") 10074 p := testProvider("test") 10075 p.PlanResourceChangeFn = testDiffFn 10076 ctx := testContext2(t, &ContextOpts{ 10077 Providers: map[addrs.Provider]providers.Factory{ 10078 addrs.NewDefaultProvider("test"): testProviderFuncFixed(p), 10079 }, 10080 }) 10081 10082 _, diags := ctx.Plan(m, states.NewState(), DefaultPlanOpts) 10083 if !diags.HasErrors() { 10084 t.Fatalf("plan supposed to error, has no errors") 10085 } 10086 10087 var rootErr, subErr bool 10088 errorSummary := "The resource test_%s.bar belongs to a provider that doesn't support provider_meta blocks" 10089 for _, diag := range diags { 10090 if diag.Description().Summary != "Provider registry.terraform.io/hashicorp/test doesn't support provider_meta" { 10091 t.Errorf("Unexpected error: %+v", diag.Description()) 10092 } 10093 switch diag.Description().Detail { 10094 case fmt.Sprintf(errorSummary, "instance"): 10095 rootErr = true 10096 case fmt.Sprintf(errorSummary, "resource"): 10097 subErr = true 10098 default: 10099 t.Errorf("Unexpected error: %s", diag.Description()) 10100 } 10101 } 10102 if !rootErr { 10103 t.Errorf("Expected unsupported provider_meta block error for root module, none received") 10104 } 10105 if !subErr { 10106 t.Errorf("Expected unsupported provider_meta block error for sub-module, none received") 10107 } 10108 } 10109 10110 func TestContext2Apply_ProviderMeta_plan_setInvalid(t *testing.T) { 10111 m := testModule(t, "provider-meta-set") 10112 p := testProvider("test") 10113 p.PlanResourceChangeFn = testDiffFn 10114 schema := p.ProviderSchema() 10115 schema.ProviderMeta = &configschema.Block{ 10116 Attributes: map[string]*configschema.Attribute{ 10117 "quux": { 10118 Type: cty.String, 10119 Required: true, 10120 }, 10121 }, 10122 } 10123 p.GetProviderSchemaResponse = getProviderSchemaResponseFromProviderSchema(schema) 10124 ctx := testContext2(t, &ContextOpts{ 10125 Providers: map[addrs.Provider]providers.Factory{ 10126 addrs.NewDefaultProvider("test"): testProviderFuncFixed(p), 10127 }, 10128 }) 10129 10130 _, diags := ctx.Plan(m, states.NewState(), DefaultPlanOpts) 10131 if !diags.HasErrors() { 10132 t.Fatalf("plan supposed to error, has no errors") 10133 } 10134 10135 var reqErr, invalidErr bool 10136 for _, diag := range diags { 10137 switch diag.Description().Summary { 10138 case "Missing required argument": 10139 if diag.Description().Detail == `The argument "quux" is required, but no definition was found.` { 10140 reqErr = true 10141 } else { 10142 t.Errorf("Unexpected error %+v", diag.Description()) 10143 } 10144 case "Unsupported argument": 10145 if diag.Description().Detail == `An argument named "baz" is not expected here.` { 10146 invalidErr = true 10147 } else { 10148 t.Errorf("Unexpected error %+v", diag.Description()) 10149 } 10150 default: 10151 t.Errorf("Unexpected error %+v", diag.Description()) 10152 } 10153 } 10154 if !reqErr { 10155 t.Errorf("Expected missing required argument error, none received") 10156 } 10157 if !invalidErr { 10158 t.Errorf("Expected unsupported argument error, none received") 10159 } 10160 } 10161 10162 func TestContext2Apply_ProviderMeta_refresh_set(t *testing.T) { 10163 m := testModule(t, "provider-meta-set") 10164 p := testProvider("test") 10165 p.PlanResourceChangeFn = testDiffFn 10166 schema := p.ProviderSchema() 10167 schema.ProviderMeta = &configschema.Block{ 10168 Attributes: map[string]*configschema.Attribute{ 10169 "baz": { 10170 Type: cty.String, 10171 Required: true, 10172 }, 10173 }, 10174 } 10175 rrcPMs := map[string]cty.Value{} 10176 p.ReadResourceFn = func(req providers.ReadResourceRequest) (resp providers.ReadResourceResponse) { 10177 rrcPMs[req.TypeName] = req.ProviderMeta 10178 newState, err := p.GetProviderSchemaResponse.ResourceTypes[req.TypeName].Block.CoerceValue(req.PriorState) 10179 if err != nil { 10180 panic(err) 10181 } 10182 resp.NewState = newState 10183 return resp 10184 } 10185 p.GetProviderSchemaResponse = getProviderSchemaResponseFromProviderSchema(schema) 10186 ctx := testContext2(t, &ContextOpts{ 10187 Providers: map[addrs.Provider]providers.Factory{ 10188 addrs.NewDefaultProvider("test"): testProviderFuncFixed(p), 10189 }, 10190 }) 10191 10192 plan, diags := ctx.Plan(m, states.NewState(), DefaultPlanOpts) 10193 assertNoErrors(t, diags) 10194 10195 state, diags := ctx.Apply(plan, m) 10196 assertNoErrors(t, diags) 10197 10198 _, diags = ctx.Refresh(m, state, DefaultPlanOpts) 10199 assertNoErrors(t, diags) 10200 10201 if !p.ReadResourceCalled { 10202 t.Fatalf("ReadResource not called") 10203 } 10204 10205 expectations := map[string]cty.Value{} 10206 10207 if pm, ok := rrcPMs["test_resource"]; !ok { 10208 t.Fatalf("sub-module ReadResource not called") 10209 } else if pm.IsNull() { 10210 t.Fatalf("null ProviderMeta in sub-module ReadResource") 10211 } else { 10212 expectations["quux-submodule"] = pm 10213 } 10214 10215 if pm, ok := rrcPMs["test_instance"]; !ok { 10216 t.Fatalf("root module ReadResource not called") 10217 } else if pm.IsNull() { 10218 t.Fatalf("null ProviderMeta in root module ReadResource") 10219 } else { 10220 expectations["quux"] = pm 10221 } 10222 10223 type metaStruct struct { 10224 Baz string `cty:"baz"` 10225 } 10226 10227 for expected, v := range expectations { 10228 var meta metaStruct 10229 err := gocty.FromCtyValue(v, &meta) 10230 if err != nil { 10231 t.Fatalf("Error parsing cty value: %s", err) 10232 } 10233 if meta.Baz != expected { 10234 t.Fatalf("Expected meta.Baz to be %q, got %q", expected, meta.Baz) 10235 } 10236 } 10237 } 10238 10239 func TestContext2Apply_ProviderMeta_refresh_setNoSchema(t *testing.T) { 10240 m := testModule(t, "provider-meta-set") 10241 p := testProvider("test") 10242 p.PlanResourceChangeFn = testDiffFn 10243 10244 // we need a schema for plan/apply so they don't error 10245 schema := p.ProviderSchema() 10246 schema.ProviderMeta = &configschema.Block{ 10247 Attributes: map[string]*configschema.Attribute{ 10248 "baz": { 10249 Type: cty.String, 10250 Required: true, 10251 }, 10252 }, 10253 } 10254 p.GetProviderSchemaResponse = getProviderSchemaResponseFromProviderSchema(schema) 10255 ctx := testContext2(t, &ContextOpts{ 10256 Providers: map[addrs.Provider]providers.Factory{ 10257 addrs.NewDefaultProvider("test"): testProviderFuncFixed(p), 10258 }, 10259 }) 10260 10261 plan, diags := ctx.Plan(m, states.NewState(), DefaultPlanOpts) 10262 assertNoErrors(t, diags) 10263 10264 state, diags := ctx.Apply(plan, m) 10265 assertNoErrors(t, diags) 10266 10267 // drop the schema before refresh, to test that it errors 10268 schema.ProviderMeta = nil 10269 p.GetProviderSchemaResponse = getProviderSchemaResponseFromProviderSchema(schema) 10270 ctx = testContext2(t, &ContextOpts{ 10271 Providers: map[addrs.Provider]providers.Factory{ 10272 addrs.NewDefaultProvider("test"): testProviderFuncFixed(p), 10273 }, 10274 }) 10275 10276 _, diags = ctx.Refresh(m, state, DefaultPlanOpts) 10277 if !diags.HasErrors() { 10278 t.Fatalf("refresh supposed to error, has no errors") 10279 } 10280 10281 var rootErr, subErr bool 10282 errorSummary := "The resource test_%s.bar belongs to a provider that doesn't support provider_meta blocks" 10283 for _, diag := range diags { 10284 if diag.Description().Summary != "Provider registry.terraform.io/hashicorp/test doesn't support provider_meta" { 10285 t.Errorf("Unexpected error: %+v", diag.Description()) 10286 } 10287 switch diag.Description().Detail { 10288 case fmt.Sprintf(errorSummary, "instance"): 10289 rootErr = true 10290 case fmt.Sprintf(errorSummary, "resource"): 10291 subErr = true 10292 default: 10293 t.Errorf("Unexpected error: %s", diag.Description()) 10294 } 10295 } 10296 if !rootErr { 10297 t.Errorf("Expected unsupported provider_meta block error for root module, none received") 10298 } 10299 if !subErr { 10300 t.Errorf("Expected unsupported provider_meta block error for sub-module, none received") 10301 } 10302 } 10303 10304 func TestContext2Apply_ProviderMeta_refresh_setInvalid(t *testing.T) { 10305 m := testModule(t, "provider-meta-set") 10306 p := testProvider("test") 10307 p.PlanResourceChangeFn = testDiffFn 10308 10309 // we need a matching schema for plan/apply so they don't error 10310 schema := p.ProviderSchema() 10311 schema.ProviderMeta = &configschema.Block{ 10312 Attributes: map[string]*configschema.Attribute{ 10313 "baz": { 10314 Type: cty.String, 10315 Required: true, 10316 }, 10317 }, 10318 } 10319 p.GetProviderSchemaResponse = getProviderSchemaResponseFromProviderSchema(schema) 10320 ctx := testContext2(t, &ContextOpts{ 10321 Providers: map[addrs.Provider]providers.Factory{ 10322 addrs.NewDefaultProvider("test"): testProviderFuncFixed(p), 10323 }, 10324 }) 10325 10326 plan, diags := ctx.Plan(m, states.NewState(), DefaultPlanOpts) 10327 assertNoErrors(t, diags) 10328 10329 state, diags := ctx.Apply(plan, m) 10330 assertNoErrors(t, diags) 10331 10332 // change the schema before refresh, to test that it errors 10333 schema.ProviderMeta = &configschema.Block{ 10334 Attributes: map[string]*configschema.Attribute{ 10335 "quux": { 10336 Type: cty.String, 10337 Required: true, 10338 }, 10339 }, 10340 } 10341 p.GetProviderSchemaResponse = getProviderSchemaResponseFromProviderSchema(schema) 10342 ctx = testContext2(t, &ContextOpts{ 10343 Providers: map[addrs.Provider]providers.Factory{ 10344 addrs.NewDefaultProvider("test"): testProviderFuncFixed(p), 10345 }, 10346 }) 10347 10348 _, diags = ctx.Refresh(m, state, DefaultPlanOpts) 10349 if !diags.HasErrors() { 10350 t.Fatalf("refresh supposed to error, has no errors") 10351 } 10352 10353 var reqErr, invalidErr bool 10354 for _, diag := range diags { 10355 switch diag.Description().Summary { 10356 case "Missing required argument": 10357 if diag.Description().Detail == `The argument "quux" is required, but no definition was found.` { 10358 reqErr = true 10359 } else { 10360 t.Errorf("Unexpected error %+v", diag.Description()) 10361 } 10362 case "Unsupported argument": 10363 if diag.Description().Detail == `An argument named "baz" is not expected here.` { 10364 invalidErr = true 10365 } else { 10366 t.Errorf("Unexpected error %+v", diag.Description()) 10367 } 10368 default: 10369 t.Errorf("Unexpected error %+v", diag.Description()) 10370 } 10371 } 10372 if !reqErr { 10373 t.Errorf("Expected missing required argument error, none received") 10374 } 10375 if !invalidErr { 10376 t.Errorf("Expected unsupported argument error, none received") 10377 } 10378 } 10379 10380 func TestContext2Apply_ProviderMeta_refreshdata_set(t *testing.T) { 10381 m := testModule(t, "provider-meta-data-set") 10382 p := testProvider("test") 10383 p.PlanResourceChangeFn = testDiffFn 10384 schema := p.ProviderSchema() 10385 schema.ProviderMeta = &configschema.Block{ 10386 Attributes: map[string]*configschema.Attribute{ 10387 "baz": { 10388 Type: cty.String, 10389 Required: true, 10390 }, 10391 }, 10392 } 10393 p.GetProviderSchemaResponse = getProviderSchemaResponseFromProviderSchema(schema) 10394 ctx := testContext2(t, &ContextOpts{ 10395 Providers: map[addrs.Provider]providers.Factory{ 10396 addrs.NewDefaultProvider("test"): testProviderFuncFixed(p), 10397 }, 10398 }) 10399 rdsPMs := map[string]cty.Value{} 10400 p.ReadDataSourceFn = func(req providers.ReadDataSourceRequest) providers.ReadDataSourceResponse { 10401 rdsPMs[req.TypeName] = req.ProviderMeta 10402 switch req.TypeName { 10403 case "test_data_source": 10404 log.Printf("[TRACE] test_data_source RDSR returning") 10405 return providers.ReadDataSourceResponse{ 10406 State: cty.ObjectVal(map[string]cty.Value{ 10407 "id": cty.StringVal("yo"), 10408 "foo": cty.StringVal("bar"), 10409 }), 10410 } 10411 case "test_file": 10412 log.Printf("[TRACE] test_file RDSR returning") 10413 return providers.ReadDataSourceResponse{ 10414 State: cty.ObjectVal(map[string]cty.Value{ 10415 "id": cty.StringVal("bar"), 10416 "rendered": cty.StringVal("baz"), 10417 "template": cty.StringVal(""), 10418 }), 10419 } 10420 default: 10421 // config drift, oops 10422 log.Printf("[TRACE] unknown request TypeName: %q", req.TypeName) 10423 return providers.ReadDataSourceResponse{} 10424 } 10425 } 10426 10427 plan, diags := ctx.Plan(m, states.NewState(), DefaultPlanOpts) 10428 assertNoErrors(t, diags) 10429 10430 state, diags := ctx.Apply(plan, m) 10431 assertNoErrors(t, diags) 10432 10433 _, diags = ctx.Refresh(m, state, DefaultPlanOpts) 10434 assertNoErrors(t, diags) 10435 10436 if !p.ReadDataSourceCalled { 10437 t.Fatalf("ReadDataSource not called") 10438 } 10439 10440 expectations := map[string]cty.Value{} 10441 10442 if pm, ok := rdsPMs["test_file"]; !ok { 10443 t.Fatalf("sub-module ReadDataSource not called") 10444 } else if pm.IsNull() { 10445 t.Fatalf("null ProviderMeta in sub-module ReadDataSource") 10446 } else { 10447 expectations["quux-submodule"] = pm 10448 } 10449 10450 if pm, ok := rdsPMs["test_data_source"]; !ok { 10451 t.Fatalf("root module ReadDataSource not called") 10452 } else if pm.IsNull() { 10453 t.Fatalf("null ProviderMeta in root module ReadDataSource") 10454 } else { 10455 expectations["quux"] = pm 10456 } 10457 10458 type metaStruct struct { 10459 Baz string `cty:"baz"` 10460 } 10461 10462 for expected, v := range expectations { 10463 var meta metaStruct 10464 err := gocty.FromCtyValue(v, &meta) 10465 if err != nil { 10466 t.Fatalf("Error parsing cty value: %s", err) 10467 } 10468 if meta.Baz != expected { 10469 t.Fatalf("Expected meta.Baz to be %q, got %q", expected, meta.Baz) 10470 } 10471 } 10472 } 10473 10474 func TestContext2Apply_ProviderMeta_refreshdata_unset(t *testing.T) { 10475 m := testModule(t, "provider-meta-data-unset") 10476 p := testProvider("test") 10477 p.PlanResourceChangeFn = testDiffFn 10478 schema := p.ProviderSchema() 10479 schema.ProviderMeta = &configschema.Block{ 10480 Attributes: map[string]*configschema.Attribute{ 10481 "baz": { 10482 Type: cty.String, 10483 Required: true, 10484 }, 10485 }, 10486 } 10487 p.GetProviderSchemaResponse = getProviderSchemaResponseFromProviderSchema(schema) 10488 ctx := testContext2(t, &ContextOpts{ 10489 Providers: map[addrs.Provider]providers.Factory{ 10490 addrs.NewDefaultProvider("test"): testProviderFuncFixed(p), 10491 }, 10492 }) 10493 rdsPMs := map[string]cty.Value{} 10494 p.ReadDataSourceFn = func(req providers.ReadDataSourceRequest) providers.ReadDataSourceResponse { 10495 rdsPMs[req.TypeName] = req.ProviderMeta 10496 switch req.TypeName { 10497 case "test_data_source": 10498 return providers.ReadDataSourceResponse{ 10499 State: cty.ObjectVal(map[string]cty.Value{ 10500 "id": cty.StringVal("yo"), 10501 "foo": cty.StringVal("bar"), 10502 }), 10503 } 10504 case "test_file": 10505 return providers.ReadDataSourceResponse{ 10506 State: cty.ObjectVal(map[string]cty.Value{ 10507 "id": cty.StringVal("bar"), 10508 "rendered": cty.StringVal("baz"), 10509 "template": cty.StringVal(""), 10510 }), 10511 } 10512 default: 10513 // config drift, oops 10514 return providers.ReadDataSourceResponse{} 10515 } 10516 } 10517 10518 plan, diags := ctx.Plan(m, states.NewState(), DefaultPlanOpts) 10519 assertNoErrors(t, diags) 10520 10521 _, diags = ctx.Apply(plan, m) 10522 assertNoErrors(t, diags) 10523 10524 if !p.ReadDataSourceCalled { 10525 t.Fatalf("ReadDataSource not called") 10526 } 10527 10528 if pm, ok := rdsPMs["test_file"]; !ok { 10529 t.Fatalf("sub-module ReadDataSource not called") 10530 } else if !pm.IsNull() { 10531 t.Fatalf("non-null ProviderMeta in sub-module ReadDataSource") 10532 } 10533 10534 if pm, ok := rdsPMs["test_data_source"]; !ok { 10535 t.Fatalf("root module ReadDataSource not called") 10536 } else if !pm.IsNull() { 10537 t.Fatalf("non-null ProviderMeta in root module ReadDataSource") 10538 } 10539 } 10540 10541 func TestContext2Apply_ProviderMeta_refreshdata_setNoSchema(t *testing.T) { 10542 m := testModule(t, "provider-meta-data-set") 10543 p := testProvider("test") 10544 p.PlanResourceChangeFn = testDiffFn 10545 ctx := testContext2(t, &ContextOpts{ 10546 Providers: map[addrs.Provider]providers.Factory{ 10547 addrs.NewDefaultProvider("test"): testProviderFuncFixed(p), 10548 }, 10549 }) 10550 p.ReadDataSourceResponse = &providers.ReadDataSourceResponse{ 10551 State: cty.ObjectVal(map[string]cty.Value{ 10552 "id": cty.StringVal("yo"), 10553 "foo": cty.StringVal("bar"), 10554 }), 10555 } 10556 10557 _, diags := ctx.Refresh(m, states.NewState(), DefaultPlanOpts) 10558 if !diags.HasErrors() { 10559 t.Fatalf("refresh supposed to error, has no errors") 10560 } 10561 10562 var rootErr, subErr bool 10563 errorSummary := "The resource data.test_%s.foo belongs to a provider that doesn't support provider_meta blocks" 10564 for _, diag := range diags { 10565 if diag.Description().Summary != "Provider registry.terraform.io/hashicorp/test doesn't support provider_meta" { 10566 t.Errorf("Unexpected error: %+v", diag.Description()) 10567 } 10568 switch diag.Description().Detail { 10569 case fmt.Sprintf(errorSummary, "data_source"): 10570 rootErr = true 10571 case fmt.Sprintf(errorSummary, "file"): 10572 subErr = true 10573 default: 10574 t.Errorf("Unexpected error: %s", diag.Description()) 10575 } 10576 } 10577 if !rootErr { 10578 t.Errorf("Expected unsupported provider_meta block error for root module, none received") 10579 } 10580 if !subErr { 10581 t.Errorf("Expected unsupported provider_meta block error for sub-module, none received") 10582 } 10583 } 10584 10585 func TestContext2Apply_ProviderMeta_refreshdata_setInvalid(t *testing.T) { 10586 m := testModule(t, "provider-meta-data-set") 10587 p := testProvider("test") 10588 p.PlanResourceChangeFn = testDiffFn 10589 schema := p.ProviderSchema() 10590 schema.ProviderMeta = &configschema.Block{ 10591 Attributes: map[string]*configschema.Attribute{ 10592 "quux": { 10593 Type: cty.String, 10594 Required: true, 10595 }, 10596 }, 10597 } 10598 p.GetProviderSchemaResponse = getProviderSchemaResponseFromProviderSchema(schema) 10599 ctx := testContext2(t, &ContextOpts{ 10600 Providers: map[addrs.Provider]providers.Factory{ 10601 addrs.NewDefaultProvider("test"): testProviderFuncFixed(p), 10602 }, 10603 }) 10604 p.ReadDataSourceResponse = &providers.ReadDataSourceResponse{ 10605 State: cty.ObjectVal(map[string]cty.Value{ 10606 "id": cty.StringVal("yo"), 10607 "foo": cty.StringVal("bar"), 10608 }), 10609 } 10610 10611 _, diags := ctx.Refresh(m, states.NewState(), DefaultPlanOpts) 10612 if !diags.HasErrors() { 10613 t.Fatalf("refresh supposed to error, has no errors") 10614 } 10615 10616 var reqErr, invalidErr bool 10617 for _, diag := range diags { 10618 switch diag.Description().Summary { 10619 case "Missing required argument": 10620 if diag.Description().Detail == `The argument "quux" is required, but no definition was found.` { 10621 reqErr = true 10622 } else { 10623 t.Errorf("Unexpected error %+v", diag.Description()) 10624 } 10625 case "Unsupported argument": 10626 if diag.Description().Detail == `An argument named "baz" is not expected here.` { 10627 invalidErr = true 10628 } else { 10629 t.Errorf("Unexpected error %+v", diag.Description()) 10630 } 10631 default: 10632 t.Errorf("Unexpected error %+v", diag.Description()) 10633 } 10634 } 10635 if !reqErr { 10636 t.Errorf("Expected missing required argument error, none received") 10637 } 10638 if !invalidErr { 10639 t.Errorf("Expected unsupported argument error, none received") 10640 } 10641 } 10642 10643 func TestContext2Apply_expandModuleVariables(t *testing.T) { 10644 m := testModuleInline(t, map[string]string{ 10645 "main.tf": ` 10646 module "mod1" { 10647 for_each = toset(["a"]) 10648 source = "./mod" 10649 } 10650 10651 module "mod2" { 10652 source = "./mod" 10653 in = module.mod1["a"].out 10654 } 10655 `, 10656 "mod/main.tf": ` 10657 resource "aws_instance" "foo" { 10658 foo = var.in 10659 } 10660 10661 variable "in" { 10662 type = string 10663 default = "default" 10664 } 10665 10666 output "out" { 10667 value = aws_instance.foo.id 10668 } 10669 `, 10670 }) 10671 10672 p := testProvider("aws") 10673 p.PlanResourceChangeFn = testDiffFn 10674 p.ApplyResourceChangeFn = testApplyFn 10675 ctx := testContext2(t, &ContextOpts{ 10676 Providers: map[addrs.Provider]providers.Factory{ 10677 addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p), 10678 }, 10679 }) 10680 10681 plan, diags := ctx.Plan(m, states.NewState(), DefaultPlanOpts) 10682 if diags.HasErrors() { 10683 t.Fatal(diags.ErrWithWarnings()) 10684 } 10685 10686 state, diags := ctx.Apply(plan, m) 10687 if diags.HasErrors() { 10688 t.Fatal(diags.ErrWithWarnings()) 10689 } 10690 10691 expected := `<no state> 10692 module.mod1["a"]: 10693 aws_instance.foo: 10694 ID = foo 10695 provider = provider["registry.terraform.io/hashicorp/aws"] 10696 foo = default 10697 type = aws_instance 10698 10699 Outputs: 10700 10701 out = foo 10702 module.mod2: 10703 aws_instance.foo: 10704 ID = foo 10705 provider = provider["registry.terraform.io/hashicorp/aws"] 10706 foo = foo 10707 type = aws_instance 10708 10709 Dependencies: 10710 module.mod1.aws_instance.foo` 10711 10712 if state.String() != expected { 10713 t.Fatalf("expected:\n%s\ngot:\n%s\n", expected, state) 10714 } 10715 } 10716 10717 func TestContext2Apply_inheritAndStoreCBD(t *testing.T) { 10718 m := testModuleInline(t, map[string]string{ 10719 "main.tf": ` 10720 resource "aws_instance" "foo" { 10721 } 10722 10723 resource "aws_instance" "cbd" { 10724 foo = aws_instance.foo.id 10725 lifecycle { 10726 create_before_destroy = true 10727 } 10728 } 10729 `, 10730 }) 10731 10732 p := testProvider("aws") 10733 p.PlanResourceChangeFn = testDiffFn 10734 ctx := testContext2(t, &ContextOpts{ 10735 Providers: map[addrs.Provider]providers.Factory{ 10736 addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p), 10737 }, 10738 }) 10739 10740 plan, diags := ctx.Plan(m, states.NewState(), DefaultPlanOpts) 10741 if diags.HasErrors() { 10742 t.Fatal(diags.ErrWithWarnings()) 10743 } 10744 10745 state, diags := ctx.Apply(plan, m) 10746 if diags.HasErrors() { 10747 t.Fatal(diags.ErrWithWarnings()) 10748 } 10749 10750 foo := state.ResourceInstance(mustResourceInstanceAddr("aws_instance.foo")) 10751 if !foo.Current.CreateBeforeDestroy { 10752 t.Fatal("aws_instance.foo should also be create_before_destroy") 10753 } 10754 } 10755 10756 func TestContext2Apply_moduleDependsOn(t *testing.T) { 10757 m := testModule(t, "apply-module-depends-on") 10758 10759 p := testProvider("test") 10760 10761 // each instance being applied should happen in sequential order 10762 applied := int64(0) 10763 10764 p.ReadDataSourceFn = func(req providers.ReadDataSourceRequest) providers.ReadDataSourceResponse { 10765 cfg := req.Config.AsValueMap() 10766 foo := cfg["foo"].AsString() 10767 ord := atomic.LoadInt64(&applied) 10768 10769 resp := providers.ReadDataSourceResponse{ 10770 State: cty.ObjectVal(map[string]cty.Value{ 10771 "id": cty.StringVal("data"), 10772 "foo": cfg["foo"], 10773 }), 10774 } 10775 10776 if foo == "a" && ord < 4 { 10777 // due to data source "a"'s module depending on instance 4, this 10778 // should not be less than 4 10779 resp.Diagnostics = resp.Diagnostics.Append(fmt.Errorf("data source a read too early")) 10780 } 10781 if foo == "b" && ord < 1 { 10782 // due to data source "b"'s module depending on instance 1, this 10783 // should not be less than 1 10784 resp.Diagnostics = resp.Diagnostics.Append(fmt.Errorf("data source b read too early")) 10785 } 10786 return resp 10787 } 10788 p.PlanResourceChangeFn = testDiffFn 10789 10790 p.ApplyResourceChangeFn = func(req providers.ApplyResourceChangeRequest) (resp providers.ApplyResourceChangeResponse) { 10791 state := req.PlannedState.AsValueMap() 10792 num, _ := state["num"].AsBigFloat().Float64() 10793 ord := int64(num) 10794 if !atomic.CompareAndSwapInt64(&applied, ord-1, ord) { 10795 actual := atomic.LoadInt64(&applied) 10796 resp.Diagnostics = resp.Diagnostics.Append(fmt.Errorf("instance %d was applied after %d", ord, actual)) 10797 } 10798 10799 state["id"] = cty.StringVal(fmt.Sprintf("test_%d", ord)) 10800 state["type"] = cty.StringVal("test_instance") 10801 resp.NewState = cty.ObjectVal(state) 10802 10803 return resp 10804 } 10805 10806 ctx := testContext2(t, &ContextOpts{ 10807 Providers: map[addrs.Provider]providers.Factory{ 10808 addrs.NewDefaultProvider("test"): testProviderFuncFixed(p), 10809 }, 10810 }) 10811 10812 plan, diags := ctx.Plan(m, states.NewState(), DefaultPlanOpts) 10813 if diags.HasErrors() { 10814 t.Fatal(diags.ErrWithWarnings()) 10815 } 10816 10817 state, diags := ctx.Apply(plan, m) 10818 if diags.HasErrors() { 10819 t.Fatal(diags.ErrWithWarnings()) 10820 } 10821 10822 plan, diags = ctx.Plan(m, state, DefaultPlanOpts) 10823 if diags.HasErrors() { 10824 t.Fatal(diags.ErrWithWarnings()) 10825 } 10826 10827 for _, res := range plan.Changes.Resources { 10828 if res.Action != plans.NoOp { 10829 t.Fatalf("expected NoOp, got %s for %s", res.Action, res.Addr) 10830 } 10831 } 10832 } 10833 10834 func TestContext2Apply_moduleSelfReference(t *testing.T) { 10835 m := testModuleInline(t, map[string]string{ 10836 "main.tf": ` 10837 module "test" { 10838 source = "./test" 10839 10840 a = module.test.b 10841 } 10842 10843 output "c" { 10844 value = module.test.c 10845 } 10846 `, 10847 "test/main.tf": ` 10848 variable "a" {} 10849 10850 resource "test_instance" "test" { 10851 } 10852 10853 output "b" { 10854 value = test_instance.test.id 10855 } 10856 10857 output "c" { 10858 value = var.a 10859 }`}) 10860 10861 p := testProvider("test") 10862 p.PlanResourceChangeFn = testDiffFn 10863 ctx := testContext2(t, &ContextOpts{ 10864 Providers: map[addrs.Provider]providers.Factory{ 10865 addrs.NewDefaultProvider("test"): testProviderFuncFixed(p), 10866 }, 10867 }) 10868 10869 plan, diags := ctx.Plan(m, states.NewState(), DefaultPlanOpts) 10870 if diags.HasErrors() { 10871 t.Fatal(diags.ErrWithWarnings()) 10872 } 10873 10874 state, diags := ctx.Apply(plan, m) 10875 if diags.HasErrors() { 10876 t.Fatal(diags.ErrWithWarnings()) 10877 } 10878 10879 ctx = testContext2(t, &ContextOpts{ 10880 Providers: map[addrs.Provider]providers.Factory{ 10881 addrs.NewDefaultProvider("test"): testProviderFuncFixed(p), 10882 }, 10883 }) 10884 10885 plan, diags = ctx.Plan(m, state, &PlanOpts{ 10886 Mode: plans.DestroyMode, 10887 }) 10888 if diags.HasErrors() { 10889 t.Fatal(diags.ErrWithWarnings()) 10890 } 10891 10892 state, diags = ctx.Apply(plan, m) 10893 if diags.HasErrors() { 10894 t.Fatal(diags.ErrWithWarnings()) 10895 } 10896 10897 if !state.Empty() { 10898 t.Fatal("expected empty state, got:", state) 10899 } 10900 } 10901 10902 func TestContext2Apply_moduleExpandDependsOn(t *testing.T) { 10903 m := testModuleInline(t, map[string]string{ 10904 "main.tf": ` 10905 module "child" { 10906 count = 1 10907 source = "./child" 10908 10909 depends_on = [test_instance.a, test_instance.b] 10910 } 10911 10912 resource "test_instance" "a" { 10913 } 10914 10915 10916 resource "test_instance" "b" { 10917 } 10918 `, 10919 "child/main.tf": ` 10920 resource "test_instance" "foo" { 10921 } 10922 10923 output "myoutput" { 10924 value = "literal string" 10925 } 10926 `}) 10927 10928 p := testProvider("test") 10929 p.PlanResourceChangeFn = testDiffFn 10930 ctx := testContext2(t, &ContextOpts{ 10931 Providers: map[addrs.Provider]providers.Factory{ 10932 addrs.NewDefaultProvider("test"): testProviderFuncFixed(p), 10933 }, 10934 }) 10935 10936 plan, diags := ctx.Plan(m, states.NewState(), DefaultPlanOpts) 10937 if diags.HasErrors() { 10938 t.Fatal(diags.ErrWithWarnings()) 10939 } 10940 10941 state, diags := ctx.Apply(plan, m) 10942 if diags.HasErrors() { 10943 t.Fatal(diags.ErrWithWarnings()) 10944 } 10945 10946 ctx = testContext2(t, &ContextOpts{ 10947 Providers: map[addrs.Provider]providers.Factory{ 10948 addrs.NewDefaultProvider("test"): testProviderFuncFixed(p), 10949 }, 10950 }) 10951 10952 plan, diags = ctx.Plan(m, state, &PlanOpts{ 10953 Mode: plans.DestroyMode, 10954 }) 10955 if diags.HasErrors() { 10956 t.Fatal(diags.ErrWithWarnings()) 10957 } 10958 10959 state, diags = ctx.Apply(plan, m) 10960 if diags.HasErrors() { 10961 t.Fatal(diags.ErrWithWarnings()) 10962 } 10963 10964 if !state.Empty() { 10965 t.Fatal("expected empty state, got:", state) 10966 } 10967 } 10968 10969 func TestContext2Apply_scaleInCBD(t *testing.T) { 10970 m := testModuleInline(t, map[string]string{ 10971 "main.tf": ` 10972 variable "ct" { 10973 type = number 10974 } 10975 10976 resource "test_instance" "a" { 10977 count = var.ct 10978 } 10979 10980 resource "test_instance" "b" { 10981 require_new = local.removable 10982 lifecycle { 10983 create_before_destroy = true 10984 } 10985 } 10986 10987 resource "test_instance" "c" { 10988 require_new = test_instance.b.id 10989 lifecycle { 10990 create_before_destroy = true 10991 } 10992 } 10993 10994 output "out" { 10995 value = join(".", test_instance.a[*].id) 10996 } 10997 10998 locals { 10999 removable = join(".", test_instance.a[*].id) 11000 } 11001 `}) 11002 11003 state := states.NewState() 11004 root := state.EnsureModule(addrs.RootModuleInstance) 11005 root.SetResourceInstanceCurrent( 11006 mustResourceInstanceAddr("test_instance.a[0]").Resource, 11007 &states.ResourceInstanceObjectSrc{ 11008 Status: states.ObjectReady, 11009 AttrsJSON: []byte(`{"id":"a0"}`), 11010 Dependencies: []addrs.ConfigResource{}, 11011 CreateBeforeDestroy: true, 11012 }, 11013 mustProviderConfig(`provider["registry.terraform.io/hashicorp/test"]`), 11014 ) 11015 root.SetResourceInstanceCurrent( 11016 mustResourceInstanceAddr("test_instance.a[1]").Resource, 11017 &states.ResourceInstanceObjectSrc{ 11018 Status: states.ObjectReady, 11019 AttrsJSON: []byte(`{"id":"a1"}`), 11020 Dependencies: []addrs.ConfigResource{}, 11021 CreateBeforeDestroy: true, 11022 }, 11023 mustProviderConfig(`provider["registry.terraform.io/hashicorp/test"]`), 11024 ) 11025 root.SetResourceInstanceCurrent( 11026 mustResourceInstanceAddr("test_instance.b").Resource, 11027 &states.ResourceInstanceObjectSrc{ 11028 Status: states.ObjectReady, 11029 AttrsJSON: []byte(`{"id":"b", "require_new":"old.old"}`), 11030 Dependencies: []addrs.ConfigResource{mustConfigResourceAddr("test_instance.a")}, 11031 CreateBeforeDestroy: true, 11032 }, 11033 mustProviderConfig(`provider["registry.terraform.io/hashicorp/test"]`), 11034 ) 11035 root.SetResourceInstanceCurrent( 11036 mustResourceInstanceAddr("test_instance.c").Resource, 11037 &states.ResourceInstanceObjectSrc{ 11038 Status: states.ObjectReady, 11039 AttrsJSON: []byte(`{"id":"c", "require_new":"b"}`), 11040 Dependencies: []addrs.ConfigResource{ 11041 mustConfigResourceAddr("test_instance.a"), 11042 mustConfigResourceAddr("test_instance.b"), 11043 }, 11044 CreateBeforeDestroy: true, 11045 }, 11046 mustProviderConfig(`provider["registry.terraform.io/hashicorp/test"]`), 11047 ) 11048 11049 p := testProvider("test") 11050 11051 p.PlanResourceChangeFn = func(r providers.PlanResourceChangeRequest) (resp providers.PlanResourceChangeResponse) { 11052 // this is a destroy plan 11053 if r.ProposedNewState.IsNull() { 11054 resp.PlannedState = r.ProposedNewState 11055 resp.PlannedPrivate = r.PriorPrivate 11056 return resp 11057 } 11058 11059 n := r.ProposedNewState.AsValueMap() 11060 11061 if r.PriorState.IsNull() { 11062 n["id"] = cty.UnknownVal(cty.String) 11063 resp.PlannedState = cty.ObjectVal(n) 11064 return resp 11065 } 11066 11067 p := r.PriorState.AsValueMap() 11068 11069 priorRN := p["require_new"] 11070 newRN := n["require_new"] 11071 11072 if eq := priorRN.Equals(newRN); !eq.IsKnown() || eq.False() { 11073 resp.RequiresReplace = []cty.Path{{cty.GetAttrStep{Name: "require_new"}}} 11074 n["id"] = cty.UnknownVal(cty.String) 11075 } 11076 11077 resp.PlannedState = cty.ObjectVal(n) 11078 return resp 11079 } 11080 11081 // reduce the count to 1 11082 ctx := testContext2(t, &ContextOpts{ 11083 Providers: map[addrs.Provider]providers.Factory{ 11084 addrs.NewDefaultProvider("test"): testProviderFuncFixed(p), 11085 }, 11086 }) 11087 11088 plan, diags := ctx.Plan(m, state, &PlanOpts{ 11089 Mode: plans.NormalMode, 11090 SetVariables: InputValues{ 11091 "ct": &InputValue{ 11092 Value: cty.NumberIntVal(1), 11093 SourceType: ValueFromCaller, 11094 }, 11095 }, 11096 }) 11097 if diags.HasErrors() { 11098 t.Fatal(diags.ErrWithWarnings()) 11099 } 11100 { 11101 addr := mustResourceInstanceAddr("test_instance.a[0]") 11102 change := plan.Changes.ResourceInstance(addr) 11103 if change == nil { 11104 t.Fatalf("no planned change for %s", addr) 11105 } 11106 if got, want := change.PrevRunAddr, mustResourceInstanceAddr("test_instance.a[0]"); !want.Equal(got) { 11107 t.Errorf("wrong previous run address for %s %s; want %s", addr, got, want) 11108 } 11109 if got, want := change.Action, plans.NoOp; got != want { 11110 t.Errorf("wrong action for %s %s; want %s", addr, got, want) 11111 } 11112 if got, want := change.ActionReason, plans.ResourceInstanceChangeNoReason; got != want { 11113 t.Errorf("wrong action reason for %s %s; want %s", addr, got, want) 11114 } 11115 } 11116 { 11117 addr := mustResourceInstanceAddr("test_instance.a[1]") 11118 change := plan.Changes.ResourceInstance(addr) 11119 if change == nil { 11120 t.Fatalf("no planned change for %s", addr) 11121 } 11122 if got, want := change.PrevRunAddr, mustResourceInstanceAddr("test_instance.a[1]"); !want.Equal(got) { 11123 t.Errorf("wrong previous run address for %s %s; want %s", addr, got, want) 11124 } 11125 if got, want := change.Action, plans.Delete; got != want { 11126 t.Errorf("wrong action for %s %s; want %s", addr, got, want) 11127 } 11128 if got, want := change.ActionReason, plans.ResourceInstanceDeleteBecauseCountIndex; got != want { 11129 t.Errorf("wrong action reason for %s %s; want %s", addr, got, want) 11130 } 11131 } 11132 11133 state, diags = ctx.Apply(plan, m) 11134 if diags.HasErrors() { 11135 log.Fatal(diags.ErrWithWarnings()) 11136 } 11137 11138 // check the output, as those can't cause an error planning the value 11139 out := state.RootModule().OutputValues["out"].Value.AsString() 11140 if out != "a0" { 11141 t.Fatalf(`expected output "a0", got: %q`, out) 11142 } 11143 11144 // reduce the count to 0 11145 ctx = testContext2(t, &ContextOpts{ 11146 Providers: map[addrs.Provider]providers.Factory{ 11147 addrs.NewDefaultProvider("test"): testProviderFuncFixed(p), 11148 }, 11149 }) 11150 11151 plan, diags = ctx.Plan(m, state, &PlanOpts{ 11152 Mode: plans.NormalMode, 11153 SetVariables: InputValues{ 11154 "ct": &InputValue{ 11155 Value: cty.NumberIntVal(0), 11156 SourceType: ValueFromCaller, 11157 }, 11158 }, 11159 }) 11160 if diags.HasErrors() { 11161 t.Fatal(diags.ErrWithWarnings()) 11162 } 11163 { 11164 addr := mustResourceInstanceAddr("test_instance.a[0]") 11165 change := plan.Changes.ResourceInstance(addr) 11166 if change == nil { 11167 t.Fatalf("no planned change for %s", addr) 11168 } 11169 if got, want := change.PrevRunAddr, mustResourceInstanceAddr("test_instance.a[0]"); !want.Equal(got) { 11170 t.Errorf("wrong previous run address for %s %s; want %s", addr, got, want) 11171 } 11172 if got, want := change.Action, plans.Delete; got != want { 11173 t.Errorf("wrong action for %s %s; want %s", addr, got, want) 11174 } 11175 if got, want := change.ActionReason, plans.ResourceInstanceDeleteBecauseCountIndex; got != want { 11176 t.Errorf("wrong action reason for %s %s; want %s", addr, got, want) 11177 } 11178 } 11179 { 11180 addr := mustResourceInstanceAddr("test_instance.a[1]") 11181 change := plan.Changes.ResourceInstance(addr) 11182 if change != nil { 11183 // It was already removed in the previous plan/apply 11184 t.Errorf("unexpected planned change for %s", addr) 11185 } 11186 } 11187 11188 state, diags = ctx.Apply(plan, m) 11189 if diags.HasErrors() { 11190 t.Fatal(diags.ErrWithWarnings()) 11191 } 11192 11193 // check the output, as those can't cause an error planning the value 11194 out = state.RootModule().OutputValues["out"].Value.AsString() 11195 if out != "" { 11196 t.Fatalf(`expected output "", got: %q`, out) 11197 } 11198 } 11199 11200 // Ensure that we can destroy when a provider references a resource that will 11201 // also be destroyed 11202 func TestContext2Apply_destroyProviderReference(t *testing.T) { 11203 m := testModuleInline(t, map[string]string{ 11204 "main.tf": ` 11205 provider "null" { 11206 value = "" 11207 } 11208 11209 module "mod" { 11210 source = "./mod" 11211 } 11212 11213 provider "test" { 11214 value = module.mod.output 11215 } 11216 11217 resource "test_instance" "bar" { 11218 } 11219 `, 11220 "mod/main.tf": ` 11221 data "null_data_source" "foo" { 11222 count = 1 11223 } 11224 11225 11226 output "output" { 11227 value = data.null_data_source.foo[0].output 11228 } 11229 `}) 11230 11231 schemaFn := func(name string) *ProviderSchema { 11232 return &ProviderSchema{ 11233 Provider: &configschema.Block{ 11234 Attributes: map[string]*configschema.Attribute{ 11235 "value": { 11236 Type: cty.String, 11237 Required: true, 11238 }, 11239 }, 11240 }, 11241 ResourceTypes: map[string]*configschema.Block{ 11242 name + "_instance": { 11243 Attributes: map[string]*configschema.Attribute{ 11244 "id": { 11245 Type: cty.String, 11246 Computed: true, 11247 }, 11248 "foo": { 11249 Type: cty.String, 11250 Optional: true, 11251 }, 11252 }, 11253 }, 11254 }, 11255 DataSources: map[string]*configschema.Block{ 11256 name + "_data_source": { 11257 Attributes: map[string]*configschema.Attribute{ 11258 "id": { 11259 Type: cty.String, 11260 Computed: true, 11261 }, 11262 "output": { 11263 Type: cty.String, 11264 Computed: true, 11265 }, 11266 }, 11267 }, 11268 }, 11269 } 11270 } 11271 11272 testP := new(MockProvider) 11273 testP.ReadResourceFn = func(req providers.ReadResourceRequest) providers.ReadResourceResponse { 11274 return providers.ReadResourceResponse{NewState: req.PriorState} 11275 } 11276 testP.GetProviderSchemaResponse = getProviderSchemaResponseFromProviderSchema(schemaFn("test")) 11277 11278 providerConfig := "" 11279 testP.ConfigureProviderFn = func(req providers.ConfigureProviderRequest) (resp providers.ConfigureProviderResponse) { 11280 value := req.Config.GetAttr("value") 11281 if value.IsKnown() && !value.IsNull() { 11282 providerConfig = value.AsString() 11283 } else { 11284 providerConfig = "" 11285 } 11286 return resp 11287 } 11288 testP.ApplyResourceChangeFn = func(req providers.ApplyResourceChangeRequest) (resp providers.ApplyResourceChangeResponse) { 11289 if providerConfig != "valid" { 11290 resp.Diagnostics = resp.Diagnostics.Append(fmt.Errorf("provider config is %q", providerConfig)) 11291 return 11292 } 11293 return testApplyFn(req) 11294 } 11295 testP.PlanResourceChangeFn = testDiffFn 11296 11297 nullP := new(MockProvider) 11298 nullP.ReadResourceFn = func(req providers.ReadResourceRequest) providers.ReadResourceResponse { 11299 return providers.ReadResourceResponse{NewState: req.PriorState} 11300 } 11301 nullP.GetProviderSchemaResponse = getProviderSchemaResponseFromProviderSchema(schemaFn("null")) 11302 11303 nullP.ApplyResourceChangeFn = testApplyFn 11304 nullP.PlanResourceChangeFn = testDiffFn 11305 11306 nullP.ReadDataSourceResponse = &providers.ReadDataSourceResponse{ 11307 State: cty.ObjectVal(map[string]cty.Value{ 11308 "id": cty.StringVal("ID"), 11309 "output": cty.StringVal("valid"), 11310 }), 11311 } 11312 11313 ctx := testContext2(t, &ContextOpts{ 11314 Providers: map[addrs.Provider]providers.Factory{ 11315 addrs.NewDefaultProvider("test"): testProviderFuncFixed(testP), 11316 addrs.NewDefaultProvider("null"): testProviderFuncFixed(nullP), 11317 }, 11318 }) 11319 11320 plan, diags := ctx.Plan(m, states.NewState(), DefaultPlanOpts) 11321 assertNoErrors(t, diags) 11322 11323 state, diags := ctx.Apply(plan, m) 11324 if diags.HasErrors() { 11325 t.Fatalf("apply errors: %s", diags.Err()) 11326 } 11327 11328 ctx = testContext2(t, &ContextOpts{ 11329 Providers: map[addrs.Provider]providers.Factory{ 11330 addrs.NewDefaultProvider("test"): testProviderFuncFixed(testP), 11331 addrs.NewDefaultProvider("null"): testProviderFuncFixed(nullP), 11332 }, 11333 }) 11334 11335 plan, diags = ctx.Plan(m, state, &PlanOpts{ 11336 Mode: plans.DestroyMode, 11337 }) 11338 assertNoErrors(t, diags) 11339 11340 if _, diags := ctx.Apply(plan, m); diags.HasErrors() { 11341 t.Fatalf("destroy apply errors: %s", diags.Err()) 11342 } 11343 } 11344 11345 // Destroying properly requires pruning out all unneeded config nodes to 11346 // prevent incorrect expansion evaluation. 11347 func TestContext2Apply_destroyInterModuleExpansion(t *testing.T) { 11348 m := testModuleInline(t, map[string]string{ 11349 "main.tf": ` 11350 data "test_data_source" "a" { 11351 for_each = { 11352 one = "thing" 11353 } 11354 } 11355 11356 locals { 11357 module_input = { 11358 for k, v in data.test_data_source.a : k => v.id 11359 } 11360 } 11361 11362 module "mod1" { 11363 source = "./mod" 11364 input = local.module_input 11365 } 11366 11367 module "mod2" { 11368 source = "./mod" 11369 input = module.mod1.outputs 11370 } 11371 11372 resource "test_instance" "bar" { 11373 for_each = module.mod2.outputs 11374 } 11375 11376 output "module_output" { 11377 value = module.mod2.outputs 11378 } 11379 output "test_instances" { 11380 value = test_instance.bar 11381 } 11382 `, 11383 "mod/main.tf": ` 11384 variable "input" { 11385 } 11386 11387 data "test_data_source" "foo" { 11388 for_each = var.input 11389 } 11390 11391 output "outputs" { 11392 value = data.test_data_source.foo 11393 } 11394 `}) 11395 11396 p := testProvider("test") 11397 p.PlanResourceChangeFn = testDiffFn 11398 p.ReadDataSourceFn = func(req providers.ReadDataSourceRequest) providers.ReadDataSourceResponse { 11399 return providers.ReadDataSourceResponse{ 11400 State: cty.ObjectVal(map[string]cty.Value{ 11401 "id": cty.StringVal("data_source"), 11402 "foo": cty.StringVal("output"), 11403 }), 11404 } 11405 } 11406 11407 ctx := testContext2(t, &ContextOpts{ 11408 Providers: map[addrs.Provider]providers.Factory{ 11409 addrs.NewDefaultProvider("test"): testProviderFuncFixed(p), 11410 }, 11411 }) 11412 11413 plan, diags := ctx.Plan(m, states.NewState(), DefaultPlanOpts) 11414 assertNoErrors(t, diags) 11415 11416 state, diags := ctx.Apply(plan, m) 11417 if diags.HasErrors() { 11418 t.Fatalf("apply errors: %s", diags.Err()) 11419 } 11420 11421 destroy := func() { 11422 ctx = testContext2(t, &ContextOpts{ 11423 Providers: map[addrs.Provider]providers.Factory{ 11424 addrs.NewDefaultProvider("test"): testProviderFuncFixed(p), 11425 }, 11426 }) 11427 11428 plan, diags = ctx.Plan(m, state, &PlanOpts{ 11429 Mode: plans.DestroyMode, 11430 }) 11431 assertNoErrors(t, diags) 11432 11433 state, diags = ctx.Apply(plan, m) 11434 if diags.HasErrors() { 11435 t.Fatalf("destroy apply errors: %s", diags.Err()) 11436 } 11437 } 11438 11439 destroy() 11440 // Destroying again from the empty state should not cause any errors either 11441 destroy() 11442 } 11443 11444 func TestContext2Apply_createBeforeDestroyWithModule(t *testing.T) { 11445 m := testModuleInline(t, map[string]string{ 11446 "main.tf": ` 11447 variable "v" {} 11448 11449 module "mod" { 11450 source = "./mod" 11451 in = var.v 11452 } 11453 11454 resource "test_resource" "a" { 11455 value = var.v 11456 depends_on = [module.mod] 11457 lifecycle { 11458 create_before_destroy = true 11459 } 11460 } 11461 `, 11462 "mod/main.tf": ` 11463 variable "in" {} 11464 11465 resource "test_resource" "a" { 11466 value = var.in 11467 } 11468 `}) 11469 11470 p := testProvider("test") 11471 p.PlanResourceChangeFn = func(req providers.PlanResourceChangeRequest) (resp providers.PlanResourceChangeResponse) { 11472 // this is a destroy plan 11473 if req.ProposedNewState.IsNull() { 11474 resp.PlannedState = req.ProposedNewState 11475 resp.PlannedPrivate = req.PriorPrivate 11476 return resp 11477 } 11478 11479 proposed := req.ProposedNewState.AsValueMap() 11480 proposed["id"] = cty.UnknownVal(cty.String) 11481 11482 resp.PlannedState = cty.ObjectVal(proposed) 11483 resp.RequiresReplace = []cty.Path{{cty.GetAttrStep{Name: "value"}}} 11484 return resp 11485 } 11486 11487 ctx := testContext2(t, &ContextOpts{ 11488 Providers: map[addrs.Provider]providers.Factory{ 11489 addrs.NewDefaultProvider("test"): testProviderFuncFixed(p), 11490 }, 11491 }) 11492 11493 plan, diags := ctx.Plan(m, states.NewState(), &PlanOpts{ 11494 Mode: plans.NormalMode, 11495 SetVariables: InputValues{ 11496 "v": &InputValue{ 11497 Value: cty.StringVal("A"), 11498 }, 11499 }, 11500 }) 11501 assertNoErrors(t, diags) 11502 11503 state, diags := ctx.Apply(plan, m) 11504 if diags.HasErrors() { 11505 t.Fatalf("apply errors: %s", diags.Err()) 11506 } 11507 11508 ctx = testContext2(t, &ContextOpts{ 11509 Providers: map[addrs.Provider]providers.Factory{ 11510 addrs.NewDefaultProvider("test"): testProviderFuncFixed(p), 11511 }, 11512 }) 11513 11514 plan, diags = ctx.Plan(m, state, &PlanOpts{ 11515 Mode: plans.NormalMode, 11516 SetVariables: InputValues{ 11517 "v": &InputValue{ 11518 Value: cty.StringVal("B"), 11519 }, 11520 }, 11521 }) 11522 assertNoErrors(t, diags) 11523 11524 _, diags = ctx.Apply(plan, m) 11525 if diags.HasErrors() { 11526 t.Fatalf("apply errors: %s", diags.Err()) 11527 } 11528 } 11529 11530 func TestContext2Apply_forcedCBD(t *testing.T) { 11531 m := testModuleInline(t, map[string]string{ 11532 "main.tf": ` 11533 variable "v" {} 11534 11535 resource "test_instance" "a" { 11536 require_new = var.v 11537 } 11538 11539 resource "test_instance" "b" { 11540 depends_on = [test_instance.a] 11541 lifecycle { 11542 create_before_destroy = true 11543 } 11544 } 11545 `}) 11546 11547 p := testProvider("test") 11548 p.PlanResourceChangeFn = testDiffFn 11549 11550 ctx := testContext2(t, &ContextOpts{ 11551 Providers: map[addrs.Provider]providers.Factory{ 11552 addrs.NewDefaultProvider("test"): testProviderFuncFixed(p), 11553 }, 11554 }) 11555 11556 plan, diags := ctx.Plan(m, states.NewState(), &PlanOpts{ 11557 Mode: plans.NormalMode, 11558 SetVariables: InputValues{ 11559 "v": &InputValue{ 11560 Value: cty.StringVal("A"), 11561 }, 11562 }, 11563 }) 11564 assertNoErrors(t, diags) 11565 11566 state, diags := ctx.Apply(plan, m) 11567 if diags.HasErrors() { 11568 t.Fatalf("apply errors: %s", diags.Err()) 11569 } 11570 11571 ctx = testContext2(t, &ContextOpts{ 11572 Providers: map[addrs.Provider]providers.Factory{ 11573 addrs.NewDefaultProvider("test"): testProviderFuncFixed(p), 11574 }, 11575 }) 11576 11577 plan, diags = ctx.Plan(m, state, &PlanOpts{ 11578 Mode: plans.NormalMode, 11579 SetVariables: InputValues{ 11580 "v": &InputValue{ 11581 Value: cty.StringVal("B"), 11582 }, 11583 }, 11584 }) 11585 assertNoErrors(t, diags) 11586 11587 _, diags = ctx.Apply(plan, m) 11588 if diags.HasErrors() { 11589 t.Fatalf("apply errors: %s", diags.Err()) 11590 } 11591 } 11592 11593 func TestContext2Apply_removeReferencedResource(t *testing.T) { 11594 m := testModuleInline(t, map[string]string{ 11595 "main.tf": ` 11596 variable "ct" { 11597 } 11598 11599 resource "test_resource" "to_remove" { 11600 count = var.ct 11601 } 11602 11603 resource "test_resource" "c" { 11604 value = join("", test_resource.to_remove[*].id) 11605 } 11606 `}) 11607 11608 p := testProvider("test") 11609 p.PlanResourceChangeFn = testDiffFn 11610 11611 ctx := testContext2(t, &ContextOpts{ 11612 Providers: map[addrs.Provider]providers.Factory{ 11613 addrs.NewDefaultProvider("test"): testProviderFuncFixed(p), 11614 }, 11615 }) 11616 11617 plan, diags := ctx.Plan(m, states.NewState(), &PlanOpts{ 11618 Mode: plans.NormalMode, 11619 SetVariables: InputValues{ 11620 "ct": &InputValue{ 11621 Value: cty.NumberIntVal(1), 11622 }, 11623 }, 11624 }) 11625 assertNoErrors(t, diags) 11626 11627 state, diags := ctx.Apply(plan, m) 11628 if diags.HasErrors() { 11629 t.Fatalf("apply errors: %s", diags.Err()) 11630 } 11631 11632 ctx = testContext2(t, &ContextOpts{ 11633 Providers: map[addrs.Provider]providers.Factory{ 11634 addrs.NewDefaultProvider("test"): testProviderFuncFixed(p), 11635 }, 11636 }) 11637 11638 plan, diags = ctx.Plan(m, state, &PlanOpts{ 11639 Mode: plans.NormalMode, 11640 SetVariables: InputValues{ 11641 "ct": &InputValue{ 11642 Value: cty.NumberIntVal(0), 11643 }, 11644 }, 11645 }) 11646 assertNoErrors(t, diags) 11647 11648 _, diags = ctx.Apply(plan, m) 11649 if diags.HasErrors() { 11650 t.Fatalf("apply errors: %s", diags.Err()) 11651 } 11652 } 11653 11654 func TestContext2Apply_variableSensitivity(t *testing.T) { 11655 m := testModuleInline(t, map[string]string{ 11656 "main.tf": ` 11657 variable "sensitive_var" { 11658 default = "foo" 11659 sensitive = true 11660 } 11661 11662 variable "sensitive_id" { 11663 default = "secret id" 11664 sensitive = true 11665 } 11666 11667 resource "test_resource" "foo" { 11668 value = var.sensitive_var 11669 11670 network_interface { 11671 network_interface_id = var.sensitive_id 11672 } 11673 }`, 11674 }) 11675 11676 p := new(MockProvider) 11677 p.ReadResourceFn = func(req providers.ReadResourceRequest) providers.ReadResourceResponse { 11678 return providers.ReadResourceResponse{NewState: req.PriorState} 11679 } 11680 p.GetProviderSchemaResponse = getProviderSchemaResponseFromProviderSchema(&ProviderSchema{ 11681 Provider: &configschema.Block{}, 11682 ResourceTypes: map[string]*configschema.Block{ 11683 "test_resource": { 11684 Attributes: map[string]*configschema.Attribute{ 11685 "id": { 11686 Type: cty.String, 11687 Computed: true, 11688 }, 11689 "value": { 11690 Type: cty.String, 11691 Optional: true, 11692 Computed: true, 11693 }, 11694 }, 11695 BlockTypes: map[string]*configschema.NestedBlock{ 11696 "network_interface": { 11697 Block: configschema.Block{ 11698 Attributes: map[string]*configschema.Attribute{ 11699 "network_interface_id": {Type: cty.String, Optional: true}, 11700 "device_index": {Type: cty.Number, Optional: true}, 11701 }, 11702 }, 11703 Nesting: configschema.NestingSet, 11704 }, 11705 }, 11706 }, 11707 }, 11708 }) 11709 p.PlanResourceChangeFn = testDiffFn 11710 11711 ctx := testContext2(t, &ContextOpts{ 11712 Providers: map[addrs.Provider]providers.Factory{ 11713 addrs.NewDefaultProvider("test"): testProviderFuncFixed(p), 11714 }, 11715 }) 11716 11717 plan, diags := ctx.Plan(m, states.NewState(), SimplePlanOpts(plans.NormalMode, testInputValuesUnset(m.Module.Variables))) 11718 assertNoErrors(t, diags) 11719 11720 state, diags := ctx.Apply(plan, m) 11721 if diags.HasErrors() { 11722 t.Fatalf("apply errors: %s", diags.Err()) 11723 } 11724 11725 // Run a second apply with no changes 11726 ctx = testContext2(t, &ContextOpts{ 11727 Providers: map[addrs.Provider]providers.Factory{ 11728 addrs.NewDefaultProvider("test"): testProviderFuncFixed(p), 11729 }, 11730 }) 11731 11732 plan, diags = ctx.Plan(m, state, SimplePlanOpts(plans.NormalMode, testInputValuesUnset(m.Module.Variables))) 11733 assertNoErrors(t, diags) 11734 11735 state, diags = ctx.Apply(plan, m) 11736 if diags.HasErrors() { 11737 t.Fatalf("apply errors: %s", diags.Err()) 11738 } 11739 11740 // Now change the variable value for sensitive_var 11741 ctx = testContext2(t, &ContextOpts{ 11742 Providers: map[addrs.Provider]providers.Factory{ 11743 addrs.NewDefaultProvider("test"): testProviderFuncFixed(p), 11744 }, 11745 }) 11746 11747 plan, diags = ctx.Plan(m, state, &PlanOpts{ 11748 Mode: plans.NormalMode, 11749 SetVariables: InputValues{ 11750 "sensitive_id": &InputValue{Value: cty.NilVal}, 11751 "sensitive_var": &InputValue{ 11752 Value: cty.StringVal("bar"), 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_variableSensitivityPropagation(t *testing.T) { 11765 m := testModuleInline(t, map[string]string{ 11766 "main.tf": ` 11767 variable "sensitive_map" { 11768 type = map(string) 11769 default = { 11770 "x" = "foo" 11771 } 11772 sensitive = true 11773 } 11774 11775 resource "test_resource" "foo" { 11776 value = var.sensitive_map.x 11777 } 11778 `, 11779 }) 11780 11781 p := testProvider("test") 11782 p.PlanResourceChangeFn = testDiffFn 11783 11784 ctx := testContext2(t, &ContextOpts{ 11785 Providers: map[addrs.Provider]providers.Factory{ 11786 addrs.NewDefaultProvider("test"): testProviderFuncFixed(p), 11787 }, 11788 }) 11789 11790 plan, diags := ctx.Plan(m, states.NewState(), SimplePlanOpts(plans.NormalMode, testInputValuesUnset(m.Module.Variables))) 11791 if diags.HasErrors() { 11792 t.Fatalf("plan errors: %s", diags.Err()) 11793 } 11794 11795 verifySensitiveValue := func(pvms []cty.PathValueMarks) { 11796 if len(pvms) != 1 { 11797 t.Fatalf("expected 1 sensitive path, got %d", len(pvms)) 11798 } 11799 pvm := pvms[0] 11800 if gotPath, wantPath := pvm.Path, cty.GetAttrPath("value"); !gotPath.Equals(wantPath) { 11801 t.Errorf("wrong path\n got: %#v\nwant: %#v", gotPath, wantPath) 11802 } 11803 if gotMarks, wantMarks := pvm.Marks, cty.NewValueMarks(marks.Sensitive); !gotMarks.Equal(wantMarks) { 11804 t.Errorf("wrong marks\n got: %#v\nwant: %#v", gotMarks, wantMarks) 11805 } 11806 } 11807 11808 addr := mustResourceInstanceAddr("test_resource.foo") 11809 fooChangeSrc := plan.Changes.ResourceInstance(addr) 11810 verifySensitiveValue(fooChangeSrc.AfterValMarks) 11811 11812 state, diags := ctx.Apply(plan, m) 11813 if diags.HasErrors() { 11814 t.Fatalf("apply errors: %s", diags.Err()) 11815 } 11816 11817 fooState := state.ResourceInstance(addr) 11818 verifySensitiveValue(fooState.Current.AttrSensitivePaths) 11819 } 11820 11821 func TestContext2Apply_variableSensitivityProviders(t *testing.T) { 11822 m := testModuleInline(t, map[string]string{ 11823 "main.tf": ` 11824 resource "test_resource" "foo" { 11825 sensitive_value = "should get marked" 11826 } 11827 11828 resource "test_resource" "bar" { 11829 value = test_resource.foo.sensitive_value 11830 random = test_resource.foo.id # not sensitive 11831 11832 nesting_single { 11833 value = "abc" 11834 sensitive_value = "xyz" 11835 } 11836 } 11837 11838 resource "test_resource" "baz" { 11839 value = test_resource.bar.nesting_single.sensitive_value 11840 } 11841 `, 11842 }) 11843 11844 p := testProvider("test") 11845 p.PlanResourceChangeFn = testDiffFn 11846 11847 ctx := testContext2(t, &ContextOpts{ 11848 Providers: map[addrs.Provider]providers.Factory{ 11849 addrs.NewDefaultProvider("test"): testProviderFuncFixed(p), 11850 }, 11851 }) 11852 11853 plan, diags := ctx.Plan(m, states.NewState(), DefaultPlanOpts) 11854 if diags.HasErrors() { 11855 t.Fatalf("plan errors: %s", diags.Err()) 11856 } 11857 11858 verifySensitiveValue := func(pvms []cty.PathValueMarks) { 11859 if len(pvms) != 1 { 11860 t.Fatalf("expected 1 sensitive path, got %d", len(pvms)) 11861 } 11862 pvm := pvms[0] 11863 if gotPath, wantPath := pvm.Path, cty.GetAttrPath("value"); !gotPath.Equals(wantPath) { 11864 t.Errorf("wrong path\n got: %#v\nwant: %#v", gotPath, wantPath) 11865 } 11866 if gotMarks, wantMarks := pvm.Marks, cty.NewValueMarks(marks.Sensitive); !gotMarks.Equal(wantMarks) { 11867 t.Errorf("wrong marks\n got: %#v\nwant: %#v", gotMarks, wantMarks) 11868 } 11869 } 11870 11871 // Sensitive attributes (defined by the provider) are marked 11872 // as sensitive when referenced from another resource 11873 // "bar" references sensitive resources in "foo" 11874 barAddr := mustResourceInstanceAddr("test_resource.bar") 11875 barChangeSrc := plan.Changes.ResourceInstance(barAddr) 11876 verifySensitiveValue(barChangeSrc.AfterValMarks) 11877 11878 bazAddr := mustResourceInstanceAddr("test_resource.baz") 11879 bazChangeSrc := plan.Changes.ResourceInstance(bazAddr) 11880 verifySensitiveValue(bazChangeSrc.AfterValMarks) 11881 11882 state, diags := ctx.Apply(plan, m) 11883 if diags.HasErrors() { 11884 t.Fatalf("apply errors: %s", diags.Err()) 11885 } 11886 11887 barState := state.ResourceInstance(barAddr) 11888 verifySensitiveValue(barState.Current.AttrSensitivePaths) 11889 11890 bazState := state.ResourceInstance(bazAddr) 11891 verifySensitiveValue(bazState.Current.AttrSensitivePaths) 11892 } 11893 11894 func TestContext2Apply_variableSensitivityChange(t *testing.T) { 11895 m := testModuleInline(t, map[string]string{ 11896 "main.tf": ` 11897 variable "sensitive_var" { 11898 default = "hello" 11899 sensitive = true 11900 } 11901 11902 resource "test_resource" "foo" { 11903 value = var.sensitive_var 11904 }`, 11905 }) 11906 11907 p := testProvider("test") 11908 p.PlanResourceChangeFn = testDiffFn 11909 11910 ctx := testContext2(t, &ContextOpts{ 11911 Providers: map[addrs.Provider]providers.Factory{ 11912 addrs.NewDefaultProvider("test"): testProviderFuncFixed(p), 11913 }, 11914 }) 11915 11916 state := states.BuildState(func(s *states.SyncState) { 11917 s.SetResourceInstanceCurrent( 11918 addrs.Resource{ 11919 Mode: addrs.ManagedResourceMode, 11920 Type: "test_resource", 11921 Name: "foo", 11922 }.Instance(addrs.NoKey).Absolute(addrs.RootModuleInstance), 11923 &states.ResourceInstanceObjectSrc{ 11924 Status: states.ObjectReady, 11925 AttrsJSON: []byte(`{"id":"foo", "value":"hello"}`), 11926 // No AttrSensitivePaths present 11927 }, 11928 addrs.AbsProviderConfig{ 11929 Provider: addrs.NewDefaultProvider("test"), 11930 Module: addrs.RootModule, 11931 }, 11932 ) 11933 }) 11934 11935 plan, diags := ctx.Plan(m, state, SimplePlanOpts(plans.NormalMode, testInputValuesUnset(m.Module.Variables))) 11936 assertNoErrors(t, diags) 11937 11938 addr := mustResourceInstanceAddr("test_resource.foo") 11939 11940 state, diags = ctx.Apply(plan, m) 11941 assertNoErrors(t, diags) 11942 11943 fooState := state.ResourceInstance(addr) 11944 11945 if len(fooState.Current.AttrSensitivePaths) != 1 { 11946 t.Fatalf("wrong number of sensitive paths, expected 1, got, %v", len(fooState.Current.AttrSensitivePaths)) 11947 } 11948 got := fooState.Current.AttrSensitivePaths[0] 11949 want := cty.PathValueMarks{ 11950 Path: cty.GetAttrPath("value"), 11951 Marks: cty.NewValueMarks(marks.Sensitive), 11952 } 11953 11954 if !got.Equal(want) { 11955 t.Fatalf("wrong value marks; got:\n%#v\n\nwant:\n%#v\n", got, want) 11956 } 11957 11958 m2 := testModuleInline(t, map[string]string{ 11959 "main.tf": ` 11960 variable "sensitive_var" { 11961 default = "hello" 11962 sensitive = false 11963 } 11964 11965 resource "test_resource" "foo" { 11966 value = var.sensitive_var 11967 }`, 11968 }) 11969 11970 ctx2 := testContext2(t, &ContextOpts{ 11971 Providers: map[addrs.Provider]providers.Factory{ 11972 addrs.NewDefaultProvider("test"): testProviderFuncFixed(p), 11973 }, 11974 }) 11975 11976 // NOTE: Prior to our refactoring to make the state an explicit argument 11977 // of Plan, as opposed to hidden state inside Context, this test was 11978 // calling ctx.Apply instead of ctx2.Apply and thus using the previous 11979 // plan instead of this new plan. "Fixing" it to use the new plan seems 11980 // to break the test, so we've preserved that oddity here by saving the 11981 // old plan as oldPlan and essentially discarding the new plan entirely, 11982 // but this seems rather suspicious and we should ideally figure out what 11983 // this test was originally intending to do and make it do that. 11984 oldPlan := plan 11985 _, diags = ctx2.Plan(m2, state, SimplePlanOpts(plans.NormalMode, testInputValuesUnset(m.Module.Variables))) 11986 assertNoErrors(t, diags) 11987 stateWithoutSensitive, diags := ctx.Apply(oldPlan, m) 11988 assertNoErrors(t, diags) 11989 11990 fooState2 := stateWithoutSensitive.ResourceInstance(addr) 11991 if len(fooState2.Current.AttrSensitivePaths) > 0 { 11992 t.Fatalf( 11993 "wrong number of sensitive paths, expected 0, got, %v\n%s", 11994 len(fooState2.Current.AttrSensitivePaths), 11995 spew.Sdump(fooState2.Current.AttrSensitivePaths), 11996 ) 11997 } 11998 } 11999 12000 func TestContext2Apply_moduleVariableOptionalAttributes(t *testing.T) { 12001 m := testModuleInline(t, map[string]string{ 12002 "main.tf": ` 12003 variable "in" { 12004 type = object({ 12005 required = string 12006 optional = optional(string) 12007 default = optional(bool, true) 12008 nested = optional( 12009 map(object({ 12010 a = optional(string, "foo") 12011 b = optional(number, 5) 12012 })), { 12013 "boop": {} 12014 } 12015 ) 12016 }) 12017 } 12018 12019 output "out" { 12020 value = var.in 12021 } 12022 `}) 12023 12024 ctx := testContext2(t, &ContextOpts{}) 12025 12026 plan, diags := ctx.Plan(m, states.NewState(), &PlanOpts{ 12027 Mode: plans.NormalMode, 12028 SetVariables: InputValues{ 12029 "in": &InputValue{ 12030 Value: cty.MapVal(map[string]cty.Value{ 12031 "required": cty.StringVal("boop"), 12032 }), 12033 SourceType: ValueFromCaller, 12034 }, 12035 }, 12036 }) 12037 if diags.HasErrors() { 12038 t.Fatal(diags.ErrWithWarnings()) 12039 } 12040 12041 state, diags := ctx.Apply(plan, m) 12042 if diags.HasErrors() { 12043 t.Fatal(diags.ErrWithWarnings()) 12044 } 12045 12046 got := state.RootModule().OutputValues["out"].Value 12047 want := cty.ObjectVal(map[string]cty.Value{ 12048 "required": cty.StringVal("boop"), 12049 12050 // Because "optional" was marked as optional, it got silently filled 12051 // in as a null value of string type rather than returning an error. 12052 "optional": cty.NullVal(cty.String), 12053 12054 // Similarly, "default" was marked as optional with a default value, 12055 // and since it was omitted should be filled in with that default. 12056 "default": cty.True, 12057 12058 // Nested is a complex structure which has fully described defaults, 12059 // so again it should be filled with the default structure. 12060 "nested": cty.MapVal(map[string]cty.Value{ 12061 "boop": cty.ObjectVal(map[string]cty.Value{ 12062 "a": cty.StringVal("foo"), 12063 "b": cty.NumberIntVal(5), 12064 }), 12065 }), 12066 }) 12067 if !want.RawEquals(got) { 12068 t.Fatalf("wrong result\ngot: %#v\nwant: %#v", got, want) 12069 } 12070 } 12071 12072 func TestContext2Apply_moduleVariableOptionalAttributesDefault(t *testing.T) { 12073 m := testModuleInline(t, map[string]string{ 12074 "main.tf": ` 12075 variable "in" { 12076 type = object({ 12077 required = string 12078 optional = optional(string) 12079 default = optional(bool, true) 12080 }) 12081 default = { 12082 required = "boop" 12083 } 12084 } 12085 12086 output "out" { 12087 value = var.in 12088 } 12089 `}) 12090 12091 ctx := testContext2(t, &ContextOpts{}) 12092 12093 // We don't specify a value for the variable here, relying on its defined 12094 // default. 12095 plan, diags := ctx.Plan(m, states.NewState(), SimplePlanOpts(plans.NormalMode, testInputValuesUnset(m.Module.Variables))) 12096 if diags.HasErrors() { 12097 t.Fatal(diags.ErrWithWarnings()) 12098 } 12099 12100 state, diags := ctx.Apply(plan, m) 12101 if diags.HasErrors() { 12102 t.Fatal(diags.ErrWithWarnings()) 12103 } 12104 12105 got := state.RootModule().OutputValues["out"].Value 12106 want := cty.ObjectVal(map[string]cty.Value{ 12107 "required": cty.StringVal("boop"), 12108 12109 // "optional" is not present in the variable default, so it is filled 12110 // with null. 12111 "optional": cty.NullVal(cty.String), 12112 12113 // Similarly, "default" is not present in the variable default, so its 12114 // value is replaced with the type's specified default. 12115 "default": cty.True, 12116 }) 12117 if !want.RawEquals(got) { 12118 t.Fatalf("wrong result\ngot: %#v\nwant: %#v", got, want) 12119 } 12120 } 12121 12122 func TestContext2Apply_moduleVariableOptionalAttributesDefaultNull(t *testing.T) { 12123 m := testModuleInline(t, map[string]string{ 12124 "main.tf": ` 12125 variable "in" { 12126 type = object({ 12127 required = string 12128 optional = optional(string) 12129 default = optional(bool, true) 12130 }) 12131 default = null 12132 } 12133 12134 # Wrap the input variable in a tuple because a null output value is elided from 12135 # the plan, which prevents us from testing its type. 12136 output "out" { 12137 value = [var.in] 12138 } 12139 `}) 12140 12141 ctx := testContext2(t, &ContextOpts{}) 12142 12143 // We don't specify a value for the variable here, relying on its defined 12144 // default. 12145 plan, diags := ctx.Plan(m, states.NewState(), SimplePlanOpts(plans.NormalMode, testInputValuesUnset(m.Module.Variables))) 12146 if diags.HasErrors() { 12147 t.Fatal(diags.ErrWithWarnings()) 12148 } 12149 12150 state, diags := ctx.Apply(plan, m) 12151 if diags.HasErrors() { 12152 t.Fatal(diags.ErrWithWarnings()) 12153 } 12154 12155 got := state.RootModule().OutputValues["out"].Value 12156 // The null default value should be bound, after type converting to the 12157 // full object type 12158 want := cty.TupleVal([]cty.Value{cty.NullVal(cty.Object(map[string]cty.Type{ 12159 "required": cty.String, 12160 "optional": cty.String, 12161 "default": cty.Bool, 12162 }))}) 12163 if !want.RawEquals(got) { 12164 t.Fatalf("wrong result\ngot: %#v\nwant: %#v", got, want) 12165 } 12166 } 12167 12168 func TestContext2Apply_provisionerSensitive(t *testing.T) { 12169 m := testModule(t, "apply-provisioner-sensitive") 12170 p := testProvider("aws") 12171 12172 pr := testProvisioner() 12173 pr.ProvisionResourceFn = func(req provisioners.ProvisionResourceRequest) (resp provisioners.ProvisionResourceResponse) { 12174 if req.Config.ContainsMarked() { 12175 t.Fatalf("unexpectedly marked config value: %#v", req.Config) 12176 } 12177 command := req.Config.GetAttr("command") 12178 if command.IsMarked() { 12179 t.Fatalf("unexpectedly marked command argument: %#v", command.Marks()) 12180 } 12181 req.UIOutput.Output(fmt.Sprintf("Executing: %q", command.AsString())) 12182 return 12183 } 12184 p.PlanResourceChangeFn = testDiffFn 12185 p.ApplyResourceChangeFn = testApplyFn 12186 12187 h := new(MockHook) 12188 ctx := testContext2(t, &ContextOpts{ 12189 Hooks: []Hook{h}, 12190 Providers: map[addrs.Provider]providers.Factory{ 12191 addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p), 12192 }, 12193 Provisioners: map[string]provisioners.Factory{ 12194 "shell": testProvisionerFuncFixed(pr), 12195 }, 12196 }) 12197 12198 plan, diags := ctx.Plan(m, states.NewState(), &PlanOpts{ 12199 Mode: plans.NormalMode, 12200 SetVariables: InputValues{ 12201 "password": &InputValue{ 12202 Value: cty.StringVal("secret"), 12203 SourceType: ValueFromCaller, 12204 }, 12205 }, 12206 }) 12207 assertNoErrors(t, diags) 12208 12209 // "restart" provisioner 12210 pr.CloseCalled = false 12211 12212 state, diags := ctx.Apply(plan, m) 12213 if diags.HasErrors() { 12214 logDiagnostics(t, diags) 12215 t.Fatal("apply failed") 12216 } 12217 12218 actual := strings.TrimSpace(state.String()) 12219 expected := strings.TrimSpace(testTerraformApplyProvisionerSensitiveStr) 12220 if actual != expected { 12221 t.Fatalf("wrong result\n\ngot:\n%s\n\nwant:\n%s", actual, expected) 12222 } 12223 12224 // Verify apply was invoked 12225 if !pr.ProvisionResourceCalled { 12226 t.Fatalf("provisioner was not called on apply") 12227 } 12228 12229 // Verify output was suppressed 12230 if !h.ProvisionOutputCalled { 12231 t.Fatalf("ProvisionOutput hook not called") 12232 } 12233 if got, doNotWant := h.ProvisionOutputMessage, "secret"; strings.Contains(got, doNotWant) { 12234 t.Errorf("sensitive value %q included in output:\n%s", doNotWant, got) 12235 } 12236 if got, want := h.ProvisionOutputMessage, "output suppressed"; !strings.Contains(got, want) { 12237 t.Errorf("expected hook to be called with %q, but was:\n%s", want, got) 12238 } 12239 } 12240 12241 func TestContext2Apply_warnings(t *testing.T) { 12242 m := testModuleInline(t, map[string]string{ 12243 "main.tf": ` 12244 resource "test_resource" "foo" { 12245 }`, 12246 }) 12247 12248 p := testProvider("test") 12249 p.PlanResourceChangeFn = testDiffFn 12250 12251 p.ApplyResourceChangeFn = func(req providers.ApplyResourceChangeRequest) providers.ApplyResourceChangeResponse { 12252 resp := testApplyFn(req) 12253 12254 resp.Diagnostics = resp.Diagnostics.Append(tfdiags.SimpleWarning("warning")) 12255 return resp 12256 } 12257 12258 ctx := testContext2(t, &ContextOpts{ 12259 Providers: map[addrs.Provider]providers.Factory{ 12260 addrs.NewDefaultProvider("test"): testProviderFuncFixed(p), 12261 }, 12262 }) 12263 12264 plan, diags := ctx.Plan(m, states.NewState(), DefaultPlanOpts) 12265 assertNoErrors(t, diags) 12266 12267 state, diags := ctx.Apply(plan, m) 12268 if diags.HasErrors() { 12269 t.Fatalf("diags: %s", diags.Err()) 12270 } 12271 12272 inst := state.ResourceInstance(mustResourceInstanceAddr("test_resource.foo")) 12273 if inst == nil { 12274 t.Fatal("missing 'test_resource.foo' in state:", state) 12275 } 12276 } 12277 12278 func TestContext2Apply_rpcDiagnostics(t *testing.T) { 12279 m := testModuleInline(t, map[string]string{ 12280 "main.tf": ` 12281 resource "test_instance" "a" { 12282 } 12283 `, 12284 }) 12285 12286 p := testProvider("test") 12287 p.PlanResourceChangeFn = testDiffFn 12288 p.ApplyResourceChangeFn = func(req providers.ApplyResourceChangeRequest) (resp providers.ApplyResourceChangeResponse) { 12289 resp = testApplyFn(req) 12290 resp.Diagnostics = resp.Diagnostics.Append(tfdiags.SimpleWarning("don't frobble")) 12291 return resp 12292 } 12293 12294 p.GetProviderSchemaResponse = getProviderSchemaResponseFromProviderSchema(&ProviderSchema{ 12295 ResourceTypes: map[string]*configschema.Block{ 12296 "test_instance": { 12297 Attributes: map[string]*configschema.Attribute{ 12298 "id": {Type: cty.String, Computed: true}, 12299 }, 12300 }, 12301 }, 12302 }) 12303 12304 ctx := testContext2(t, &ContextOpts{ 12305 Providers: map[addrs.Provider]providers.Factory{ 12306 addrs.NewDefaultProvider("test"): testProviderFuncFixed(p), 12307 }, 12308 }) 12309 plan, diags := ctx.Plan(m, states.NewState(), DefaultPlanOpts) 12310 if diags.HasErrors() { 12311 t.Fatal(diags.Err()) 12312 } 12313 12314 _, diags = ctx.Apply(plan, m) 12315 if diags.HasErrors() { 12316 t.Fatal(diags.Err()) 12317 } 12318 12319 if len(diags) == 0 { 12320 t.Fatal("expected warnings") 12321 } 12322 12323 for _, d := range diags { 12324 des := d.Description().Summary 12325 if !strings.Contains(des, "frobble") { 12326 t.Fatalf(`expected frobble, got %q`, des) 12327 } 12328 } 12329 } 12330 12331 func TestContext2Apply_dataSensitive(t *testing.T) { 12332 m := testModule(t, "apply-data-sensitive") 12333 p := testProvider("null") 12334 p.PlanResourceChangeFn = testDiffFn 12335 p.ReadDataSourceFn = func(req providers.ReadDataSourceRequest) providers.ReadDataSourceResponse { 12336 // add the required id 12337 m := req.Config.AsValueMap() 12338 m["id"] = cty.StringVal("foo") 12339 12340 return providers.ReadDataSourceResponse{ 12341 State: cty.ObjectVal(m), 12342 } 12343 } 12344 12345 ctx := testContext2(t, &ContextOpts{ 12346 Providers: map[addrs.Provider]providers.Factory{ 12347 addrs.NewDefaultProvider("null"): testProviderFuncFixed(p), 12348 }, 12349 }) 12350 12351 plan, diags := ctx.Plan(m, states.NewState(), SimplePlanOpts(plans.NormalMode, testInputValuesUnset(m.Module.Variables))) 12352 if diags.HasErrors() { 12353 t.Fatalf("diags: %s", diags.Err()) 12354 } else { 12355 t.Logf(legacyDiffComparisonString(plan.Changes)) 12356 } 12357 12358 state, diags := ctx.Apply(plan, m) 12359 assertNoErrors(t, diags) 12360 12361 addr := mustResourceInstanceAddr("data.null_data_source.testing") 12362 12363 dataSourceState := state.ResourceInstance(addr) 12364 pvms := dataSourceState.Current.AttrSensitivePaths 12365 if len(pvms) != 1 { 12366 t.Fatalf("expected 1 sensitive path, got %d", len(pvms)) 12367 } 12368 pvm := pvms[0] 12369 if gotPath, wantPath := pvm.Path, cty.GetAttrPath("foo"); !gotPath.Equals(wantPath) { 12370 t.Errorf("wrong path\n got: %#v\nwant: %#v", gotPath, wantPath) 12371 } 12372 if gotMarks, wantMarks := pvm.Marks, cty.NewValueMarks(marks.Sensitive); !gotMarks.Equal(wantMarks) { 12373 t.Errorf("wrong marks\n got: %#v\nwant: %#v", gotMarks, wantMarks) 12374 } 12375 } 12376 12377 func TestContext2Apply_errorRestorePrivateData(t *testing.T) { 12378 // empty config to remove our resource 12379 m := testModuleInline(t, map[string]string{ 12380 "main.tf": "", 12381 }) 12382 12383 p := simpleMockProvider() 12384 p.ApplyResourceChangeResponse = &providers.ApplyResourceChangeResponse{ 12385 // we error during apply, which will trigger core to preserve the last 12386 // known state, including private data 12387 Diagnostics: tfdiags.Diagnostics(nil).Append(errors.New("oops")), 12388 } 12389 12390 addr := mustResourceInstanceAddr("test_object.a") 12391 12392 state := states.BuildState(func(s *states.SyncState) { 12393 s.SetResourceInstanceCurrent(addr, &states.ResourceInstanceObjectSrc{ 12394 Status: states.ObjectReady, 12395 AttrsJSON: []byte(`{"id":"foo"}`), 12396 Private: []byte("private"), 12397 }, mustProviderConfig(`provider["registry.terraform.io/hashicorp/test"]`)) 12398 }) 12399 12400 ctx := testContext2(t, &ContextOpts{ 12401 Providers: map[addrs.Provider]providers.Factory{ 12402 addrs.NewDefaultProvider("test"): testProviderFuncFixed(p), 12403 }, 12404 }) 12405 12406 plan, diags := ctx.Plan(m, state, DefaultPlanOpts) 12407 if diags.HasErrors() { 12408 t.Fatal(diags.Err()) 12409 } 12410 12411 state, _ = ctx.Apply(plan, m) 12412 if string(state.ResourceInstance(addr).Current.Private) != "private" { 12413 t.Fatal("missing private data in state") 12414 } 12415 } 12416 12417 func TestContext2Apply_errorRestoreStatus(t *testing.T) { 12418 // empty config to remove our resource 12419 m := testModuleInline(t, map[string]string{ 12420 "main.tf": "", 12421 }) 12422 12423 p := simpleMockProvider() 12424 p.ApplyResourceChangeFn = func(req providers.ApplyResourceChangeRequest) (resp providers.ApplyResourceChangeResponse) { 12425 // We error during apply, but return the current object state. 12426 resp.Diagnostics = resp.Diagnostics.Append(errors.New("oops")) 12427 // return a warning too to make sure it isn't dropped 12428 resp.Diagnostics = resp.Diagnostics.Append(tfdiags.SimpleWarning("warned")) 12429 resp.NewState = req.PriorState 12430 resp.Private = req.PlannedPrivate 12431 return resp 12432 } 12433 12434 addr := mustResourceInstanceAddr("test_object.a") 12435 12436 state := states.BuildState(func(s *states.SyncState) { 12437 s.SetResourceInstanceCurrent(addr, &states.ResourceInstanceObjectSrc{ 12438 Status: states.ObjectTainted, 12439 AttrsJSON: []byte(`{"test_string":"foo"}`), 12440 Private: []byte("private"), 12441 Dependencies: []addrs.ConfigResource{mustConfigResourceAddr("test_object.b")}, 12442 }, mustProviderConfig(`provider["registry.terraform.io/hashicorp/test"]`)) 12443 }) 12444 12445 ctx := testContext2(t, &ContextOpts{ 12446 Providers: map[addrs.Provider]providers.Factory{ 12447 addrs.NewDefaultProvider("test"): testProviderFuncFixed(p), 12448 }, 12449 }) 12450 12451 plan, diags := ctx.Plan(m, state, DefaultPlanOpts) 12452 if diags.HasErrors() { 12453 t.Fatal(diags.Err()) 12454 } 12455 12456 state, diags = ctx.Apply(plan, m) 12457 12458 errString := diags.ErrWithWarnings().Error() 12459 if !strings.Contains(errString, "oops") || !strings.Contains(errString, "warned") { 12460 t.Fatalf("error missing expected info: %q", errString) 12461 } 12462 12463 if len(diags) != 2 { 12464 t.Fatalf("expected 1 error and 1 warning, got: %q", errString) 12465 } 12466 12467 res := state.ResourceInstance(addr) 12468 if res == nil { 12469 t.Fatal("resource was removed from state") 12470 } 12471 12472 if res.Current.Status != states.ObjectTainted { 12473 t.Fatal("resource should still be tainted in the state") 12474 } 12475 12476 if len(res.Current.Dependencies) != 1 || !res.Current.Dependencies[0].Equal(mustConfigResourceAddr("test_object.b")) { 12477 t.Fatalf("incorrect dependencies, got %q", res.Current.Dependencies) 12478 } 12479 12480 if string(res.Current.Private) != "private" { 12481 t.Fatalf("incorrect private data, got %q", res.Current.Private) 12482 } 12483 } 12484 12485 func TestContext2Apply_nonConformingResponse(t *testing.T) { 12486 // empty config to remove our resource 12487 m := testModuleInline(t, map[string]string{ 12488 "main.tf": ` 12489 resource "test_object" "a" { 12490 test_string = "x" 12491 } 12492 `, 12493 }) 12494 12495 p := simpleMockProvider() 12496 respDiags := tfdiags.Diagnostics(nil).Append(tfdiags.SimpleWarning("warned")) 12497 respDiags = respDiags.Append(errors.New("oops")) 12498 p.ApplyResourceChangeResponse = &providers.ApplyResourceChangeResponse{ 12499 // Don't lose these diagnostics 12500 Diagnostics: respDiags, 12501 // This state is missing required attributes, and should produce an error 12502 NewState: cty.ObjectVal(map[string]cty.Value{ 12503 "test_string": cty.StringVal("x"), 12504 }), 12505 } 12506 12507 ctx := testContext2(t, &ContextOpts{ 12508 Providers: map[addrs.Provider]providers.Factory{ 12509 addrs.NewDefaultProvider("test"): testProviderFuncFixed(p), 12510 }, 12511 }) 12512 12513 plan, diags := ctx.Plan(m, states.NewState(), DefaultPlanOpts) 12514 if diags.HasErrors() { 12515 t.Fatal(diags.Err()) 12516 } 12517 12518 _, diags = ctx.Apply(plan, m) 12519 errString := diags.ErrWithWarnings().Error() 12520 if !strings.Contains(errString, "oops") || !strings.Contains(errString, "warned") { 12521 t.Fatalf("error missing expected info: %q", errString) 12522 } 12523 12524 // we should have more than the ones returned from the provider, and they 12525 // should not be coalesced into a single value 12526 if len(diags) < 3 { 12527 t.Fatalf("incorrect diagnostics, got %d values with %s", len(diags), diags.ErrWithWarnings()) 12528 } 12529 } 12530 12531 func TestContext2Apply_nilResponse(t *testing.T) { 12532 // empty config to remove our resource 12533 m := testModuleInline(t, map[string]string{ 12534 "main.tf": ` 12535 resource "test_object" "a" { 12536 } 12537 `, 12538 }) 12539 12540 p := simpleMockProvider() 12541 p.ApplyResourceChangeResponse = &providers.ApplyResourceChangeResponse{} 12542 12543 ctx := testContext2(t, &ContextOpts{ 12544 Providers: map[addrs.Provider]providers.Factory{ 12545 addrs.NewDefaultProvider("test"): testProviderFuncFixed(p), 12546 }, 12547 }) 12548 12549 plan, diags := ctx.Plan(m, states.NewState(), DefaultPlanOpts) 12550 if diags.HasErrors() { 12551 t.Fatal(diags.Err()) 12552 } 12553 12554 _, diags = ctx.Apply(plan, m) 12555 if !diags.HasErrors() { 12556 t.Fatal("expected and error") 12557 } 12558 12559 errString := diags.ErrWithWarnings().Error() 12560 if !strings.Contains(errString, "invalid nil value") { 12561 t.Fatalf("error missing expected info: %q", errString) 12562 } 12563 } 12564 12565 //////////////////////////////////////////////////////////////////////////////// 12566 // NOTE: Due to the size of this file, new tests should be added to 12567 // context_apply2_test.go. 12568 ////////////////////////////////////////////////////////////////////////////////