github.com/jaredpalmer/terraform@v1.1.0-alpha20210908.0.20210911170307-88705c943a03/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/hashicorp/terraform/internal/addrs" 22 "github.com/hashicorp/terraform/internal/configs" 23 "github.com/hashicorp/terraform/internal/configs/configschema" 24 "github.com/hashicorp/terraform/internal/configs/hcl2shim" 25 "github.com/hashicorp/terraform/internal/lang/marks" 26 "github.com/hashicorp/terraform/internal/plans" 27 "github.com/hashicorp/terraform/internal/providers" 28 "github.com/hashicorp/terraform/internal/provisioners" 29 "github.com/hashicorp/terraform/internal/states" 30 "github.com/hashicorp/terraform/internal/tfdiags" 31 "github.com/zclconf/go-cty/cty" 32 "github.com/zclconf/go-cty/cty/gocty" 33 ) 34 35 func TestContext2Apply_basic(t *testing.T) { 36 m := testModule(t, "apply-good") 37 p := testProvider("aws") 38 p.PlanResourceChangeFn = testDiffFn 39 p.ApplyResourceChangeFn = testApplyFn 40 ctx := testContext2(t, &ContextOpts{ 41 Providers: map[addrs.Provider]providers.Factory{ 42 addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p), 43 }, 44 }) 45 46 plan, diags := ctx.Plan(m, states.NewState(), DefaultPlanOpts) 47 assertNoErrors(t, diags) 48 49 state, diags := ctx.Apply(plan, m) 50 if diags.HasErrors() { 51 t.Fatalf("diags: %s", diags.Err()) 52 } 53 54 mod := state.RootModule() 55 if len(mod.Resources) < 2 { 56 t.Fatalf("bad: %#v", mod.Resources) 57 } 58 59 actual := strings.TrimSpace(state.String()) 60 expected := strings.TrimSpace(testTerraformApplyStr) 61 if actual != expected { 62 t.Fatalf("wrong result\n\ngot:\n%s\n\nwant:\n%s", actual, expected) 63 } 64 } 65 66 func TestContext2Apply_unstable(t *testing.T) { 67 // This tests behavior when the configuration contains an unstable value, 68 // such as the result of uuid() or timestamp(), where each call produces 69 // a different result. 70 // 71 // This is an important case to test because we need to ensure that 72 // we don't re-call the function during the apply phase: the value should 73 // be fixed during plan 74 75 m := testModule(t, "apply-unstable") 76 p := testProvider("test") 77 p.PlanResourceChangeFn = testDiffFn 78 ctx := testContext2(t, &ContextOpts{ 79 Providers: map[addrs.Provider]providers.Factory{ 80 addrs.NewDefaultProvider("test"): testProviderFuncFixed(p), 81 }, 82 }) 83 84 plan, diags := ctx.Plan(m, states.NewState(), DefaultPlanOpts) 85 if diags.HasErrors() { 86 t.Fatalf("unexpected error during Plan: %s", diags.Err()) 87 } 88 89 addr := addrs.Resource{ 90 Mode: addrs.ManagedResourceMode, 91 Type: "test_resource", 92 Name: "foo", 93 }.Instance(addrs.NoKey).Absolute(addrs.RootModuleInstance) 94 schema := p.GetProviderSchemaResponse.ResourceTypes["test_resource"].Block 95 rds := plan.Changes.ResourceInstance(addr) 96 rd, err := rds.Decode(schema.ImpliedType()) 97 if err != nil { 98 t.Fatal(err) 99 } 100 if rd.After.GetAttr("random").IsKnown() { 101 t.Fatalf("Attribute 'random' has known value %#v; should be unknown in plan", rd.After.GetAttr("random")) 102 } 103 104 state, diags := ctx.Apply(plan, m) 105 if diags.HasErrors() { 106 t.Fatalf("unexpected error during Apply: %s", diags.Err()) 107 } 108 109 mod := state.Module(addr.Module) 110 rss := state.ResourceInstance(addr) 111 112 if len(mod.Resources) != 1 { 113 t.Fatalf("wrong number of resources %d; want 1", len(mod.Resources)) 114 } 115 116 rs, err := rss.Current.Decode(schema.ImpliedType()) 117 if err != nil { 118 t.Fatalf("decode error: %v", err) 119 } 120 got := rs.Value.GetAttr("random") 121 if !got.IsKnown() { 122 t.Fatalf("random is still unknown after apply") 123 } 124 if got, want := len(got.AsString()), 36; got != want { 125 t.Fatalf("random string has wrong length %d; want %d", got, want) 126 } 127 } 128 129 func TestContext2Apply_escape(t *testing.T) { 130 m := testModule(t, "apply-escape") 131 p := testProvider("aws") 132 p.PlanResourceChangeFn = testDiffFn 133 p.ApplyResourceChangeFn = testApplyFn 134 ctx := testContext2(t, &ContextOpts{ 135 Providers: map[addrs.Provider]providers.Factory{ 136 addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p), 137 }, 138 }) 139 140 plan, diags := ctx.Plan(m, states.NewState(), DefaultPlanOpts) 141 assertNoErrors(t, diags) 142 143 state, diags := ctx.Apply(plan, m) 144 if diags.HasErrors() { 145 t.Fatalf("diags: %s", diags.Err()) 146 } 147 148 checkStateString(t, state, ` 149 aws_instance.bar: 150 ID = foo 151 provider = provider["registry.terraform.io/hashicorp/aws"] 152 foo = "bar" 153 type = aws_instance 154 `) 155 } 156 157 func TestContext2Apply_resourceCountOneList(t *testing.T) { 158 m := testModule(t, "apply-resource-count-one-list") 159 p := testProvider("null") 160 p.PlanResourceChangeFn = testDiffFn 161 p.ApplyResourceChangeFn = testApplyFn 162 ctx := testContext2(t, &ContextOpts{ 163 Providers: map[addrs.Provider]providers.Factory{ 164 addrs.NewDefaultProvider("null"): testProviderFuncFixed(p), 165 }, 166 }) 167 168 plan, diags := ctx.Plan(m, states.NewState(), DefaultPlanOpts) 169 assertNoErrors(t, diags) 170 171 state, diags := ctx.Apply(plan, m) 172 assertNoDiagnostics(t, diags) 173 174 got := strings.TrimSpace(state.String()) 175 want := strings.TrimSpace(`null_resource.foo.0: 176 ID = foo 177 provider = provider["registry.terraform.io/hashicorp/null"] 178 179 Outputs: 180 181 test = [foo]`) 182 if got != want { 183 t.Fatalf("got:\n%s\n\nwant:\n%s\n", got, want) 184 } 185 } 186 func TestContext2Apply_resourceCountZeroList(t *testing.T) { 187 m := testModule(t, "apply-resource-count-zero-list") 188 p := testProvider("null") 189 p.PlanResourceChangeFn = testDiffFn 190 ctx := testContext2(t, &ContextOpts{ 191 Providers: map[addrs.Provider]providers.Factory{ 192 addrs.NewDefaultProvider("null"): testProviderFuncFixed(p), 193 }, 194 }) 195 196 plan, diags := ctx.Plan(m, states.NewState(), DefaultPlanOpts) 197 assertNoErrors(t, diags) 198 199 state, diags := ctx.Apply(plan, m) 200 if diags.HasErrors() { 201 t.Fatalf("diags: %s", diags.Err()) 202 } 203 204 got := strings.TrimSpace(state.String()) 205 want := strings.TrimSpace(`<no state> 206 Outputs: 207 208 test = []`) 209 if got != want { 210 t.Fatalf("wrong state\n\ngot:\n%s\n\nwant:\n%s\n", got, want) 211 } 212 } 213 214 func TestContext2Apply_resourceDependsOnModule(t *testing.T) { 215 m := testModule(t, "apply-resource-depends-on-module") 216 p := testProvider("aws") 217 p.PlanResourceChangeFn = testDiffFn 218 219 // verify the apply happens in the correct order 220 var mu sync.Mutex 221 var order []string 222 223 p.ApplyResourceChangeFn = func(req providers.ApplyResourceChangeRequest) (resp providers.ApplyResourceChangeResponse) { 224 ami := req.PlannedState.GetAttr("ami").AsString() 225 switch ami { 226 case "child": 227 228 // make the child slower than the parent 229 time.Sleep(50 * time.Millisecond) 230 231 mu.Lock() 232 order = append(order, "child") 233 mu.Unlock() 234 case "parent": 235 mu.Lock() 236 order = append(order, "parent") 237 mu.Unlock() 238 } 239 240 return testApplyFn(req) 241 } 242 243 ctx := testContext2(t, &ContextOpts{ 244 Providers: map[addrs.Provider]providers.Factory{ 245 addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p), 246 }, 247 }) 248 249 plan, diags := ctx.Plan(m, states.NewState(), DefaultPlanOpts) 250 assertNoErrors(t, diags) 251 252 state, diags := ctx.Apply(plan, m) 253 if diags.HasErrors() { 254 t.Fatalf("diags: %s", diags.Err()) 255 } 256 257 if !reflect.DeepEqual(order, []string{"child", "parent"}) { 258 t.Fatal("resources applied out of order") 259 } 260 261 checkStateString(t, state, testTerraformApplyResourceDependsOnModuleStr) 262 } 263 264 // Test that without a config, the Dependencies in the state are enough 265 // to maintain proper ordering. 266 func TestContext2Apply_resourceDependsOnModuleStateOnly(t *testing.T) { 267 m := testModule(t, "apply-resource-depends-on-module-empty") 268 p := testProvider("aws") 269 p.PlanResourceChangeFn = testDiffFn 270 271 state := states.NewState() 272 root := state.EnsureModule(addrs.RootModuleInstance) 273 root.SetResourceInstanceCurrent( 274 mustResourceInstanceAddr("aws_instance.a").Resource, 275 &states.ResourceInstanceObjectSrc{ 276 Status: states.ObjectReady, 277 AttrsJSON: []byte(`{"id":"parent"}`), 278 Dependencies: []addrs.ConfigResource{mustConfigResourceAddr("module.child.aws_instance.child")}, 279 }, 280 mustProviderConfig(`provider["registry.terraform.io/hashicorp/aws"]`), 281 ) 282 child := state.EnsureModule(addrs.RootModuleInstance.Child("child", addrs.NoKey)) 283 child.SetResourceInstanceCurrent( 284 mustResourceInstanceAddr("aws_instance.child").Resource, 285 &states.ResourceInstanceObjectSrc{ 286 Status: states.ObjectReady, 287 AttrsJSON: []byte(`{"id":"child"}`), 288 }, 289 mustProviderConfig(`provider["registry.terraform.io/hashicorp/aws"]`), 290 ) 291 292 { 293 // verify the apply happens in the correct order 294 var mu sync.Mutex 295 var order []string 296 297 p.ApplyResourceChangeFn = func(req providers.ApplyResourceChangeRequest) (resp providers.ApplyResourceChangeResponse) { 298 id := req.PriorState.GetAttr("id") 299 if id.IsKnown() && id.AsString() == "parent" { 300 // make the dep slower than the parent 301 time.Sleep(50 * time.Millisecond) 302 303 mu.Lock() 304 order = append(order, "child") 305 mu.Unlock() 306 } else { 307 mu.Lock() 308 order = append(order, "parent") 309 mu.Unlock() 310 } 311 312 return testApplyFn(req) 313 } 314 315 ctx := testContext2(t, &ContextOpts{ 316 Providers: map[addrs.Provider]providers.Factory{ 317 addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p), 318 }, 319 }) 320 321 plan, diags := ctx.Plan(m, state, DefaultPlanOpts) 322 assertNoErrors(t, diags) 323 324 state, diags := ctx.Apply(plan, m) 325 assertNoErrors(t, diags) 326 327 if !reflect.DeepEqual(order, []string{"child", "parent"}) { 328 t.Fatal("resources applied out of order") 329 } 330 331 checkStateString(t, state, "<no state>") 332 } 333 } 334 335 func TestContext2Apply_resourceDependsOnModuleDestroy(t *testing.T) { 336 m := testModule(t, "apply-resource-depends-on-module") 337 p := testProvider("aws") 338 p.PlanResourceChangeFn = testDiffFn 339 340 var globalState *states.State 341 { 342 ctx := testContext2(t, &ContextOpts{ 343 Providers: map[addrs.Provider]providers.Factory{ 344 addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p), 345 }, 346 }) 347 348 plan, diags := ctx.Plan(m, states.NewState(), DefaultPlanOpts) 349 assertNoErrors(t, diags) 350 351 state, diags := ctx.Apply(plan, m) 352 if diags.HasErrors() { 353 t.Fatalf("diags: %s", diags.Err()) 354 } 355 356 globalState = state 357 } 358 359 { 360 // Wait for the dependency, sleep, and verify the graph never 361 // called a child. 362 var called int32 363 var checked bool 364 p.ApplyResourceChangeFn = func(req providers.ApplyResourceChangeRequest) (resp providers.ApplyResourceChangeResponse) { 365 ami := req.PriorState.GetAttr("ami").AsString() 366 if ami == "parent" { 367 checked = true 368 369 // Sleep to allow parallel execution 370 time.Sleep(50 * time.Millisecond) 371 372 // Verify that called is 0 (dep not called) 373 if atomic.LoadInt32(&called) != 0 { 374 resp.Diagnostics = resp.Diagnostics.Append(fmt.Errorf("module child should not be called")) 375 return resp 376 } 377 } 378 379 atomic.AddInt32(&called, 1) 380 return testApplyFn(req) 381 } 382 383 ctx := testContext2(t, &ContextOpts{ 384 Providers: map[addrs.Provider]providers.Factory{ 385 addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p), 386 }, 387 }) 388 389 plan, diags := ctx.Plan(m, globalState, &PlanOpts{ 390 Mode: plans.DestroyMode, 391 }) 392 assertNoErrors(t, diags) 393 394 state, diags := ctx.Apply(plan, m) 395 if diags.HasErrors() { 396 t.Fatalf("diags: %s", diags.Err()) 397 } 398 399 if !checked { 400 t.Fatal("should check") 401 } 402 403 checkStateString(t, state, `<no state>`) 404 } 405 } 406 407 func TestContext2Apply_resourceDependsOnModuleGrandchild(t *testing.T) { 408 m := testModule(t, "apply-resource-depends-on-module-deep") 409 p := testProvider("aws") 410 p.PlanResourceChangeFn = testDiffFn 411 412 { 413 // Wait for the dependency, sleep, and verify the graph never 414 // called a child. 415 var called int32 416 var checked bool 417 p.ApplyResourceChangeFn = func(req providers.ApplyResourceChangeRequest) (resp providers.ApplyResourceChangeResponse) { 418 planned := req.PlannedState.AsValueMap() 419 if ami, ok := planned["ami"]; ok && ami.AsString() == "grandchild" { 420 checked = true 421 422 // Sleep to allow parallel execution 423 time.Sleep(50 * time.Millisecond) 424 425 // Verify that called is 0 (dep not called) 426 if atomic.LoadInt32(&called) != 0 { 427 resp.Diagnostics = resp.Diagnostics.Append(fmt.Errorf("aws_instance.a should not be called")) 428 return resp 429 } 430 } 431 432 atomic.AddInt32(&called, 1) 433 return testApplyFn(req) 434 } 435 436 ctx := testContext2(t, &ContextOpts{ 437 Providers: map[addrs.Provider]providers.Factory{ 438 addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p), 439 }, 440 }) 441 442 plan, diags := ctx.Plan(m, states.NewState(), DefaultPlanOpts) 443 assertNoErrors(t, diags) 444 445 state, diags := ctx.Apply(plan, m) 446 if diags.HasErrors() { 447 t.Fatalf("diags: %s", diags.Err()) 448 } 449 450 if !checked { 451 t.Fatal("should check") 452 } 453 454 checkStateString(t, state, testTerraformApplyResourceDependsOnModuleDeepStr) 455 } 456 } 457 458 func TestContext2Apply_resourceDependsOnModuleInModule(t *testing.T) { 459 m := testModule(t, "apply-resource-depends-on-module-in-module") 460 p := testProvider("aws") 461 p.PlanResourceChangeFn = testDiffFn 462 463 { 464 // Wait for the dependency, sleep, and verify the graph never 465 // called a child. 466 var called int32 467 var checked bool 468 p.ApplyResourceChangeFn = func(req providers.ApplyResourceChangeRequest) (resp providers.ApplyResourceChangeResponse) { 469 planned := req.PlannedState.AsValueMap() 470 if ami, ok := planned["ami"]; ok && ami.AsString() == "grandchild" { 471 checked = true 472 473 // Sleep to allow parallel execution 474 time.Sleep(50 * time.Millisecond) 475 476 // Verify that called is 0 (dep not called) 477 if atomic.LoadInt32(&called) != 0 { 478 resp.Diagnostics = resp.Diagnostics.Append(fmt.Errorf("something else was applied before grandchild; grandchild should be first")) 479 return resp 480 } 481 } 482 483 atomic.AddInt32(&called, 1) 484 return testApplyFn(req) 485 } 486 487 ctx := testContext2(t, &ContextOpts{ 488 Providers: map[addrs.Provider]providers.Factory{ 489 addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p), 490 }, 491 }) 492 493 plan, diags := ctx.Plan(m, states.NewState(), DefaultPlanOpts) 494 assertNoErrors(t, diags) 495 496 state, diags := ctx.Apply(plan, m) 497 if diags.HasErrors() { 498 t.Fatalf("diags: %s", diags.Err()) 499 } 500 501 if !checked { 502 t.Fatal("should check") 503 } 504 505 checkStateString(t, state, testTerraformApplyResourceDependsOnModuleInModuleStr) 506 } 507 } 508 509 func TestContext2Apply_mapVarBetweenModules(t *testing.T) { 510 m := testModule(t, "apply-map-var-through-module") 511 p := testProvider("null") 512 p.PlanResourceChangeFn = testDiffFn 513 p.ApplyResourceChangeFn = testApplyFn 514 ctx := testContext2(t, &ContextOpts{ 515 Providers: map[addrs.Provider]providers.Factory{ 516 addrs.NewDefaultProvider("null"): testProviderFuncFixed(p), 517 }, 518 }) 519 520 plan, diags := ctx.Plan(m, states.NewState(), DefaultPlanOpts) 521 assertNoErrors(t, diags) 522 523 state, diags := ctx.Apply(plan, m) 524 if diags.HasErrors() { 525 t.Fatalf("diags: %s", diags.Err()) 526 } 527 528 actual := strings.TrimSpace(state.String()) 529 expected := strings.TrimSpace(`<no state> 530 Outputs: 531 532 amis_from_module = {eu-west-1:ami-789012 eu-west-2:ami-989484 us-west-1:ami-123456 us-west-2:ami-456789 } 533 534 module.test: 535 null_resource.noop: 536 ID = foo 537 provider = provider["registry.terraform.io/hashicorp/null"] 538 539 Outputs: 540 541 amis_out = {eu-west-1:ami-789012 eu-west-2:ami-989484 us-west-1:ami-123456 us-west-2:ami-456789 }`) 542 if actual != expected { 543 t.Fatalf("expected: \n%s\n\ngot: \n%s\n", expected, actual) 544 } 545 } 546 547 func TestContext2Apply_refCount(t *testing.T) { 548 m := testModule(t, "apply-ref-count") 549 p := testProvider("aws") 550 p.PlanResourceChangeFn = testDiffFn 551 p.ApplyResourceChangeFn = testApplyFn 552 ctx := testContext2(t, &ContextOpts{ 553 Providers: map[addrs.Provider]providers.Factory{ 554 addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p), 555 }, 556 }) 557 558 plan, diags := ctx.Plan(m, states.NewState(), DefaultPlanOpts) 559 assertNoErrors(t, diags) 560 561 state, diags := ctx.Apply(plan, m) 562 if diags.HasErrors() { 563 t.Fatalf("diags: %s", diags.Err()) 564 } 565 566 mod := state.RootModule() 567 if len(mod.Resources) < 2 { 568 t.Fatalf("bad: %#v", mod.Resources) 569 } 570 571 actual := strings.TrimSpace(state.String()) 572 expected := strings.TrimSpace(testTerraformApplyRefCountStr) 573 if actual != expected { 574 t.Fatalf("wrong result\n\ngot:\n%s\n\nwant:\n%s", actual, expected) 575 } 576 } 577 578 func TestContext2Apply_providerAlias(t *testing.T) { 579 m := testModule(t, "apply-provider-alias") 580 p := testProvider("aws") 581 p.PlanResourceChangeFn = testDiffFn 582 p.ApplyResourceChangeFn = testApplyFn 583 ctx := testContext2(t, &ContextOpts{ 584 Providers: map[addrs.Provider]providers.Factory{ 585 addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p), 586 }, 587 }) 588 589 plan, diags := ctx.Plan(m, states.NewState(), DefaultPlanOpts) 590 assertNoErrors(t, diags) 591 592 state, diags := ctx.Apply(plan, m) 593 if diags.HasErrors() { 594 t.Fatalf("diags: %s", diags.Err()) 595 } 596 597 mod := state.RootModule() 598 if len(mod.Resources) < 2 { 599 t.Fatalf("bad: %#v", mod.Resources) 600 } 601 602 actual := strings.TrimSpace(state.String()) 603 expected := strings.TrimSpace(testTerraformApplyProviderAliasStr) 604 if actual != expected { 605 t.Fatalf("wrong result\n\ngot:\n%s\n\nwant:\n%s", actual, expected) 606 } 607 } 608 609 // Two providers that are configured should both be configured prior to apply 610 func TestContext2Apply_providerAliasConfigure(t *testing.T) { 611 m := testModule(t, "apply-provider-alias-configure") 612 613 p2 := testProvider("another") 614 p2.ApplyResourceChangeFn = testApplyFn 615 p2.PlanResourceChangeFn = testDiffFn 616 617 ctx := testContext2(t, &ContextOpts{ 618 Providers: map[addrs.Provider]providers.Factory{ 619 addrs.NewDefaultProvider("another"): testProviderFuncFixed(p2), 620 }, 621 }) 622 623 plan, diags := ctx.Plan(m, states.NewState(), DefaultPlanOpts) 624 if diags.HasErrors() { 625 t.Fatalf("diags: %s", diags.Err()) 626 } else { 627 t.Logf(legacyDiffComparisonString(plan.Changes)) 628 } 629 630 // Configure to record calls AFTER Plan above 631 var configCount int32 632 p2.ConfigureProviderFn = func(req providers.ConfigureProviderRequest) (resp providers.ConfigureProviderResponse) { 633 atomic.AddInt32(&configCount, 1) 634 635 foo := req.Config.GetAttr("foo").AsString() 636 if foo != "bar" { 637 resp.Diagnostics = resp.Diagnostics.Append(fmt.Errorf("foo: %#v", foo)) 638 } 639 640 return 641 } 642 643 state, diags := ctx.Apply(plan, m) 644 if diags.HasErrors() { 645 t.Fatalf("diags: %s", diags.Err()) 646 } 647 648 if configCount != 2 { 649 t.Fatalf("provider config expected 2 calls, got: %d", configCount) 650 } 651 652 actual := strings.TrimSpace(state.String()) 653 expected := strings.TrimSpace(testTerraformApplyProviderAliasConfigStr) 654 if actual != expected { 655 t.Fatalf("wrong result\n\ngot:\n%s\n\nwant:\n%s", actual, expected) 656 } 657 } 658 659 // GH-2870 660 func TestContext2Apply_providerWarning(t *testing.T) { 661 m := testModule(t, "apply-provider-warning") 662 p := testProvider("aws") 663 p.PlanResourceChangeFn = testDiffFn 664 p.ApplyResourceChangeFn = testApplyFn 665 p.ValidateResourceConfigFn = func(req providers.ValidateResourceConfigRequest) (resp providers.ValidateResourceConfigResponse) { 666 resp.Diagnostics = resp.Diagnostics.Append(tfdiags.SimpleWarning("just a warning")) 667 return 668 } 669 ctx := testContext2(t, &ContextOpts{ 670 Providers: map[addrs.Provider]providers.Factory{ 671 addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p), 672 }, 673 }) 674 675 plan, diags := ctx.Plan(m, states.NewState(), DefaultPlanOpts) 676 assertNoErrors(t, diags) 677 678 state, diags := ctx.Apply(plan, m) 679 if diags.HasErrors() { 680 t.Fatalf("diags: %s", diags.Err()) 681 } 682 683 actual := strings.TrimSpace(state.String()) 684 expected := strings.TrimSpace(` 685 aws_instance.foo: 686 ID = foo 687 provider = provider["registry.terraform.io/hashicorp/aws"] 688 type = aws_instance 689 `) 690 if actual != expected { 691 t.Fatalf("got: \n%s\n\nexpected:\n%s", actual, expected) 692 } 693 694 if !p.ConfigureProviderCalled { 695 t.Fatalf("provider Configure() was never called!") 696 } 697 } 698 699 func TestContext2Apply_emptyModule(t *testing.T) { 700 // A module with only outputs (no resources) 701 m := testModule(t, "apply-empty-module") 702 p := testProvider("aws") 703 p.PlanResourceChangeFn = testDiffFn 704 ctx := testContext2(t, &ContextOpts{ 705 Providers: map[addrs.Provider]providers.Factory{ 706 addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p), 707 }, 708 }) 709 710 plan, diags := ctx.Plan(m, states.NewState(), DefaultPlanOpts) 711 assertNoErrors(t, diags) 712 713 state, diags := ctx.Apply(plan, m) 714 if diags.HasErrors() { 715 t.Fatalf("diags: %s", diags.Err()) 716 } 717 718 actual := strings.TrimSpace(state.String()) 719 actual = strings.Replace(actual, " ", "", -1) 720 expected := strings.TrimSpace(testTerraformApplyEmptyModuleStr) 721 if actual != expected { 722 t.Fatalf("bad: \n%s\nexpect:\n%s", actual, expected) 723 } 724 } 725 726 func TestContext2Apply_createBeforeDestroy(t *testing.T) { 727 m := testModule(t, "apply-good-create-before") 728 p := testProvider("aws") 729 p.PlanResourceChangeFn = testDiffFn 730 p.ApplyResourceChangeFn = testApplyFn 731 state := states.NewState() 732 root := state.EnsureModule(addrs.RootModuleInstance) 733 root.SetResourceInstanceCurrent( 734 mustResourceInstanceAddr("aws_instance.bar").Resource, 735 &states.ResourceInstanceObjectSrc{ 736 Status: states.ObjectReady, 737 AttrsJSON: []byte(`{"id":"bar", "require_new": "abc"}`), 738 }, 739 mustProviderConfig(`provider["registry.terraform.io/hashicorp/aws"]`), 740 ) 741 ctx := testContext2(t, &ContextOpts{ 742 Providers: map[addrs.Provider]providers.Factory{ 743 addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p), 744 }, 745 }) 746 747 plan, diags := ctx.Plan(m, state, DefaultPlanOpts) 748 if diags.HasErrors() { 749 t.Fatalf("diags: %s", diags.Err()) 750 } else { 751 t.Logf(legacyDiffComparisonString(plan.Changes)) 752 } 753 754 state, diags = ctx.Apply(plan, m) 755 if diags.HasErrors() { 756 t.Fatalf("diags: %s", diags.Err()) 757 } 758 759 mod := state.RootModule() 760 if got, want := len(mod.Resources), 1; got != want { 761 t.Logf("state:\n%s", state) 762 t.Fatalf("wrong number of resources %d; want %d", got, want) 763 } 764 765 actual := strings.TrimSpace(state.String()) 766 expected := strings.TrimSpace(testTerraformApplyCreateBeforeStr) 767 if actual != expected { 768 t.Fatalf("expected:\n%s\ngot:\n%s", expected, actual) 769 } 770 } 771 772 func TestContext2Apply_createBeforeDestroyUpdate(t *testing.T) { 773 m := testModule(t, "apply-good-create-before-update") 774 p := testProvider("aws") 775 p.PlanResourceChangeFn = testDiffFn 776 777 // signal that resource foo has started applying 778 fooChan := make(chan struct{}) 779 780 p.ApplyResourceChangeFn = func(req providers.ApplyResourceChangeRequest) (resp providers.ApplyResourceChangeResponse) { 781 id := req.PriorState.GetAttr("id").AsString() 782 switch id { 783 case "bar": 784 select { 785 case <-fooChan: 786 resp.Diagnostics = resp.Diagnostics.Append(errors.New("bar must be updated before foo is destroyed")) 787 return resp 788 case <-time.After(100 * time.Millisecond): 789 // wait a moment to ensure that foo is not going to be destroyed first 790 } 791 case "foo": 792 close(fooChan) 793 } 794 795 return testApplyFn(req) 796 } 797 798 state := states.NewState() 799 root := state.EnsureModule(addrs.RootModuleInstance) 800 fooAddr := mustResourceInstanceAddr("aws_instance.foo") 801 root.SetResourceInstanceCurrent( 802 fooAddr.Resource, 803 &states.ResourceInstanceObjectSrc{ 804 Status: states.ObjectReady, 805 AttrsJSON: []byte(`{"id":"foo","foo":"bar"}`), 806 CreateBeforeDestroy: true, 807 }, 808 mustProviderConfig(`provider["registry.terraform.io/hashicorp/aws"]`), 809 ) 810 root.SetResourceInstanceCurrent( 811 mustResourceInstanceAddr("aws_instance.bar").Resource, 812 &states.ResourceInstanceObjectSrc{ 813 Status: states.ObjectReady, 814 AttrsJSON: []byte(`{"id":"bar","foo":"bar"}`), 815 CreateBeforeDestroy: true, 816 Dependencies: []addrs.ConfigResource{fooAddr.ContainingResource().Config()}, 817 }, 818 mustProviderConfig(`provider["registry.terraform.io/hashicorp/aws"]`), 819 ) 820 821 ctx := testContext2(t, &ContextOpts{ 822 Providers: map[addrs.Provider]providers.Factory{ 823 addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p), 824 }, 825 }) 826 827 plan, diags := ctx.Plan(m, state, DefaultPlanOpts) 828 if diags.HasErrors() { 829 t.Fatalf("diags: %s", diags.Err()) 830 } else { 831 t.Logf(legacyDiffComparisonString(plan.Changes)) 832 } 833 834 state, diags = ctx.Apply(plan, m) 835 if diags.HasErrors() { 836 t.Fatalf("diags: %s", diags.Err()) 837 } 838 839 mod := state.RootModule() 840 if len(mod.Resources) != 1 { 841 t.Fatalf("bad: %s", state) 842 } 843 844 actual := strings.TrimSpace(state.String()) 845 expected := strings.TrimSpace(testTerraformApplyCreateBeforeUpdateStr) 846 if actual != expected { 847 t.Fatalf("wrong result\n\ngot:\n%s\n\nwant:\n%s", actual, expected) 848 } 849 } 850 851 // This tests that when a CBD resource depends on a non-CBD resource, 852 // we can still properly apply changes that require new for both. 853 func TestContext2Apply_createBeforeDestroy_dependsNonCBD(t *testing.T) { 854 m := testModule(t, "apply-cbd-depends-non-cbd") 855 p := testProvider("aws") 856 p.PlanResourceChangeFn = testDiffFn 857 p.ApplyResourceChangeFn = testApplyFn 858 859 state := states.NewState() 860 root := state.EnsureModule(addrs.RootModuleInstance) 861 root.SetResourceInstanceCurrent( 862 mustResourceInstanceAddr("aws_instance.bar").Resource, 863 &states.ResourceInstanceObjectSrc{ 864 Status: states.ObjectReady, 865 AttrsJSON: []byte(`{"id":"bar", "require_new": "abc"}`), 866 }, 867 mustProviderConfig(`provider["registry.terraform.io/hashicorp/aws"]`), 868 ) 869 root.SetResourceInstanceCurrent( 870 mustResourceInstanceAddr("aws_instance.foo").Resource, 871 &states.ResourceInstanceObjectSrc{ 872 Status: states.ObjectReady, 873 AttrsJSON: []byte(`{"id":"foo", "require_new": "abc"}`), 874 }, 875 mustProviderConfig(`provider["registry.terraform.io/hashicorp/aws"]`), 876 ) 877 878 ctx := testContext2(t, &ContextOpts{ 879 Providers: map[addrs.Provider]providers.Factory{ 880 addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p), 881 }, 882 }) 883 884 plan, diags := ctx.Plan(m, state, DefaultPlanOpts) 885 if diags.HasErrors() { 886 t.Fatalf("diags: %s", diags.Err()) 887 } else { 888 t.Logf(legacyDiffComparisonString(plan.Changes)) 889 } 890 891 state, diags = ctx.Apply(plan, m) 892 if diags.HasErrors() { 893 t.Fatalf("diags: %s", diags.Err()) 894 } 895 896 checkStateString(t, state, ` 897 aws_instance.bar: 898 ID = foo 899 provider = provider["registry.terraform.io/hashicorp/aws"] 900 require_new = yes 901 type = aws_instance 902 value = foo 903 904 Dependencies: 905 aws_instance.foo 906 aws_instance.foo: 907 ID = foo 908 provider = provider["registry.terraform.io/hashicorp/aws"] 909 require_new = yes 910 type = aws_instance 911 `) 912 } 913 914 func TestContext2Apply_createBeforeDestroy_hook(t *testing.T) { 915 h := new(MockHook) 916 m := testModule(t, "apply-good-create-before") 917 p := testProvider("aws") 918 p.PlanResourceChangeFn = testDiffFn 919 p.ApplyResourceChangeFn = testApplyFn 920 state := states.NewState() 921 root := state.EnsureModule(addrs.RootModuleInstance) 922 root.SetResourceInstanceCurrent( 923 mustResourceInstanceAddr("aws_instance.bar").Resource, 924 &states.ResourceInstanceObjectSrc{ 925 Status: states.ObjectReady, 926 AttrsJSON: []byte(`{"id":"bar", "require_new": "abc"}`), 927 }, 928 mustProviderConfig(`provider["registry.terraform.io/hashicorp/aws"]`), 929 ) 930 931 var actual []cty.Value 932 var actualLock sync.Mutex 933 h.PostApplyFn = func(addr addrs.AbsResourceInstance, gen states.Generation, sv cty.Value, e error) (HookAction, error) { 934 actualLock.Lock() 935 936 defer actualLock.Unlock() 937 actual = append(actual, sv) 938 return HookActionContinue, nil 939 } 940 941 ctx := testContext2(t, &ContextOpts{ 942 Hooks: []Hook{h}, 943 Providers: map[addrs.Provider]providers.Factory{ 944 addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p), 945 }, 946 }) 947 948 plan, diags := ctx.Plan(m, state, DefaultPlanOpts) 949 if diags.HasErrors() { 950 t.Fatalf("diags: %s", diags.Err()) 951 } else { 952 t.Logf(legacyDiffComparisonString(plan.Changes)) 953 } 954 955 if _, diags := ctx.Apply(plan, m); diags.HasErrors() { 956 t.Fatalf("apply errors: %s", diags.Err()) 957 } 958 959 expected := []cty.Value{ 960 cty.ObjectVal(map[string]cty.Value{ 961 "id": cty.StringVal("foo"), 962 "require_new": cty.StringVal("xyz"), 963 "type": cty.StringVal("aws_instance"), 964 }), 965 cty.NullVal(cty.DynamicPseudoType), 966 } 967 968 cmpOpt := cmp.Transformer("ctyshim", hcl2shim.ConfigValueFromHCL2) 969 if !cmp.Equal(actual, expected, cmpOpt) { 970 t.Fatalf("wrong state snapshot sequence\n%s", cmp.Diff(expected, actual, cmpOpt)) 971 } 972 } 973 974 // Test that we can perform an apply with CBD in a count with deposed instances. 975 func TestContext2Apply_createBeforeDestroy_deposedCount(t *testing.T) { 976 m := testModule(t, "apply-cbd-count") 977 p := testProvider("aws") 978 p.PlanResourceChangeFn = testDiffFn 979 p.ApplyResourceChangeFn = testApplyFn 980 981 state := states.NewState() 982 root := state.EnsureModule(addrs.RootModuleInstance) 983 root.SetResourceInstanceCurrent( 984 mustResourceInstanceAddr("aws_instance.bar[0]").Resource, 985 &states.ResourceInstanceObjectSrc{ 986 Status: states.ObjectTainted, 987 AttrsJSON: []byte(`{"id":"bar"}`), 988 }, 989 mustProviderConfig(`provider["registry.terraform.io/hashicorp/aws"]`), 990 ) 991 root.SetResourceInstanceDeposed( 992 mustResourceInstanceAddr("aws_instance.bar[0]").Resource, 993 states.NewDeposedKey(), 994 &states.ResourceInstanceObjectSrc{ 995 Status: states.ObjectTainted, 996 AttrsJSON: []byte(`{"id":"foo"}`), 997 }, 998 mustProviderConfig(`provider["registry.terraform.io/hashicorp/aws"]`), 999 ) 1000 root.SetResourceInstanceCurrent( 1001 mustResourceInstanceAddr("aws_instance.bar[1]").Resource, 1002 &states.ResourceInstanceObjectSrc{ 1003 Status: states.ObjectTainted, 1004 AttrsJSON: []byte(`{"id":"bar"}`), 1005 }, 1006 mustProviderConfig(`provider["registry.terraform.io/hashicorp/aws"]`), 1007 ) 1008 root.SetResourceInstanceDeposed( 1009 mustResourceInstanceAddr("aws_instance.bar[1]").Resource, 1010 states.NewDeposedKey(), 1011 &states.ResourceInstanceObjectSrc{ 1012 Status: states.ObjectTainted, 1013 AttrsJSON: []byte(`{"id":"bar"}`), 1014 }, 1015 mustProviderConfig(`provider["registry.terraform.io/hashicorp/aws"]`), 1016 ) 1017 1018 ctx := testContext2(t, &ContextOpts{ 1019 Providers: map[addrs.Provider]providers.Factory{ 1020 addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p), 1021 }, 1022 }) 1023 1024 plan, diags := ctx.Plan(m, state, DefaultPlanOpts) 1025 if diags.HasErrors() { 1026 t.Fatalf("diags: %s", diags.Err()) 1027 } else { 1028 t.Logf(legacyDiffComparisonString(plan.Changes)) 1029 } 1030 1031 state, diags = ctx.Apply(plan, m) 1032 if diags.HasErrors() { 1033 t.Fatalf("diags: %s", diags.Err()) 1034 } 1035 1036 checkStateString(t, state, ` 1037 aws_instance.bar.0: 1038 ID = foo 1039 provider = provider["registry.terraform.io/hashicorp/aws"] 1040 foo = bar 1041 type = aws_instance 1042 aws_instance.bar.1: 1043 ID = foo 1044 provider = provider["registry.terraform.io/hashicorp/aws"] 1045 foo = bar 1046 type = aws_instance 1047 `) 1048 } 1049 1050 // Test that when we have a deposed instance but a good primary, we still 1051 // destroy the deposed instance. 1052 func TestContext2Apply_createBeforeDestroy_deposedOnly(t *testing.T) { 1053 m := testModule(t, "apply-cbd-deposed-only") 1054 p := testProvider("aws") 1055 p.PlanResourceChangeFn = testDiffFn 1056 p.ApplyResourceChangeFn = testApplyFn 1057 1058 state := states.NewState() 1059 root := state.EnsureModule(addrs.RootModuleInstance) 1060 root.SetResourceInstanceCurrent( 1061 mustResourceInstanceAddr("aws_instance.bar").Resource, 1062 &states.ResourceInstanceObjectSrc{ 1063 Status: states.ObjectReady, 1064 AttrsJSON: []byte(`{"id":"bar"}`), 1065 }, 1066 mustProviderConfig(`provider["registry.terraform.io/hashicorp/aws"]`), 1067 ) 1068 root.SetResourceInstanceDeposed( 1069 mustResourceInstanceAddr("aws_instance.bar").Resource, 1070 states.NewDeposedKey(), 1071 &states.ResourceInstanceObjectSrc{ 1072 Status: states.ObjectTainted, 1073 AttrsJSON: []byte(`{"id":"foo"}`), 1074 }, 1075 mustProviderConfig(`provider["registry.terraform.io/hashicorp/aws"]`), 1076 ) 1077 1078 ctx := testContext2(t, &ContextOpts{ 1079 Providers: map[addrs.Provider]providers.Factory{ 1080 addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p), 1081 }, 1082 }) 1083 1084 plan, diags := ctx.Plan(m, state, DefaultPlanOpts) 1085 if diags.HasErrors() { 1086 t.Fatalf("diags: %s", diags.Err()) 1087 } else { 1088 t.Logf(legacyDiffComparisonString(plan.Changes)) 1089 } 1090 1091 state, diags = ctx.Apply(plan, m) 1092 if diags.HasErrors() { 1093 t.Fatalf("diags: %s", diags.Err()) 1094 } 1095 1096 checkStateString(t, state, ` 1097 aws_instance.bar: 1098 ID = bar 1099 provider = provider["registry.terraform.io/hashicorp/aws"] 1100 type = aws_instance 1101 `) 1102 } 1103 1104 func TestContext2Apply_destroyComputed(t *testing.T) { 1105 m := testModule(t, "apply-destroy-computed") 1106 p := testProvider("aws") 1107 p.PlanResourceChangeFn = testDiffFn 1108 state := states.NewState() 1109 root := state.EnsureModule(addrs.RootModuleInstance) 1110 root.SetResourceInstanceCurrent( 1111 mustResourceInstanceAddr("aws_instance.foo").Resource, 1112 &states.ResourceInstanceObjectSrc{ 1113 Status: states.ObjectReady, 1114 AttrsJSON: []byte(`{"id":"foo", "output": "value"}`), 1115 }, 1116 mustProviderConfig(`provider["registry.terraform.io/hashicorp/aws"]`), 1117 ) 1118 ctx := testContext2(t, &ContextOpts{ 1119 Providers: map[addrs.Provider]providers.Factory{ 1120 addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p), 1121 }, 1122 }) 1123 1124 plan, diags := ctx.Plan(m, state, &PlanOpts{ 1125 Mode: plans.DestroyMode, 1126 }) 1127 if diags.HasErrors() { 1128 logDiagnostics(t, diags) 1129 t.Fatal("plan failed") 1130 } else { 1131 t.Logf("plan:\n\n%s", legacyDiffComparisonString(plan.Changes)) 1132 } 1133 1134 if _, diags := ctx.Apply(plan, m); diags.HasErrors() { 1135 logDiagnostics(t, diags) 1136 t.Fatal("apply failed") 1137 } 1138 } 1139 1140 // Test that the destroy operation uses depends_on as a source of ordering. 1141 func TestContext2Apply_destroyDependsOn(t *testing.T) { 1142 // It is possible for this to be racy, so we loop a number of times 1143 // just to check. 1144 for i := 0; i < 10; i++ { 1145 testContext2Apply_destroyDependsOn(t) 1146 } 1147 } 1148 1149 func testContext2Apply_destroyDependsOn(t *testing.T) { 1150 m := testModule(t, "apply-destroy-depends-on") 1151 p := testProvider("aws") 1152 p.PlanResourceChangeFn = testDiffFn 1153 1154 state := states.NewState() 1155 root := state.EnsureModule(addrs.RootModuleInstance) 1156 root.SetResourceInstanceCurrent( 1157 mustResourceInstanceAddr("aws_instance.bar").Resource, 1158 &states.ResourceInstanceObjectSrc{ 1159 Status: states.ObjectReady, 1160 AttrsJSON: []byte(`{"id":"bar"}`), 1161 }, 1162 mustProviderConfig(`provider["registry.terraform.io/hashicorp/aws"]`), 1163 ) 1164 root.SetResourceInstanceCurrent( 1165 mustResourceInstanceAddr("aws_instance.foo").Resource, 1166 &states.ResourceInstanceObjectSrc{ 1167 Status: states.ObjectReady, 1168 AttrsJSON: []byte(`{"id":"foo"}`), 1169 Dependencies: []addrs.ConfigResource{mustConfigResourceAddr("aws_instance.bar")}, 1170 }, 1171 mustProviderConfig(`provider["registry.terraform.io/hashicorp/aws"]`), 1172 ) 1173 1174 // Record the order we see Apply 1175 var actual []string 1176 var actualLock sync.Mutex 1177 p.ApplyResourceChangeFn = func(req providers.ApplyResourceChangeRequest) providers.ApplyResourceChangeResponse { 1178 actualLock.Lock() 1179 defer actualLock.Unlock() 1180 id := req.PriorState.GetAttr("id").AsString() 1181 actual = append(actual, id) 1182 1183 return testApplyFn(req) 1184 } 1185 1186 ctx := testContext2(t, &ContextOpts{ 1187 Providers: map[addrs.Provider]providers.Factory{ 1188 addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p), 1189 }, 1190 Parallelism: 1, // To check ordering 1191 }) 1192 1193 plan, diags := ctx.Plan(m, state, &PlanOpts{ 1194 Mode: plans.DestroyMode, 1195 }) 1196 assertNoErrors(t, diags) 1197 1198 if _, diags := ctx.Apply(plan, m); diags.HasErrors() { 1199 t.Fatalf("apply errors: %s", diags.Err()) 1200 } 1201 1202 expected := []string{"foo", "bar"} 1203 if !reflect.DeepEqual(actual, expected) { 1204 t.Fatalf("wrong order\ngot: %#v\nwant: %#v", actual, expected) 1205 } 1206 } 1207 1208 // Test that destroy ordering is correct with dependencies only 1209 // in the state. 1210 func TestContext2Apply_destroyDependsOnStateOnly(t *testing.T) { 1211 newState := states.NewState() 1212 root := newState.EnsureModule(addrs.RootModuleInstance) 1213 root.SetResourceInstanceCurrent( 1214 addrs.Resource{ 1215 Mode: addrs.ManagedResourceMode, 1216 Type: "aws_instance", 1217 Name: "foo", 1218 }.Instance(addrs.NoKey), 1219 &states.ResourceInstanceObjectSrc{ 1220 Status: states.ObjectReady, 1221 AttrsJSON: []byte(`{"id":"foo"}`), 1222 Dependencies: []addrs.ConfigResource{}, 1223 }, 1224 addrs.AbsProviderConfig{ 1225 Provider: addrs.NewDefaultProvider("aws"), 1226 Module: addrs.RootModule, 1227 }, 1228 ) 1229 root.SetResourceInstanceCurrent( 1230 addrs.Resource{ 1231 Mode: addrs.ManagedResourceMode, 1232 Type: "aws_instance", 1233 Name: "bar", 1234 }.Instance(addrs.NoKey), 1235 &states.ResourceInstanceObjectSrc{ 1236 Status: states.ObjectReady, 1237 AttrsJSON: []byte(`{"id":"bar"}`), 1238 Dependencies: []addrs.ConfigResource{ 1239 { 1240 Resource: addrs.Resource{ 1241 Mode: addrs.ManagedResourceMode, 1242 Type: "aws_instance", 1243 Name: "foo", 1244 }, 1245 Module: root.Addr.Module(), 1246 }, 1247 }, 1248 }, 1249 addrs.AbsProviderConfig{ 1250 Provider: addrs.NewDefaultProvider("aws"), 1251 Module: addrs.RootModule, 1252 }, 1253 ) 1254 1255 // It is possible for this to be racy, so we loop a number of times 1256 // just to check. 1257 for i := 0; i < 10; i++ { 1258 t.Run("new", func(t *testing.T) { 1259 testContext2Apply_destroyDependsOnStateOnly(t, newState) 1260 }) 1261 } 1262 } 1263 1264 func testContext2Apply_destroyDependsOnStateOnly(t *testing.T, state *states.State) { 1265 state = state.DeepCopy() 1266 m := testModule(t, "empty") 1267 p := testProvider("aws") 1268 p.PlanResourceChangeFn = testDiffFn 1269 // Record the order we see Apply 1270 var actual []string 1271 var actualLock sync.Mutex 1272 p.ApplyResourceChangeFn = func(req providers.ApplyResourceChangeRequest) providers.ApplyResourceChangeResponse { 1273 actualLock.Lock() 1274 defer actualLock.Unlock() 1275 id := req.PriorState.GetAttr("id").AsString() 1276 actual = append(actual, id) 1277 return testApplyFn(req) 1278 } 1279 1280 ctx := testContext2(t, &ContextOpts{ 1281 Providers: map[addrs.Provider]providers.Factory{ 1282 addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p), 1283 }, 1284 Parallelism: 1, // To check ordering 1285 }) 1286 1287 plan, diags := ctx.Plan(m, state, &PlanOpts{ 1288 Mode: plans.DestroyMode, 1289 }) 1290 assertNoErrors(t, diags) 1291 1292 if _, diags := ctx.Apply(plan, m); diags.HasErrors() { 1293 t.Fatalf("apply errors: %s", diags.Err()) 1294 } 1295 1296 expected := []string{"bar", "foo"} 1297 if !reflect.DeepEqual(actual, expected) { 1298 t.Fatalf("wrong order\ngot: %#v\nwant: %#v", actual, expected) 1299 } 1300 } 1301 1302 // Test that destroy ordering is correct with dependencies only 1303 // in the state within a module (GH-11749) 1304 func TestContext2Apply_destroyDependsOnStateOnlyModule(t *testing.T) { 1305 newState := states.NewState() 1306 child := newState.EnsureModule(addrs.RootModuleInstance.Child("child", addrs.NoKey)) 1307 child.SetResourceInstanceCurrent( 1308 addrs.Resource{ 1309 Mode: addrs.ManagedResourceMode, 1310 Type: "aws_instance", 1311 Name: "foo", 1312 }.Instance(addrs.NoKey), 1313 &states.ResourceInstanceObjectSrc{ 1314 Status: states.ObjectReady, 1315 AttrsJSON: []byte(`{"id":"foo"}`), 1316 Dependencies: []addrs.ConfigResource{}, 1317 }, 1318 addrs.AbsProviderConfig{ 1319 Provider: addrs.NewDefaultProvider("aws"), 1320 Module: addrs.RootModule, 1321 }, 1322 ) 1323 child.SetResourceInstanceCurrent( 1324 addrs.Resource{ 1325 Mode: addrs.ManagedResourceMode, 1326 Type: "aws_instance", 1327 Name: "bar", 1328 }.Instance(addrs.NoKey), 1329 &states.ResourceInstanceObjectSrc{ 1330 Status: states.ObjectReady, 1331 AttrsJSON: []byte(`{"id":"bar"}`), 1332 Dependencies: []addrs.ConfigResource{ 1333 { 1334 Resource: addrs.Resource{ 1335 Mode: addrs.ManagedResourceMode, 1336 Type: "aws_instance", 1337 Name: "foo", 1338 }, 1339 Module: child.Addr.Module(), 1340 }, 1341 }, 1342 }, 1343 addrs.AbsProviderConfig{ 1344 Provider: addrs.NewDefaultProvider("aws"), 1345 Module: addrs.RootModule, 1346 }, 1347 ) 1348 1349 // It is possible for this to be racy, so we loop a number of times 1350 // just to check. 1351 for i := 0; i < 10; i++ { 1352 t.Run("new", func(t *testing.T) { 1353 testContext2Apply_destroyDependsOnStateOnlyModule(t, newState) 1354 }) 1355 } 1356 } 1357 1358 func testContext2Apply_destroyDependsOnStateOnlyModule(t *testing.T, state *states.State) { 1359 state = state.DeepCopy() 1360 m := testModule(t, "empty") 1361 p := testProvider("aws") 1362 p.PlanResourceChangeFn = testDiffFn 1363 1364 // Record the order we see Apply 1365 var actual []string 1366 var actualLock sync.Mutex 1367 p.ApplyResourceChangeFn = func(req providers.ApplyResourceChangeRequest) providers.ApplyResourceChangeResponse { 1368 actualLock.Lock() 1369 defer actualLock.Unlock() 1370 id := req.PriorState.GetAttr("id").AsString() 1371 actual = append(actual, id) 1372 return testApplyFn(req) 1373 } 1374 1375 ctx := testContext2(t, &ContextOpts{ 1376 Providers: map[addrs.Provider]providers.Factory{ 1377 addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p), 1378 }, 1379 Parallelism: 1, // To check ordering 1380 }) 1381 1382 plan, diags := ctx.Plan(m, state, &PlanOpts{ 1383 Mode: plans.DestroyMode, 1384 }) 1385 assertNoErrors(t, diags) 1386 1387 if _, diags := ctx.Apply(plan, m); diags.HasErrors() { 1388 t.Fatalf("apply errors: %s", diags.Err()) 1389 } 1390 1391 expected := []string{"bar", "foo"} 1392 if !reflect.DeepEqual(actual, expected) { 1393 t.Fatalf("wrong order\ngot: %#v\nwant: %#v", actual, expected) 1394 } 1395 } 1396 1397 func TestContext2Apply_dataBasic(t *testing.T) { 1398 m := testModule(t, "apply-data-basic") 1399 p := testProvider("null") 1400 p.PlanResourceChangeFn = testDiffFn 1401 p.ReadDataSourceResponse = &providers.ReadDataSourceResponse{ 1402 State: cty.ObjectVal(map[string]cty.Value{ 1403 "id": cty.StringVal("yo"), 1404 "foo": cty.NullVal(cty.String), 1405 }), 1406 } 1407 1408 ctx := testContext2(t, &ContextOpts{ 1409 Providers: map[addrs.Provider]providers.Factory{ 1410 addrs.NewDefaultProvider("null"): testProviderFuncFixed(p), 1411 }, 1412 }) 1413 1414 plan, diags := ctx.Plan(m, states.NewState(), DefaultPlanOpts) 1415 if diags.HasErrors() { 1416 t.Fatalf("diags: %s", diags.Err()) 1417 } else { 1418 t.Logf(legacyDiffComparisonString(plan.Changes)) 1419 } 1420 1421 state, diags := ctx.Apply(plan, m) 1422 assertNoErrors(t, diags) 1423 1424 actual := strings.TrimSpace(state.String()) 1425 expected := strings.TrimSpace(testTerraformApplyDataBasicStr) 1426 if actual != expected { 1427 t.Fatalf("wrong result\n\ngot:\n%s\n\nwant:\n%s", actual, expected) 1428 } 1429 } 1430 1431 func TestContext2Apply_destroyData(t *testing.T) { 1432 m := testModule(t, "apply-destroy-data-resource") 1433 p := testProvider("null") 1434 p.PlanResourceChangeFn = testDiffFn 1435 p.ReadDataSourceFn = func(req providers.ReadDataSourceRequest) providers.ReadDataSourceResponse { 1436 return providers.ReadDataSourceResponse{ 1437 State: req.Config, 1438 } 1439 } 1440 1441 state := states.NewState() 1442 root := state.EnsureModule(addrs.RootModuleInstance) 1443 root.SetResourceInstanceCurrent( 1444 mustResourceInstanceAddr("data.null_data_source.testing").Resource, 1445 &states.ResourceInstanceObjectSrc{ 1446 Status: states.ObjectReady, 1447 AttrsJSON: []byte(`{"id":"-"}`), 1448 }, 1449 mustProviderConfig(`provider["registry.terraform.io/hashicorp/null"]`), 1450 ) 1451 1452 hook := &testHook{} 1453 ctx := testContext2(t, &ContextOpts{ 1454 Providers: map[addrs.Provider]providers.Factory{ 1455 addrs.NewDefaultProvider("null"): testProviderFuncFixed(p), 1456 }, 1457 Hooks: []Hook{hook}, 1458 }) 1459 1460 plan, diags := ctx.Plan(m, state, &PlanOpts{ 1461 Mode: plans.DestroyMode, 1462 }) 1463 if diags.HasErrors() { 1464 t.Fatalf("diags: %s", diags.Err()) 1465 } else { 1466 t.Logf(legacyDiffComparisonString(plan.Changes)) 1467 } 1468 1469 newState, diags := ctx.Apply(plan, m) 1470 if diags.HasErrors() { 1471 t.Fatalf("diags: %s", diags.Err()) 1472 } 1473 1474 if got := len(newState.Modules); got != 1 { 1475 t.Fatalf("state has %d modules after destroy; want 1", got) 1476 } 1477 1478 if got := len(newState.RootModule().Resources); got != 0 { 1479 t.Fatalf("state has %d resources after destroy; want 0", got) 1480 } 1481 1482 wantHookCalls := []*testHookCall{ 1483 {"PreDiff", "data.null_data_source.testing"}, 1484 {"PostDiff", "data.null_data_source.testing"}, 1485 {"PreDiff", "data.null_data_source.testing"}, 1486 {"PostDiff", "data.null_data_source.testing"}, 1487 {"PostStateUpdate", ""}, 1488 } 1489 if !reflect.DeepEqual(hook.Calls, wantHookCalls) { 1490 t.Errorf("wrong hook calls\ngot: %swant: %s", spew.Sdump(hook.Calls), spew.Sdump(wantHookCalls)) 1491 } 1492 } 1493 1494 // https://github.com/hashicorp/terraform/pull/5096 1495 func TestContext2Apply_destroySkipsCBD(t *testing.T) { 1496 // Config contains CBD resource depending on non-CBD resource, which triggers 1497 // a cycle if they are both replaced, but should _not_ trigger a cycle when 1498 // just doing a `terraform destroy`. 1499 m := testModule(t, "apply-destroy-cbd") 1500 p := testProvider("aws") 1501 p.PlanResourceChangeFn = testDiffFn 1502 state := states.NewState() 1503 root := state.EnsureModule(addrs.RootModuleInstance) 1504 root.SetResourceInstanceCurrent( 1505 mustResourceInstanceAddr("aws_instance.foo").Resource, 1506 &states.ResourceInstanceObjectSrc{ 1507 Status: states.ObjectReady, 1508 AttrsJSON: []byte(`{"id":"foo"}`), 1509 }, 1510 mustProviderConfig(`provider["registry.terraform.io/hashicorp/aws"]`), 1511 ) 1512 root.SetResourceInstanceCurrent( 1513 mustResourceInstanceAddr("aws_instance.bar").Resource, 1514 &states.ResourceInstanceObjectSrc{ 1515 Status: states.ObjectReady, 1516 AttrsJSON: []byte(`{"id":"foo"}`), 1517 }, 1518 mustProviderConfig(`provider["registry.terraform.io/hashicorp/aws"]`), 1519 ) 1520 1521 ctx := testContext2(t, &ContextOpts{ 1522 Providers: map[addrs.Provider]providers.Factory{ 1523 addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p), 1524 }, 1525 }) 1526 1527 plan, diags := ctx.Plan(m, state, &PlanOpts{ 1528 Mode: plans.DestroyMode, 1529 }) 1530 if diags.HasErrors() { 1531 t.Fatalf("diags: %s", diags.Err()) 1532 } else { 1533 t.Logf(legacyDiffComparisonString(plan.Changes)) 1534 } 1535 1536 if _, diags := ctx.Apply(plan, m); diags.HasErrors() { 1537 t.Fatalf("apply errors: %s", diags.Err()) 1538 } 1539 } 1540 1541 func TestContext2Apply_destroyModuleVarProviderConfig(t *testing.T) { 1542 m := testModule(t, "apply-destroy-mod-var-provider-config") 1543 p := testProvider("aws") 1544 p.PlanResourceChangeFn = testDiffFn 1545 state := states.NewState() 1546 root := state.EnsureModule(addrs.RootModuleInstance) 1547 root.SetResourceInstanceCurrent( 1548 mustResourceInstanceAddr("aws_instance.foo").Resource, 1549 &states.ResourceInstanceObjectSrc{ 1550 Status: states.ObjectReady, 1551 AttrsJSON: []byte(`{"id":"foo"}`), 1552 }, 1553 mustProviderConfig(`provider["registry.terraform.io/hashicorp/aws"]`), 1554 ) 1555 ctx := testContext2(t, &ContextOpts{ 1556 Providers: map[addrs.Provider]providers.Factory{ 1557 addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p), 1558 }, 1559 }) 1560 1561 plan, diags := ctx.Plan(m, state, &PlanOpts{ 1562 Mode: plans.DestroyMode, 1563 }) 1564 assertNoErrors(t, diags) 1565 1566 _, diags = ctx.Apply(plan, m) 1567 if diags.HasErrors() { 1568 t.Fatalf("diags: %s", diags.Err()) 1569 } 1570 } 1571 1572 func TestContext2Apply_destroyCrossProviders(t *testing.T) { 1573 m := testModule(t, "apply-destroy-cross-providers") 1574 1575 p_aws := testProvider("aws") 1576 p_aws.ApplyResourceChangeFn = testApplyFn 1577 p_aws.PlanResourceChangeFn = testDiffFn 1578 p_aws.GetProviderSchemaResponse = getProviderSchemaResponseFromProviderSchema(&ProviderSchema{ 1579 ResourceTypes: map[string]*configschema.Block{ 1580 "aws_instance": { 1581 Attributes: map[string]*configschema.Attribute{ 1582 "id": { 1583 Type: cty.String, 1584 Computed: true, 1585 }, 1586 }, 1587 }, 1588 "aws_vpc": { 1589 Attributes: map[string]*configschema.Attribute{ 1590 "id": { 1591 Type: cty.String, 1592 Computed: true, 1593 }, 1594 "value": { 1595 Type: cty.String, 1596 Optional: true, 1597 }, 1598 }, 1599 }, 1600 }, 1601 }) 1602 1603 providers := map[addrs.Provider]providers.Factory{ 1604 addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p_aws), 1605 } 1606 1607 ctx, m, state := getContextForApply_destroyCrossProviders(t, m, providers) 1608 1609 plan, diags := ctx.Plan(m, state, &PlanOpts{ 1610 Mode: plans.DestroyMode, 1611 }) 1612 assertNoErrors(t, diags) 1613 1614 if _, diags := ctx.Apply(plan, m); diags.HasErrors() { 1615 logDiagnostics(t, diags) 1616 t.Fatal("apply failed") 1617 } 1618 } 1619 1620 func getContextForApply_destroyCrossProviders(t *testing.T, m *configs.Config, providerFactories map[addrs.Provider]providers.Factory) (*Context, *configs.Config, *states.State) { 1621 state := states.NewState() 1622 root := state.EnsureModule(addrs.RootModuleInstance) 1623 root.SetResourceInstanceCurrent( 1624 mustResourceInstanceAddr("aws_instance.shared").Resource, 1625 &states.ResourceInstanceObjectSrc{ 1626 Status: states.ObjectReady, 1627 AttrsJSON: []byte(`{"id":"test"}`), 1628 }, 1629 mustProviderConfig(`provider["registry.terraform.io/hashicorp/aws"]`), 1630 ) 1631 child := state.EnsureModule(addrs.RootModuleInstance.Child("child", addrs.NoKey)) 1632 child.SetResourceInstanceCurrent( 1633 mustResourceInstanceAddr("aws_vpc.bar").Resource, 1634 &states.ResourceInstanceObjectSrc{ 1635 Status: states.ObjectReady, 1636 AttrsJSON: []byte(`{"id": "vpc-aaabbb12", "value":"test"}`), 1637 }, 1638 mustProviderConfig(`provider["registry.terraform.io/hashicorp/aws"]`), 1639 ) 1640 1641 ctx := testContext2(t, &ContextOpts{ 1642 Providers: providerFactories, 1643 }) 1644 1645 return ctx, m, state 1646 } 1647 1648 func TestContext2Apply_minimal(t *testing.T) { 1649 m := testModule(t, "apply-minimal") 1650 p := testProvider("aws") 1651 p.PlanResourceChangeFn = testDiffFn 1652 p.ApplyResourceChangeFn = testApplyFn 1653 ctx := testContext2(t, &ContextOpts{ 1654 Providers: map[addrs.Provider]providers.Factory{ 1655 addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p), 1656 }, 1657 }) 1658 1659 plan, diags := ctx.Plan(m, states.NewState(), DefaultPlanOpts) 1660 assertNoErrors(t, diags) 1661 1662 state, diags := ctx.Apply(plan, m) 1663 if diags.HasErrors() { 1664 t.Fatalf("diags: %s", diags.Err()) 1665 } 1666 1667 actual := strings.TrimSpace(state.String()) 1668 expected := strings.TrimSpace(testTerraformApplyMinimalStr) 1669 if actual != expected { 1670 t.Fatalf("wrong result\n\ngot:\n%s\n\nwant:\n%s", actual, expected) 1671 } 1672 } 1673 1674 func TestContext2Apply_cancel(t *testing.T) { 1675 stopped := false 1676 1677 m := testModule(t, "apply-cancel") 1678 p := testProvider("aws") 1679 ctx := testContext2(t, &ContextOpts{ 1680 Providers: map[addrs.Provider]providers.Factory{ 1681 addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p), 1682 }, 1683 }) 1684 1685 p.ApplyResourceChangeFn = func(req providers.ApplyResourceChangeRequest) providers.ApplyResourceChangeResponse { 1686 if !stopped { 1687 stopped = true 1688 go ctx.Stop() 1689 1690 for { 1691 if ctx.sh.Stopped() { 1692 break 1693 } 1694 time.Sleep(10 * time.Millisecond) 1695 } 1696 } 1697 return testApplyFn(req) 1698 } 1699 p.PlanResourceChangeFn = testDiffFn 1700 1701 plan, diags := ctx.Plan(m, states.NewState(), DefaultPlanOpts) 1702 assertNoErrors(t, diags) 1703 1704 // Start the Apply in a goroutine 1705 var applyDiags tfdiags.Diagnostics 1706 stateCh := make(chan *states.State) 1707 go func() { 1708 state, diags := ctx.Apply(plan, m) 1709 applyDiags = diags 1710 1711 stateCh <- state 1712 }() 1713 1714 state := <-stateCh 1715 // only expecting an early exit error 1716 if !applyDiags.HasErrors() { 1717 t.Fatal("expected early exit error") 1718 } 1719 1720 for _, d := range applyDiags { 1721 desc := d.Description() 1722 if desc.Summary != "execution halted" { 1723 t.Fatalf("unexpected error: %v", applyDiags.Err()) 1724 } 1725 } 1726 1727 actual := strings.TrimSpace(state.String()) 1728 expected := strings.TrimSpace(testTerraformApplyCancelStr) 1729 if actual != expected { 1730 t.Fatalf("wrong result\n\ngot:\n%s\n\nwant:\n%s", actual, expected) 1731 } 1732 1733 if !p.StopCalled { 1734 t.Fatal("stop should be called") 1735 } 1736 } 1737 1738 func TestContext2Apply_cancelBlock(t *testing.T) { 1739 m := testModule(t, "apply-cancel-block") 1740 p := testProvider("aws") 1741 ctx := testContext2(t, &ContextOpts{ 1742 Providers: map[addrs.Provider]providers.Factory{ 1743 addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p), 1744 }, 1745 }) 1746 1747 applyCh := make(chan struct{}) 1748 p.PlanResourceChangeFn = testDiffFn 1749 p.ApplyResourceChangeFn = func(req providers.ApplyResourceChangeRequest) providers.ApplyResourceChangeResponse { 1750 close(applyCh) 1751 1752 for !ctx.sh.Stopped() { 1753 // Wait for stop to be called. We call Gosched here so that 1754 // the other goroutines can always be scheduled to set Stopped. 1755 runtime.Gosched() 1756 } 1757 1758 // Sleep 1759 time.Sleep(100 * time.Millisecond) 1760 return testApplyFn(req) 1761 } 1762 1763 plan, diags := ctx.Plan(m, states.NewState(), DefaultPlanOpts) 1764 assertNoErrors(t, diags) 1765 1766 // Start the Apply in a goroutine 1767 var applyDiags tfdiags.Diagnostics 1768 stateCh := make(chan *states.State) 1769 go func() { 1770 state, diags := ctx.Apply(plan, m) 1771 applyDiags = diags 1772 1773 stateCh <- state 1774 }() 1775 1776 stopDone := make(chan struct{}) 1777 go func() { 1778 defer close(stopDone) 1779 <-applyCh 1780 ctx.Stop() 1781 }() 1782 1783 // Make sure that stop blocks 1784 select { 1785 case <-stopDone: 1786 t.Fatal("stop should block") 1787 case <-time.After(10 * time.Millisecond): 1788 } 1789 1790 // Wait for stop 1791 select { 1792 case <-stopDone: 1793 case <-time.After(500 * time.Millisecond): 1794 t.Fatal("stop should be done") 1795 } 1796 1797 // Wait for apply to complete 1798 state := <-stateCh 1799 // only expecting an early exit error 1800 if !applyDiags.HasErrors() { 1801 t.Fatal("expected early exit error") 1802 } 1803 1804 for _, d := range applyDiags { 1805 desc := d.Description() 1806 if desc.Summary != "execution halted" { 1807 t.Fatalf("unexpected error: %v", applyDiags.Err()) 1808 } 1809 } 1810 1811 checkStateString(t, state, ` 1812 aws_instance.foo: 1813 ID = foo 1814 provider = provider["registry.terraform.io/hashicorp/aws"] 1815 num = 2 1816 type = aws_instance 1817 `) 1818 } 1819 1820 func TestContext2Apply_cancelProvisioner(t *testing.T) { 1821 m := testModule(t, "apply-cancel-provisioner") 1822 p := testProvider("aws") 1823 p.PlanResourceChangeFn = testDiffFn 1824 p.ApplyResourceChangeFn = testApplyFn 1825 1826 pr := testProvisioner() 1827 pr.GetSchemaResponse = provisioners.GetSchemaResponse{ 1828 Provisioner: &configschema.Block{ 1829 Attributes: map[string]*configschema.Attribute{ 1830 "foo": { 1831 Type: cty.String, 1832 Optional: true, 1833 }, 1834 }, 1835 }, 1836 } 1837 1838 ctx := testContext2(t, &ContextOpts{ 1839 Providers: map[addrs.Provider]providers.Factory{ 1840 addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p), 1841 }, 1842 Provisioners: map[string]provisioners.Factory{ 1843 "shell": testProvisionerFuncFixed(pr), 1844 }, 1845 }) 1846 1847 prStopped := make(chan struct{}) 1848 pr.ProvisionResourceFn = func(req provisioners.ProvisionResourceRequest) (resp provisioners.ProvisionResourceResponse) { 1849 // Start the stop process 1850 go ctx.Stop() 1851 1852 <-prStopped 1853 return 1854 } 1855 pr.StopFn = func() error { 1856 close(prStopped) 1857 return nil 1858 } 1859 1860 plan, diags := ctx.Plan(m, states.NewState(), DefaultPlanOpts) 1861 assertNoErrors(t, diags) 1862 1863 // Start the Apply in a goroutine 1864 var applyDiags tfdiags.Diagnostics 1865 stateCh := make(chan *states.State) 1866 go func() { 1867 state, diags := ctx.Apply(plan, m) 1868 applyDiags = diags 1869 1870 stateCh <- state 1871 }() 1872 1873 // Wait for completion 1874 state := <-stateCh 1875 1876 // we are expecting only an early exit error 1877 if !applyDiags.HasErrors() { 1878 t.Fatal("expected early exit error") 1879 } 1880 1881 for _, d := range applyDiags { 1882 desc := d.Description() 1883 if desc.Summary != "execution halted" { 1884 t.Fatalf("unexpected error: %v", applyDiags.Err()) 1885 } 1886 } 1887 1888 checkStateString(t, state, ` 1889 aws_instance.foo: (tainted) 1890 ID = foo 1891 provider = provider["registry.terraform.io/hashicorp/aws"] 1892 num = 2 1893 type = aws_instance 1894 `) 1895 1896 if !pr.StopCalled { 1897 t.Fatal("stop should be called") 1898 } 1899 } 1900 1901 func TestContext2Apply_compute(t *testing.T) { 1902 m := testModule(t, "apply-compute") 1903 p := testProvider("aws") 1904 p.PlanResourceChangeFn = testDiffFn 1905 p.ApplyResourceChangeFn = testApplyFn 1906 p.GetProviderSchemaResponse = getProviderSchemaResponseFromProviderSchema(&ProviderSchema{ 1907 ResourceTypes: map[string]*configschema.Block{ 1908 "aws_instance": { 1909 Attributes: map[string]*configschema.Attribute{ 1910 "num": { 1911 Type: cty.Number, 1912 Optional: true, 1913 }, 1914 "compute": { 1915 Type: cty.String, 1916 Optional: true, 1917 }, 1918 "compute_value": { 1919 Type: cty.String, 1920 Optional: true, 1921 }, 1922 "foo": { 1923 Type: cty.String, 1924 Optional: true, 1925 }, 1926 "id": { 1927 Type: cty.String, 1928 Computed: true, 1929 }, 1930 "type": { 1931 Type: cty.String, 1932 Computed: true, 1933 }, 1934 "value": { // Populated from compute_value because compute = "value" in the config fixture 1935 Type: cty.String, 1936 Computed: true, 1937 }, 1938 }, 1939 }, 1940 }, 1941 }) 1942 1943 ctx := testContext2(t, &ContextOpts{ 1944 Providers: map[addrs.Provider]providers.Factory{ 1945 addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p), 1946 }, 1947 }) 1948 1949 plan, diags := ctx.Plan(m, states.NewState(), &PlanOpts{ 1950 SetVariables: InputValues{ 1951 "value": &InputValue{ 1952 Value: cty.NumberIntVal(1), 1953 SourceType: ValueFromCaller, 1954 }, 1955 }, 1956 }) 1957 assertNoErrors(t, diags) 1958 1959 state, diags := ctx.Apply(plan, m) 1960 if diags.HasErrors() { 1961 t.Fatalf("unexpected errors: %s", diags.Err()) 1962 } 1963 1964 actual := strings.TrimSpace(state.String()) 1965 expected := strings.TrimSpace(testTerraformApplyComputeStr) 1966 if actual != expected { 1967 t.Fatalf("wrong result\n\ngot:\n%s\n\nwant:\n%s", actual, expected) 1968 } 1969 } 1970 1971 func TestContext2Apply_countDecrease(t *testing.T) { 1972 m := testModule(t, "apply-count-dec") 1973 p := testProvider("aws") 1974 p.PlanResourceChangeFn = testDiffFn 1975 p.ApplyResourceChangeFn = testApplyFn 1976 state := states.NewState() 1977 root := state.EnsureModule(addrs.RootModuleInstance) 1978 root.SetResourceInstanceCurrent( 1979 mustResourceInstanceAddr("aws_instance.foo[0]").Resource, 1980 &states.ResourceInstanceObjectSrc{ 1981 Status: states.ObjectReady, 1982 AttrsJSON: []byte(`{"id":"bar","foo": "foo","type": "aws_instance"}`), 1983 }, 1984 mustProviderConfig(`provider["registry.terraform.io/hashicorp/aws"]`), 1985 ) 1986 root.SetResourceInstanceCurrent( 1987 mustResourceInstanceAddr("aws_instance.foo[1]").Resource, 1988 &states.ResourceInstanceObjectSrc{ 1989 Status: states.ObjectReady, 1990 AttrsJSON: []byte(`{"id":"bar","foo": "foo","type": "aws_instance"}`), 1991 }, 1992 mustProviderConfig(`provider["registry.terraform.io/hashicorp/aws"]`), 1993 ) 1994 root.SetResourceInstanceCurrent( 1995 mustResourceInstanceAddr("aws_instance.foo[2]").Resource, 1996 &states.ResourceInstanceObjectSrc{ 1997 Status: states.ObjectReady, 1998 AttrsJSON: []byte(`{"id":"bar", "foo": "foo", "type": "aws_instance"}`), 1999 }, 2000 mustProviderConfig(`provider["registry.terraform.io/hashicorp/aws"]`), 2001 ) 2002 2003 ctx := testContext2(t, &ContextOpts{ 2004 Providers: map[addrs.Provider]providers.Factory{ 2005 addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p), 2006 }, 2007 }) 2008 2009 plan, diags := ctx.Plan(m, state, DefaultPlanOpts) 2010 assertNoErrors(t, diags) 2011 2012 s, diags := ctx.Apply(plan, m) 2013 assertNoErrors(t, diags) 2014 2015 actual := strings.TrimSpace(s.String()) 2016 expected := strings.TrimSpace(testTerraformApplyCountDecStr) 2017 if actual != expected { 2018 t.Fatalf("wrong result\n\ngot:\n%s\n\nwant:\n%s", actual, expected) 2019 } 2020 } 2021 2022 func TestContext2Apply_countDecreaseToOneX(t *testing.T) { 2023 m := testModule(t, "apply-count-dec-one") 2024 p := testProvider("aws") 2025 p.PlanResourceChangeFn = testDiffFn 2026 state := states.NewState() 2027 root := state.EnsureModule(addrs.RootModuleInstance) 2028 root.SetResourceInstanceCurrent( 2029 mustResourceInstanceAddr("aws_instance.foo[0]").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 root.SetResourceInstanceCurrent( 2037 mustResourceInstanceAddr("aws_instance.foo[1]").Resource, 2038 &states.ResourceInstanceObjectSrc{ 2039 Status: states.ObjectReady, 2040 AttrsJSON: []byte(`{"id":"bar"}`), 2041 }, 2042 mustProviderConfig(`provider["registry.terraform.io/hashicorp/aws"]`), 2043 ) 2044 root.SetResourceInstanceCurrent( 2045 mustResourceInstanceAddr("aws_instance.foo[2]").Resource, 2046 &states.ResourceInstanceObjectSrc{ 2047 Status: states.ObjectReady, 2048 AttrsJSON: []byte(`{"id":"bar"}`), 2049 }, 2050 mustProviderConfig(`provider["registry.terraform.io/hashicorp/aws"]`), 2051 ) 2052 2053 ctx := testContext2(t, &ContextOpts{ 2054 Providers: map[addrs.Provider]providers.Factory{ 2055 addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p), 2056 }, 2057 }) 2058 2059 plan, diags := ctx.Plan(m, state, DefaultPlanOpts) 2060 assertNoErrors(t, diags) 2061 2062 s, diags := ctx.Apply(plan, m) 2063 if diags.HasErrors() { 2064 t.Fatalf("diags: %s", diags.Err()) 2065 } 2066 2067 actual := strings.TrimSpace(s.String()) 2068 expected := strings.TrimSpace(testTerraformApplyCountDecToOneStr) 2069 if actual != expected { 2070 t.Fatalf("wrong result\n\ngot:\n%s\n\nwant:\n%s", actual, expected) 2071 } 2072 } 2073 2074 // https://github.com/PeoplePerHour/terraform/pull/11 2075 // 2076 // This tests a case where both a "resource" and "resource.0" are in 2077 // the state file, which apparently is a reasonable backwards compatibility 2078 // concern found in the above 3rd party repo. 2079 func TestContext2Apply_countDecreaseToOneCorrupted(t *testing.T) { 2080 m := testModule(t, "apply-count-dec-one") 2081 p := testProvider("aws") 2082 p.PlanResourceChangeFn = testDiffFn 2083 state := states.NewState() 2084 root := state.EnsureModule(addrs.RootModuleInstance) 2085 root.SetResourceInstanceCurrent( 2086 mustResourceInstanceAddr("aws_instance.foo").Resource, 2087 &states.ResourceInstanceObjectSrc{ 2088 Status: states.ObjectReady, 2089 AttrsJSON: []byte(`{"id":"bar", "foo": "foo", "type": "aws_instance"}`), 2090 }, 2091 mustProviderConfig(`provider["registry.terraform.io/hashicorp/aws"]`), 2092 ) 2093 root.SetResourceInstanceCurrent( 2094 mustResourceInstanceAddr("aws_instance.foo[0]").Resource, 2095 &states.ResourceInstanceObjectSrc{ 2096 Status: states.ObjectReady, 2097 AttrsJSON: []byte(`{"id":"baz", "type": "aws_instance"}`), 2098 }, 2099 mustProviderConfig(`provider["registry.terraform.io/hashicorp/aws"]`), 2100 ) 2101 2102 ctx := testContext2(t, &ContextOpts{ 2103 Providers: map[addrs.Provider]providers.Factory{ 2104 addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p), 2105 }, 2106 }) 2107 2108 plan, diags := ctx.Plan(m, state, DefaultPlanOpts) 2109 assertNoErrors(t, diags) 2110 { 2111 got := strings.TrimSpace(legacyPlanComparisonString(state, plan.Changes)) 2112 want := strings.TrimSpace(testTerraformApplyCountDecToOneCorruptedPlanStr) 2113 if got != want { 2114 t.Fatalf("wrong plan result\ngot:\n%s\nwant:\n%s", got, want) 2115 } 2116 } 2117 2118 s, diags := ctx.Apply(plan, m) 2119 if diags.HasErrors() { 2120 t.Fatalf("diags: %s", diags.Err()) 2121 } 2122 2123 actual := strings.TrimSpace(s.String()) 2124 expected := strings.TrimSpace(testTerraformApplyCountDecToOneCorruptedStr) 2125 if actual != expected { 2126 t.Fatalf("wrong final state\n\ngot:\n%s\n\nwant:\n%s", actual, expected) 2127 } 2128 } 2129 2130 func TestContext2Apply_countTainted(t *testing.T) { 2131 m := testModule(t, "apply-count-tainted") 2132 p := testProvider("aws") 2133 p.PlanResourceChangeFn = testDiffFn 2134 p.ApplyResourceChangeFn = testApplyFn 2135 state := states.NewState() 2136 root := state.EnsureModule(addrs.RootModuleInstance) 2137 root.SetResourceInstanceCurrent( 2138 mustResourceInstanceAddr("aws_instance.foo[0]").Resource, 2139 &states.ResourceInstanceObjectSrc{ 2140 Status: states.ObjectTainted, 2141 AttrsJSON: []byte(`{"id":"bar", "type": "aws_instance", "foo": "foo"}`), 2142 }, 2143 mustProviderConfig(`provider["registry.terraform.io/hashicorp/aws"]`), 2144 ) 2145 ctx := testContext2(t, &ContextOpts{ 2146 Providers: map[addrs.Provider]providers.Factory{ 2147 addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p), 2148 }, 2149 }) 2150 2151 plan, diags := ctx.Plan(m, state, DefaultPlanOpts) 2152 assertNoErrors(t, diags) 2153 { 2154 got := strings.TrimSpace(legacyDiffComparisonString(plan.Changes)) 2155 want := strings.TrimSpace(` 2156 DESTROY/CREATE: aws_instance.foo[0] 2157 foo: "foo" => "foo" 2158 id: "bar" => "<computed>" 2159 type: "aws_instance" => "<computed>" 2160 CREATE: aws_instance.foo[1] 2161 foo: "" => "foo" 2162 id: "" => "<computed>" 2163 type: "" => "<computed>" 2164 `) 2165 if got != want { 2166 t.Fatalf("wrong plan\n\ngot:\n%s\n\nwant:\n%s", got, want) 2167 } 2168 } 2169 2170 s, diags := ctx.Apply(plan, m) 2171 assertNoErrors(t, diags) 2172 2173 got := strings.TrimSpace(s.String()) 2174 want := strings.TrimSpace(` 2175 aws_instance.foo.0: 2176 ID = foo 2177 provider = provider["registry.terraform.io/hashicorp/aws"] 2178 foo = foo 2179 type = aws_instance 2180 aws_instance.foo.1: 2181 ID = foo 2182 provider = provider["registry.terraform.io/hashicorp/aws"] 2183 foo = foo 2184 type = aws_instance 2185 `) 2186 if got != want { 2187 t.Fatalf("wrong final state\n\ngot:\n%s\n\nwant:\n%s", got, want) 2188 } 2189 } 2190 2191 func TestContext2Apply_countVariable(t *testing.T) { 2192 m := testModule(t, "apply-count-variable") 2193 p := testProvider("aws") 2194 p.PlanResourceChangeFn = testDiffFn 2195 p.ApplyResourceChangeFn = testApplyFn 2196 ctx := testContext2(t, &ContextOpts{ 2197 Providers: map[addrs.Provider]providers.Factory{ 2198 addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p), 2199 }, 2200 }) 2201 2202 plan, diags := ctx.Plan(m, states.NewState(), DefaultPlanOpts) 2203 assertNoErrors(t, diags) 2204 2205 state, diags := ctx.Apply(plan, m) 2206 if diags.HasErrors() { 2207 t.Fatalf("diags: %s", diags.Err()) 2208 } 2209 2210 actual := strings.TrimSpace(state.String()) 2211 expected := strings.TrimSpace(testTerraformApplyCountVariableStr) 2212 if actual != expected { 2213 t.Fatalf("wrong result\n\ngot:\n%s\n\nwant:\n%s", actual, expected) 2214 } 2215 } 2216 2217 func TestContext2Apply_countVariableRef(t *testing.T) { 2218 m := testModule(t, "apply-count-variable-ref") 2219 p := testProvider("aws") 2220 p.PlanResourceChangeFn = testDiffFn 2221 p.ApplyResourceChangeFn = testApplyFn 2222 ctx := testContext2(t, &ContextOpts{ 2223 Providers: map[addrs.Provider]providers.Factory{ 2224 addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p), 2225 }, 2226 }) 2227 2228 plan, diags := ctx.Plan(m, states.NewState(), DefaultPlanOpts) 2229 assertNoErrors(t, diags) 2230 2231 state, diags := ctx.Apply(plan, m) 2232 if diags.HasErrors() { 2233 t.Fatalf("diags: %s", diags.Err()) 2234 } 2235 2236 actual := strings.TrimSpace(state.String()) 2237 expected := strings.TrimSpace(testTerraformApplyCountVariableRefStr) 2238 if actual != expected { 2239 t.Fatalf("wrong result\n\ngot:\n%s\n\nwant:\n%s", actual, expected) 2240 } 2241 } 2242 2243 func TestContext2Apply_provisionerInterpCount(t *testing.T) { 2244 // This test ensures that a provisioner can interpolate a resource count 2245 // even though the provisioner expression is evaluated during the plan 2246 // walk. https://github.com/hashicorp/terraform/issues/16840 2247 2248 m, snap := testModuleWithSnapshot(t, "apply-provisioner-interp-count") 2249 2250 p := testProvider("aws") 2251 p.PlanResourceChangeFn = testDiffFn 2252 2253 pr := testProvisioner() 2254 2255 Providers := map[addrs.Provider]providers.Factory{ 2256 addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p), 2257 } 2258 2259 provisioners := map[string]provisioners.Factory{ 2260 "local-exec": testProvisionerFuncFixed(pr), 2261 } 2262 ctx := testContext2(t, &ContextOpts{ 2263 Providers: Providers, 2264 Provisioners: provisioners, 2265 }) 2266 2267 plan, diags := ctx.Plan(m, states.NewState(), DefaultPlanOpts) 2268 assertNoErrors(t, diags) 2269 2270 // We'll marshal and unmarshal the plan here, to ensure that we have 2271 // a clean new context as would be created if we separately ran 2272 // terraform plan -out=tfplan && terraform apply tfplan 2273 ctxOpts, m, plan, err := contextOptsForPlanViaFile(snap, plan) 2274 if err != nil { 2275 t.Fatal(err) 2276 } 2277 ctxOpts.Providers = Providers 2278 ctxOpts.Provisioners = provisioners 2279 ctx, diags = NewContext(ctxOpts) 2280 if diags.HasErrors() { 2281 t.Fatalf("failed to create context for plan: %s", diags.Err()) 2282 } 2283 2284 // Applying the plan should now succeed 2285 _, diags = ctx.Apply(plan, m) 2286 if diags.HasErrors() { 2287 t.Fatalf("apply failed unexpectedly: %s", diags.Err()) 2288 } 2289 2290 // Verify apply was invoked 2291 if !pr.ProvisionResourceCalled { 2292 t.Fatalf("provisioner was not called") 2293 } 2294 } 2295 2296 func TestContext2Apply_foreachVariable(t *testing.T) { 2297 m := testModule(t, "plan-for-each-unknown-value") 2298 p := testProvider("aws") 2299 p.PlanResourceChangeFn = testDiffFn 2300 p.ApplyResourceChangeFn = testApplyFn 2301 ctx := testContext2(t, &ContextOpts{ 2302 Providers: map[addrs.Provider]providers.Factory{ 2303 addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p), 2304 }, 2305 }) 2306 2307 plan, diags := ctx.Plan(m, states.NewState(), &PlanOpts{ 2308 Mode: plans.NormalMode, 2309 SetVariables: InputValues{ 2310 "foo": &InputValue{ 2311 Value: cty.StringVal("hello"), 2312 }, 2313 }, 2314 }) 2315 assertNoErrors(t, diags) 2316 2317 state, diags := ctx.Apply(plan, m) 2318 if diags.HasErrors() { 2319 t.Fatalf("diags: %s", diags.Err()) 2320 } 2321 2322 actual := strings.TrimSpace(state.String()) 2323 expected := strings.TrimSpace(testTerraformApplyForEachVariableStr) 2324 if actual != expected { 2325 t.Fatalf("wrong result\n\ngot:\n%s\n\nwant:\n%s", actual, expected) 2326 } 2327 } 2328 2329 func TestContext2Apply_moduleBasic(t *testing.T) { 2330 m := testModule(t, "apply-module") 2331 p := testProvider("aws") 2332 p.PlanResourceChangeFn = testDiffFn 2333 p.ApplyResourceChangeFn = testApplyFn 2334 ctx := testContext2(t, &ContextOpts{ 2335 Providers: map[addrs.Provider]providers.Factory{ 2336 addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p), 2337 }, 2338 }) 2339 2340 plan, diags := ctx.Plan(m, states.NewState(), DefaultPlanOpts) 2341 assertNoErrors(t, diags) 2342 2343 state, diags := ctx.Apply(plan, m) 2344 if diags.HasErrors() { 2345 t.Fatalf("diags: %s", diags.Err()) 2346 } 2347 2348 actual := strings.TrimSpace(state.String()) 2349 expected := strings.TrimSpace(testTerraformApplyModuleStr) 2350 if actual != expected { 2351 t.Fatalf("bad, expected:\n%s\n\nactual:\n%s", expected, actual) 2352 } 2353 } 2354 2355 func TestContext2Apply_moduleDestroyOrder(t *testing.T) { 2356 m := testModule(t, "apply-module-destroy-order") 2357 p := testProvider("aws") 2358 p.PlanResourceChangeFn = testDiffFn 2359 2360 // Create a custom apply function to track the order they were destroyed 2361 var order []string 2362 var orderLock sync.Mutex 2363 p.ApplyResourceChangeFn = func(req providers.ApplyResourceChangeRequest) (resp providers.ApplyResourceChangeResponse) { 2364 id := req.PriorState.GetAttr("id").AsString() 2365 2366 if id == "b" { 2367 // Pause briefly to make any race conditions more visible, since 2368 // missing edges here can cause undeterministic ordering. 2369 time.Sleep(100 * time.Millisecond) 2370 } 2371 2372 orderLock.Lock() 2373 defer orderLock.Unlock() 2374 2375 order = append(order, id) 2376 resp.NewState = req.PlannedState 2377 return resp 2378 } 2379 2380 p.GetProviderSchemaResponse = getProviderSchemaResponseFromProviderSchema(&ProviderSchema{ 2381 ResourceTypes: map[string]*configschema.Block{ 2382 "aws_instance": { 2383 Attributes: map[string]*configschema.Attribute{ 2384 "id": {Type: cty.String, Required: true}, 2385 "blah": {Type: cty.String, Optional: true}, 2386 "value": {Type: cty.String, Optional: true}, 2387 }, 2388 }, 2389 }, 2390 }) 2391 2392 state := states.NewState() 2393 child := state.EnsureModule(addrs.RootModuleInstance.Child("child", addrs.NoKey)) 2394 child.SetResourceInstanceCurrent( 2395 mustResourceInstanceAddr("aws_instance.a").Resource, 2396 &states.ResourceInstanceObjectSrc{ 2397 Status: states.ObjectReady, 2398 AttrsJSON: []byte(`{"id":"a"}`), 2399 }, 2400 mustProviderConfig(`provider["registry.terraform.io/hashicorp/aws"]`), 2401 ) 2402 root := state.EnsureModule(addrs.RootModuleInstance) 2403 root.SetResourceInstanceCurrent( 2404 mustResourceInstanceAddr("aws_instance.b").Resource, 2405 &states.ResourceInstanceObjectSrc{ 2406 Status: states.ObjectReady, 2407 AttrsJSON: []byte(`{"id":"b"}`), 2408 Dependencies: []addrs.ConfigResource{mustConfigResourceAddr("module.child.aws_instance.a")}, 2409 }, 2410 mustProviderConfig(`provider["registry.terraform.io/hashicorp/aws"]`), 2411 ) 2412 2413 ctx := testContext2(t, &ContextOpts{ 2414 Providers: map[addrs.Provider]providers.Factory{ 2415 addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p), 2416 }, 2417 }) 2418 2419 plan, diags := ctx.Plan(m, state, &PlanOpts{ 2420 Mode: plans.DestroyMode, 2421 }) 2422 assertNoErrors(t, diags) 2423 2424 state, diags = ctx.Apply(plan, m) 2425 if diags.HasErrors() { 2426 t.Fatalf("diags: %s", diags.Err()) 2427 } 2428 2429 expected := []string{"b", "a"} 2430 if !reflect.DeepEqual(order, expected) { 2431 t.Errorf("wrong order\ngot: %#v\nwant: %#v", order, expected) 2432 } 2433 2434 { 2435 actual := strings.TrimSpace(state.String()) 2436 expected := strings.TrimSpace(testTerraformApplyModuleDestroyOrderStr) 2437 if actual != expected { 2438 t.Errorf("wrong final state\n\ngot:\n%s\n\nwant:\n%s", actual, expected) 2439 } 2440 } 2441 } 2442 2443 func TestContext2Apply_moduleInheritAlias(t *testing.T) { 2444 m := testModule(t, "apply-module-provider-inherit-alias") 2445 p := testProvider("aws") 2446 p.PlanResourceChangeFn = testDiffFn 2447 p.ApplyResourceChangeFn = testApplyFn 2448 2449 p.ConfigureProviderFn = func(req providers.ConfigureProviderRequest) (resp providers.ConfigureProviderResponse) { 2450 val := req.Config.GetAttr("value") 2451 if val.IsNull() { 2452 return 2453 } 2454 2455 root := req.Config.GetAttr("root") 2456 if !root.IsNull() { 2457 resp.Diagnostics = resp.Diagnostics.Append(fmt.Errorf("child should not get root")) 2458 } 2459 2460 return 2461 } 2462 2463 ctx := testContext2(t, &ContextOpts{ 2464 Providers: map[addrs.Provider]providers.Factory{ 2465 addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p), 2466 }, 2467 }) 2468 2469 plan, diags := ctx.Plan(m, states.NewState(), DefaultPlanOpts) 2470 assertNoErrors(t, diags) 2471 2472 state, diags := ctx.Apply(plan, m) 2473 if diags.HasErrors() { 2474 t.Fatalf("diags: %s", diags.Err()) 2475 } 2476 2477 checkStateString(t, state, ` 2478 <no state> 2479 module.child: 2480 aws_instance.foo: 2481 ID = foo 2482 provider = provider["registry.terraform.io/hashicorp/aws"].eu 2483 type = aws_instance 2484 `) 2485 } 2486 2487 func TestContext2Apply_orphanResource(t *testing.T) { 2488 // This is a two-step test: 2489 // 1. Apply a configuration with resources that have count set. 2490 // This should place the empty resource object in the state to record 2491 // that each exists, and record any instances. 2492 // 2. Apply an empty configuration against the same state, which should 2493 // then clean up both the instances and the containing resource objects. 2494 p := testProvider("test") 2495 p.PlanResourceChangeFn = testDiffFn 2496 p.ApplyResourceChangeFn = testApplyFn 2497 p.GetProviderSchemaResponse = getProviderSchemaResponseFromProviderSchema(&ProviderSchema{ 2498 ResourceTypes: map[string]*configschema.Block{ 2499 "test_thing": { 2500 Attributes: map[string]*configschema.Attribute{ 2501 "id": {Type: cty.String, Computed: true}, 2502 "foo": {Type: cty.String, Optional: true}, 2503 }, 2504 }, 2505 }, 2506 }) 2507 2508 // Step 1: create the resources and instances 2509 m := testModule(t, "apply-orphan-resource") 2510 ctx := testContext2(t, &ContextOpts{ 2511 Providers: map[addrs.Provider]providers.Factory{ 2512 addrs.NewDefaultProvider("test"): testProviderFuncFixed(p), 2513 }, 2514 }) 2515 plan, diags := ctx.Plan(m, states.NewState(), DefaultPlanOpts) 2516 assertNoErrors(t, diags) 2517 state, diags := ctx.Apply(plan, m) 2518 assertNoErrors(t, diags) 2519 2520 // At this point both resources should be recorded in the state, along 2521 // with the single instance associated with test_thing.one. 2522 want := states.BuildState(func(s *states.SyncState) { 2523 providerAddr := addrs.AbsProviderConfig{ 2524 Provider: addrs.NewDefaultProvider("test"), 2525 Module: addrs.RootModule, 2526 } 2527 oneAddr := addrs.Resource{ 2528 Mode: addrs.ManagedResourceMode, 2529 Type: "test_thing", 2530 Name: "one", 2531 }.Absolute(addrs.RootModuleInstance) 2532 s.SetResourceProvider(oneAddr, providerAddr) 2533 s.SetResourceInstanceCurrent(oneAddr.Instance(addrs.IntKey(0)), &states.ResourceInstanceObjectSrc{ 2534 Status: states.ObjectReady, 2535 AttrsJSON: []byte(`{"id":"foo"}`), 2536 }, providerAddr) 2537 }) 2538 2539 if state.String() != want.String() { 2540 t.Fatalf("wrong state after step 1\n%s", cmp.Diff(want, state)) 2541 } 2542 2543 // Step 2: update with an empty config, to destroy everything 2544 m = testModule(t, "empty") 2545 ctx = testContext2(t, &ContextOpts{ 2546 Providers: map[addrs.Provider]providers.Factory{ 2547 addrs.NewDefaultProvider("test"): testProviderFuncFixed(p), 2548 }, 2549 }) 2550 plan, diags = ctx.Plan(m, state, DefaultPlanOpts) 2551 assertNoErrors(t, diags) 2552 state, diags = ctx.Apply(plan, m) 2553 assertNoErrors(t, diags) 2554 2555 // The state should now be _totally_ empty, with just an empty root module 2556 // (since that always exists) and no resources at all. 2557 want = states.NewState() 2558 if !cmp.Equal(state, want) { 2559 t.Fatalf("wrong state after step 2\ngot: %swant: %s", spew.Sdump(state), spew.Sdump(want)) 2560 } 2561 2562 } 2563 2564 func TestContext2Apply_moduleOrphanInheritAlias(t *testing.T) { 2565 m := testModule(t, "apply-module-provider-inherit-alias-orphan") 2566 p := testProvider("aws") 2567 p.PlanResourceChangeFn = testDiffFn 2568 2569 p.ConfigureProviderFn = func(req providers.ConfigureProviderRequest) (resp providers.ConfigureProviderResponse) { 2570 val := req.Config.GetAttr("value") 2571 if val.IsNull() { 2572 return 2573 } 2574 2575 root := req.Config.GetAttr("root") 2576 if !root.IsNull() { 2577 resp.Diagnostics = resp.Diagnostics.Append(fmt.Errorf("child should not get root")) 2578 } 2579 2580 return 2581 } 2582 2583 // Create a state with an orphan module 2584 state := states.NewState() 2585 child := state.EnsureModule(addrs.RootModuleInstance.Child("child", addrs.NoKey)) 2586 child.SetResourceInstanceCurrent( 2587 mustResourceInstanceAddr("aws_instance.bar").Resource, 2588 &states.ResourceInstanceObjectSrc{ 2589 Status: states.ObjectReady, 2590 AttrsJSON: []byte(`{"id":"bar"}`), 2591 }, 2592 mustProviderConfig(`provider["registry.terraform.io/hashicorp/aws"]`), 2593 ) 2594 2595 ctx := testContext2(t, &ContextOpts{ 2596 Providers: map[addrs.Provider]providers.Factory{ 2597 addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p), 2598 }, 2599 }) 2600 2601 plan, diags := ctx.Plan(m, state, DefaultPlanOpts) 2602 assertNoErrors(t, diags) 2603 2604 state, diags = ctx.Apply(plan, m) 2605 if diags.HasErrors() { 2606 t.Fatalf("diags: %s", diags.Err()) 2607 } 2608 2609 if !p.ConfigureProviderCalled { 2610 t.Fatal("must call configure") 2611 } 2612 2613 checkStateString(t, state, "<no state>") 2614 } 2615 2616 func TestContext2Apply_moduleOrphanProvider(t *testing.T) { 2617 m := testModule(t, "apply-module-orphan-provider-inherit") 2618 p := testProvider("aws") 2619 p.PlanResourceChangeFn = testDiffFn 2620 2621 p.ConfigureProviderFn = func(req providers.ConfigureProviderRequest) (resp providers.ConfigureProviderResponse) { 2622 val := req.Config.GetAttr("value") 2623 if val.IsNull() { 2624 resp.Diagnostics = resp.Diagnostics.Append(fmt.Errorf("value is not found")) 2625 } 2626 2627 return 2628 } 2629 2630 // Create a state with an orphan module 2631 state := states.NewState() 2632 child := state.EnsureModule(addrs.RootModuleInstance.Child("child", addrs.NoKey)) 2633 child.SetResourceInstanceCurrent( 2634 mustResourceInstanceAddr("aws_instance.bar").Resource, 2635 &states.ResourceInstanceObjectSrc{ 2636 Status: states.ObjectReady, 2637 AttrsJSON: []byte(`{"id":"bar"}`), 2638 }, 2639 mustProviderConfig(`provider["registry.terraform.io/hashicorp/aws"]`), 2640 ) 2641 2642 ctx := testContext2(t, &ContextOpts{ 2643 Providers: map[addrs.Provider]providers.Factory{ 2644 addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p), 2645 }, 2646 }) 2647 2648 plan, diags := ctx.Plan(m, state, DefaultPlanOpts) 2649 assertNoErrors(t, diags) 2650 2651 if _, diags := ctx.Apply(plan, m); diags.HasErrors() { 2652 t.Fatalf("apply errors: %s", diags.Err()) 2653 } 2654 } 2655 2656 func TestContext2Apply_moduleOrphanGrandchildProvider(t *testing.T) { 2657 m := testModule(t, "apply-module-orphan-provider-inherit") 2658 p := testProvider("aws") 2659 p.PlanResourceChangeFn = testDiffFn 2660 2661 p.ConfigureProviderFn = func(req providers.ConfigureProviderRequest) (resp providers.ConfigureProviderResponse) { 2662 val := req.Config.GetAttr("value") 2663 if val.IsNull() { 2664 resp.Diagnostics = resp.Diagnostics.Append(fmt.Errorf("value is not found")) 2665 } 2666 2667 return 2668 } 2669 2670 // Create a state with an orphan module that is nested (grandchild) 2671 state := states.NewState() 2672 child := state.EnsureModule(addrs.RootModuleInstance.Child("parent", addrs.NoKey).Child("child", addrs.NoKey)) 2673 child.SetResourceInstanceCurrent( 2674 mustResourceInstanceAddr("aws_instance.bar").Resource, 2675 &states.ResourceInstanceObjectSrc{ 2676 Status: states.ObjectReady, 2677 AttrsJSON: []byte(`{"id":"bar"}`), 2678 }, 2679 mustProviderConfig(`provider["registry.terraform.io/hashicorp/aws"]`), 2680 ) 2681 2682 ctx := testContext2(t, &ContextOpts{ 2683 Providers: map[addrs.Provider]providers.Factory{ 2684 addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p), 2685 }, 2686 }) 2687 2688 plan, diags := ctx.Plan(m, state, DefaultPlanOpts) 2689 assertNoErrors(t, diags) 2690 2691 if _, diags := ctx.Apply(plan, m); diags.HasErrors() { 2692 t.Fatalf("apply errors: %s", diags.Err()) 2693 } 2694 } 2695 2696 func TestContext2Apply_moduleGrandchildProvider(t *testing.T) { 2697 m := testModule(t, "apply-module-grandchild-provider-inherit") 2698 p := testProvider("aws") 2699 p.PlanResourceChangeFn = testDiffFn 2700 2701 var callLock sync.Mutex 2702 called := false 2703 p.ConfigureProviderFn = func(req providers.ConfigureProviderRequest) (resp providers.ConfigureProviderResponse) { 2704 val := req.Config.GetAttr("value") 2705 if val.IsNull() { 2706 resp.Diagnostics = resp.Diagnostics.Append(fmt.Errorf("value is not found")) 2707 } 2708 2709 callLock.Lock() 2710 called = true 2711 callLock.Unlock() 2712 2713 return 2714 } 2715 2716 ctx := testContext2(t, &ContextOpts{ 2717 Providers: map[addrs.Provider]providers.Factory{ 2718 addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p), 2719 }, 2720 }) 2721 2722 plan, diags := ctx.Plan(m, states.NewState(), DefaultPlanOpts) 2723 assertNoErrors(t, diags) 2724 2725 if _, diags := ctx.Apply(plan, m); diags.HasErrors() { 2726 t.Fatalf("apply errors: %s", diags.Err()) 2727 } 2728 2729 callLock.Lock() 2730 defer callLock.Unlock() 2731 if called != true { 2732 t.Fatalf("err: configure never called") 2733 } 2734 } 2735 2736 // This tests an issue where all the providers in a module but not 2737 // in the root weren't being added to the root properly. In this test 2738 // case: aws is explicitly added to root, but "test" should be added to. 2739 // With the bug, it wasn't. 2740 func TestContext2Apply_moduleOnlyProvider(t *testing.T) { 2741 m := testModule(t, "apply-module-only-provider") 2742 p := testProvider("aws") 2743 p.PlanResourceChangeFn = testDiffFn 2744 p.ApplyResourceChangeFn = testApplyFn 2745 pTest := testProvider("test") 2746 pTest.ApplyResourceChangeFn = testApplyFn 2747 pTest.PlanResourceChangeFn = testDiffFn 2748 2749 ctx := testContext2(t, &ContextOpts{ 2750 Providers: map[addrs.Provider]providers.Factory{ 2751 addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p), 2752 addrs.NewDefaultProvider("test"): testProviderFuncFixed(pTest), 2753 }, 2754 }) 2755 2756 plan, diags := ctx.Plan(m, states.NewState(), DefaultPlanOpts) 2757 assertNoErrors(t, diags) 2758 2759 state, diags := ctx.Apply(plan, m) 2760 if diags.HasErrors() { 2761 t.Fatalf("diags: %s", diags.Err()) 2762 } 2763 2764 actual := strings.TrimSpace(state.String()) 2765 expected := strings.TrimSpace(testTerraformApplyModuleOnlyProviderStr) 2766 if actual != expected { 2767 t.Fatalf("wrong result\n\ngot:\n%s\n\nwant:\n%s", actual, expected) 2768 } 2769 } 2770 2771 func TestContext2Apply_moduleProviderAlias(t *testing.T) { 2772 m := testModule(t, "apply-module-provider-alias") 2773 p := testProvider("aws") 2774 p.PlanResourceChangeFn = testDiffFn 2775 p.ApplyResourceChangeFn = testApplyFn 2776 ctx := testContext2(t, &ContextOpts{ 2777 Providers: map[addrs.Provider]providers.Factory{ 2778 addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p), 2779 }, 2780 }) 2781 2782 plan, diags := ctx.Plan(m, states.NewState(), DefaultPlanOpts) 2783 assertNoErrors(t, diags) 2784 2785 state, diags := ctx.Apply(plan, m) 2786 if diags.HasErrors() { 2787 t.Fatalf("diags: %s", diags.Err()) 2788 } 2789 2790 actual := strings.TrimSpace(state.String()) 2791 expected := strings.TrimSpace(testTerraformApplyModuleProviderAliasStr) 2792 if actual != expected { 2793 t.Fatalf("wrong result\n\ngot:\n%s\n\nwant:\n%s", actual, expected) 2794 } 2795 } 2796 2797 func TestContext2Apply_moduleProviderAliasTargets(t *testing.T) { 2798 m := testModule(t, "apply-module-provider-alias") 2799 p := testProvider("aws") 2800 p.PlanResourceChangeFn = testDiffFn 2801 ctx := testContext2(t, &ContextOpts{ 2802 Providers: map[addrs.Provider]providers.Factory{ 2803 addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p), 2804 }, 2805 }) 2806 2807 plan, diags := ctx.Plan(m, states.NewState(), &PlanOpts{ 2808 Mode: plans.NormalMode, 2809 Targets: []addrs.Targetable{ 2810 addrs.ConfigResource{ 2811 Module: addrs.RootModule, 2812 Resource: addrs.Resource{ 2813 Mode: addrs.ManagedResourceMode, 2814 Type: "nonexistent", 2815 Name: "thing", 2816 }, 2817 }, 2818 }, 2819 }) 2820 assertNoErrors(t, diags) 2821 2822 state, diags := ctx.Apply(plan, m) 2823 if diags.HasErrors() { 2824 t.Fatalf("diags: %s", diags.Err()) 2825 } 2826 2827 actual := strings.TrimSpace(state.String()) 2828 expected := strings.TrimSpace(` 2829 <no state> 2830 `) 2831 if actual != expected { 2832 t.Fatalf("wrong result\n\ngot:\n%s\n\nwant:\n%s", actual, expected) 2833 } 2834 } 2835 2836 func TestContext2Apply_moduleProviderCloseNested(t *testing.T) { 2837 m := testModule(t, "apply-module-provider-close-nested") 2838 p := testProvider("aws") 2839 p.PlanResourceChangeFn = testDiffFn 2840 state := states.NewState() 2841 root := state.EnsureModule(addrs.RootModuleInstance) 2842 root.SetResourceInstanceCurrent( 2843 mustResourceInstanceAddr("aws_instance.foo").Resource, 2844 &states.ResourceInstanceObjectSrc{ 2845 Status: states.ObjectReady, 2846 AttrsJSON: []byte(`{"id":"bar"}`), 2847 }, 2848 mustProviderConfig(`provider["registry.terraform.io/hashicorp/aws"]`), 2849 ) 2850 2851 ctx := testContext2(t, &ContextOpts{ 2852 Providers: map[addrs.Provider]providers.Factory{ 2853 addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p), 2854 }, 2855 }) 2856 2857 plan, diags := ctx.Plan(m, state, &PlanOpts{ 2858 Mode: plans.DestroyMode, 2859 }) 2860 assertNoErrors(t, diags) 2861 2862 if _, diags := ctx.Apply(plan, m); diags.HasErrors() { 2863 t.Fatalf("apply errors: %s", diags.Err()) 2864 } 2865 } 2866 2867 // Tests that variables used as module vars that reference data that 2868 // already exists in the state and requires no diff works properly. This 2869 // fixes an issue faced where module variables were pruned because they were 2870 // accessing "non-existent" resources (they existed, just not in the graph 2871 // cause they weren't in the diff). 2872 func TestContext2Apply_moduleVarRefExisting(t *testing.T) { 2873 m := testModule(t, "apply-ref-existing") 2874 p := testProvider("aws") 2875 p.PlanResourceChangeFn = testDiffFn 2876 p.ApplyResourceChangeFn = testApplyFn 2877 state := states.NewState() 2878 root := state.EnsureModule(addrs.RootModuleInstance) 2879 root.SetResourceInstanceCurrent( 2880 mustResourceInstanceAddr("aws_instance.foo").Resource, 2881 &states.ResourceInstanceObjectSrc{ 2882 Status: states.ObjectReady, 2883 AttrsJSON: []byte(`{"id":"foo","foo":"bar"}`), 2884 }, 2885 mustProviderConfig(`provider["registry.terraform.io/hashicorp/aws"]`), 2886 ) 2887 2888 ctx := testContext2(t, &ContextOpts{ 2889 Providers: map[addrs.Provider]providers.Factory{ 2890 addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p), 2891 }, 2892 }) 2893 2894 plan, diags := ctx.Plan(m, state, DefaultPlanOpts) 2895 assertNoErrors(t, diags) 2896 2897 state, diags = ctx.Apply(plan, m) 2898 if diags.HasErrors() { 2899 t.Fatalf("diags: %s", diags.Err()) 2900 } 2901 2902 actual := strings.TrimSpace(state.String()) 2903 expected := strings.TrimSpace(testTerraformApplyModuleVarRefExistingStr) 2904 if actual != expected { 2905 t.Fatalf("wrong result\n\ngot:\n%s\n\nwant:\n%s", actual, expected) 2906 } 2907 } 2908 2909 func TestContext2Apply_moduleVarResourceCount(t *testing.T) { 2910 m := testModule(t, "apply-module-var-resource-count") 2911 p := testProvider("aws") 2912 p.PlanResourceChangeFn = testDiffFn 2913 ctx := testContext2(t, &ContextOpts{ 2914 Providers: map[addrs.Provider]providers.Factory{ 2915 addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p), 2916 }, 2917 }) 2918 2919 plan, diags := ctx.Plan(m, states.NewState(), &PlanOpts{ 2920 Mode: plans.DestroyMode, 2921 SetVariables: InputValues{ 2922 "num": &InputValue{ 2923 Value: cty.NumberIntVal(2), 2924 SourceType: ValueFromCaller, 2925 }, 2926 }, 2927 }) 2928 assertNoErrors(t, diags) 2929 2930 state, diags := ctx.Apply(plan, m) 2931 assertNoErrors(t, diags) 2932 2933 ctx = testContext2(t, &ContextOpts{ 2934 Providers: map[addrs.Provider]providers.Factory{ 2935 addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p), 2936 }, 2937 }) 2938 2939 plan, diags = ctx.Plan(m, state, &PlanOpts{ 2940 Mode: plans.NormalMode, 2941 SetVariables: InputValues{ 2942 "num": &InputValue{ 2943 Value: cty.NumberIntVal(5), 2944 SourceType: ValueFromCaller, 2945 }, 2946 }, 2947 }) 2948 assertNoErrors(t, diags) 2949 2950 if _, diags := ctx.Apply(plan, m); diags.HasErrors() { 2951 t.Fatalf("apply errors: %s", diags.Err()) 2952 } 2953 } 2954 2955 // GH-819 2956 func TestContext2Apply_moduleBool(t *testing.T) { 2957 m := testModule(t, "apply-module-bool") 2958 p := testProvider("aws") 2959 p.PlanResourceChangeFn = testDiffFn 2960 p.ApplyResourceChangeFn = testApplyFn 2961 ctx := testContext2(t, &ContextOpts{ 2962 Providers: map[addrs.Provider]providers.Factory{ 2963 addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p), 2964 }, 2965 }) 2966 2967 plan, diags := ctx.Plan(m, states.NewState(), DefaultPlanOpts) 2968 assertNoErrors(t, diags) 2969 2970 state, diags := ctx.Apply(plan, m) 2971 if diags.HasErrors() { 2972 t.Fatalf("diags: %s", diags.Err()) 2973 } 2974 2975 actual := strings.TrimSpace(state.String()) 2976 expected := strings.TrimSpace(testTerraformApplyModuleBoolStr) 2977 if actual != expected { 2978 t.Fatalf("wrong result\n\ngot:\n%s\n\nwant:\n%s", actual, expected) 2979 } 2980 } 2981 2982 // Tests that a module can be targeted and everything is properly created. 2983 // This adds to the plan test to also just verify that apply works. 2984 func TestContext2Apply_moduleTarget(t *testing.T) { 2985 m := testModule(t, "plan-targeted-cross-module") 2986 p := testProvider("aws") 2987 p.PlanResourceChangeFn = testDiffFn 2988 p.ApplyResourceChangeFn = testApplyFn 2989 ctx := testContext2(t, &ContextOpts{ 2990 Providers: map[addrs.Provider]providers.Factory{ 2991 addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p), 2992 }, 2993 }) 2994 2995 plan, diags := ctx.Plan(m, states.NewState(), &PlanOpts{ 2996 Mode: plans.NormalMode, 2997 Targets: []addrs.Targetable{ 2998 addrs.RootModuleInstance.Child("B", addrs.NoKey), 2999 }, 3000 }) 3001 assertNoErrors(t, diags) 3002 3003 state, diags := ctx.Apply(plan, m) 3004 if diags.HasErrors() { 3005 t.Fatalf("diags: %s", diags.Err()) 3006 } 3007 3008 checkStateString(t, state, ` 3009 <no state> 3010 module.A: 3011 aws_instance.foo: 3012 ID = foo 3013 provider = provider["registry.terraform.io/hashicorp/aws"] 3014 foo = bar 3015 type = aws_instance 3016 3017 Outputs: 3018 3019 value = foo 3020 module.B: 3021 aws_instance.bar: 3022 ID = foo 3023 provider = provider["registry.terraform.io/hashicorp/aws"] 3024 foo = foo 3025 type = aws_instance 3026 3027 Dependencies: 3028 module.A.aws_instance.foo 3029 `) 3030 } 3031 3032 func TestContext2Apply_multiProvider(t *testing.T) { 3033 m := testModule(t, "apply-multi-provider") 3034 p := testProvider("aws") 3035 p.PlanResourceChangeFn = testDiffFn 3036 p.ApplyResourceChangeFn = testApplyFn 3037 3038 pDO := testProvider("do") 3039 pDO.ApplyResourceChangeFn = testApplyFn 3040 pDO.PlanResourceChangeFn = testDiffFn 3041 3042 ctx := testContext2(t, &ContextOpts{ 3043 Providers: map[addrs.Provider]providers.Factory{ 3044 addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p), 3045 addrs.NewDefaultProvider("do"): testProviderFuncFixed(pDO), 3046 }, 3047 }) 3048 3049 plan, diags := ctx.Plan(m, states.NewState(), DefaultPlanOpts) 3050 assertNoErrors(t, diags) 3051 3052 state, diags := ctx.Apply(plan, m) 3053 if diags.HasErrors() { 3054 t.Fatalf("diags: %s", diags.Err()) 3055 } 3056 3057 mod := state.RootModule() 3058 if len(mod.Resources) < 2 { 3059 t.Fatalf("bad: %#v", mod.Resources) 3060 } 3061 3062 actual := strings.TrimSpace(state.String()) 3063 expected := strings.TrimSpace(testTerraformApplyMultiProviderStr) 3064 if actual != expected { 3065 t.Fatalf("wrong result\n\ngot:\n%s\n\nwant:\n%s", actual, expected) 3066 } 3067 } 3068 3069 func TestContext2Apply_multiProviderDestroy(t *testing.T) { 3070 m := testModule(t, "apply-multi-provider-destroy") 3071 p := testProvider("aws") 3072 p.PlanResourceChangeFn = testDiffFn 3073 p.GetProviderSchemaResponse = getProviderSchemaResponseFromProviderSchema(&ProviderSchema{ 3074 Provider: &configschema.Block{ 3075 Attributes: map[string]*configschema.Attribute{ 3076 "addr": {Type: cty.String, Optional: true}, 3077 }, 3078 }, 3079 ResourceTypes: map[string]*configschema.Block{ 3080 "aws_instance": { 3081 Attributes: map[string]*configschema.Attribute{ 3082 "id": {Type: cty.String, Computed: true}, 3083 "foo": {Type: cty.String, Optional: true}, 3084 }, 3085 }, 3086 }, 3087 }) 3088 3089 p2 := testProvider("vault") 3090 p2.ApplyResourceChangeFn = testApplyFn 3091 p2.PlanResourceChangeFn = testDiffFn 3092 p2.GetProviderSchemaResponse = getProviderSchemaResponseFromProviderSchema(&ProviderSchema{ 3093 ResourceTypes: map[string]*configschema.Block{ 3094 "vault_instance": { 3095 Attributes: map[string]*configschema.Attribute{ 3096 "id": {Type: cty.String, Computed: true}, 3097 }, 3098 }, 3099 }, 3100 }) 3101 3102 var state *states.State 3103 3104 // First, create the instances 3105 { 3106 ctx := testContext2(t, &ContextOpts{ 3107 Providers: map[addrs.Provider]providers.Factory{ 3108 addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p), 3109 addrs.NewDefaultProvider("vault"): testProviderFuncFixed(p2), 3110 }, 3111 }) 3112 3113 plan, diags := ctx.Plan(m, states.NewState(), DefaultPlanOpts) 3114 assertNoErrors(t, diags) 3115 3116 s, diags := ctx.Apply(plan, m) 3117 assertNoErrors(t, diags) 3118 3119 state = s 3120 } 3121 3122 // Destroy them 3123 { 3124 // Verify that aws_instance.bar is destroyed first 3125 var checked bool 3126 var called int32 3127 var lock sync.Mutex 3128 applyFn := func(req providers.ApplyResourceChangeRequest) (resp providers.ApplyResourceChangeResponse) { 3129 lock.Lock() 3130 defer lock.Unlock() 3131 3132 if req.TypeName == "aws_instance" { 3133 checked = true 3134 3135 // Sleep to allow parallel execution 3136 time.Sleep(50 * time.Millisecond) 3137 3138 // Verify that called is 0 (dep not called) 3139 if atomic.LoadInt32(&called) != 0 { 3140 resp.Diagnostics = resp.Diagnostics.Append(fmt.Errorf("nothing else should be called")) 3141 return resp 3142 } 3143 } 3144 3145 atomic.AddInt32(&called, 1) 3146 return testApplyFn(req) 3147 } 3148 3149 // Set the apply functions 3150 p.ApplyResourceChangeFn = applyFn 3151 p2.ApplyResourceChangeFn = applyFn 3152 3153 ctx := testContext2(t, &ContextOpts{ 3154 Providers: map[addrs.Provider]providers.Factory{ 3155 addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p), 3156 addrs.NewDefaultProvider("vault"): testProviderFuncFixed(p2), 3157 }, 3158 }) 3159 3160 plan, diags := ctx.Plan(m, state, &PlanOpts{ 3161 Mode: plans.DestroyMode, 3162 }) 3163 assertNoErrors(t, diags) 3164 3165 s, diags := ctx.Apply(plan, m) 3166 assertNoErrors(t, diags) 3167 3168 if !checked { 3169 t.Fatal("should be checked") 3170 } 3171 3172 state = s 3173 } 3174 3175 checkStateString(t, state, `<no state>`) 3176 } 3177 3178 // This is like the multiProviderDestroy test except it tests that 3179 // dependent resources within a child module that inherit provider 3180 // configuration are still destroyed first. 3181 func TestContext2Apply_multiProviderDestroyChild(t *testing.T) { 3182 m := testModule(t, "apply-multi-provider-destroy-child") 3183 p := testProvider("aws") 3184 p.PlanResourceChangeFn = testDiffFn 3185 p.GetProviderSchemaResponse = getProviderSchemaResponseFromProviderSchema(&ProviderSchema{ 3186 Provider: &configschema.Block{ 3187 Attributes: map[string]*configschema.Attribute{ 3188 "value": {Type: cty.String, Optional: true}, 3189 }, 3190 }, 3191 ResourceTypes: map[string]*configschema.Block{ 3192 "aws_instance": { 3193 Attributes: map[string]*configschema.Attribute{ 3194 "id": {Type: cty.String, Computed: true}, 3195 "foo": {Type: cty.String, Optional: true}, 3196 }, 3197 }, 3198 }, 3199 }) 3200 3201 p2 := testProvider("vault") 3202 p2.ApplyResourceChangeFn = testApplyFn 3203 p2.PlanResourceChangeFn = testDiffFn 3204 p2.GetProviderSchemaResponse = getProviderSchemaResponseFromProviderSchema(&ProviderSchema{ 3205 Provider: &configschema.Block{}, 3206 ResourceTypes: map[string]*configschema.Block{ 3207 "vault_instance": { 3208 Attributes: map[string]*configschema.Attribute{ 3209 "id": {Type: cty.String, Computed: true}, 3210 }, 3211 }, 3212 }, 3213 }) 3214 3215 var state *states.State 3216 3217 // First, create the instances 3218 { 3219 ctx := testContext2(t, &ContextOpts{ 3220 Providers: map[addrs.Provider]providers.Factory{ 3221 addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p), 3222 addrs.NewDefaultProvider("vault"): testProviderFuncFixed(p2), 3223 }, 3224 }) 3225 3226 plan, diags := ctx.Plan(m, states.NewState(), DefaultPlanOpts) 3227 assertNoErrors(t, diags) 3228 3229 s, diags := ctx.Apply(plan, m) 3230 if diags.HasErrors() { 3231 t.Fatalf("diags: %s", diags.Err()) 3232 } 3233 3234 state = s 3235 } 3236 3237 // Destroy them 3238 { 3239 // Verify that aws_instance.bar is destroyed first 3240 var checked bool 3241 var called int32 3242 var lock sync.Mutex 3243 applyFn := func(req providers.ApplyResourceChangeRequest) (resp providers.ApplyResourceChangeResponse) { 3244 lock.Lock() 3245 defer lock.Unlock() 3246 3247 if req.TypeName == "aws_instance" { 3248 checked = true 3249 3250 // Sleep to allow parallel execution 3251 time.Sleep(50 * time.Millisecond) 3252 3253 // Verify that called is 0 (dep not called) 3254 if atomic.LoadInt32(&called) != 0 { 3255 resp.Diagnostics = resp.Diagnostics.Append(fmt.Errorf("nothing else should be called")) 3256 return resp 3257 } 3258 } 3259 3260 atomic.AddInt32(&called, 1) 3261 return testApplyFn(req) 3262 } 3263 3264 // Set the apply functions 3265 p.ApplyResourceChangeFn = applyFn 3266 p2.ApplyResourceChangeFn = applyFn 3267 3268 ctx := testContext2(t, &ContextOpts{ 3269 Providers: map[addrs.Provider]providers.Factory{ 3270 addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p), 3271 addrs.NewDefaultProvider("vault"): testProviderFuncFixed(p2), 3272 }, 3273 }) 3274 3275 plan, diags := ctx.Plan(m, state, &PlanOpts{ 3276 Mode: plans.DestroyMode, 3277 }) 3278 assertNoErrors(t, diags) 3279 3280 s, diags := ctx.Apply(plan, m) 3281 if diags.HasErrors() { 3282 t.Fatalf("diags: %s", diags.Err()) 3283 } 3284 3285 if !checked { 3286 t.Fatal("should be checked") 3287 } 3288 3289 state = s 3290 } 3291 3292 checkStateString(t, state, ` 3293 <no state> 3294 `) 3295 } 3296 3297 func TestContext2Apply_multiVar(t *testing.T) { 3298 m := testModule(t, "apply-multi-var") 3299 p := testProvider("aws") 3300 p.PlanResourceChangeFn = testDiffFn 3301 3302 // First, apply with a count of 3 3303 ctx := testContext2(t, &ContextOpts{ 3304 Providers: map[addrs.Provider]providers.Factory{ 3305 addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p), 3306 }, 3307 }) 3308 3309 plan, diags := ctx.Plan(m, states.NewState(), &PlanOpts{ 3310 Mode: plans.NormalMode, 3311 SetVariables: InputValues{ 3312 "num": &InputValue{ 3313 Value: cty.NumberIntVal(3), 3314 SourceType: ValueFromCaller, 3315 }, 3316 }, 3317 }) 3318 assertNoErrors(t, diags) 3319 3320 state, diags := ctx.Apply(plan, m) 3321 if diags.HasErrors() { 3322 t.Fatalf("diags: %s", diags.Err()) 3323 } 3324 3325 actual := state.RootModule().OutputValues["output"] 3326 expected := cty.StringVal("bar0,bar1,bar2") 3327 if actual == nil || actual.Value != expected { 3328 t.Fatalf("wrong value\ngot: %#v\nwant: %#v", actual.Value, expected) 3329 } 3330 3331 t.Logf("Initial state: %s", state.String()) 3332 3333 // Apply again, reduce the count to 1 3334 { 3335 ctx := testContext2(t, &ContextOpts{ 3336 Providers: map[addrs.Provider]providers.Factory{ 3337 addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p), 3338 }, 3339 }) 3340 3341 plan, diags := ctx.Plan(m, state, &PlanOpts{ 3342 Mode: plans.NormalMode, 3343 SetVariables: InputValues{ 3344 "num": &InputValue{ 3345 Value: cty.NumberIntVal(1), 3346 SourceType: ValueFromCaller, 3347 }, 3348 }, 3349 }) 3350 assertNoErrors(t, diags) 3351 3352 state, diags := ctx.Apply(plan, m) 3353 if diags.HasErrors() { 3354 t.Fatalf("diags: %s", diags.Err()) 3355 } 3356 3357 t.Logf("End state: %s", state.String()) 3358 3359 actual := state.RootModule().OutputValues["output"] 3360 if actual == nil { 3361 t.Fatal("missing output") 3362 } 3363 3364 expected := cty.StringVal("bar0") 3365 if actual.Value != expected { 3366 t.Fatalf("wrong value\ngot: %#v\nwant: %#v", actual.Value, expected) 3367 } 3368 } 3369 } 3370 3371 // This is a holistic test of multi-var (aka "splat variable") handling 3372 // across several different Terraform subsystems. This is here because 3373 // historically there were quirky differences in handling across different 3374 // parts of Terraform and so here we want to assert the expected behavior and 3375 // ensure that it remains consistent in future. 3376 func TestContext2Apply_multiVarComprehensive(t *testing.T) { 3377 m := testModule(t, "apply-multi-var-comprehensive") 3378 p := testProvider("test") 3379 3380 configs := map[string]cty.Value{} 3381 var configsLock sync.Mutex 3382 3383 p.ApplyResourceChangeFn = testApplyFn 3384 p.PlanResourceChangeFn = func(req providers.PlanResourceChangeRequest) providers.PlanResourceChangeResponse { 3385 proposed := req.ProposedNewState 3386 configsLock.Lock() 3387 defer configsLock.Unlock() 3388 key := proposed.GetAttr("key").AsString() 3389 // This test was originally written using the legacy p.PlanResourceChangeFn interface, 3390 // and so the assertions below expect an old-style ResourceConfig, which 3391 // we'll construct via our shim for now to avoid rewriting all of the 3392 // assertions. 3393 configs[key] = req.ProposedNewState 3394 3395 retVals := make(map[string]cty.Value) 3396 for it := proposed.ElementIterator(); it.Next(); { 3397 idxVal, val := it.Element() 3398 idx := idxVal.AsString() 3399 3400 switch idx { 3401 case "id": 3402 retVals[idx] = cty.UnknownVal(cty.String) 3403 case "name": 3404 retVals[idx] = cty.StringVal(key) 3405 default: 3406 retVals[idx] = val 3407 } 3408 } 3409 3410 return providers.PlanResourceChangeResponse{ 3411 PlannedState: cty.ObjectVal(retVals), 3412 } 3413 } 3414 3415 p.GetProviderSchemaResponse = getProviderSchemaResponseFromProviderSchema(&ProviderSchema{ 3416 ResourceTypes: map[string]*configschema.Block{ 3417 "test_thing": { 3418 Attributes: map[string]*configschema.Attribute{ 3419 "key": {Type: cty.String, Required: true}, 3420 3421 "source_id": {Type: cty.String, Optional: true}, 3422 "source_name": {Type: cty.String, Optional: true}, 3423 "first_source_id": {Type: cty.String, Optional: true}, 3424 "first_source_name": {Type: cty.String, Optional: true}, 3425 "source_ids": {Type: cty.List(cty.String), Optional: true}, 3426 "source_names": {Type: cty.List(cty.String), Optional: true}, 3427 "source_ids_from_func": {Type: cty.List(cty.String), Optional: true}, 3428 "source_names_from_func": {Type: cty.List(cty.String), Optional: true}, 3429 "source_ids_wrapped": {Type: cty.List(cty.List(cty.String)), Optional: true}, 3430 "source_names_wrapped": {Type: cty.List(cty.List(cty.String)), Optional: true}, 3431 3432 "id": {Type: cty.String, Computed: true}, 3433 "name": {Type: cty.String, Computed: true}, 3434 }, 3435 }, 3436 }, 3437 }) 3438 3439 // First, apply with a count of 3 3440 ctx := testContext2(t, &ContextOpts{ 3441 Providers: map[addrs.Provider]providers.Factory{ 3442 addrs.NewDefaultProvider("test"): testProviderFuncFixed(p), 3443 }, 3444 }) 3445 3446 plan, diags := ctx.Plan(m, states.NewState(), &PlanOpts{ 3447 Mode: plans.NormalMode, 3448 SetVariables: InputValues{ 3449 "num": &InputValue{ 3450 Value: cty.NumberIntVal(3), 3451 SourceType: ValueFromCaller, 3452 }, 3453 }, 3454 }) 3455 assertNoErrors(t, diags) 3456 3457 checkConfig := func(key string, want cty.Value) { 3458 configsLock.Lock() 3459 defer configsLock.Unlock() 3460 3461 got, ok := configs[key] 3462 if !ok { 3463 t.Errorf("no config recorded for %s; expected a configuration", key) 3464 return 3465 } 3466 3467 t.Run("config for "+key, func(t *testing.T) { 3468 for _, problem := range deep.Equal(got, want) { 3469 t.Errorf(problem) 3470 } 3471 }) 3472 } 3473 3474 checkConfig("multi_count_var.0", cty.ObjectVal(map[string]cty.Value{ 3475 "source_id": cty.UnknownVal(cty.String), 3476 "source_name": cty.StringVal("source.0"), 3477 })) 3478 checkConfig("multi_count_var.2", cty.ObjectVal(map[string]cty.Value{ 3479 "source_id": cty.UnknownVal(cty.String), 3480 "source_name": cty.StringVal("source.2"), 3481 })) 3482 checkConfig("multi_count_derived.0", cty.ObjectVal(map[string]cty.Value{ 3483 "source_id": cty.UnknownVal(cty.String), 3484 "source_name": cty.StringVal("source.0"), 3485 })) 3486 checkConfig("multi_count_derived.2", cty.ObjectVal(map[string]cty.Value{ 3487 "source_id": cty.UnknownVal(cty.String), 3488 "source_name": cty.StringVal("source.2"), 3489 })) 3490 checkConfig("whole_splat", cty.ObjectVal(map[string]cty.Value{ 3491 "source_ids": cty.ListVal([]cty.Value{ 3492 cty.UnknownVal(cty.String), 3493 cty.UnknownVal(cty.String), 3494 cty.UnknownVal(cty.String), 3495 }), 3496 "source_names": cty.ListVal([]cty.Value{ 3497 cty.StringVal("source.0"), 3498 cty.StringVal("source.1"), 3499 cty.StringVal("source.2"), 3500 }), 3501 "source_ids_from_func": cty.UnknownVal(cty.String), 3502 "source_names_from_func": cty.ListVal([]cty.Value{ 3503 cty.StringVal("source.0"), 3504 cty.StringVal("source.1"), 3505 cty.StringVal("source.2"), 3506 }), 3507 "source_ids_wrapped": cty.ListVal([]cty.Value{ 3508 cty.ListVal([]cty.Value{ 3509 cty.UnknownVal(cty.String), 3510 cty.UnknownVal(cty.String), 3511 cty.UnknownVal(cty.String), 3512 }), 3513 }), 3514 "source_names_wrapped": cty.ListVal([]cty.Value{ 3515 cty.ListVal([]cty.Value{ 3516 cty.StringVal("source.0"), 3517 cty.StringVal("source.1"), 3518 cty.StringVal("source.2"), 3519 }), 3520 }), 3521 "first_source_id": cty.UnknownVal(cty.String), 3522 "first_source_name": cty.StringVal("source.0"), 3523 })) 3524 checkConfig("child.whole_splat", cty.ObjectVal(map[string]cty.Value{ 3525 "source_ids": cty.ListVal([]cty.Value{ 3526 cty.UnknownVal(cty.String), 3527 cty.UnknownVal(cty.String), 3528 cty.UnknownVal(cty.String), 3529 }), 3530 "source_names": cty.ListVal([]cty.Value{ 3531 cty.StringVal("source.0"), 3532 cty.StringVal("source.1"), 3533 cty.StringVal("source.2"), 3534 }), 3535 "source_ids_wrapped": cty.ListVal([]cty.Value{ 3536 cty.ListVal([]cty.Value{ 3537 cty.UnknownVal(cty.String), 3538 cty.UnknownVal(cty.String), 3539 cty.UnknownVal(cty.String), 3540 }), 3541 }), 3542 "source_names_wrapped": cty.ListVal([]cty.Value{ 3543 cty.ListVal([]cty.Value{ 3544 cty.StringVal("source.0"), 3545 cty.StringVal("source.1"), 3546 cty.StringVal("source.2"), 3547 }), 3548 }), 3549 })) 3550 3551 t.Run("apply", func(t *testing.T) { 3552 state, diags := ctx.Apply(plan, m) 3553 if diags.HasErrors() { 3554 t.Fatalf("error during apply: %s", diags.Err()) 3555 } 3556 3557 want := map[string]interface{}{ 3558 "source_ids": []interface{}{"foo", "foo", "foo"}, 3559 "source_names": []interface{}{ 3560 "source.0", 3561 "source.1", 3562 "source.2", 3563 }, 3564 } 3565 got := map[string]interface{}{} 3566 for k, s := range state.RootModule().OutputValues { 3567 got[k] = hcl2shim.ConfigValueFromHCL2(s.Value) 3568 } 3569 if !reflect.DeepEqual(got, want) { 3570 t.Errorf( 3571 "wrong outputs\ngot: %s\nwant: %s", 3572 spew.Sdump(got), spew.Sdump(want), 3573 ) 3574 } 3575 }) 3576 } 3577 3578 // Test that multi-var (splat) access is ordered by count, not by 3579 // value. 3580 func TestContext2Apply_multiVarOrder(t *testing.T) { 3581 m := testModule(t, "apply-multi-var-order") 3582 p := testProvider("aws") 3583 p.PlanResourceChangeFn = testDiffFn 3584 3585 // First, apply with a count of 3 3586 ctx := testContext2(t, &ContextOpts{ 3587 Providers: map[addrs.Provider]providers.Factory{ 3588 addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p), 3589 }, 3590 }) 3591 3592 plan, diags := ctx.Plan(m, states.NewState(), DefaultPlanOpts) 3593 assertNoErrors(t, diags) 3594 3595 state, diags := ctx.Apply(plan, m) 3596 if diags.HasErrors() { 3597 t.Fatalf("diags: %s", diags.Err()) 3598 } 3599 3600 t.Logf("State: %s", state.String()) 3601 3602 actual := state.RootModule().OutputValues["should-be-11"] 3603 expected := cty.StringVal("index-11") 3604 if actual == nil || actual.Value != expected { 3605 t.Fatalf("wrong value\ngot: %#v\nwant: %#v", actual.Value, expected) 3606 } 3607 } 3608 3609 // Test that multi-var (splat) access is ordered by count, not by 3610 // value, through interpolations. 3611 func TestContext2Apply_multiVarOrderInterp(t *testing.T) { 3612 m := testModule(t, "apply-multi-var-order-interp") 3613 p := testProvider("aws") 3614 p.PlanResourceChangeFn = testDiffFn 3615 3616 // First, apply with a count of 3 3617 ctx := testContext2(t, &ContextOpts{ 3618 Providers: map[addrs.Provider]providers.Factory{ 3619 addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p), 3620 }, 3621 }) 3622 3623 plan, diags := ctx.Plan(m, states.NewState(), DefaultPlanOpts) 3624 assertNoErrors(t, diags) 3625 3626 state, diags := ctx.Apply(plan, m) 3627 if diags.HasErrors() { 3628 t.Fatalf("diags: %s", diags.Err()) 3629 } 3630 3631 t.Logf("State: %s", state.String()) 3632 3633 actual := state.RootModule().OutputValues["should-be-11"] 3634 expected := cty.StringVal("baz-index-11") 3635 if actual == nil || actual.Value != expected { 3636 t.Fatalf("wrong value\ngot: %#v\nwant: %#v", actual.Value, expected) 3637 } 3638 } 3639 3640 // Based on GH-10440 where a graph edge wasn't properly being created 3641 // between a modified resource and a count instance being destroyed. 3642 func TestContext2Apply_multiVarCountDec(t *testing.T) { 3643 var s *states.State 3644 3645 // First create resources. Nothing sneaky here. 3646 { 3647 m := testModule(t, "apply-multi-var-count-dec") 3648 p := testProvider("aws") 3649 p.PlanResourceChangeFn = testDiffFn 3650 p.ApplyResourceChangeFn = testApplyFn 3651 ctx := testContext2(t, &ContextOpts{ 3652 Providers: map[addrs.Provider]providers.Factory{ 3653 addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p), 3654 }, 3655 }) 3656 3657 log.Print("\n========\nStep 1 Plan\n========") 3658 plan, diags := ctx.Plan(m, states.NewState(), &PlanOpts{ 3659 Mode: plans.NormalMode, 3660 SetVariables: InputValues{ 3661 "num": &InputValue{ 3662 Value: cty.NumberIntVal(2), 3663 SourceType: ValueFromCaller, 3664 }, 3665 }, 3666 }) 3667 assertNoErrors(t, diags) 3668 3669 log.Print("\n========\nStep 1 Apply\n========") 3670 state, diags := ctx.Apply(plan, m) 3671 if diags.HasErrors() { 3672 t.Fatalf("diags: %s", diags.Err()) 3673 } 3674 3675 t.Logf("Step 1 state:\n%s", state) 3676 3677 s = state 3678 } 3679 3680 // Decrease the count by 1 and verify that everything happens in the 3681 // right order. 3682 m := testModule(t, "apply-multi-var-count-dec") 3683 p := testProvider("aws") 3684 p.PlanResourceChangeFn = testDiffFn 3685 3686 // Verify that aws_instance.bar is modified first and nothing 3687 // else happens at the same time. 3688 { 3689 var checked bool 3690 var called int32 3691 var lock sync.Mutex 3692 p.ApplyResourceChangeFn = func(req providers.ApplyResourceChangeRequest) (resp providers.ApplyResourceChangeResponse) { 3693 lock.Lock() 3694 defer lock.Unlock() 3695 3696 if !req.PlannedState.IsNull() { 3697 s := req.PlannedState.AsValueMap() 3698 if ami, ok := s["ami"]; ok && !ami.IsNull() && ami.AsString() == "special" { 3699 checked = true 3700 3701 // Sleep to allow parallel execution 3702 time.Sleep(50 * time.Millisecond) 3703 3704 // Verify that called is 0 (dep not called) 3705 if atomic.LoadInt32(&called) != 1 { 3706 resp.Diagnostics = resp.Diagnostics.Append(fmt.Errorf("nothing else should be called")) 3707 return 3708 } 3709 } 3710 } 3711 atomic.AddInt32(&called, 1) 3712 return testApplyFn(req) 3713 } 3714 3715 ctx := testContext2(t, &ContextOpts{ 3716 Providers: map[addrs.Provider]providers.Factory{ 3717 addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p), 3718 }, 3719 }) 3720 3721 log.Print("\n========\nStep 2 Plan\n========") 3722 plan, diags := ctx.Plan(m, s, &PlanOpts{ 3723 Mode: plans.NormalMode, 3724 SetVariables: InputValues{ 3725 "num": &InputValue{ 3726 Value: cty.NumberIntVal(1), 3727 SourceType: ValueFromCaller, 3728 }, 3729 }, 3730 }) 3731 assertNoErrors(t, diags) 3732 3733 t.Logf("Step 2 plan:\n%s", legacyDiffComparisonString(plan.Changes)) 3734 3735 log.Print("\n========\nStep 2 Apply\n========") 3736 _, diags = ctx.Apply(plan, m) 3737 if diags.HasErrors() { 3738 t.Fatalf("apply errors: %s", diags.Err()) 3739 } 3740 3741 if !checked { 3742 t.Error("apply never called") 3743 } 3744 } 3745 } 3746 3747 // Test that we can resolve a multi-var (splat) for the first resource 3748 // created in a non-root module, which happens when the module state doesn't 3749 // exist yet. 3750 // https://github.com/hashicorp/terraform/issues/14438 3751 func TestContext2Apply_multiVarMissingState(t *testing.T) { 3752 m := testModule(t, "apply-multi-var-missing-state") 3753 p := testProvider("test") 3754 p.PlanResourceChangeFn = testDiffFn 3755 p.GetProviderSchemaResponse = getProviderSchemaResponseFromProviderSchema(&ProviderSchema{ 3756 ResourceTypes: map[string]*configschema.Block{ 3757 "test_thing": { 3758 Attributes: map[string]*configschema.Attribute{ 3759 "a_ids": {Type: cty.String, Optional: true}, 3760 "id": {Type: cty.String, Computed: true}, 3761 }, 3762 }, 3763 }, 3764 }) 3765 3766 // First, apply with a count of 3 3767 ctx := testContext2(t, &ContextOpts{ 3768 Providers: map[addrs.Provider]providers.Factory{ 3769 addrs.NewDefaultProvider("test"): testProviderFuncFixed(p), 3770 }, 3771 }) 3772 3773 plan, diags := ctx.Plan(m, states.NewState(), DefaultPlanOpts) 3774 assertNoErrors(t, diags) 3775 3776 // Before the relevant bug was fixed, Terraform would panic during apply. 3777 if _, diags := ctx.Apply(plan, m); diags.HasErrors() { 3778 t.Fatalf("apply failed: %s", diags.Err()) 3779 } 3780 3781 // If we get here with no errors or panics then our test was successful. 3782 } 3783 3784 func TestContext2Apply_outputOrphan(t *testing.T) { 3785 m := testModule(t, "apply-output-orphan") 3786 p := testProvider("aws") 3787 p.PlanResourceChangeFn = testDiffFn 3788 3789 state := states.NewState() 3790 root := state.EnsureModule(addrs.RootModuleInstance) 3791 root.SetOutputValue("foo", cty.StringVal("bar"), false) 3792 root.SetOutputValue("bar", cty.StringVal("baz"), false) 3793 3794 ctx := testContext2(t, &ContextOpts{ 3795 Providers: map[addrs.Provider]providers.Factory{ 3796 addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p), 3797 }, 3798 }) 3799 3800 plan, diags := ctx.Plan(m, state, DefaultPlanOpts) 3801 assertNoErrors(t, diags) 3802 3803 state, diags = ctx.Apply(plan, m) 3804 if diags.HasErrors() { 3805 t.Fatalf("diags: %s", diags.Err()) 3806 } 3807 3808 actual := strings.TrimSpace(state.String()) 3809 expected := strings.TrimSpace(testTerraformApplyOutputOrphanStr) 3810 if actual != expected { 3811 t.Fatalf("wrong result\n\ngot:\n%s\n\nwant:\n%s", actual, expected) 3812 } 3813 } 3814 3815 func TestContext2Apply_outputOrphanModule(t *testing.T) { 3816 m := testModule(t, "apply-output-orphan-module") 3817 p := testProvider("aws") 3818 p.PlanResourceChangeFn = testDiffFn 3819 3820 state := states.NewState() 3821 3822 ctx := testContext2(t, &ContextOpts{ 3823 Providers: map[addrs.Provider]providers.Factory{ 3824 addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p), 3825 }, 3826 }) 3827 3828 plan, diags := ctx.Plan(m, state, DefaultPlanOpts) 3829 assertNoErrors(t, diags) 3830 3831 s, diags := ctx.Apply(plan, m) 3832 if diags.HasErrors() { 3833 t.Fatalf("diags: %s", diags.Err()) 3834 } 3835 3836 actual := strings.TrimSpace(s.String()) 3837 expected := strings.TrimSpace(testTerraformApplyOutputOrphanModuleStr) 3838 if actual != expected { 3839 t.Fatalf("expected:\n%s\n\ngot:\n%s", expected, actual) 3840 } 3841 3842 // now apply with no module in the config, which should remove the 3843 // remaining output 3844 ctx = testContext2(t, &ContextOpts{ 3845 Providers: map[addrs.Provider]providers.Factory{ 3846 addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p), 3847 }, 3848 }) 3849 3850 emptyConfig := configs.NewEmptyConfig() 3851 3852 // NOTE: While updating this test to pass the state in as a Plan argument, 3853 // rather than into the testContext2 call above, it previously said 3854 // State: state.DeepCopy(), which is a little weird since we just 3855 // created "s" above as the result of the previous apply, but I've preserved 3856 // it to avoid changing the flow of this test in case that's important 3857 // for some reason. 3858 plan, diags = ctx.Plan(emptyConfig, state.DeepCopy(), DefaultPlanOpts) 3859 assertNoErrors(t, diags) 3860 3861 state, diags = ctx.Apply(plan, emptyConfig) 3862 if diags.HasErrors() { 3863 t.Fatalf("diags: %s", diags.Err()) 3864 } 3865 3866 if !state.Empty() { 3867 t.Fatalf("wrong final state %s\nwant empty state", spew.Sdump(state)) 3868 } 3869 } 3870 3871 func TestContext2Apply_providerComputedVar(t *testing.T) { 3872 m := testModule(t, "apply-provider-computed") 3873 p := testProvider("aws") 3874 p.PlanResourceChangeFn = testDiffFn 3875 3876 pTest := testProvider("test") 3877 pTest.ApplyResourceChangeFn = testApplyFn 3878 pTest.PlanResourceChangeFn = testDiffFn 3879 3880 ctx := testContext2(t, &ContextOpts{ 3881 Providers: map[addrs.Provider]providers.Factory{ 3882 addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p), 3883 addrs.NewDefaultProvider("test"): testProviderFuncFixed(pTest), 3884 }, 3885 }) 3886 3887 p.ConfigureProviderFn = func(req providers.ConfigureProviderRequest) (resp providers.ConfigureProviderResponse) { 3888 val := req.Config.GetAttr("value") 3889 if val.IsNull() { 3890 resp.Diagnostics = resp.Diagnostics.Append(fmt.Errorf("value is not found")) 3891 return 3892 } 3893 return 3894 } 3895 3896 plan, diags := ctx.Plan(m, states.NewState(), DefaultPlanOpts) 3897 assertNoErrors(t, diags) 3898 3899 if _, diags := ctx.Apply(plan, m); diags.HasErrors() { 3900 t.Fatalf("apply errors: %s", diags.Err()) 3901 } 3902 } 3903 3904 func TestContext2Apply_providerConfigureDisabled(t *testing.T) { 3905 m := testModule(t, "apply-provider-configure-disabled") 3906 p := testProvider("aws") 3907 p.PlanResourceChangeFn = testDiffFn 3908 3909 p.ConfigureProviderFn = func(req providers.ConfigureProviderRequest) (resp providers.ConfigureProviderResponse) { 3910 val := req.Config.GetAttr("value") 3911 if val.IsNull() { 3912 resp.Diagnostics = resp.Diagnostics.Append(fmt.Errorf("value is not found")) 3913 } 3914 3915 return 3916 } 3917 3918 ctx := testContext2(t, &ContextOpts{ 3919 Providers: map[addrs.Provider]providers.Factory{ 3920 addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p), 3921 }, 3922 }) 3923 3924 plan, diags := ctx.Plan(m, states.NewState(), DefaultPlanOpts) 3925 assertNoErrors(t, diags) 3926 3927 if _, diags := ctx.Apply(plan, m); diags.HasErrors() { 3928 t.Fatalf("apply errors: %s", diags.Err()) 3929 } 3930 3931 if !p.ConfigureProviderCalled { 3932 t.Fatal("configure never called") 3933 } 3934 } 3935 3936 func TestContext2Apply_provisionerModule(t *testing.T) { 3937 m := testModule(t, "apply-provisioner-module") 3938 3939 p := testProvider("aws") 3940 p.PlanResourceChangeFn = testDiffFn 3941 p.ApplyResourceChangeFn = testApplyFn 3942 3943 pr := testProvisioner() 3944 pr.GetSchemaResponse = provisioners.GetSchemaResponse{ 3945 Provisioner: &configschema.Block{ 3946 Attributes: map[string]*configschema.Attribute{ 3947 "foo": {Type: cty.String, Optional: true}, 3948 }, 3949 }, 3950 } 3951 3952 ctx := testContext2(t, &ContextOpts{ 3953 Providers: map[addrs.Provider]providers.Factory{ 3954 addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p), 3955 }, 3956 Provisioners: map[string]provisioners.Factory{ 3957 "shell": testProvisionerFuncFixed(pr), 3958 }, 3959 }) 3960 3961 plan, diags := ctx.Plan(m, states.NewState(), DefaultPlanOpts) 3962 assertNoErrors(t, diags) 3963 3964 state, diags := ctx.Apply(plan, m) 3965 if diags.HasErrors() { 3966 t.Fatalf("diags: %s", diags.Err()) 3967 } 3968 3969 actual := strings.TrimSpace(state.String()) 3970 expected := strings.TrimSpace(testTerraformApplyProvisionerModuleStr) 3971 if actual != expected { 3972 t.Fatalf("wrong result\n\ngot:\n%s\n\nwant:\n%s", actual, expected) 3973 } 3974 3975 // Verify apply was invoked 3976 if !pr.ProvisionResourceCalled { 3977 t.Fatalf("provisioner not invoked") 3978 } 3979 } 3980 3981 func TestContext2Apply_Provisioner_compute(t *testing.T) { 3982 m := testModule(t, "apply-provisioner-compute") 3983 p := testProvider("aws") 3984 pr := testProvisioner() 3985 p.PlanResourceChangeFn = testDiffFn 3986 p.ApplyResourceChangeFn = testApplyFn 3987 pr.ProvisionResourceFn = func(req provisioners.ProvisionResourceRequest) (resp provisioners.ProvisionResourceResponse) { 3988 3989 val := req.Config.GetAttr("command").AsString() 3990 if val != "computed_value" { 3991 t.Fatalf("bad value for foo: %q", val) 3992 } 3993 req.UIOutput.Output(fmt.Sprintf("Executing: %q", val)) 3994 3995 return 3996 } 3997 h := new(MockHook) 3998 ctx := testContext2(t, &ContextOpts{ 3999 Hooks: []Hook{h}, 4000 Providers: map[addrs.Provider]providers.Factory{ 4001 addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p), 4002 }, 4003 Provisioners: map[string]provisioners.Factory{ 4004 "shell": testProvisionerFuncFixed(pr), 4005 }, 4006 }) 4007 4008 plan, diags := ctx.Plan(m, states.NewState(), &PlanOpts{ 4009 Mode: plans.NormalMode, 4010 SetVariables: InputValues{ 4011 "value": &InputValue{ 4012 Value: cty.NumberIntVal(1), 4013 SourceType: ValueFromCaller, 4014 }, 4015 }, 4016 }) 4017 assertNoErrors(t, diags) 4018 4019 state, diags := ctx.Apply(plan, m) 4020 if diags.HasErrors() { 4021 t.Fatalf("diags: %s", diags.Err()) 4022 } 4023 4024 actual := strings.TrimSpace(state.String()) 4025 expected := strings.TrimSpace(testTerraformApplyProvisionerStr) 4026 if actual != expected { 4027 t.Fatalf("wrong result\n\ngot:\n%s\n\nwant:\n%s", actual, expected) 4028 } 4029 4030 // Verify apply was invoked 4031 if !pr.ProvisionResourceCalled { 4032 t.Fatalf("provisioner not invoked") 4033 } 4034 4035 // Verify output was rendered 4036 if !h.ProvisionOutputCalled { 4037 t.Fatalf("ProvisionOutput hook not called") 4038 } 4039 if got, want := h.ProvisionOutputMessage, `Executing: "computed_value"`; got != want { 4040 t.Errorf("expected output to be %q, but was %q", want, got) 4041 } 4042 } 4043 4044 func TestContext2Apply_provisionerCreateFail(t *testing.T) { 4045 m := testModule(t, "apply-provisioner-fail-create") 4046 p := testProvider("aws") 4047 pr := testProvisioner() 4048 p.PlanResourceChangeFn = testDiffFn 4049 4050 p.ApplyResourceChangeFn = func(req providers.ApplyResourceChangeRequest) providers.ApplyResourceChangeResponse { 4051 resp := testApplyFn(req) 4052 resp.Diagnostics = resp.Diagnostics.Append(fmt.Errorf("error")) 4053 4054 return resp 4055 } 4056 4057 ctx := testContext2(t, &ContextOpts{ 4058 Providers: map[addrs.Provider]providers.Factory{ 4059 addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p), 4060 }, 4061 Provisioners: map[string]provisioners.Factory{ 4062 "shell": testProvisionerFuncFixed(pr), 4063 }, 4064 }) 4065 4066 plan, diags := ctx.Plan(m, states.NewState(), DefaultPlanOpts) 4067 assertNoErrors(t, diags) 4068 4069 state, diags := ctx.Apply(plan, m) 4070 if diags == nil { 4071 t.Fatal("should error") 4072 } 4073 4074 got := strings.TrimSpace(state.String()) 4075 want := strings.TrimSpace(testTerraformApplyProvisionerFailCreateStr) 4076 if got != want { 4077 t.Fatalf("wrong result\n\ngot:\n%s\n\nwant:\n%s", got, want) 4078 } 4079 } 4080 4081 func TestContext2Apply_provisionerCreateFailNoId(t *testing.T) { 4082 m := testModule(t, "apply-provisioner-fail-create") 4083 p := testProvider("aws") 4084 pr := testProvisioner() 4085 p.PlanResourceChangeFn = testDiffFn 4086 4087 p.ApplyResourceChangeFn = func(req providers.ApplyResourceChangeRequest) (resp providers.ApplyResourceChangeResponse) { 4088 resp.Diagnostics = resp.Diagnostics.Append(fmt.Errorf("error")) 4089 return 4090 } 4091 4092 ctx := testContext2(t, &ContextOpts{ 4093 Providers: map[addrs.Provider]providers.Factory{ 4094 addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p), 4095 }, 4096 Provisioners: map[string]provisioners.Factory{ 4097 "shell": testProvisionerFuncFixed(pr), 4098 }, 4099 }) 4100 4101 plan, diags := ctx.Plan(m, states.NewState(), DefaultPlanOpts) 4102 assertNoErrors(t, diags) 4103 4104 state, diags := ctx.Apply(plan, m) 4105 if diags == nil { 4106 t.Fatal("should error") 4107 } 4108 4109 actual := strings.TrimSpace(state.String()) 4110 expected := strings.TrimSpace(testTerraformApplyProvisionerFailCreateNoIdStr) 4111 if actual != expected { 4112 t.Fatalf("wrong result\n\ngot:\n%s\n\nwant:\n%s", actual, expected) 4113 } 4114 } 4115 4116 func TestContext2Apply_provisionerFail(t *testing.T) { 4117 m := testModule(t, "apply-provisioner-fail") 4118 p := testProvider("aws") 4119 p.PlanResourceChangeFn = testDiffFn 4120 p.ApplyResourceChangeFn = testApplyFn 4121 pr := testProvisioner() 4122 pr.ProvisionResourceFn = func(req provisioners.ProvisionResourceRequest) (resp provisioners.ProvisionResourceResponse) { 4123 resp.Diagnostics = resp.Diagnostics.Append(fmt.Errorf("EXPLOSION")) 4124 return 4125 } 4126 4127 ctx := testContext2(t, &ContextOpts{ 4128 Providers: map[addrs.Provider]providers.Factory{ 4129 addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p), 4130 }, 4131 Provisioners: map[string]provisioners.Factory{ 4132 "shell": testProvisionerFuncFixed(pr), 4133 }, 4134 }) 4135 4136 plan, diags := ctx.Plan(m, states.NewState(), DefaultPlanOpts) 4137 assertNoErrors(t, diags) 4138 4139 state, diags := ctx.Apply(plan, m) 4140 if diags == nil { 4141 t.Fatal("should error") 4142 } 4143 4144 actual := strings.TrimSpace(state.String()) 4145 expected := strings.TrimSpace(testTerraformApplyProvisionerFailStr) 4146 if actual != expected { 4147 t.Fatalf("wrong result\n\ngot:\n%s\n\nwant:\n%s", actual, expected) 4148 } 4149 } 4150 4151 func TestContext2Apply_provisionerFail_createBeforeDestroy(t *testing.T) { 4152 m := testModule(t, "apply-provisioner-fail-create-before") 4153 p := testProvider("aws") 4154 pr := testProvisioner() 4155 p.PlanResourceChangeFn = testDiffFn 4156 p.ApplyResourceChangeFn = testApplyFn 4157 pr.ProvisionResourceFn = func(req provisioners.ProvisionResourceRequest) (resp provisioners.ProvisionResourceResponse) { 4158 resp.Diagnostics = resp.Diagnostics.Append(fmt.Errorf("EXPLOSION")) 4159 return 4160 } 4161 4162 state := states.NewState() 4163 root := state.EnsureModule(addrs.RootModuleInstance) 4164 root.SetResourceInstanceCurrent( 4165 mustResourceInstanceAddr("aws_instance.bar").Resource, 4166 &states.ResourceInstanceObjectSrc{ 4167 Status: states.ObjectReady, 4168 AttrsJSON: []byte(`{"id":"bar","require_new":"abc"}`), 4169 }, 4170 mustProviderConfig(`provider["registry.terraform.io/hashicorp/aws"]`), 4171 ) 4172 4173 ctx := testContext2(t, &ContextOpts{ 4174 Providers: map[addrs.Provider]providers.Factory{ 4175 addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p), 4176 }, 4177 Provisioners: map[string]provisioners.Factory{ 4178 "shell": testProvisionerFuncFixed(pr), 4179 }, 4180 }) 4181 4182 plan, diags := ctx.Plan(m, state, DefaultPlanOpts) 4183 assertNoErrors(t, diags) 4184 4185 state, diags = ctx.Apply(plan, m) 4186 if !diags.HasErrors() { 4187 t.Fatal("should error") 4188 } 4189 4190 actual := strings.TrimSpace(state.String()) 4191 expected := strings.TrimSpace(testTerraformApplyProvisionerFailCreateBeforeDestroyStr) 4192 if actual != expected { 4193 t.Fatalf("expected:\n%s\n:got\n%s", expected, actual) 4194 } 4195 } 4196 4197 func TestContext2Apply_error_createBeforeDestroy(t *testing.T) { 4198 m := testModule(t, "apply-error-create-before") 4199 p := testProvider("aws") 4200 4201 state := states.NewState() 4202 root := state.EnsureModule(addrs.RootModuleInstance) 4203 root.SetResourceInstanceCurrent( 4204 mustResourceInstanceAddr("aws_instance.bar").Resource, 4205 &states.ResourceInstanceObjectSrc{ 4206 Status: states.ObjectReady, 4207 AttrsJSON: []byte(`{"id":"bar", "require_new": "abc","type":"aws_instance"}`), 4208 }, 4209 mustProviderConfig(`provider["registry.terraform.io/hashicorp/aws"]`), 4210 ) 4211 4212 ctx := testContext2(t, &ContextOpts{ 4213 Providers: map[addrs.Provider]providers.Factory{ 4214 addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p), 4215 }, 4216 }) 4217 p.ApplyResourceChangeFn = func(req providers.ApplyResourceChangeRequest) (resp providers.ApplyResourceChangeResponse) { 4218 resp.Diagnostics = resp.Diagnostics.Append(fmt.Errorf("placeholder error from ApplyFn")) 4219 return 4220 } 4221 p.PlanResourceChangeFn = testDiffFn 4222 4223 plan, diags := ctx.Plan(m, state, DefaultPlanOpts) 4224 assertNoErrors(t, diags) 4225 4226 state, diags = ctx.Apply(plan, m) 4227 if !diags.HasErrors() { 4228 t.Fatal("should have error") 4229 } 4230 if got, want := diags.Err().Error(), "placeholder error from ApplyFn"; got != want { 4231 // We're looking for our artificial error from ApplyFn above, whose 4232 // message is literally "placeholder error from ApplyFn". 4233 t.Fatalf("wrong error\ngot: %s\nwant: %s", got, want) 4234 } 4235 4236 actual := strings.TrimSpace(state.String()) 4237 expected := strings.TrimSpace(testTerraformApplyErrorCreateBeforeDestroyStr) 4238 if actual != expected { 4239 t.Fatalf("wrong final state\ngot:\n%s\n\nwant:\n%s", actual, expected) 4240 } 4241 } 4242 4243 func TestContext2Apply_errorDestroy_createBeforeDestroy(t *testing.T) { 4244 m := testModule(t, "apply-error-create-before") 4245 p := testProvider("aws") 4246 4247 state := states.NewState() 4248 root := state.EnsureModule(addrs.RootModuleInstance) 4249 root.SetResourceInstanceCurrent( 4250 mustResourceInstanceAddr("aws_instance.bar").Resource, 4251 &states.ResourceInstanceObjectSrc{ 4252 Status: states.ObjectReady, 4253 AttrsJSON: []byte(`{"id":"bar", "require_new": "abc"}`), 4254 }, 4255 mustProviderConfig(`provider["registry.terraform.io/hashicorp/aws"]`), 4256 ) 4257 4258 ctx := testContext2(t, &ContextOpts{ 4259 Providers: map[addrs.Provider]providers.Factory{ 4260 addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p), 4261 }, 4262 }) 4263 p.ApplyResourceChangeFn = func(req providers.ApplyResourceChangeRequest) (resp providers.ApplyResourceChangeResponse) { 4264 // Fail the destroy! 4265 if req.PlannedState.IsNull() { 4266 resp.NewState = req.PriorState 4267 resp.Diagnostics = resp.Diagnostics.Append(fmt.Errorf("error")) 4268 return 4269 } 4270 4271 return testApplyFn(req) 4272 } 4273 p.PlanResourceChangeFn = testDiffFn 4274 4275 plan, diags := ctx.Plan(m, state, DefaultPlanOpts) 4276 assertNoErrors(t, diags) 4277 4278 state, diags = ctx.Apply(plan, m) 4279 if !diags.HasErrors() { 4280 t.Fatal("should have error") 4281 } 4282 4283 actual := strings.TrimSpace(state.String()) 4284 expected := strings.TrimSpace(testTerraformApplyErrorDestroyCreateBeforeDestroyStr) 4285 if actual != expected { 4286 t.Fatalf("bad: actual:\n%s\n\nexpected:\n%s", actual, expected) 4287 } 4288 } 4289 4290 func TestContext2Apply_multiDepose_createBeforeDestroy(t *testing.T) { 4291 m := testModule(t, "apply-multi-depose-create-before-destroy") 4292 p := testProvider("aws") 4293 ps := map[addrs.Provider]providers.Factory{addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p)} 4294 p.GetProviderSchemaResponse = getProviderSchemaResponseFromProviderSchema(&ProviderSchema{ 4295 ResourceTypes: map[string]*configschema.Block{ 4296 "aws_instance": { 4297 Attributes: map[string]*configschema.Attribute{ 4298 "require_new": {Type: cty.String, Optional: true}, 4299 "id": {Type: cty.String, Computed: true}, 4300 }, 4301 }, 4302 }, 4303 }) 4304 4305 state := states.NewState() 4306 root := state.EnsureModule(addrs.RootModuleInstance) 4307 root.SetResourceInstanceCurrent( 4308 mustResourceInstanceAddr("aws_instance.web").Resource, 4309 &states.ResourceInstanceObjectSrc{ 4310 Status: states.ObjectReady, 4311 AttrsJSON: []byte(`{"id":"foo"}`), 4312 }, 4313 mustProviderConfig(`provider["registry.terraform.io/hashicorp/aws"]`), 4314 ) 4315 4316 p.PlanResourceChangeFn = testDiffFn 4317 4318 ctx := testContext2(t, &ContextOpts{ 4319 Providers: ps, 4320 }) 4321 createdInstanceId := "bar" 4322 // Create works 4323 createFunc := func(req providers.ApplyResourceChangeRequest) (resp providers.ApplyResourceChangeResponse) { 4324 s := req.PlannedState.AsValueMap() 4325 s["id"] = cty.StringVal(createdInstanceId) 4326 resp.NewState = cty.ObjectVal(s) 4327 return 4328 } 4329 4330 // Destroy starts broken 4331 destroyFunc := func(req providers.ApplyResourceChangeRequest) (resp providers.ApplyResourceChangeResponse) { 4332 resp.NewState = req.PriorState 4333 resp.Diagnostics = resp.Diagnostics.Append(fmt.Errorf("destroy failed")) 4334 return 4335 } 4336 p.ApplyResourceChangeFn = func(req providers.ApplyResourceChangeRequest) (resp providers.ApplyResourceChangeResponse) { 4337 if req.PlannedState.IsNull() { 4338 return destroyFunc(req) 4339 } else { 4340 return createFunc(req) 4341 } 4342 } 4343 4344 plan, diags := ctx.Plan(m, state, &PlanOpts{ 4345 Mode: plans.NormalMode, 4346 SetVariables: InputValues{ 4347 "require_new": &InputValue{ 4348 Value: cty.StringVal("yes"), 4349 }, 4350 }, 4351 }) 4352 assertNoErrors(t, diags) 4353 4354 // Destroy is broken, so even though CBD successfully replaces the instance, 4355 // we'll have to save the Deposed instance to destroy later 4356 state, diags = ctx.Apply(plan, m) 4357 if !diags.HasErrors() { 4358 t.Fatal("should have error") 4359 } 4360 4361 checkStateString(t, state, ` 4362 aws_instance.web: (1 deposed) 4363 ID = bar 4364 provider = provider["registry.terraform.io/hashicorp/aws"] 4365 require_new = yes 4366 Deposed ID 1 = foo 4367 `) 4368 4369 createdInstanceId = "baz" 4370 ctx = testContext2(t, &ContextOpts{ 4371 Providers: ps, 4372 }) 4373 4374 plan, diags = ctx.Plan(m, state, &PlanOpts{ 4375 Mode: plans.NormalMode, 4376 SetVariables: InputValues{ 4377 "require_new": &InputValue{ 4378 Value: cty.StringVal("baz"), 4379 }, 4380 }, 4381 }) 4382 assertNoErrors(t, diags) 4383 4384 // We're replacing the primary instance once again. Destroy is _still_ 4385 // broken, so the Deposed list gets longer 4386 state, diags = ctx.Apply(plan, m) 4387 if !diags.HasErrors() { 4388 t.Fatal("should have error") 4389 } 4390 4391 // For this one we can't rely on checkStateString because its result is 4392 // not deterministic when multiple deposed objects are present. Instead, 4393 // we will probe the state object directly. 4394 { 4395 is := state.RootModule().Resources["aws_instance.web"].Instances[addrs.NoKey] 4396 if is.Current == nil { 4397 t.Fatalf("no current object for aws_instance web; should have one") 4398 } 4399 if !bytes.Contains(is.Current.AttrsJSON, []byte("baz")) { 4400 t.Fatalf("incorrect current object attrs %s; want id=baz", is.Current.AttrsJSON) 4401 } 4402 if got, want := len(is.Deposed), 2; got != want { 4403 t.Fatalf("wrong number of deposed instances %d; want %d", got, want) 4404 } 4405 var foos, bars int 4406 for _, obj := range is.Deposed { 4407 if bytes.Contains(obj.AttrsJSON, []byte("foo")) { 4408 foos++ 4409 } 4410 if bytes.Contains(obj.AttrsJSON, []byte("bar")) { 4411 bars++ 4412 } 4413 } 4414 if got, want := foos, 1; got != want { 4415 t.Fatalf("wrong number of deposed instances with id=foo %d; want %d", got, want) 4416 } 4417 if got, want := bars, 1; got != want { 4418 t.Fatalf("wrong number of deposed instances with id=bar %d; want %d", got, want) 4419 } 4420 } 4421 4422 // Destroy partially fixed! 4423 destroyFunc = func(req providers.ApplyResourceChangeRequest) (resp providers.ApplyResourceChangeResponse) { 4424 s := req.PriorState.AsValueMap() 4425 id := s["id"].AsString() 4426 if id == "foo" || id == "baz" { 4427 resp.NewState = cty.NullVal(req.PriorState.Type()) 4428 } else { 4429 resp.NewState = req.PriorState 4430 resp.Diagnostics = resp.Diagnostics.Append(fmt.Errorf("destroy partially failed")) 4431 } 4432 return 4433 } 4434 4435 createdInstanceId = "qux" 4436 ctx = testContext2(t, &ContextOpts{ 4437 Providers: ps, 4438 }) 4439 plan, diags = ctx.Plan(m, state, &PlanOpts{ 4440 Mode: plans.NormalMode, 4441 SetVariables: InputValues{ 4442 "require_new": &InputValue{ 4443 Value: cty.StringVal("qux"), 4444 }, 4445 }, 4446 }) 4447 assertNoErrors(t, diags) 4448 4449 state, diags = ctx.Apply(plan, m) 4450 // Expect error because 1/2 of Deposed destroys failed 4451 if !diags.HasErrors() { 4452 t.Fatal("should have error") 4453 } 4454 4455 // foo and baz are now gone, bar sticks around 4456 checkStateString(t, state, ` 4457 aws_instance.web: (1 deposed) 4458 ID = qux 4459 provider = provider["registry.terraform.io/hashicorp/aws"] 4460 require_new = qux 4461 Deposed ID 1 = bar 4462 `) 4463 4464 // Destroy working fully! 4465 destroyFunc = func(req providers.ApplyResourceChangeRequest) (resp providers.ApplyResourceChangeResponse) { 4466 resp.NewState = cty.NullVal(req.PriorState.Type()) 4467 return 4468 } 4469 4470 createdInstanceId = "quux" 4471 ctx = testContext2(t, &ContextOpts{ 4472 Providers: ps, 4473 }) 4474 plan, diags = ctx.Plan(m, state, &PlanOpts{ 4475 Mode: plans.NormalMode, 4476 SetVariables: InputValues{ 4477 "require_new": &InputValue{ 4478 Value: cty.StringVal("quux"), 4479 }, 4480 }, 4481 }) 4482 assertNoErrors(t, diags) 4483 state, diags = ctx.Apply(plan, m) 4484 if diags.HasErrors() { 4485 t.Fatal("should not have error:", diags.Err()) 4486 } 4487 4488 // And finally the state is clean 4489 checkStateString(t, state, ` 4490 aws_instance.web: 4491 ID = quux 4492 provider = provider["registry.terraform.io/hashicorp/aws"] 4493 require_new = quux 4494 `) 4495 } 4496 4497 // Verify that a normal provisioner with on_failure "continue" set won't 4498 // taint the resource and continues executing. 4499 func TestContext2Apply_provisionerFailContinue(t *testing.T) { 4500 m := testModule(t, "apply-provisioner-fail-continue") 4501 p := testProvider("aws") 4502 pr := testProvisioner() 4503 p.PlanResourceChangeFn = testDiffFn 4504 p.ApplyResourceChangeFn = testApplyFn 4505 4506 pr.ProvisionResourceFn = func(req provisioners.ProvisionResourceRequest) (resp provisioners.ProvisionResourceResponse) { 4507 resp.Diagnostics = resp.Diagnostics.Append(fmt.Errorf("provisioner error")) 4508 return 4509 } 4510 4511 ctx := testContext2(t, &ContextOpts{ 4512 Providers: map[addrs.Provider]providers.Factory{ 4513 addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p), 4514 }, 4515 Provisioners: map[string]provisioners.Factory{ 4516 "shell": testProvisionerFuncFixed(pr), 4517 }, 4518 }) 4519 4520 plan, diags := ctx.Plan(m, states.NewState(), DefaultPlanOpts) 4521 assertNoErrors(t, diags) 4522 4523 state, diags := ctx.Apply(plan, m) 4524 if diags.HasErrors() { 4525 t.Fatalf("diags: %s", diags.Err()) 4526 } 4527 4528 checkStateString(t, state, ` 4529 aws_instance.foo: 4530 ID = foo 4531 provider = provider["registry.terraform.io/hashicorp/aws"] 4532 foo = bar 4533 type = aws_instance 4534 `) 4535 4536 // Verify apply was invoked 4537 if !pr.ProvisionResourceCalled { 4538 t.Fatalf("provisioner not invoked") 4539 } 4540 } 4541 4542 // Verify that a normal provisioner with on_failure "continue" records 4543 // the error with the hook. 4544 func TestContext2Apply_provisionerFailContinueHook(t *testing.T) { 4545 h := new(MockHook) 4546 m := testModule(t, "apply-provisioner-fail-continue") 4547 p := testProvider("aws") 4548 pr := testProvisioner() 4549 p.PlanResourceChangeFn = testDiffFn 4550 pr.ProvisionResourceFn = func(req provisioners.ProvisionResourceRequest) (resp provisioners.ProvisionResourceResponse) { 4551 resp.Diagnostics = resp.Diagnostics.Append(fmt.Errorf("provisioner error")) 4552 return 4553 } 4554 4555 ctx := testContext2(t, &ContextOpts{ 4556 Hooks: []Hook{h}, 4557 Providers: map[addrs.Provider]providers.Factory{ 4558 addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p), 4559 }, 4560 Provisioners: map[string]provisioners.Factory{ 4561 "shell": testProvisionerFuncFixed(pr), 4562 }, 4563 }) 4564 4565 plan, diags := ctx.Plan(m, states.NewState(), DefaultPlanOpts) 4566 assertNoErrors(t, diags) 4567 4568 if _, diags := ctx.Apply(plan, m); diags.HasErrors() { 4569 t.Fatalf("apply errors: %s", diags.Err()) 4570 } 4571 4572 if !h.PostProvisionInstanceStepCalled { 4573 t.Fatal("PostProvisionInstanceStep not called") 4574 } 4575 if h.PostProvisionInstanceStepErrorArg == nil { 4576 t.Fatal("should have error") 4577 } 4578 } 4579 4580 func TestContext2Apply_provisionerDestroy(t *testing.T) { 4581 m := testModule(t, "apply-provisioner-destroy") 4582 p := testProvider("aws") 4583 pr := testProvisioner() 4584 p.PlanResourceChangeFn = testDiffFn 4585 pr.ProvisionResourceFn = func(req provisioners.ProvisionResourceRequest) (resp provisioners.ProvisionResourceResponse) { 4586 val := req.Config.GetAttr("command").AsString() 4587 if val != "destroy a bar" { 4588 t.Fatalf("bad value for foo: %q", val) 4589 } 4590 4591 return 4592 } 4593 4594 state := states.NewState() 4595 root := state.RootModule() 4596 root.SetResourceInstanceCurrent( 4597 mustResourceInstanceAddr(`aws_instance.foo["a"]`).Resource, 4598 &states.ResourceInstanceObjectSrc{ 4599 Status: states.ObjectReady, 4600 AttrsJSON: []byte(`{"id":"bar","foo":"bar"}`), 4601 }, 4602 mustProviderConfig(`provider["registry.terraform.io/hashicorp/aws"]`), 4603 ) 4604 4605 ctx := testContext2(t, &ContextOpts{ 4606 Providers: map[addrs.Provider]providers.Factory{ 4607 addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p), 4608 }, 4609 Provisioners: map[string]provisioners.Factory{ 4610 "shell": testProvisionerFuncFixed(pr), 4611 }, 4612 }) 4613 4614 plan, diags := ctx.Plan(m, state, &PlanOpts{ 4615 Mode: plans.DestroyMode, 4616 }) 4617 assertNoErrors(t, diags) 4618 4619 state, diags = ctx.Apply(plan, m) 4620 if diags.HasErrors() { 4621 t.Fatalf("diags: %s", diags.Err()) 4622 } 4623 4624 checkStateString(t, state, `<no state>`) 4625 4626 // Verify apply was invoked 4627 if !pr.ProvisionResourceCalled { 4628 t.Fatalf("provisioner not invoked") 4629 } 4630 } 4631 4632 // Verify that on destroy provisioner failure, nothing happens to the instance 4633 func TestContext2Apply_provisionerDestroyFail(t *testing.T) { 4634 m := testModule(t, "apply-provisioner-destroy") 4635 p := testProvider("aws") 4636 pr := testProvisioner() 4637 p.PlanResourceChangeFn = testDiffFn 4638 pr.ProvisionResourceFn = func(req provisioners.ProvisionResourceRequest) (resp provisioners.ProvisionResourceResponse) { 4639 resp.Diagnostics = resp.Diagnostics.Append(fmt.Errorf("provisioner error")) 4640 return 4641 } 4642 4643 state := states.NewState() 4644 root := state.RootModule() 4645 root.SetResourceInstanceCurrent( 4646 mustResourceInstanceAddr(`aws_instance.foo["a"]`).Resource, 4647 &states.ResourceInstanceObjectSrc{ 4648 Status: states.ObjectReady, 4649 AttrsJSON: []byte(`{"id":"bar","foo":"bar"}`), 4650 }, 4651 mustProviderConfig(`provider["registry.terraform.io/hashicorp/aws"]`), 4652 ) 4653 4654 ctx := testContext2(t, &ContextOpts{ 4655 Providers: map[addrs.Provider]providers.Factory{ 4656 addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p), 4657 }, 4658 Provisioners: map[string]provisioners.Factory{ 4659 "shell": testProvisionerFuncFixed(pr), 4660 }, 4661 }) 4662 4663 plan, diags := ctx.Plan(m, state, &PlanOpts{ 4664 Mode: plans.DestroyMode, 4665 }) 4666 assertNoErrors(t, diags) 4667 4668 state, diags = ctx.Apply(plan, m) 4669 if diags == nil { 4670 t.Fatal("should error") 4671 } 4672 4673 checkStateString(t, state, ` 4674 aws_instance.foo["a"]: 4675 ID = bar 4676 provider = provider["registry.terraform.io/hashicorp/aws"] 4677 foo = bar 4678 `) 4679 4680 // Verify apply was invoked 4681 if !pr.ProvisionResourceCalled { 4682 t.Fatalf("provisioner not invoked") 4683 } 4684 } 4685 4686 // Verify that on destroy provisioner failure with "continue" that 4687 // we continue to the next provisioner. 4688 func TestContext2Apply_provisionerDestroyFailContinue(t *testing.T) { 4689 m := testModule(t, "apply-provisioner-destroy-continue") 4690 p := testProvider("aws") 4691 pr := testProvisioner() 4692 p.PlanResourceChangeFn = testDiffFn 4693 4694 var l sync.Mutex 4695 var calls []string 4696 pr.ProvisionResourceFn = func(req provisioners.ProvisionResourceRequest) (resp provisioners.ProvisionResourceResponse) { 4697 val := req.Config.GetAttr("command") 4698 if val.IsNull() { 4699 t.Fatalf("bad value for foo: %#v", val) 4700 } 4701 4702 l.Lock() 4703 defer l.Unlock() 4704 calls = append(calls, val.AsString()) 4705 resp.Diagnostics = resp.Diagnostics.Append(fmt.Errorf("provisioner error")) 4706 return 4707 } 4708 4709 state := states.NewState() 4710 root := state.RootModule() 4711 root.SetResourceInstanceCurrent( 4712 mustResourceInstanceAddr(`aws_instance.foo["a"]`).Resource, 4713 &states.ResourceInstanceObjectSrc{ 4714 Status: states.ObjectReady, 4715 AttrsJSON: []byte(`{"id":"bar"}`), 4716 }, 4717 mustProviderConfig(`provider["registry.terraform.io/hashicorp/aws"]`), 4718 ) 4719 4720 ctx := testContext2(t, &ContextOpts{ 4721 Providers: map[addrs.Provider]providers.Factory{ 4722 addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p), 4723 }, 4724 Provisioners: map[string]provisioners.Factory{ 4725 "shell": testProvisionerFuncFixed(pr), 4726 }, 4727 }) 4728 4729 plan, diags := ctx.Plan(m, state, &PlanOpts{ 4730 Mode: plans.DestroyMode, 4731 }) 4732 assertNoErrors(t, diags) 4733 4734 state, diags = ctx.Apply(plan, m) 4735 if diags.HasErrors() { 4736 t.Fatalf("diags: %s", diags.Err()) 4737 } 4738 4739 checkStateString(t, state, `<no state>`) 4740 4741 // Verify apply was invoked 4742 if !pr.ProvisionResourceCalled { 4743 t.Fatalf("provisioner not invoked") 4744 } 4745 4746 expected := []string{"one", "two"} 4747 if !reflect.DeepEqual(calls, expected) { 4748 t.Fatalf("wrong commands\ngot: %#v\nwant: %#v", calls, expected) 4749 } 4750 } 4751 4752 // Verify that on destroy provisioner failure with "continue" that 4753 // we continue to the next provisioner. But if the next provisioner defines 4754 // to fail, then we fail after running it. 4755 func TestContext2Apply_provisionerDestroyFailContinueFail(t *testing.T) { 4756 m := testModule(t, "apply-provisioner-destroy-fail") 4757 p := testProvider("aws") 4758 pr := testProvisioner() 4759 p.PlanResourceChangeFn = testDiffFn 4760 4761 var l sync.Mutex 4762 var calls []string 4763 pr.ProvisionResourceFn = func(req provisioners.ProvisionResourceRequest) (resp provisioners.ProvisionResourceResponse) { 4764 val := req.Config.GetAttr("command") 4765 if val.IsNull() { 4766 t.Fatalf("bad value for foo: %#v", val) 4767 } 4768 4769 l.Lock() 4770 defer l.Unlock() 4771 calls = append(calls, val.AsString()) 4772 resp.Diagnostics = resp.Diagnostics.Append(fmt.Errorf("provisioner error")) 4773 return 4774 } 4775 4776 state := states.NewState() 4777 root := state.EnsureModule(addrs.RootModuleInstance) 4778 root.SetResourceInstanceCurrent( 4779 mustResourceInstanceAddr("aws_instance.foo").Resource, 4780 &states.ResourceInstanceObjectSrc{ 4781 Status: states.ObjectReady, 4782 AttrsJSON: []byte(`{"id":"bar"}`), 4783 }, 4784 mustProviderConfig(`provider["registry.terraform.io/hashicorp/aws"]`), 4785 ) 4786 4787 ctx := testContext2(t, &ContextOpts{ 4788 Providers: map[addrs.Provider]providers.Factory{ 4789 addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p), 4790 }, 4791 Provisioners: map[string]provisioners.Factory{ 4792 "shell": testProvisionerFuncFixed(pr), 4793 }, 4794 }) 4795 4796 plan, diags := ctx.Plan(m, state, &PlanOpts{ 4797 Mode: plans.DestroyMode, 4798 }) 4799 assertNoErrors(t, diags) 4800 4801 state, diags = ctx.Apply(plan, m) 4802 if diags == nil { 4803 t.Fatal("apply succeeded; wanted error from second provisioner") 4804 } 4805 4806 checkStateString(t, state, ` 4807 aws_instance.foo: 4808 ID = bar 4809 provider = provider["registry.terraform.io/hashicorp/aws"] 4810 `) 4811 4812 // Verify apply was invoked 4813 if !pr.ProvisionResourceCalled { 4814 t.Fatalf("provisioner not invoked") 4815 } 4816 4817 expected := []string{"one", "two"} 4818 if !reflect.DeepEqual(calls, expected) { 4819 t.Fatalf("bad: %#v", calls) 4820 } 4821 } 4822 4823 // Verify destroy provisioners are not run for tainted instances. 4824 func TestContext2Apply_provisionerDestroyTainted(t *testing.T) { 4825 m := testModule(t, "apply-provisioner-destroy") 4826 p := testProvider("aws") 4827 pr := testProvisioner() 4828 p.PlanResourceChangeFn = testDiffFn 4829 p.ApplyResourceChangeFn = testApplyFn 4830 4831 destroyCalled := false 4832 pr.ProvisionResourceFn = func(req provisioners.ProvisionResourceRequest) (resp provisioners.ProvisionResourceResponse) { 4833 expected := "create a b" 4834 val := req.Config.GetAttr("command") 4835 if val.AsString() != expected { 4836 t.Fatalf("bad value for command: %#v", val) 4837 } 4838 4839 return 4840 } 4841 4842 state := states.NewState() 4843 root := state.RootModule() 4844 root.SetResourceInstanceCurrent( 4845 mustResourceInstanceAddr(`aws_instance.foo["a"]`).Resource, 4846 &states.ResourceInstanceObjectSrc{ 4847 Status: states.ObjectTainted, 4848 AttrsJSON: []byte(`{"id":"bar"}`), 4849 }, 4850 mustProviderConfig(`provider["registry.terraform.io/hashicorp/aws"]`), 4851 ) 4852 4853 ctx := testContext2(t, &ContextOpts{ 4854 Providers: map[addrs.Provider]providers.Factory{ 4855 addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p), 4856 }, 4857 Provisioners: map[string]provisioners.Factory{ 4858 "shell": testProvisionerFuncFixed(pr), 4859 }, 4860 }) 4861 4862 plan, diags := ctx.Plan(m, state, &PlanOpts{ 4863 Mode: plans.NormalMode, 4864 SetVariables: InputValues{ 4865 "input": &InputValue{ 4866 Value: cty.MapVal(map[string]cty.Value{ 4867 "a": cty.StringVal("b"), 4868 }), 4869 SourceType: ValueFromInput, 4870 }, 4871 }, 4872 }) 4873 assertNoErrors(t, diags) 4874 4875 state, diags = ctx.Apply(plan, m) 4876 if diags.HasErrors() { 4877 t.Fatalf("diags: %s", diags.Err()) 4878 } 4879 4880 checkStateString(t, state, ` 4881 aws_instance.foo["a"]: 4882 ID = foo 4883 provider = provider["registry.terraform.io/hashicorp/aws"] 4884 foo = bar 4885 type = aws_instance 4886 `) 4887 4888 // Verify apply was invoked 4889 if !pr.ProvisionResourceCalled { 4890 t.Fatalf("provisioner not invoked") 4891 } 4892 4893 if destroyCalled { 4894 t.Fatal("destroy should not be called") 4895 } 4896 } 4897 4898 func TestContext2Apply_provisionerResourceRef(t *testing.T) { 4899 m := testModule(t, "apply-provisioner-resource-ref") 4900 p := testProvider("aws") 4901 p.PlanResourceChangeFn = testDiffFn 4902 p.ApplyResourceChangeFn = testApplyFn 4903 4904 pr := testProvisioner() 4905 pr.ProvisionResourceFn = func(req provisioners.ProvisionResourceRequest) (resp provisioners.ProvisionResourceResponse) { 4906 val := req.Config.GetAttr("command") 4907 if val.AsString() != "2" { 4908 t.Fatalf("bad value for command: %#v", val) 4909 } 4910 4911 return 4912 } 4913 4914 ctx := testContext2(t, &ContextOpts{ 4915 Providers: map[addrs.Provider]providers.Factory{ 4916 addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p), 4917 }, 4918 Provisioners: map[string]provisioners.Factory{ 4919 "shell": testProvisionerFuncFixed(pr), 4920 }, 4921 }) 4922 4923 plan, diags := ctx.Plan(m, states.NewState(), DefaultPlanOpts) 4924 assertNoErrors(t, diags) 4925 4926 state, diags := ctx.Apply(plan, m) 4927 if diags.HasErrors() { 4928 t.Fatalf("diags: %s", diags.Err()) 4929 } 4930 4931 actual := strings.TrimSpace(state.String()) 4932 expected := strings.TrimSpace(testTerraformApplyProvisionerResourceRefStr) 4933 if actual != expected { 4934 t.Fatalf("wrong result\n\ngot:\n%s\n\nwant:\n%s", actual, expected) 4935 } 4936 4937 // Verify apply was invoked 4938 if !pr.ProvisionResourceCalled { 4939 t.Fatalf("provisioner not invoked") 4940 } 4941 } 4942 4943 func TestContext2Apply_provisionerSelfRef(t *testing.T) { 4944 m := testModule(t, "apply-provisioner-self-ref") 4945 p := testProvider("aws") 4946 pr := testProvisioner() 4947 p.PlanResourceChangeFn = testDiffFn 4948 p.ApplyResourceChangeFn = testApplyFn 4949 pr.ProvisionResourceFn = func(req provisioners.ProvisionResourceRequest) (resp provisioners.ProvisionResourceResponse) { 4950 val := req.Config.GetAttr("command") 4951 if val.AsString() != "bar" { 4952 t.Fatalf("bad value for command: %#v", val) 4953 } 4954 4955 return 4956 } 4957 4958 ctx := testContext2(t, &ContextOpts{ 4959 Providers: map[addrs.Provider]providers.Factory{ 4960 addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p), 4961 }, 4962 Provisioners: map[string]provisioners.Factory{ 4963 "shell": testProvisionerFuncFixed(pr), 4964 }, 4965 }) 4966 4967 plan, diags := ctx.Plan(m, states.NewState(), DefaultPlanOpts) 4968 assertNoErrors(t, diags) 4969 4970 state, diags := ctx.Apply(plan, m) 4971 if diags.HasErrors() { 4972 t.Fatalf("diags: %s", diags.Err()) 4973 } 4974 4975 actual := strings.TrimSpace(state.String()) 4976 expected := strings.TrimSpace(testTerraformApplyProvisionerSelfRefStr) 4977 if actual != expected { 4978 t.Fatalf("wrong result\n\ngot:\n%s\n\nwant:\n%s", actual, expected) 4979 } 4980 4981 // Verify apply was invoked 4982 if !pr.ProvisionResourceCalled { 4983 t.Fatalf("provisioner not invoked") 4984 } 4985 } 4986 4987 func TestContext2Apply_provisionerMultiSelfRef(t *testing.T) { 4988 var lock sync.Mutex 4989 commands := make([]string, 0, 5) 4990 4991 m := testModule(t, "apply-provisioner-multi-self-ref") 4992 p := testProvider("aws") 4993 pr := testProvisioner() 4994 p.PlanResourceChangeFn = testDiffFn 4995 p.ApplyResourceChangeFn = testApplyFn 4996 pr.ProvisionResourceFn = func(req provisioners.ProvisionResourceRequest) (resp provisioners.ProvisionResourceResponse) { 4997 lock.Lock() 4998 defer lock.Unlock() 4999 5000 val := req.Config.GetAttr("command") 5001 if val.IsNull() { 5002 t.Fatalf("bad value for command: %#v", val) 5003 } 5004 5005 commands = append(commands, val.AsString()) 5006 return 5007 } 5008 5009 ctx := testContext2(t, &ContextOpts{ 5010 Providers: map[addrs.Provider]providers.Factory{ 5011 addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p), 5012 }, 5013 Provisioners: map[string]provisioners.Factory{ 5014 "shell": testProvisionerFuncFixed(pr), 5015 }, 5016 }) 5017 5018 plan, diags := ctx.Plan(m, states.NewState(), DefaultPlanOpts) 5019 assertNoErrors(t, diags) 5020 5021 state, diags := ctx.Apply(plan, m) 5022 if diags.HasErrors() { 5023 t.Fatalf("diags: %s", diags.Err()) 5024 } 5025 5026 actual := strings.TrimSpace(state.String()) 5027 expected := strings.TrimSpace(testTerraformApplyProvisionerMultiSelfRefStr) 5028 if actual != expected { 5029 t.Fatalf("wrong result\n\ngot:\n%s\n\nwant:\n%s", actual, expected) 5030 } 5031 5032 // Verify apply was invoked 5033 if !pr.ProvisionResourceCalled { 5034 t.Fatalf("provisioner not invoked") 5035 } 5036 5037 // Verify our result 5038 sort.Strings(commands) 5039 expectedCommands := []string{"number 0", "number 1", "number 2"} 5040 if !reflect.DeepEqual(commands, expectedCommands) { 5041 t.Fatalf("bad: %#v", commands) 5042 } 5043 } 5044 5045 func TestContext2Apply_provisionerMultiSelfRefSingle(t *testing.T) { 5046 var lock sync.Mutex 5047 order := make([]string, 0, 5) 5048 5049 m := testModule(t, "apply-provisioner-multi-self-ref-single") 5050 p := testProvider("aws") 5051 pr := testProvisioner() 5052 p.PlanResourceChangeFn = testDiffFn 5053 p.ApplyResourceChangeFn = testApplyFn 5054 pr.ProvisionResourceFn = func(req provisioners.ProvisionResourceRequest) (resp provisioners.ProvisionResourceResponse) { 5055 lock.Lock() 5056 defer lock.Unlock() 5057 5058 val := req.Config.GetAttr("order") 5059 if val.IsNull() { 5060 t.Fatalf("no val for order") 5061 } 5062 5063 order = append(order, val.AsString()) 5064 return 5065 } 5066 5067 ctx := testContext2(t, &ContextOpts{ 5068 Providers: map[addrs.Provider]providers.Factory{ 5069 addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p), 5070 }, 5071 Provisioners: map[string]provisioners.Factory{ 5072 "shell": testProvisionerFuncFixed(pr), 5073 }, 5074 }) 5075 5076 plan, diags := ctx.Plan(m, states.NewState(), DefaultPlanOpts) 5077 assertNoErrors(t, diags) 5078 5079 state, diags := ctx.Apply(plan, m) 5080 if diags.HasErrors() { 5081 t.Fatalf("diags: %s", diags.Err()) 5082 } 5083 5084 actual := strings.TrimSpace(state.String()) 5085 expected := strings.TrimSpace(testTerraformApplyProvisionerMultiSelfRefSingleStr) 5086 if actual != expected { 5087 t.Fatalf("wrong result\n\ngot:\n%s\n\nwant:\n%s", actual, expected) 5088 } 5089 5090 // Verify apply was invoked 5091 if !pr.ProvisionResourceCalled { 5092 t.Fatalf("provisioner not invoked") 5093 } 5094 5095 // Verify our result 5096 sort.Strings(order) 5097 expectedOrder := []string{"0", "1", "2"} 5098 if !reflect.DeepEqual(order, expectedOrder) { 5099 t.Fatalf("bad: %#v", order) 5100 } 5101 } 5102 5103 func TestContext2Apply_provisionerExplicitSelfRef(t *testing.T) { 5104 m := testModule(t, "apply-provisioner-explicit-self-ref") 5105 p := testProvider("aws") 5106 pr := testProvisioner() 5107 p.PlanResourceChangeFn = testDiffFn 5108 pr.ProvisionResourceFn = func(req provisioners.ProvisionResourceRequest) (resp provisioners.ProvisionResourceResponse) { 5109 val := req.Config.GetAttr("command") 5110 if val.IsNull() || val.AsString() != "bar" { 5111 t.Fatalf("bad value for command: %#v", val) 5112 } 5113 5114 return 5115 } 5116 5117 var state *states.State 5118 { 5119 ctx := testContext2(t, &ContextOpts{ 5120 Providers: map[addrs.Provider]providers.Factory{ 5121 addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p), 5122 }, 5123 Provisioners: map[string]provisioners.Factory{ 5124 "shell": testProvisionerFuncFixed(pr), 5125 }, 5126 }) 5127 5128 plan, diags := ctx.Plan(m, states.NewState(), DefaultPlanOpts) 5129 if diags.HasErrors() { 5130 t.Fatalf("diags: %s", diags.Err()) 5131 } 5132 5133 state, diags = ctx.Apply(plan, m) 5134 if diags.HasErrors() { 5135 t.Fatalf("diags: %s", diags.Err()) 5136 } 5137 5138 // Verify apply was invoked 5139 if !pr.ProvisionResourceCalled { 5140 t.Fatalf("provisioner not invoked") 5141 } 5142 } 5143 5144 { 5145 ctx := testContext2(t, &ContextOpts{ 5146 Providers: map[addrs.Provider]providers.Factory{ 5147 addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p), 5148 }, 5149 Provisioners: map[string]provisioners.Factory{ 5150 "shell": testProvisionerFuncFixed(pr), 5151 }, 5152 }) 5153 5154 plan, diags := ctx.Plan(m, state, &PlanOpts{ 5155 Mode: plans.DestroyMode, 5156 }) 5157 if diags.HasErrors() { 5158 t.Fatalf("diags: %s", diags.Err()) 5159 } 5160 5161 state, diags = ctx.Apply(plan, m) 5162 if diags.HasErrors() { 5163 t.Fatalf("diags: %s", diags.Err()) 5164 } 5165 5166 checkStateString(t, state, `<no state>`) 5167 } 5168 } 5169 5170 func TestContext2Apply_provisionerForEachSelfRef(t *testing.T) { 5171 m := testModule(t, "apply-provisioner-for-each-self") 5172 p := testProvider("aws") 5173 pr := testProvisioner() 5174 p.PlanResourceChangeFn = testDiffFn 5175 5176 pr.ProvisionResourceFn = func(req provisioners.ProvisionResourceRequest) (resp provisioners.ProvisionResourceResponse) { 5177 val := req.Config.GetAttr("command") 5178 if val.IsNull() { 5179 t.Fatalf("bad value for command: %#v", val) 5180 } 5181 5182 return resp 5183 } 5184 5185 ctx := testContext2(t, &ContextOpts{ 5186 Providers: map[addrs.Provider]providers.Factory{ 5187 addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p), 5188 }, 5189 Provisioners: map[string]provisioners.Factory{ 5190 "shell": testProvisionerFuncFixed(pr), 5191 }, 5192 }) 5193 5194 plan, diags := ctx.Plan(m, states.NewState(), DefaultPlanOpts) 5195 assertNoErrors(t, diags) 5196 5197 _, diags = ctx.Apply(plan, m) 5198 if diags.HasErrors() { 5199 t.Fatalf("diags: %s", diags.Err()) 5200 } 5201 } 5202 5203 // Provisioner should NOT run on a diff, only create 5204 func TestContext2Apply_Provisioner_Diff(t *testing.T) { 5205 m := testModule(t, "apply-provisioner-diff") 5206 p := testProvider("aws") 5207 pr := testProvisioner() 5208 p.PlanResourceChangeFn = testDiffFn 5209 p.ApplyResourceChangeFn = testApplyFn 5210 ctx := testContext2(t, &ContextOpts{ 5211 Providers: map[addrs.Provider]providers.Factory{ 5212 addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p), 5213 }, 5214 Provisioners: map[string]provisioners.Factory{ 5215 "shell": testProvisionerFuncFixed(pr), 5216 }, 5217 }) 5218 5219 plan, diags := ctx.Plan(m, states.NewState(), DefaultPlanOpts) 5220 assertNoErrors(t, diags) 5221 5222 state, diags := ctx.Apply(plan, m) 5223 if diags.HasErrors() { 5224 logDiagnostics(t, diags) 5225 t.Fatal("apply failed") 5226 } 5227 5228 actual := strings.TrimSpace(state.String()) 5229 expected := strings.TrimSpace(testTerraformApplyProvisionerDiffStr) 5230 if actual != expected { 5231 t.Fatalf("wrong result\n\ngot:\n%s\n\nwant:\n%s", actual, expected) 5232 } 5233 5234 // Verify apply was invoked 5235 if !pr.ProvisionResourceCalled { 5236 t.Fatalf("provisioner was not called on first apply") 5237 } 5238 pr.ProvisionResourceCalled = false 5239 5240 // Change the state to force a diff 5241 mod := state.RootModule() 5242 obj := mod.Resources["aws_instance.bar"].Instances[addrs.NoKey].Current 5243 var attrs map[string]interface{} 5244 err := json.Unmarshal(obj.AttrsJSON, &attrs) 5245 if err != nil { 5246 t.Fatal(err) 5247 } 5248 attrs["foo"] = "baz" 5249 obj.AttrsJSON, err = json.Marshal(attrs) 5250 if err != nil { 5251 t.Fatal(err) 5252 } 5253 5254 // Re-create context with state 5255 ctx = testContext2(t, &ContextOpts{ 5256 Providers: map[addrs.Provider]providers.Factory{ 5257 addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p), 5258 }, 5259 Provisioners: map[string]provisioners.Factory{ 5260 "shell": testProvisionerFuncFixed(pr), 5261 }, 5262 }) 5263 5264 plan, diags = ctx.Plan(m, state, DefaultPlanOpts) 5265 assertNoErrors(t, diags) 5266 5267 state2, diags := ctx.Apply(plan, m) 5268 if diags.HasErrors() { 5269 logDiagnostics(t, diags) 5270 t.Fatal("apply failed") 5271 } 5272 5273 actual = strings.TrimSpace(state2.String()) 5274 if actual != expected { 5275 t.Fatalf("wrong result\n\ngot:\n%s\n\nwant:\n%s", actual, expected) 5276 } 5277 5278 // Verify apply was NOT invoked 5279 if pr.ProvisionResourceCalled { 5280 t.Fatalf("provisioner was called on second apply; should not have been") 5281 } 5282 } 5283 5284 func TestContext2Apply_outputDiffVars(t *testing.T) { 5285 m := testModule(t, "apply-good") 5286 p := testProvider("aws") 5287 5288 state := states.NewState() 5289 root := state.EnsureModule(addrs.RootModuleInstance) 5290 root.SetResourceInstanceCurrent( 5291 mustResourceInstanceAddr("aws_instance.baz").Resource, 5292 &states.ResourceInstanceObjectSrc{ 5293 Status: states.ObjectReady, 5294 AttrsJSON: []byte(`{"id":"bar"}`), 5295 }, 5296 mustProviderConfig(`provider["registry.terraform.io/hashicorp/aws"]`), 5297 ) 5298 5299 ctx := testContext2(t, &ContextOpts{ 5300 Providers: map[addrs.Provider]providers.Factory{ 5301 addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p), 5302 }, 5303 }) 5304 5305 p.PlanResourceChangeFn = testDiffFn 5306 //func(info *InstanceInfo, s *InstanceState, rc *ResourceConfig) (*InstanceDiff, error) { 5307 // d := &InstanceDiff{ 5308 // Attributes: map[string]*ResourceAttrDiff{}, 5309 // } 5310 // if new, ok := rc.Get("value"); ok { 5311 // d.Attributes["value"] = &ResourceAttrDiff{ 5312 // New: new.(string), 5313 // } 5314 // } 5315 // if new, ok := rc.Get("foo"); ok { 5316 // d.Attributes["foo"] = &ResourceAttrDiff{ 5317 // New: new.(string), 5318 // } 5319 // } else if rc.IsComputed("foo") { 5320 // d.Attributes["foo"] = &ResourceAttrDiff{ 5321 // NewComputed: true, 5322 // Type: DiffAttrOutput, // This doesn't actually really do anything anymore, but this test originally set it. 5323 // } 5324 // } 5325 // if new, ok := rc.Get("num"); ok { 5326 // d.Attributes["num"] = &ResourceAttrDiff{ 5327 // New: fmt.Sprintf("%#v", new), 5328 // } 5329 // } 5330 // return d, nil 5331 //} 5332 5333 plan, diags := ctx.Plan(m, state, DefaultPlanOpts) 5334 assertNoErrors(t, diags) 5335 5336 _, diags = ctx.Apply(plan, m) 5337 assertNoErrors(t, diags) 5338 } 5339 5340 func TestContext2Apply_destroyX(t *testing.T) { 5341 m := testModule(t, "apply-destroy") 5342 h := new(HookRecordApplyOrder) 5343 p := testProvider("aws") 5344 p.PlanResourceChangeFn = testDiffFn 5345 ctx := testContext2(t, &ContextOpts{ 5346 Hooks: []Hook{h}, 5347 Providers: map[addrs.Provider]providers.Factory{ 5348 addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p), 5349 }, 5350 }) 5351 5352 // First plan and apply a create operation 5353 plan, diags := ctx.Plan(m, states.NewState(), DefaultPlanOpts) 5354 assertNoErrors(t, diags) 5355 5356 state, diags := ctx.Apply(plan, m) 5357 if diags.HasErrors() { 5358 t.Fatalf("diags: %s", diags.Err()) 5359 } 5360 5361 // Next, plan and apply a destroy operation 5362 h.Active = true 5363 ctx = testContext2(t, &ContextOpts{ 5364 Hooks: []Hook{h}, 5365 Providers: map[addrs.Provider]providers.Factory{ 5366 addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p), 5367 }, 5368 }) 5369 5370 plan, diags = ctx.Plan(m, state, &PlanOpts{ 5371 Mode: plans.DestroyMode, 5372 }) 5373 assertNoErrors(t, diags) 5374 5375 state, diags = ctx.Apply(plan, m) 5376 if diags.HasErrors() { 5377 t.Fatalf("diags: %s", diags.Err()) 5378 } 5379 5380 // Test that things were destroyed 5381 actual := strings.TrimSpace(state.String()) 5382 expected := strings.TrimSpace(testTerraformApplyDestroyStr) 5383 if actual != expected { 5384 t.Fatalf("wrong result\n\ngot:\n%s\n\nwant:\n%s", actual, expected) 5385 } 5386 5387 // Test that things were destroyed _in the right order_ 5388 expected2 := []string{"aws_instance.bar", "aws_instance.foo"} 5389 actual2 := h.IDs 5390 if !reflect.DeepEqual(actual2, expected2) { 5391 t.Fatalf("expected: %#v\n\ngot:%#v", expected2, actual2) 5392 } 5393 } 5394 5395 func TestContext2Apply_destroyOrder(t *testing.T) { 5396 m := testModule(t, "apply-destroy") 5397 h := new(HookRecordApplyOrder) 5398 p := testProvider("aws") 5399 p.PlanResourceChangeFn = testDiffFn 5400 ctx := testContext2(t, &ContextOpts{ 5401 Hooks: []Hook{h}, 5402 Providers: map[addrs.Provider]providers.Factory{ 5403 addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p), 5404 }, 5405 }) 5406 5407 // First plan and apply a create operation 5408 plan, diags := ctx.Plan(m, states.NewState(), DefaultPlanOpts) 5409 assertNoErrors(t, diags) 5410 5411 state, diags := ctx.Apply(plan, m) 5412 if diags.HasErrors() { 5413 t.Fatalf("diags: %s", diags.Err()) 5414 } 5415 5416 t.Logf("State 1: %s", state) 5417 5418 // Next, plan and apply a destroy 5419 h.Active = true 5420 ctx = testContext2(t, &ContextOpts{ 5421 Hooks: []Hook{h}, 5422 Providers: map[addrs.Provider]providers.Factory{ 5423 addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p), 5424 }, 5425 }) 5426 5427 plan, diags = ctx.Plan(m, state, &PlanOpts{ 5428 Mode: plans.DestroyMode, 5429 }) 5430 assertNoErrors(t, diags) 5431 5432 state, diags = ctx.Apply(plan, m) 5433 if diags.HasErrors() { 5434 t.Fatalf("diags: %s", diags.Err()) 5435 } 5436 5437 // Test that things were destroyed 5438 actual := strings.TrimSpace(state.String()) 5439 expected := strings.TrimSpace(testTerraformApplyDestroyStr) 5440 if actual != expected { 5441 t.Fatalf("wrong result\n\ngot:\n%s\n\nwant:\n%s", actual, expected) 5442 } 5443 5444 // Test that things were destroyed _in the right order_ 5445 expected2 := []string{"aws_instance.bar", "aws_instance.foo"} 5446 actual2 := h.IDs 5447 if !reflect.DeepEqual(actual2, expected2) { 5448 t.Fatalf("expected: %#v\n\ngot:%#v", expected2, actual2) 5449 } 5450 } 5451 5452 // https://github.com/hashicorp/terraform/issues/2767 5453 func TestContext2Apply_destroyModulePrefix(t *testing.T) { 5454 m := testModule(t, "apply-destroy-module-resource-prefix") 5455 h := new(MockHook) 5456 p := testProvider("aws") 5457 p.PlanResourceChangeFn = testDiffFn 5458 ctx := testContext2(t, &ContextOpts{ 5459 Hooks: []Hook{h}, 5460 Providers: map[addrs.Provider]providers.Factory{ 5461 addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p), 5462 }, 5463 }) 5464 5465 // First plan and apply a create operation 5466 plan, diags := ctx.Plan(m, states.NewState(), DefaultPlanOpts) 5467 assertNoErrors(t, diags) 5468 5469 state, diags := ctx.Apply(plan, m) 5470 if diags.HasErrors() { 5471 t.Fatalf("diags: %s", diags.Err()) 5472 } 5473 5474 // Verify that we got the apply info correct 5475 if v := h.PreApplyAddr.String(); v != "module.child.aws_instance.foo" { 5476 t.Fatalf("bad: %s", v) 5477 } 5478 5479 // Next, plan and apply a destroy operation and reset the hook 5480 h = new(MockHook) 5481 ctx = testContext2(t, &ContextOpts{ 5482 Hooks: []Hook{h}, 5483 Providers: map[addrs.Provider]providers.Factory{ 5484 addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p), 5485 }, 5486 }) 5487 5488 plan, diags = ctx.Plan(m, state, &PlanOpts{ 5489 Mode: plans.DestroyMode, 5490 }) 5491 assertNoErrors(t, diags) 5492 5493 _, diags = ctx.Apply(plan, m) 5494 if diags.HasErrors() { 5495 t.Fatalf("diags: %s", diags.Err()) 5496 } 5497 5498 // Test that things were destroyed 5499 if v := h.PreApplyAddr.String(); v != "module.child.aws_instance.foo" { 5500 t.Fatalf("bad: %s", v) 5501 } 5502 } 5503 5504 func TestContext2Apply_destroyNestedModule(t *testing.T) { 5505 m := testModule(t, "apply-destroy-nested-module") 5506 p := testProvider("aws") 5507 p.PlanResourceChangeFn = testDiffFn 5508 5509 state := states.NewState() 5510 root := state.EnsureModule(addrs.RootModuleInstance) 5511 root.SetResourceInstanceCurrent( 5512 mustResourceInstanceAddr("aws_instance.bar").Resource, 5513 &states.ResourceInstanceObjectSrc{ 5514 Status: states.ObjectReady, 5515 AttrsJSON: []byte(`{"id":"bar"}`), 5516 }, 5517 mustProviderConfig(`provider["registry.terraform.io/hashicorp/aws"]`), 5518 ) 5519 5520 ctx := testContext2(t, &ContextOpts{ 5521 Providers: map[addrs.Provider]providers.Factory{ 5522 addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p), 5523 }, 5524 }) 5525 5526 // First plan and apply a create operation 5527 plan, diags := ctx.Plan(m, state, DefaultPlanOpts) 5528 assertNoErrors(t, diags) 5529 5530 s, 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(s.String()) 5537 if actual != "<no state>" { 5538 t.Fatalf("expected no state, got: %s", actual) 5539 } 5540 } 5541 5542 func TestContext2Apply_destroyDeeplyNestedModule(t *testing.T) { 5543 m := testModule(t, "apply-destroy-deeply-nested-module") 5544 p := testProvider("aws") 5545 p.PlanResourceChangeFn = testDiffFn 5546 5547 state := states.NewState() 5548 root := state.EnsureModule(addrs.RootModuleInstance) 5549 root.SetResourceInstanceCurrent( 5550 mustResourceInstanceAddr("aws_instance.bar").Resource, 5551 &states.ResourceInstanceObjectSrc{ 5552 Status: states.ObjectReady, 5553 AttrsJSON: []byte(`{"id":"bar"}`), 5554 }, 5555 mustProviderConfig(`provider["registry.terraform.io/hashicorp/aws"]`), 5556 ) 5557 5558 ctx := testContext2(t, &ContextOpts{ 5559 Providers: map[addrs.Provider]providers.Factory{ 5560 addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p), 5561 }, 5562 }) 5563 5564 // First plan and apply a create operation 5565 plan, diags := ctx.Plan(m, state, DefaultPlanOpts) 5566 assertNoErrors(t, diags) 5567 5568 s, diags := ctx.Apply(plan, m) 5569 if diags.HasErrors() { 5570 t.Fatalf("diags: %s", diags.Err()) 5571 } 5572 5573 // Test that things were destroyed 5574 if !s.Empty() { 5575 t.Fatalf("wrong final state %s\nwant empty state", spew.Sdump(s)) 5576 } 5577 } 5578 5579 // https://github.com/hashicorp/terraform/issues/5440 5580 func TestContext2Apply_destroyModuleWithAttrsReferencingResource(t *testing.T) { 5581 m, snap := testModuleWithSnapshot(t, "apply-destroy-module-with-attrs") 5582 p := testProvider("aws") 5583 p.PlanResourceChangeFn = testDiffFn 5584 5585 var state *states.State 5586 { 5587 ctx := testContext2(t, &ContextOpts{ 5588 Providers: map[addrs.Provider]providers.Factory{ 5589 addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p), 5590 }, 5591 }) 5592 5593 // First plan and apply a create operation 5594 plan, diags := ctx.Plan(m, states.NewState(), DefaultPlanOpts) 5595 if diags.HasErrors() { 5596 t.Fatalf("plan diags: %s", diags.Err()) 5597 } else { 5598 t.Logf("Step 1 plan: %s", legacyDiffComparisonString(plan.Changes)) 5599 } 5600 5601 state, diags = ctx.Apply(plan, m) 5602 if diags.HasErrors() { 5603 t.Fatalf("apply errs: %s", diags.Err()) 5604 } 5605 5606 t.Logf("Step 1 state: %s", state) 5607 } 5608 5609 h := new(HookRecordApplyOrder) 5610 h.Active = true 5611 5612 { 5613 ctx := testContext2(t, &ContextOpts{ 5614 Hooks: []Hook{h}, 5615 Providers: map[addrs.Provider]providers.Factory{ 5616 addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p), 5617 }, 5618 }) 5619 5620 // First plan and apply a create operation 5621 plan, diags := ctx.Plan(m, state, &PlanOpts{ 5622 Mode: plans.DestroyMode, 5623 }) 5624 if diags.HasErrors() { 5625 t.Fatalf("destroy plan err: %s", diags.Err()) 5626 } 5627 5628 t.Logf("Step 2 plan: %s", legacyDiffComparisonString(plan.Changes)) 5629 5630 ctxOpts, m, plan, err := contextOptsForPlanViaFile(snap, plan) 5631 if err != nil { 5632 t.Fatalf("failed to round-trip through planfile: %s", err) 5633 } 5634 5635 ctxOpts.Providers = map[addrs.Provider]providers.Factory{ 5636 addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p), 5637 } 5638 5639 ctx, diags = NewContext(ctxOpts) 5640 if diags.HasErrors() { 5641 t.Fatalf("err: %s", diags.Err()) 5642 } 5643 5644 state, diags = ctx.Apply(plan, m) 5645 if diags.HasErrors() { 5646 t.Fatalf("destroy apply err: %s", diags.Err()) 5647 } 5648 5649 t.Logf("Step 2 state: %s", state) 5650 } 5651 5652 //Test that things were destroyed 5653 if state.HasResources() { 5654 t.Fatal("expected empty state, got:", state) 5655 } 5656 } 5657 5658 func TestContext2Apply_destroyWithModuleVariableAndCount(t *testing.T) { 5659 m, snap := testModuleWithSnapshot(t, "apply-destroy-mod-var-and-count") 5660 p := testProvider("aws") 5661 p.PlanResourceChangeFn = testDiffFn 5662 5663 var state *states.State 5664 { 5665 ctx := testContext2(t, &ContextOpts{ 5666 Providers: map[addrs.Provider]providers.Factory{ 5667 addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p), 5668 }, 5669 }) 5670 5671 // First plan and apply a create operation 5672 plan, diags := ctx.Plan(m, states.NewState(), DefaultPlanOpts) 5673 assertNoErrors(t, diags) 5674 5675 state, diags = ctx.Apply(plan, m) 5676 if diags.HasErrors() { 5677 t.Fatalf("apply err: %s", diags.Err()) 5678 } 5679 } 5680 5681 h := new(HookRecordApplyOrder) 5682 h.Active = true 5683 5684 { 5685 ctx := testContext2(t, &ContextOpts{ 5686 Hooks: []Hook{h}, 5687 Providers: map[addrs.Provider]providers.Factory{ 5688 addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p), 5689 }, 5690 }) 5691 5692 // First plan and apply a create operation 5693 plan, diags := ctx.Plan(m, state, &PlanOpts{ 5694 Mode: plans.DestroyMode, 5695 }) 5696 if diags.HasErrors() { 5697 t.Fatalf("destroy plan err: %s", diags.Err()) 5698 } 5699 5700 ctxOpts, m, plan, err := contextOptsForPlanViaFile(snap, plan) 5701 if err != nil { 5702 t.Fatalf("failed to round-trip through planfile: %s", err) 5703 } 5704 5705 ctxOpts.Providers = 5706 map[addrs.Provider]providers.Factory{ 5707 addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p), 5708 } 5709 5710 ctx, diags = NewContext(ctxOpts) 5711 if diags.HasErrors() { 5712 t.Fatalf("err: %s", diags.Err()) 5713 } 5714 5715 state, diags = ctx.Apply(plan, m) 5716 if diags.HasErrors() { 5717 t.Fatalf("destroy apply err: %s", diags.Err()) 5718 } 5719 } 5720 5721 //Test that things were destroyed 5722 actual := strings.TrimSpace(state.String()) 5723 expected := strings.TrimSpace(` 5724 <no state>`) 5725 if actual != expected { 5726 t.Fatalf("expected: \n%s\n\nbad: \n%s", expected, actual) 5727 } 5728 } 5729 5730 func TestContext2Apply_destroyTargetWithModuleVariableAndCount(t *testing.T) { 5731 m := testModule(t, "apply-destroy-mod-var-and-count") 5732 p := testProvider("aws") 5733 p.PlanResourceChangeFn = testDiffFn 5734 5735 var state *states.State 5736 { 5737 ctx := testContext2(t, &ContextOpts{ 5738 Providers: map[addrs.Provider]providers.Factory{ 5739 addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p), 5740 }, 5741 }) 5742 5743 // First plan and apply a create operation 5744 plan, diags := ctx.Plan(m, states.NewState(), DefaultPlanOpts) 5745 assertNoErrors(t, diags) 5746 5747 state, diags = ctx.Apply(plan, m) 5748 if diags.HasErrors() { 5749 t.Fatalf("apply err: %s", diags.Err()) 5750 } 5751 } 5752 5753 { 5754 ctx := testContext2(t, &ContextOpts{ 5755 Providers: map[addrs.Provider]providers.Factory{ 5756 addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p), 5757 }, 5758 }) 5759 5760 plan, diags := ctx.Plan(m, state, &PlanOpts{ 5761 Mode: plans.DestroyMode, 5762 Targets: []addrs.Targetable{ 5763 addrs.RootModuleInstance.Child("child", addrs.NoKey), 5764 }, 5765 }) 5766 if diags.HasErrors() { 5767 t.Fatalf("plan err: %s", diags) 5768 } 5769 if len(diags) != 1 { 5770 // Should have one warning that -target is in effect. 5771 t.Fatalf("got %d diagnostics in plan; want 1", len(diags)) 5772 } 5773 if got, want := diags[0].Severity(), tfdiags.Warning; got != want { 5774 t.Errorf("wrong diagnostic severity %#v; want %#v", got, want) 5775 } 5776 if got, want := diags[0].Description().Summary, "Resource targeting is in effect"; got != want { 5777 t.Errorf("wrong diagnostic summary %#v; want %#v", got, want) 5778 } 5779 5780 // Destroy, targeting the module explicitly 5781 state, diags = ctx.Apply(plan, m) 5782 if diags.HasErrors() { 5783 t.Fatalf("destroy apply err: %s", diags) 5784 } 5785 if len(diags) != 1 { 5786 t.Fatalf("got %d diagnostics; want 1", len(diags)) 5787 } 5788 if got, want := diags[0].Severity(), tfdiags.Warning; got != want { 5789 t.Errorf("wrong diagnostic severity %#v; want %#v", got, want) 5790 } 5791 if got, want := diags[0].Description().Summary, "Applied changes may be incomplete"; got != want { 5792 t.Errorf("wrong diagnostic summary %#v; want %#v", got, want) 5793 } 5794 } 5795 5796 //Test that things were destroyed 5797 actual := strings.TrimSpace(state.String()) 5798 expected := strings.TrimSpace(`<no state>`) 5799 if actual != expected { 5800 t.Fatalf("expected: \n%s\n\nbad: \n%s", expected, actual) 5801 } 5802 } 5803 5804 func TestContext2Apply_destroyWithModuleVariableAndCountNested(t *testing.T) { 5805 m, snap := testModuleWithSnapshot(t, "apply-destroy-mod-var-and-count-nested") 5806 p := testProvider("aws") 5807 p.PlanResourceChangeFn = testDiffFn 5808 5809 var state *states.State 5810 { 5811 ctx := testContext2(t, &ContextOpts{ 5812 Providers: map[addrs.Provider]providers.Factory{ 5813 addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p), 5814 }, 5815 }) 5816 5817 // First plan and apply a create operation 5818 plan, diags := ctx.Plan(m, states.NewState(), DefaultPlanOpts) 5819 assertNoErrors(t, diags) 5820 5821 state, diags = ctx.Apply(plan, m) 5822 if diags.HasErrors() { 5823 t.Fatalf("apply err: %s", diags.Err()) 5824 } 5825 } 5826 5827 h := new(HookRecordApplyOrder) 5828 h.Active = true 5829 5830 { 5831 ctx := testContext2(t, &ContextOpts{ 5832 Hooks: []Hook{h}, 5833 Providers: map[addrs.Provider]providers.Factory{ 5834 addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p), 5835 }, 5836 }) 5837 5838 // First plan and apply a create operation 5839 plan, diags := ctx.Plan(m, state, &PlanOpts{ 5840 Mode: plans.DestroyMode, 5841 }) 5842 if diags.HasErrors() { 5843 t.Fatalf("destroy plan err: %s", diags.Err()) 5844 } 5845 5846 ctxOpts, m, plan, err := contextOptsForPlanViaFile(snap, plan) 5847 if err != nil { 5848 t.Fatalf("failed to round-trip through planfile: %s", err) 5849 } 5850 5851 ctxOpts.Providers = 5852 map[addrs.Provider]providers.Factory{ 5853 addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p), 5854 } 5855 5856 ctx, diags = NewContext(ctxOpts) 5857 if diags.HasErrors() { 5858 t.Fatalf("err: %s", diags.Err()) 5859 } 5860 5861 state, diags = ctx.Apply(plan, m) 5862 if diags.HasErrors() { 5863 t.Fatalf("destroy apply err: %s", diags.Err()) 5864 } 5865 } 5866 5867 //Test that things were destroyed 5868 actual := strings.TrimSpace(state.String()) 5869 expected := strings.TrimSpace(` 5870 <no state>`) 5871 if actual != expected { 5872 t.Fatalf("expected: \n%s\n\nbad: \n%s", expected, actual) 5873 } 5874 } 5875 5876 func TestContext2Apply_destroyOutputs(t *testing.T) { 5877 m := testModule(t, "apply-destroy-outputs") 5878 p := testProvider("test") 5879 p.PlanResourceChangeFn = testDiffFn 5880 5881 p.ReadDataSourceFn = func(req providers.ReadDataSourceRequest) providers.ReadDataSourceResponse { 5882 // add the required id 5883 m := req.Config.AsValueMap() 5884 m["id"] = cty.StringVal("foo") 5885 5886 return providers.ReadDataSourceResponse{ 5887 State: cty.ObjectVal(m), 5888 } 5889 } 5890 5891 ctx := testContext2(t, &ContextOpts{ 5892 Providers: map[addrs.Provider]providers.Factory{ 5893 addrs.NewDefaultProvider("test"): testProviderFuncFixed(p), 5894 }, 5895 }) 5896 5897 // First plan and apply a create operation 5898 plan, diags := ctx.Plan(m, states.NewState(), DefaultPlanOpts) 5899 assertNoErrors(t, diags) 5900 5901 state, diags := ctx.Apply(plan, m) 5902 5903 if diags.HasErrors() { 5904 t.Fatalf("diags: %s", diags.Err()) 5905 } 5906 5907 // Next, plan and apply a destroy operation 5908 ctx = testContext2(t, &ContextOpts{ 5909 Providers: map[addrs.Provider]providers.Factory{ 5910 addrs.NewDefaultProvider("test"): testProviderFuncFixed(p), 5911 }, 5912 }) 5913 5914 plan, diags = ctx.Plan(m, state, &PlanOpts{ 5915 Mode: plans.DestroyMode, 5916 }) 5917 assertNoErrors(t, diags) 5918 5919 state, diags = ctx.Apply(plan, m) 5920 if diags.HasErrors() { 5921 t.Fatalf("diags: %s", diags.Err()) 5922 } 5923 5924 mod := state.RootModule() 5925 if len(mod.Resources) > 0 { 5926 t.Fatalf("expected no resources, got: %#v", mod) 5927 } 5928 5929 // destroying again should produce no errors 5930 ctx = testContext2(t, &ContextOpts{ 5931 Providers: map[addrs.Provider]providers.Factory{ 5932 addrs.NewDefaultProvider("test"): testProviderFuncFixed(p), 5933 }, 5934 }) 5935 plan, diags = ctx.Plan(m, state, &PlanOpts{ 5936 Mode: plans.DestroyMode, 5937 }) 5938 assertNoErrors(t, diags) 5939 5940 if _, diags := ctx.Apply(plan, m); diags.HasErrors() { 5941 t.Fatal(diags.Err()) 5942 } 5943 } 5944 5945 func TestContext2Apply_destroyOrphan(t *testing.T) { 5946 m := testModule(t, "apply-error") 5947 p := testProvider("aws") 5948 state := states.NewState() 5949 root := state.EnsureModule(addrs.RootModuleInstance) 5950 root.SetResourceInstanceCurrent( 5951 mustResourceInstanceAddr("aws_instance.baz").Resource, 5952 &states.ResourceInstanceObjectSrc{ 5953 Status: states.ObjectReady, 5954 AttrsJSON: []byte(`{"id":"bar"}`), 5955 }, 5956 mustProviderConfig(`provider["registry.terraform.io/hashicorp/aws"]`), 5957 ) 5958 ctx := testContext2(t, &ContextOpts{ 5959 Providers: map[addrs.Provider]providers.Factory{ 5960 addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p), 5961 }, 5962 }) 5963 5964 p.PlanResourceChangeFn = testDiffFn 5965 5966 plan, diags := ctx.Plan(m, state, DefaultPlanOpts) 5967 assertNoErrors(t, diags) 5968 5969 s, diags := ctx.Apply(plan, m) 5970 if diags.HasErrors() { 5971 t.Fatalf("diags: %s", diags.Err()) 5972 } 5973 5974 mod := s.RootModule() 5975 if _, ok := mod.Resources["aws_instance.baz"]; ok { 5976 t.Fatalf("bad: %#v", mod.Resources) 5977 } 5978 } 5979 5980 func TestContext2Apply_destroyTaintedProvisioner(t *testing.T) { 5981 m := testModule(t, "apply-destroy-provisioner") 5982 p := testProvider("aws") 5983 pr := testProvisioner() 5984 p.PlanResourceChangeFn = testDiffFn 5985 5986 state := states.NewState() 5987 root := state.EnsureModule(addrs.RootModuleInstance) 5988 root.SetResourceInstanceCurrent( 5989 mustResourceInstanceAddr("aws_instance.foo").Resource, 5990 &states.ResourceInstanceObjectSrc{ 5991 Status: states.ObjectReady, 5992 AttrsJSON: []byte(`{"id":"bar"}`), 5993 }, 5994 mustProviderConfig(`provider["registry.terraform.io/hashicorp/aws"]`), 5995 ) 5996 5997 ctx := testContext2(t, &ContextOpts{ 5998 Providers: map[addrs.Provider]providers.Factory{ 5999 addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p), 6000 }, 6001 Provisioners: map[string]provisioners.Factory{ 6002 "shell": testProvisionerFuncFixed(pr), 6003 }, 6004 }) 6005 6006 plan, diags := ctx.Plan(m, state, &PlanOpts{ 6007 Mode: plans.DestroyMode, 6008 }) 6009 assertNoErrors(t, diags) 6010 6011 s, diags := ctx.Apply(plan, m) 6012 if diags.HasErrors() { 6013 t.Fatalf("diags: %s", diags.Err()) 6014 } 6015 6016 if pr.ProvisionResourceCalled { 6017 t.Fatal("provisioner should not be called") 6018 } 6019 6020 actual := strings.TrimSpace(s.String()) 6021 expected := strings.TrimSpace("<no state>") 6022 if actual != expected { 6023 t.Fatalf("wrong result\n\ngot:\n%s\n\nwant:\n%s", actual, expected) 6024 } 6025 } 6026 6027 func TestContext2Apply_error(t *testing.T) { 6028 errored := false 6029 6030 m := testModule(t, "apply-error") 6031 p := testProvider("aws") 6032 ctx := testContext2(t, &ContextOpts{ 6033 Providers: map[addrs.Provider]providers.Factory{ 6034 addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p), 6035 }, 6036 }) 6037 6038 p.ApplyResourceChangeFn = func(req providers.ApplyResourceChangeRequest) (resp providers.ApplyResourceChangeResponse) { 6039 if errored { 6040 resp.NewState = req.PlannedState 6041 resp.Diagnostics = resp.Diagnostics.Append(fmt.Errorf("error")) 6042 return 6043 } 6044 errored = true 6045 6046 return testApplyFn(req) 6047 } 6048 p.PlanResourceChangeFn = testDiffFn 6049 6050 plan, diags := ctx.Plan(m, states.NewState(), DefaultPlanOpts) 6051 assertNoErrors(t, diags) 6052 6053 state, diags := ctx.Apply(plan, m) 6054 if diags == nil { 6055 t.Fatal("should have error") 6056 } 6057 6058 actual := strings.TrimSpace(state.String()) 6059 expected := strings.TrimSpace(testTerraformApplyErrorStr) 6060 if actual != expected { 6061 t.Fatalf("expected:\n%s\n\ngot:\n%s", expected, actual) 6062 } 6063 } 6064 6065 func TestContext2Apply_errorDestroy(t *testing.T) { 6066 m := testModule(t, "empty") 6067 p := testProvider("test") 6068 6069 p.GetProviderSchemaResponse = getProviderSchemaResponseFromProviderSchema(&ProviderSchema{ 6070 ResourceTypes: map[string]*configschema.Block{ 6071 "test_thing": { 6072 Attributes: map[string]*configschema.Attribute{ 6073 "id": {Type: cty.String, Optional: true}, 6074 }, 6075 }, 6076 }, 6077 }) 6078 p.PlanResourceChangeFn = func(req providers.PlanResourceChangeRequest) providers.PlanResourceChangeResponse { 6079 // Should actually be called for this test, because Terraform Core 6080 // constructs the plan for a destroy operation itself. 6081 return providers.PlanResourceChangeResponse{ 6082 PlannedState: req.ProposedNewState, 6083 } 6084 } 6085 p.ApplyResourceChangeFn = func(req providers.ApplyResourceChangeRequest) providers.ApplyResourceChangeResponse { 6086 // The apply (in this case, a destroy) always fails, so we can verify 6087 // that the object stays in the state after a destroy fails even though 6088 // we aren't returning a new state object here. 6089 return providers.ApplyResourceChangeResponse{ 6090 Diagnostics: tfdiags.Diagnostics(nil).Append(fmt.Errorf("failed")), 6091 } 6092 } 6093 6094 ctx := testContext2(t, &ContextOpts{ 6095 Providers: map[addrs.Provider]providers.Factory{ 6096 addrs.NewDefaultProvider("test"): testProviderFuncFixed(p), 6097 }, 6098 }) 6099 6100 state := states.BuildState(func(ss *states.SyncState) { 6101 ss.SetResourceInstanceCurrent( 6102 addrs.Resource{ 6103 Mode: addrs.ManagedResourceMode, 6104 Type: "test_thing", 6105 Name: "foo", 6106 }.Instance(addrs.NoKey).Absolute(addrs.RootModuleInstance), 6107 &states.ResourceInstanceObjectSrc{ 6108 Status: states.ObjectReady, 6109 AttrsJSON: []byte(`{"id":"baz"}`), 6110 }, 6111 addrs.AbsProviderConfig{ 6112 Provider: addrs.NewDefaultProvider("test"), 6113 Module: addrs.RootModule, 6114 }, 6115 ) 6116 }) 6117 plan, diags := ctx.Plan(m, state, DefaultPlanOpts) 6118 assertNoErrors(t, diags) 6119 6120 state, diags = ctx.Apply(plan, m) 6121 if !diags.HasErrors() { 6122 t.Fatal("should have error") 6123 } 6124 6125 actual := strings.TrimSpace(state.String()) 6126 expected := strings.TrimSpace(` 6127 test_thing.foo: 6128 ID = baz 6129 provider = provider["registry.terraform.io/hashicorp/test"] 6130 `) // test_thing.foo is still here, even though provider returned no new state along with its error 6131 if actual != expected { 6132 t.Fatalf("expected:\n%s\n\ngot:\n%s", expected, actual) 6133 } 6134 } 6135 6136 func TestContext2Apply_errorCreateInvalidNew(t *testing.T) { 6137 m := testModule(t, "apply-error") 6138 6139 p := testProvider("aws") 6140 p.GetProviderSchemaResponse = getProviderSchemaResponseFromProviderSchema(&ProviderSchema{ 6141 ResourceTypes: map[string]*configschema.Block{ 6142 "aws_instance": { 6143 Attributes: map[string]*configschema.Attribute{ 6144 "value": {Type: cty.String, Optional: true}, 6145 "foo": {Type: cty.String, Optional: true}, 6146 }, 6147 }, 6148 }, 6149 }) 6150 p.PlanResourceChangeFn = func(req providers.PlanResourceChangeRequest) providers.PlanResourceChangeResponse { 6151 return providers.PlanResourceChangeResponse{ 6152 PlannedState: req.ProposedNewState, 6153 } 6154 } 6155 p.ApplyResourceChangeFn = func(req providers.ApplyResourceChangeRequest) providers.ApplyResourceChangeResponse { 6156 // We're intentionally returning an inconsistent new state here 6157 // because we want to test that Terraform ignores the inconsistency 6158 // when accompanied by another error. 6159 return providers.ApplyResourceChangeResponse{ 6160 NewState: cty.ObjectVal(map[string]cty.Value{ 6161 "value": cty.StringVal("wrong wrong wrong wrong"), 6162 "foo": cty.StringVal("absolutely brimming over with wrongability"), 6163 }), 6164 Diagnostics: tfdiags.Diagnostics(nil).Append(fmt.Errorf("forced error")), 6165 } 6166 } 6167 6168 ctx := testContext2(t, &ContextOpts{ 6169 Providers: map[addrs.Provider]providers.Factory{ 6170 addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p), 6171 }, 6172 }) 6173 6174 plan, diags := ctx.Plan(m, states.NewState(), DefaultPlanOpts) 6175 assertNoErrors(t, diags) 6176 6177 state, diags := ctx.Apply(plan, m) 6178 if diags == nil { 6179 t.Fatal("should have error") 6180 } 6181 if got, want := len(diags), 1; got != want { 6182 // There should be no additional diagnostics generated by Terraform's own eval logic, 6183 // because the provider's own error supersedes them. 6184 t.Errorf("wrong number of diagnostics %d; want %d\n%s", got, want, diags.Err()) 6185 } 6186 if got, want := diags.Err().Error(), "forced error"; !strings.Contains(got, want) { 6187 t.Errorf("returned error does not contain %q, but it should\n%s", want, diags.Err()) 6188 } 6189 if got, want := len(state.RootModule().Resources), 2; got != want { 6190 t.Errorf("%d resources in state before prune; should have %d\n%s", got, want, spew.Sdump(state)) 6191 } 6192 state.PruneResourceHusks() // aws_instance.bar with no instances gets left behind when we bail out, but that's okay 6193 if got, want := len(state.RootModule().Resources), 1; got != want { 6194 t.Errorf("%d resources in state after prune; should have only one (aws_instance.foo, tainted)\n%s", got, spew.Sdump(state)) 6195 } 6196 } 6197 6198 func TestContext2Apply_errorUpdateNullNew(t *testing.T) { 6199 m := testModule(t, "apply-error") 6200 6201 p := testProvider("aws") 6202 p.GetProviderSchemaResponse = getProviderSchemaResponseFromProviderSchema(&ProviderSchema{ 6203 ResourceTypes: map[string]*configschema.Block{ 6204 "aws_instance": { 6205 Attributes: map[string]*configschema.Attribute{ 6206 "value": {Type: cty.String, Optional: true}, 6207 "foo": {Type: cty.String, Optional: true}, 6208 }, 6209 }, 6210 }, 6211 }) 6212 p.PlanResourceChangeFn = func(req providers.PlanResourceChangeRequest) providers.PlanResourceChangeResponse { 6213 return providers.PlanResourceChangeResponse{ 6214 PlannedState: req.ProposedNewState, 6215 } 6216 } 6217 p.ApplyResourceChangeFn = func(req providers.ApplyResourceChangeRequest) providers.ApplyResourceChangeResponse { 6218 // We're intentionally returning no NewState here because we want to 6219 // test that Terraform retains the prior state, rather than treating 6220 // the returned null as "no state" (object deleted). 6221 return providers.ApplyResourceChangeResponse{ 6222 Diagnostics: tfdiags.Diagnostics(nil).Append(fmt.Errorf("forced error")), 6223 } 6224 } 6225 6226 ctx := testContext2(t, &ContextOpts{ 6227 Providers: map[addrs.Provider]providers.Factory{ 6228 addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p), 6229 }, 6230 }) 6231 6232 state := states.BuildState(func(ss *states.SyncState) { 6233 ss.SetResourceInstanceCurrent( 6234 addrs.Resource{ 6235 Mode: addrs.ManagedResourceMode, 6236 Type: "aws_instance", 6237 Name: "foo", 6238 }.Instance(addrs.NoKey).Absolute(addrs.RootModuleInstance), 6239 &states.ResourceInstanceObjectSrc{ 6240 Status: states.ObjectReady, 6241 AttrsJSON: []byte(`{"value":"old"}`), 6242 }, 6243 addrs.AbsProviderConfig{ 6244 Provider: addrs.NewDefaultProvider("aws"), 6245 Module: addrs.RootModule, 6246 }, 6247 ) 6248 }) 6249 plan, diags := ctx.Plan(m, state, DefaultPlanOpts) 6250 assertNoErrors(t, diags) 6251 6252 state, diags = ctx.Apply(plan, m) 6253 if !diags.HasErrors() { 6254 t.Fatal("should have error") 6255 } 6256 if got, want := len(diags), 1; got != want { 6257 // There should be no additional diagnostics generated by Terraform's own eval logic, 6258 // because the provider's own error supersedes them. 6259 t.Errorf("wrong number of diagnostics %d; want %d\n%s", got, want, diags.Err()) 6260 } 6261 if got, want := diags.Err().Error(), "forced error"; !strings.Contains(got, want) { 6262 t.Errorf("returned error does not contain %q, but it should\n%s", want, diags.Err()) 6263 } 6264 state.PruneResourceHusks() 6265 if got, want := len(state.RootModule().Resources), 1; got != want { 6266 t.Fatalf("%d resources in state; should have only one (aws_instance.foo, unmodified)\n%s", got, spew.Sdump(state)) 6267 } 6268 6269 is := state.ResourceInstance(addrs.Resource{ 6270 Mode: addrs.ManagedResourceMode, 6271 Type: "aws_instance", 6272 Name: "foo", 6273 }.Instance(addrs.NoKey).Absolute(addrs.RootModuleInstance)) 6274 if is == nil { 6275 t.Fatalf("aws_instance.foo is not in the state after apply") 6276 } 6277 if got, want := is.Current.AttrsJSON, []byte(`"old"`); !bytes.Contains(got, want) { 6278 t.Fatalf("incorrect attributes for aws_instance.foo\ngot: %s\nwant: JSON containing %s\n\n%s", got, want, spew.Sdump(is)) 6279 } 6280 } 6281 6282 func TestContext2Apply_errorPartial(t *testing.T) { 6283 errored := false 6284 6285 m := testModule(t, "apply-error") 6286 p := testProvider("aws") 6287 6288 state := states.NewState() 6289 root := state.EnsureModule(addrs.RootModuleInstance) 6290 root.SetResourceInstanceCurrent( 6291 mustResourceInstanceAddr("aws_instance.bar").Resource, 6292 &states.ResourceInstanceObjectSrc{ 6293 Status: states.ObjectReady, 6294 AttrsJSON: []byte(`{"id":"bar","type":"aws_instance"}`), 6295 }, 6296 mustProviderConfig(`provider["registry.terraform.io/hashicorp/aws"]`), 6297 ) 6298 6299 ctx := testContext2(t, &ContextOpts{ 6300 Providers: map[addrs.Provider]providers.Factory{ 6301 addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p), 6302 }, 6303 }) 6304 6305 p.ApplyResourceChangeFn = func(req providers.ApplyResourceChangeRequest) (resp providers.ApplyResourceChangeResponse) { 6306 if errored { 6307 resp.Diagnostics = resp.Diagnostics.Append(fmt.Errorf("error")) 6308 return 6309 } 6310 errored = true 6311 6312 return testApplyFn(req) 6313 } 6314 p.PlanResourceChangeFn = testDiffFn 6315 6316 plan, diags := ctx.Plan(m, state, DefaultPlanOpts) 6317 assertNoErrors(t, diags) 6318 6319 s, diags := ctx.Apply(plan, m) 6320 if diags == nil { 6321 t.Fatal("should have error") 6322 } 6323 6324 mod := s.RootModule() 6325 if len(mod.Resources) != 2 { 6326 t.Fatalf("bad: %#v", mod.Resources) 6327 } 6328 6329 actual := strings.TrimSpace(s.String()) 6330 expected := strings.TrimSpace(testTerraformApplyErrorPartialStr) 6331 if actual != expected { 6332 t.Fatalf("expected:\n%s\n\ngot:\n%s", expected, actual) 6333 } 6334 } 6335 6336 func TestContext2Apply_hook(t *testing.T) { 6337 m := testModule(t, "apply-good") 6338 h := new(MockHook) 6339 p := testProvider("aws") 6340 p.PlanResourceChangeFn = testDiffFn 6341 ctx := testContext2(t, &ContextOpts{ 6342 Hooks: []Hook{h}, 6343 Providers: map[addrs.Provider]providers.Factory{ 6344 addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p), 6345 }, 6346 }) 6347 6348 plan, diags := ctx.Plan(m, states.NewState(), DefaultPlanOpts) 6349 assertNoErrors(t, diags) 6350 6351 if _, diags := ctx.Apply(plan, m); diags.HasErrors() { 6352 t.Fatalf("apply errors: %s", diags.Err()) 6353 } 6354 6355 if !h.PreApplyCalled { 6356 t.Fatal("should be called") 6357 } 6358 if !h.PostApplyCalled { 6359 t.Fatal("should be called") 6360 } 6361 if !h.PostStateUpdateCalled { 6362 t.Fatalf("should call post state update") 6363 } 6364 } 6365 6366 func TestContext2Apply_hookOrphan(t *testing.T) { 6367 m := testModule(t, "apply-blank") 6368 h := new(MockHook) 6369 p := testProvider("aws") 6370 p.PlanResourceChangeFn = testDiffFn 6371 6372 state := states.NewState() 6373 root := state.EnsureModule(addrs.RootModuleInstance) 6374 root.SetResourceInstanceCurrent( 6375 mustResourceInstanceAddr("aws_instance.bar").Resource, 6376 &states.ResourceInstanceObjectSrc{ 6377 Status: states.ObjectReady, 6378 AttrsJSON: []byte(`{"id":"bar"}`), 6379 }, 6380 mustProviderConfig(`provider["registry.terraform.io/hashicorp/aws"]`), 6381 ) 6382 6383 ctx := testContext2(t, &ContextOpts{ 6384 Hooks: []Hook{h}, 6385 Providers: map[addrs.Provider]providers.Factory{ 6386 addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p), 6387 }, 6388 }) 6389 6390 plan, diags := ctx.Plan(m, state, DefaultPlanOpts) 6391 assertNoErrors(t, diags) 6392 6393 if _, diags := ctx.Apply(plan, m); diags.HasErrors() { 6394 t.Fatalf("apply errors: %s", diags.Err()) 6395 } 6396 6397 if !h.PreApplyCalled { 6398 t.Fatal("should be called") 6399 } 6400 if !h.PostApplyCalled { 6401 t.Fatal("should be called") 6402 } 6403 if !h.PostStateUpdateCalled { 6404 t.Fatalf("should call post state update") 6405 } 6406 } 6407 6408 func TestContext2Apply_idAttr(t *testing.T) { 6409 m := testModule(t, "apply-idattr") 6410 p := testProvider("aws") 6411 ctx := testContext2(t, &ContextOpts{ 6412 Providers: map[addrs.Provider]providers.Factory{ 6413 addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p), 6414 }, 6415 }) 6416 6417 p.PlanResourceChangeFn = testDiffFn 6418 p.ApplyResourceChangeFn = testApplyFn 6419 6420 plan, diags := ctx.Plan(m, states.NewState(), DefaultPlanOpts) 6421 assertNoErrors(t, diags) 6422 6423 state, diags := ctx.Apply(plan, m) 6424 if diags.HasErrors() { 6425 t.Fatalf("apply errors: %s", diags.Err()) 6426 } 6427 6428 mod := state.RootModule() 6429 rs, ok := mod.Resources["aws_instance.foo"] 6430 if !ok { 6431 t.Fatal("not in state") 6432 } 6433 var attrs map[string]interface{} 6434 err := json.Unmarshal(rs.Instances[addrs.NoKey].Current.AttrsJSON, &attrs) 6435 if err != nil { 6436 t.Fatal(err) 6437 } 6438 if got, want := attrs["id"], "foo"; got != want { 6439 t.Fatalf("wrong id\ngot: %#v\nwant: %#v", got, want) 6440 } 6441 } 6442 6443 func TestContext2Apply_outputBasic(t *testing.T) { 6444 m := testModule(t, "apply-output") 6445 p := testProvider("aws") 6446 p.PlanResourceChangeFn = testDiffFn 6447 p.ApplyResourceChangeFn = testApplyFn 6448 ctx := testContext2(t, &ContextOpts{ 6449 Providers: map[addrs.Provider]providers.Factory{ 6450 addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p), 6451 }, 6452 }) 6453 6454 plan, diags := ctx.Plan(m, states.NewState(), DefaultPlanOpts) 6455 assertNoErrors(t, diags) 6456 6457 state, diags := ctx.Apply(plan, m) 6458 if diags.HasErrors() { 6459 t.Fatalf("diags: %s", diags.Err()) 6460 } 6461 6462 actual := strings.TrimSpace(state.String()) 6463 expected := strings.TrimSpace(testTerraformApplyOutputStr) 6464 if actual != expected { 6465 t.Fatalf("wrong result\n\ngot:\n%s\n\nwant:\n%s", actual, expected) 6466 } 6467 } 6468 6469 func TestContext2Apply_outputAdd(t *testing.T) { 6470 m1 := testModule(t, "apply-output-add-before") 6471 p1 := testProvider("aws") 6472 p1.ApplyResourceChangeFn = testApplyFn 6473 p1.PlanResourceChangeFn = testDiffFn 6474 ctx1 := testContext2(t, &ContextOpts{ 6475 Providers: map[addrs.Provider]providers.Factory{ 6476 addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p1), 6477 }, 6478 }) 6479 6480 plan1, diags := ctx1.Plan(m1, states.NewState(), DefaultPlanOpts) 6481 assertNoErrors(t, diags) 6482 6483 state1, diags := ctx1.Apply(plan1, m1) 6484 if diags.HasErrors() { 6485 t.Fatalf("diags: %s", diags.Err()) 6486 } 6487 6488 m2 := testModule(t, "apply-output-add-after") 6489 p2 := testProvider("aws") 6490 p2.ApplyResourceChangeFn = testApplyFn 6491 p2.PlanResourceChangeFn = testDiffFn 6492 ctx2 := testContext2(t, &ContextOpts{ 6493 Providers: map[addrs.Provider]providers.Factory{ 6494 addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p2), 6495 }, 6496 }) 6497 6498 plan2, diags := ctx1.Plan(m2, state1, DefaultPlanOpts) 6499 assertNoErrors(t, diags) 6500 6501 state2, diags := ctx2.Apply(plan2, m2) 6502 if diags.HasErrors() { 6503 t.Fatalf("diags: %s", diags.Err()) 6504 } 6505 6506 actual := strings.TrimSpace(state2.String()) 6507 expected := strings.TrimSpace(testTerraformApplyOutputAddStr) 6508 if actual != expected { 6509 t.Fatalf("wrong result\n\ngot:\n%s\n\nwant:\n%s", actual, expected) 6510 } 6511 } 6512 6513 func TestContext2Apply_outputList(t *testing.T) { 6514 m := testModule(t, "apply-output-list") 6515 p := testProvider("aws") 6516 p.PlanResourceChangeFn = testDiffFn 6517 p.ApplyResourceChangeFn = testApplyFn 6518 ctx := testContext2(t, &ContextOpts{ 6519 Providers: map[addrs.Provider]providers.Factory{ 6520 addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p), 6521 }, 6522 }) 6523 6524 plan, diags := ctx.Plan(m, states.NewState(), DefaultPlanOpts) 6525 assertNoErrors(t, diags) 6526 6527 state, diags := ctx.Apply(plan, m) 6528 if diags.HasErrors() { 6529 t.Fatalf("diags: %s", diags.Err()) 6530 } 6531 6532 actual := strings.TrimSpace(state.String()) 6533 expected := strings.TrimSpace(testTerraformApplyOutputListStr) 6534 if actual != expected { 6535 t.Fatalf("expected: \n%s\n\nbad: \n%s", expected, actual) 6536 } 6537 } 6538 6539 func TestContext2Apply_outputMulti(t *testing.T) { 6540 m := testModule(t, "apply-output-multi") 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(testTerraformApplyOutputMultiStr) 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_outputMultiIndex(t *testing.T) { 6566 m := testModule(t, "apply-output-multi-index") 6567 p := testProvider("aws") 6568 p.PlanResourceChangeFn = testDiffFn 6569 p.ApplyResourceChangeFn = testApplyFn 6570 ctx := testContext2(t, &ContextOpts{ 6571 Providers: map[addrs.Provider]providers.Factory{ 6572 addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p), 6573 }, 6574 }) 6575 6576 plan, diags := ctx.Plan(m, states.NewState(), DefaultPlanOpts) 6577 assertNoErrors(t, diags) 6578 6579 state, diags := ctx.Apply(plan, m) 6580 if diags.HasErrors() { 6581 t.Fatalf("diags: %s", diags.Err()) 6582 } 6583 6584 actual := strings.TrimSpace(state.String()) 6585 expected := strings.TrimSpace(testTerraformApplyOutputMultiIndexStr) 6586 if actual != expected { 6587 t.Fatalf("wrong result\n\ngot:\n%s\n\nwant:\n%s", actual, expected) 6588 } 6589 } 6590 6591 func TestContext2Apply_taintX(t *testing.T) { 6592 m := testModule(t, "apply-taint") 6593 p := testProvider("aws") 6594 // destroyCount tests against regression of 6595 // https://github.com/hashicorp/terraform/issues/1056 6596 var destroyCount = int32(0) 6597 var once sync.Once 6598 simulateProviderDelay := func() { 6599 time.Sleep(10 * time.Millisecond) 6600 } 6601 6602 p.ApplyResourceChangeFn = func(req providers.ApplyResourceChangeRequest) providers.ApplyResourceChangeResponse { 6603 once.Do(simulateProviderDelay) 6604 if req.PlannedState.IsNull() { 6605 atomic.AddInt32(&destroyCount, 1) 6606 } 6607 return testApplyFn(req) 6608 } 6609 p.PlanResourceChangeFn = testDiffFn 6610 6611 state := states.NewState() 6612 root := state.EnsureModule(addrs.RootModuleInstance) 6613 root.SetResourceInstanceCurrent( 6614 mustResourceInstanceAddr("aws_instance.bar").Resource, 6615 &states.ResourceInstanceObjectSrc{ 6616 Status: states.ObjectTainted, 6617 AttrsJSON: []byte(`{"id":"baz","num": "2", "type": "aws_instance"}`), 6618 }, 6619 mustProviderConfig(`provider["registry.terraform.io/hashicorp/aws"]`), 6620 ) 6621 6622 ctx := testContext2(t, &ContextOpts{ 6623 Providers: map[addrs.Provider]providers.Factory{ 6624 addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p), 6625 }, 6626 }) 6627 6628 plan, diags := ctx.Plan(m, state, DefaultPlanOpts) 6629 if diags.HasErrors() { 6630 t.Fatalf("diags: %s", diags.Err()) 6631 } else { 6632 t.Logf("plan: %s", legacyDiffComparisonString(plan.Changes)) 6633 } 6634 6635 s, diags := ctx.Apply(plan, m) 6636 if diags.HasErrors() { 6637 t.Fatalf("diags: %s", diags.Err()) 6638 } 6639 6640 actual := strings.TrimSpace(s.String()) 6641 expected := strings.TrimSpace(testTerraformApplyTaintStr) 6642 if actual != expected { 6643 t.Fatalf("bad:\n%s", actual) 6644 } 6645 6646 if destroyCount != 1 { 6647 t.Fatalf("Expected 1 destroy, got %d", destroyCount) 6648 } 6649 } 6650 6651 func TestContext2Apply_taintDep(t *testing.T) { 6652 m := testModule(t, "apply-taint-dep") 6653 p := testProvider("aws") 6654 p.PlanResourceChangeFn = testDiffFn 6655 p.ApplyResourceChangeFn = testApplyFn 6656 6657 state := states.NewState() 6658 root := state.EnsureModule(addrs.RootModuleInstance) 6659 root.SetResourceInstanceCurrent( 6660 mustResourceInstanceAddr("aws_instance.foo").Resource, 6661 &states.ResourceInstanceObjectSrc{ 6662 Status: states.ObjectTainted, 6663 AttrsJSON: []byte(`{"id":"baz","num": "2", "type": "aws_instance"}`), 6664 }, 6665 mustProviderConfig(`provider["registry.terraform.io/hashicorp/aws"]`), 6666 ) 6667 root.SetResourceInstanceCurrent( 6668 mustResourceInstanceAddr("aws_instance.bar").Resource, 6669 &states.ResourceInstanceObjectSrc{ 6670 Status: states.ObjectReady, 6671 AttrsJSON: []byte(`{"id":"bar","num": "2", "type": "aws_instance", "foo": "baz"}`), 6672 Dependencies: []addrs.ConfigResource{mustConfigResourceAddr("aws_instance.foo")}, 6673 }, 6674 mustProviderConfig(`provider["registry.terraform.io/hashicorp/aws"]`), 6675 ) 6676 6677 ctx := testContext2(t, &ContextOpts{ 6678 Providers: map[addrs.Provider]providers.Factory{ 6679 addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p), 6680 }, 6681 }) 6682 6683 plan, diags := ctx.Plan(m, state, DefaultPlanOpts) 6684 if diags.HasErrors() { 6685 t.Fatalf("diags: %s", diags.Err()) 6686 } else { 6687 t.Logf("plan: %s", legacyDiffComparisonString(plan.Changes)) 6688 } 6689 6690 s, diags := ctx.Apply(plan, m) 6691 if diags.HasErrors() { 6692 t.Fatalf("diags: %s", diags.Err()) 6693 } 6694 6695 actual := strings.TrimSpace(s.String()) 6696 expected := strings.TrimSpace(testTerraformApplyTaintDepStr) 6697 if actual != expected { 6698 t.Fatalf("bad:\n%s", actual) 6699 } 6700 } 6701 6702 func TestContext2Apply_taintDepRequiresNew(t *testing.T) { 6703 m := testModule(t, "apply-taint-dep-requires-new") 6704 p := testProvider("aws") 6705 p.PlanResourceChangeFn = testDiffFn 6706 p.ApplyResourceChangeFn = testApplyFn 6707 6708 state := states.NewState() 6709 root := state.EnsureModule(addrs.RootModuleInstance) 6710 root.SetResourceInstanceCurrent( 6711 mustResourceInstanceAddr("aws_instance.foo").Resource, 6712 &states.ResourceInstanceObjectSrc{ 6713 Status: states.ObjectTainted, 6714 AttrsJSON: []byte(`{"id":"baz","num": "2", "type": "aws_instance"}`), 6715 }, 6716 mustProviderConfig(`provider["registry.terraform.io/hashicorp/aws"]`), 6717 ) 6718 root.SetResourceInstanceCurrent( 6719 mustResourceInstanceAddr("aws_instance.bar").Resource, 6720 &states.ResourceInstanceObjectSrc{ 6721 Status: states.ObjectReady, 6722 AttrsJSON: []byte(`{"id":"bar","num": "2", "type": "aws_instance", "foo": "baz"}`), 6723 Dependencies: []addrs.ConfigResource{mustConfigResourceAddr("aws_instance.foo")}, 6724 }, 6725 mustProviderConfig(`provider["registry.terraform.io/hashicorp/aws"]`), 6726 ) 6727 6728 ctx := testContext2(t, &ContextOpts{ 6729 Providers: map[addrs.Provider]providers.Factory{ 6730 addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p), 6731 }, 6732 }) 6733 6734 plan, diags := ctx.Plan(m, state, DefaultPlanOpts) 6735 if diags.HasErrors() { 6736 t.Fatalf("diags: %s", diags.Err()) 6737 } else { 6738 t.Logf("plan: %s", legacyDiffComparisonString(plan.Changes)) 6739 } 6740 6741 s, diags := ctx.Apply(plan, m) 6742 if diags.HasErrors() { 6743 t.Fatalf("diags: %s", diags.Err()) 6744 } 6745 6746 actual := strings.TrimSpace(s.String()) 6747 expected := strings.TrimSpace(testTerraformApplyTaintDepRequireNewStr) 6748 if actual != expected { 6749 t.Fatalf("bad:\n%s", actual) 6750 } 6751 } 6752 6753 func TestContext2Apply_targeted(t *testing.T) { 6754 m := testModule(t, "apply-targeted") 6755 p := testProvider("aws") 6756 p.PlanResourceChangeFn = testDiffFn 6757 p.ApplyResourceChangeFn = testApplyFn 6758 ctx := testContext2(t, &ContextOpts{ 6759 Providers: map[addrs.Provider]providers.Factory{ 6760 addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p), 6761 }, 6762 }) 6763 6764 plan, diags := ctx.Plan(m, states.NewState(), &PlanOpts{ 6765 Mode: plans.NormalMode, 6766 Targets: []addrs.Targetable{ 6767 addrs.RootModuleInstance.Resource( 6768 addrs.ManagedResourceMode, "aws_instance", "foo", 6769 ), 6770 }, 6771 }) 6772 assertNoErrors(t, diags) 6773 6774 state, diags := ctx.Apply(plan, m) 6775 if diags.HasErrors() { 6776 t.Fatalf("diags: %s", diags.Err()) 6777 } 6778 6779 mod := state.RootModule() 6780 if len(mod.Resources) != 1 { 6781 t.Fatalf("expected 1 resource, got: %#v", mod.Resources) 6782 } 6783 6784 checkStateString(t, state, ` 6785 aws_instance.foo: 6786 ID = foo 6787 provider = provider["registry.terraform.io/hashicorp/aws"] 6788 num = 2 6789 type = aws_instance 6790 `) 6791 } 6792 6793 func TestContext2Apply_targetedCount(t *testing.T) { 6794 m := testModule(t, "apply-targeted-count") 6795 p := testProvider("aws") 6796 p.PlanResourceChangeFn = testDiffFn 6797 p.ApplyResourceChangeFn = testApplyFn 6798 ctx := testContext2(t, &ContextOpts{ 6799 Providers: map[addrs.Provider]providers.Factory{ 6800 addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p), 6801 }, 6802 }) 6803 6804 plan, diags := ctx.Plan(m, states.NewState(), &PlanOpts{ 6805 Mode: plans.NormalMode, 6806 Targets: []addrs.Targetable{ 6807 addrs.RootModuleInstance.Resource( 6808 addrs.ManagedResourceMode, "aws_instance", "foo", 6809 ), 6810 }, 6811 }) 6812 assertNoErrors(t, diags) 6813 6814 state, diags := ctx.Apply(plan, m) 6815 if diags.HasErrors() { 6816 t.Fatalf("diags: %s", diags.Err()) 6817 } 6818 6819 checkStateString(t, state, ` 6820 aws_instance.foo.0: 6821 ID = foo 6822 provider = provider["registry.terraform.io/hashicorp/aws"] 6823 type = aws_instance 6824 aws_instance.foo.1: 6825 ID = foo 6826 provider = provider["registry.terraform.io/hashicorp/aws"] 6827 type = aws_instance 6828 aws_instance.foo.2: 6829 ID = foo 6830 provider = provider["registry.terraform.io/hashicorp/aws"] 6831 type = aws_instance 6832 `) 6833 } 6834 6835 func TestContext2Apply_targetedCountIndex(t *testing.T) { 6836 m := testModule(t, "apply-targeted-count") 6837 p := testProvider("aws") 6838 p.PlanResourceChangeFn = testDiffFn 6839 p.ApplyResourceChangeFn = testApplyFn 6840 ctx := testContext2(t, &ContextOpts{ 6841 Providers: map[addrs.Provider]providers.Factory{ 6842 addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p), 6843 }, 6844 }) 6845 6846 plan, diags := ctx.Plan(m, states.NewState(), &PlanOpts{ 6847 Mode: plans.NormalMode, 6848 Targets: []addrs.Targetable{ 6849 addrs.RootModuleInstance.ResourceInstance( 6850 addrs.ManagedResourceMode, "aws_instance", "foo", addrs.IntKey(1), 6851 ), 6852 }, 6853 }) 6854 assertNoErrors(t, diags) 6855 6856 state, diags := ctx.Apply(plan, m) 6857 if diags.HasErrors() { 6858 t.Fatalf("diags: %s", diags.Err()) 6859 } 6860 6861 checkStateString(t, state, ` 6862 aws_instance.foo.1: 6863 ID = foo 6864 provider = provider["registry.terraform.io/hashicorp/aws"] 6865 type = aws_instance 6866 `) 6867 } 6868 6869 func TestContext2Apply_targetedDestroy(t *testing.T) { 6870 m := testModule(t, "destroy-targeted") 6871 p := testProvider("aws") 6872 p.PlanResourceChangeFn = testDiffFn 6873 6874 state := states.NewState() 6875 root := state.EnsureModule(addrs.RootModuleInstance) 6876 root.SetResourceInstanceCurrent( 6877 mustResourceInstanceAddr("aws_instance.a").Resource, 6878 &states.ResourceInstanceObjectSrc{ 6879 Status: states.ObjectReady, 6880 AttrsJSON: []byte(`{"id":"bar"}`), 6881 }, 6882 mustProviderConfig(`provider["registry.terraform.io/hashicorp/aws"]`), 6883 ) 6884 root.SetOutputValue("out", cty.StringVal("bar"), false) 6885 6886 child := state.EnsureModule(addrs.RootModuleInstance.Child("child", addrs.NoKey)) 6887 child.SetResourceInstanceCurrent( 6888 mustResourceInstanceAddr("aws_instance.b").Resource, 6889 &states.ResourceInstanceObjectSrc{ 6890 Status: states.ObjectReady, 6891 AttrsJSON: []byte(`{"id":"i-bcd345"}`), 6892 }, 6893 mustProviderConfig(`provider["registry.terraform.io/hashicorp/aws"]`), 6894 ) 6895 6896 ctx := testContext2(t, &ContextOpts{ 6897 Providers: map[addrs.Provider]providers.Factory{ 6898 addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p), 6899 }, 6900 }) 6901 6902 if diags := ctx.Validate(m); diags.HasErrors() { 6903 t.Fatalf("validate errors: %s", diags.Err()) 6904 } 6905 6906 plan, diags := ctx.Plan(m, state, &PlanOpts{ 6907 Mode: plans.DestroyMode, 6908 Targets: []addrs.Targetable{ 6909 addrs.RootModuleInstance.Resource( 6910 addrs.ManagedResourceMode, "aws_instance", "a", 6911 ), 6912 }, 6913 }) 6914 assertNoErrors(t, diags) 6915 6916 state, diags = ctx.Apply(plan, m) 6917 if diags.HasErrors() { 6918 t.Fatalf("diags: %s", diags.Err()) 6919 } 6920 6921 mod := state.RootModule() 6922 if len(mod.Resources) != 0 { 6923 t.Fatalf("expected 0 resources, got: %#v", mod.Resources) 6924 } 6925 6926 // the root output should not get removed; only the targeted resource. 6927 // 6928 // Note: earlier versions of this test expected 0 outputs, but it turns out 6929 // that was because Validate - not apply or destroy - removed the output 6930 // (which depends on the targeted resource) from state. That version of this 6931 // test did not match actual terraform behavior: the output remains in 6932 // state. 6933 // 6934 // TODO: Future refactoring may enable us to remove the output from state in 6935 // this case, and that would be Just Fine - this test can be modified to 6936 // expect 0 outputs. 6937 if len(mod.OutputValues) != 1 { 6938 t.Fatalf("expected 1 outputs, got: %#v", mod.OutputValues) 6939 } 6940 6941 // the module instance should remain 6942 mod = state.Module(addrs.RootModuleInstance.Child("child", addrs.NoKey)) 6943 if len(mod.Resources) != 1 { 6944 t.Fatalf("expected 1 resources, got: %#v", mod.Resources) 6945 } 6946 } 6947 6948 func TestContext2Apply_targetedDestroyCountDeps(t *testing.T) { 6949 m := testModule(t, "apply-destroy-targeted-count") 6950 p := testProvider("aws") 6951 p.PlanResourceChangeFn = testDiffFn 6952 6953 state := states.NewState() 6954 root := state.EnsureModule(addrs.RootModuleInstance) 6955 root.SetResourceInstanceCurrent( 6956 mustResourceInstanceAddr("aws_instance.foo").Resource, 6957 &states.ResourceInstanceObjectSrc{ 6958 Status: states.ObjectReady, 6959 AttrsJSON: []byte(`{"id":"i-bcd345"}`), 6960 }, 6961 mustProviderConfig(`provider["registry.terraform.io/hashicorp/aws"]`), 6962 ) 6963 root.SetResourceInstanceCurrent( 6964 mustResourceInstanceAddr("aws_instance.bar").Resource, 6965 &states.ResourceInstanceObjectSrc{ 6966 Status: states.ObjectReady, 6967 AttrsJSON: []byte(`{"id":"i-abc123"}`), 6968 Dependencies: []addrs.ConfigResource{mustConfigResourceAddr("aws_instance.foo")}, 6969 }, 6970 mustProviderConfig(`provider["registry.terraform.io/hashicorp/aws"]`), 6971 ) 6972 6973 ctx := testContext2(t, &ContextOpts{ 6974 Providers: map[addrs.Provider]providers.Factory{ 6975 addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p), 6976 }, 6977 }) 6978 6979 plan, diags := ctx.Plan(m, state, &PlanOpts{ 6980 Mode: plans.DestroyMode, 6981 Targets: []addrs.Targetable{ 6982 addrs.RootModuleInstance.Resource( 6983 addrs.ManagedResourceMode, "aws_instance", "foo", 6984 ), 6985 }, 6986 }) 6987 assertNoErrors(t, diags) 6988 6989 state, diags = ctx.Apply(plan, m) 6990 if diags.HasErrors() { 6991 t.Fatalf("diags: %s", diags.Err()) 6992 } 6993 6994 checkStateString(t, state, `<no state>`) 6995 } 6996 6997 // https://github.com/hashicorp/terraform/issues/4462 6998 func TestContext2Apply_targetedDestroyModule(t *testing.T) { 6999 m := testModule(t, "apply-targeted-module") 7000 p := testProvider("aws") 7001 p.PlanResourceChangeFn = testDiffFn 7002 7003 state := states.NewState() 7004 root := state.EnsureModule(addrs.RootModuleInstance) 7005 root.SetResourceInstanceCurrent( 7006 mustResourceInstanceAddr("aws_instance.foo").Resource, 7007 &states.ResourceInstanceObjectSrc{ 7008 Status: states.ObjectReady, 7009 AttrsJSON: []byte(`{"id":"i-bcd345"}`), 7010 }, 7011 mustProviderConfig(`provider["registry.terraform.io/hashicorp/aws"]`), 7012 ) 7013 root.SetResourceInstanceCurrent( 7014 mustResourceInstanceAddr("aws_instance.bar").Resource, 7015 &states.ResourceInstanceObjectSrc{ 7016 Status: states.ObjectReady, 7017 AttrsJSON: []byte(`{"id":"i-abc123"}`), 7018 }, 7019 mustProviderConfig(`provider["registry.terraform.io/hashicorp/aws"]`), 7020 ) 7021 child := state.EnsureModule(addrs.RootModuleInstance.Child("child", addrs.NoKey)) 7022 child.SetResourceInstanceCurrent( 7023 mustResourceInstanceAddr("aws_instance.foo").Resource, 7024 &states.ResourceInstanceObjectSrc{ 7025 Status: states.ObjectReady, 7026 AttrsJSON: []byte(`{"id":"i-bcd345"}`), 7027 }, 7028 mustProviderConfig(`provider["registry.terraform.io/hashicorp/aws"]`), 7029 ) 7030 child.SetResourceInstanceCurrent( 7031 mustResourceInstanceAddr("aws_instance.bar").Resource, 7032 &states.ResourceInstanceObjectSrc{ 7033 Status: states.ObjectReady, 7034 AttrsJSON: []byte(`{"id":"i-abc123"}`), 7035 }, 7036 mustProviderConfig(`provider["registry.terraform.io/hashicorp/aws"]`), 7037 ) 7038 7039 ctx := testContext2(t, &ContextOpts{ 7040 Providers: map[addrs.Provider]providers.Factory{ 7041 addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p), 7042 }, 7043 }) 7044 7045 plan, diags := ctx.Plan(m, state, &PlanOpts{ 7046 Mode: plans.DestroyMode, 7047 Targets: []addrs.Targetable{ 7048 addrs.RootModuleInstance.Child("child", addrs.NoKey).Resource( 7049 addrs.ManagedResourceMode, "aws_instance", "foo", 7050 ), 7051 }, 7052 }) 7053 assertNoErrors(t, diags) 7054 7055 state, diags = ctx.Apply(plan, m) 7056 if diags.HasErrors() { 7057 t.Fatalf("diags: %s", diags.Err()) 7058 } 7059 7060 checkStateString(t, state, ` 7061 aws_instance.bar: 7062 ID = i-abc123 7063 provider = provider["registry.terraform.io/hashicorp/aws"] 7064 aws_instance.foo: 7065 ID = i-bcd345 7066 provider = provider["registry.terraform.io/hashicorp/aws"] 7067 7068 module.child: 7069 aws_instance.bar: 7070 ID = i-abc123 7071 provider = provider["registry.terraform.io/hashicorp/aws"] 7072 `) 7073 } 7074 7075 func TestContext2Apply_targetedDestroyCountIndex(t *testing.T) { 7076 m := testModule(t, "apply-targeted-count") 7077 p := testProvider("aws") 7078 p.PlanResourceChangeFn = testDiffFn 7079 7080 foo := &states.ResourceInstanceObjectSrc{ 7081 Status: states.ObjectReady, 7082 AttrsJSON: []byte(`{"id":"i-bcd345"}`), 7083 } 7084 bar := &states.ResourceInstanceObjectSrc{ 7085 Status: states.ObjectReady, 7086 AttrsJSON: []byte(`{"id":"i-abc123"}`), 7087 } 7088 7089 state := states.NewState() 7090 root := state.EnsureModule(addrs.RootModuleInstance) 7091 root.SetResourceInstanceCurrent( 7092 mustResourceInstanceAddr("aws_instance.foo[0]").Resource, 7093 foo, 7094 mustProviderConfig(`provider["registry.terraform.io/hashicorp/aws"]`), 7095 ) 7096 root.SetResourceInstanceCurrent( 7097 mustResourceInstanceAddr("aws_instance.foo[1]").Resource, 7098 foo, 7099 mustProviderConfig(`provider["registry.terraform.io/hashicorp/aws"]`), 7100 ) 7101 root.SetResourceInstanceCurrent( 7102 mustResourceInstanceAddr("aws_instance.foo[2]").Resource, 7103 foo, 7104 mustProviderConfig(`provider["registry.terraform.io/hashicorp/aws"]`), 7105 ) 7106 root.SetResourceInstanceCurrent( 7107 mustResourceInstanceAddr("aws_instance.bar[0]").Resource, 7108 bar, 7109 mustProviderConfig(`provider["registry.terraform.io/hashicorp/aws"]`), 7110 ) 7111 root.SetResourceInstanceCurrent( 7112 mustResourceInstanceAddr("aws_instance.bar[1]").Resource, 7113 bar, 7114 mustProviderConfig(`provider["registry.terraform.io/hashicorp/aws"]`), 7115 ) 7116 root.SetResourceInstanceCurrent( 7117 mustResourceInstanceAddr("aws_instance.bar[2]").Resource, 7118 bar, 7119 mustProviderConfig(`provider["registry.terraform.io/hashicorp/aws"]`), 7120 ) 7121 7122 ctx := testContext2(t, &ContextOpts{ 7123 Providers: map[addrs.Provider]providers.Factory{ 7124 addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p), 7125 }, 7126 }) 7127 7128 plan, diags := ctx.Plan(m, state, &PlanOpts{ 7129 Mode: plans.DestroyMode, 7130 Targets: []addrs.Targetable{ 7131 addrs.RootModuleInstance.ResourceInstance( 7132 addrs.ManagedResourceMode, "aws_instance", "foo", addrs.IntKey(2), 7133 ), 7134 addrs.RootModuleInstance.ResourceInstance( 7135 addrs.ManagedResourceMode, "aws_instance", "bar", addrs.IntKey(1), 7136 ), 7137 }, 7138 }) 7139 assertNoErrors(t, diags) 7140 7141 state, diags = ctx.Apply(plan, m) 7142 if diags.HasErrors() { 7143 t.Fatalf("diags: %s", diags.Err()) 7144 } 7145 7146 checkStateString(t, state, ` 7147 aws_instance.bar.0: 7148 ID = i-abc123 7149 provider = provider["registry.terraform.io/hashicorp/aws"] 7150 aws_instance.bar.2: 7151 ID = i-abc123 7152 provider = provider["registry.terraform.io/hashicorp/aws"] 7153 aws_instance.foo.0: 7154 ID = i-bcd345 7155 provider = provider["registry.terraform.io/hashicorp/aws"] 7156 aws_instance.foo.1: 7157 ID = i-bcd345 7158 provider = provider["registry.terraform.io/hashicorp/aws"] 7159 `) 7160 } 7161 7162 func TestContext2Apply_targetedModule(t *testing.T) { 7163 m := testModule(t, "apply-targeted-module") 7164 p := testProvider("aws") 7165 p.PlanResourceChangeFn = testDiffFn 7166 p.ApplyResourceChangeFn = testApplyFn 7167 ctx := testContext2(t, &ContextOpts{ 7168 Providers: map[addrs.Provider]providers.Factory{ 7169 addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p), 7170 }, 7171 }) 7172 7173 plan, diags := ctx.Plan(m, states.NewState(), &PlanOpts{ 7174 Mode: plans.NormalMode, 7175 Targets: []addrs.Targetable{ 7176 addrs.RootModuleInstance.Child("child", addrs.NoKey), 7177 }, 7178 }) 7179 assertNoErrors(t, diags) 7180 7181 state, diags := ctx.Apply(plan, m) 7182 if diags.HasErrors() { 7183 t.Fatalf("diags: %s", diags.Err()) 7184 } 7185 7186 mod := state.Module(addrs.RootModuleInstance.Child("child", addrs.NoKey)) 7187 if mod == nil { 7188 t.Fatalf("no child module found in the state!\n\n%#v", state) 7189 } 7190 if len(mod.Resources) != 2 { 7191 t.Fatalf("expected 2 resources, got: %#v", mod.Resources) 7192 } 7193 7194 checkStateString(t, state, ` 7195 <no state> 7196 module.child: 7197 aws_instance.bar: 7198 ID = foo 7199 provider = provider["registry.terraform.io/hashicorp/aws"] 7200 num = 2 7201 type = aws_instance 7202 aws_instance.foo: 7203 ID = foo 7204 provider = provider["registry.terraform.io/hashicorp/aws"] 7205 num = 2 7206 type = aws_instance 7207 `) 7208 } 7209 7210 // GH-1858 7211 func TestContext2Apply_targetedModuleDep(t *testing.T) { 7212 m := testModule(t, "apply-targeted-module-dep") 7213 p := testProvider("aws") 7214 p.PlanResourceChangeFn = testDiffFn 7215 p.ApplyResourceChangeFn = testApplyFn 7216 ctx := testContext2(t, &ContextOpts{ 7217 Providers: map[addrs.Provider]providers.Factory{ 7218 addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p), 7219 }, 7220 }) 7221 7222 plan, diags := ctx.Plan(m, states.NewState(), &PlanOpts{ 7223 Mode: plans.NormalMode, 7224 Targets: []addrs.Targetable{ 7225 addrs.RootModuleInstance.Resource( 7226 addrs.ManagedResourceMode, "aws_instance", "foo", 7227 ), 7228 }, 7229 }) 7230 if diags.HasErrors() { 7231 t.Fatalf("diags: %s", diags.Err()) 7232 } else { 7233 t.Logf("Diff: %s", legacyDiffComparisonString(plan.Changes)) 7234 } 7235 7236 state, diags := ctx.Apply(plan, m) 7237 if diags.HasErrors() { 7238 t.Fatalf("diags: %s", diags.Err()) 7239 } 7240 7241 checkStateString(t, state, ` 7242 aws_instance.foo: 7243 ID = foo 7244 provider = provider["registry.terraform.io/hashicorp/aws"] 7245 foo = foo 7246 type = aws_instance 7247 7248 Dependencies: 7249 module.child.aws_instance.mod 7250 7251 module.child: 7252 aws_instance.mod: 7253 ID = foo 7254 provider = provider["registry.terraform.io/hashicorp/aws"] 7255 type = aws_instance 7256 7257 Outputs: 7258 7259 output = foo 7260 `) 7261 } 7262 7263 // GH-10911 untargeted outputs should not be in the graph, and therefore 7264 // not execute. 7265 func TestContext2Apply_targetedModuleUnrelatedOutputs(t *testing.T) { 7266 m := testModule(t, "apply-targeted-module-unrelated-outputs") 7267 p := testProvider("aws") 7268 p.PlanResourceChangeFn = testDiffFn 7269 p.ApplyResourceChangeFn = testApplyFn 7270 7271 state := states.NewState() 7272 _ = state.EnsureModule(addrs.RootModuleInstance.Child("child2", addrs.NoKey)) 7273 7274 ctx := testContext2(t, &ContextOpts{ 7275 Providers: map[addrs.Provider]providers.Factory{ 7276 addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p), 7277 }, 7278 }) 7279 7280 plan, diags := ctx.Plan(m, state, &PlanOpts{ 7281 Mode: plans.NormalMode, 7282 Targets: []addrs.Targetable{ 7283 addrs.RootModuleInstance.Child("child2", addrs.NoKey), 7284 }, 7285 }) 7286 assertNoErrors(t, diags) 7287 7288 s, diags := ctx.Apply(plan, m) 7289 if diags.HasErrors() { 7290 t.Fatalf("diags: %s", diags.Err()) 7291 } 7292 7293 // - module.child1's instance_id output is dropped because we don't preserve 7294 // non-root module outputs between runs (they can be recalculated from config) 7295 // - module.child2's instance_id is updated because its dependency is updated 7296 // - child2_id is updated because if its transitive dependency via module.child2 7297 checkStateString(t, s, ` 7298 <no state> 7299 Outputs: 7300 7301 child2_id = foo 7302 7303 module.child2: 7304 aws_instance.foo: 7305 ID = foo 7306 provider = provider["registry.terraform.io/hashicorp/aws"] 7307 type = aws_instance 7308 7309 Outputs: 7310 7311 instance_id = foo 7312 `) 7313 } 7314 7315 func TestContext2Apply_targetedModuleResource(t *testing.T) { 7316 m := testModule(t, "apply-targeted-module-resource") 7317 p := testProvider("aws") 7318 p.PlanResourceChangeFn = testDiffFn 7319 p.ApplyResourceChangeFn = testApplyFn 7320 ctx := testContext2(t, &ContextOpts{ 7321 Providers: map[addrs.Provider]providers.Factory{ 7322 addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p), 7323 }, 7324 }) 7325 7326 plan, diags := ctx.Plan(m, states.NewState(), &PlanOpts{ 7327 Mode: plans.NormalMode, 7328 Targets: []addrs.Targetable{ 7329 addrs.RootModuleInstance.Child("child", addrs.NoKey).Resource( 7330 addrs.ManagedResourceMode, "aws_instance", "foo", 7331 ), 7332 }, 7333 }) 7334 assertNoErrors(t, diags) 7335 7336 state, diags := ctx.Apply(plan, m) 7337 if diags.HasErrors() { 7338 t.Fatalf("diags: %s", diags.Err()) 7339 } 7340 7341 mod := state.Module(addrs.RootModuleInstance.Child("child", addrs.NoKey)) 7342 if mod == nil || len(mod.Resources) != 1 { 7343 t.Fatalf("expected 1 resource, got: %#v", mod) 7344 } 7345 7346 checkStateString(t, state, ` 7347 <no state> 7348 module.child: 7349 aws_instance.foo: 7350 ID = foo 7351 provider = provider["registry.terraform.io/hashicorp/aws"] 7352 num = 2 7353 type = aws_instance 7354 `) 7355 } 7356 7357 func TestContext2Apply_targetedResourceOrphanModule(t *testing.T) { 7358 m := testModule(t, "apply-targeted-resource-orphan-module") 7359 p := testProvider("aws") 7360 p.PlanResourceChangeFn = testDiffFn 7361 7362 state := states.NewState() 7363 child := state.EnsureModule(addrs.RootModuleInstance.Child("parent", addrs.NoKey)) 7364 child.SetResourceInstanceCurrent( 7365 mustResourceInstanceAddr("aws_instance.bar").Resource, 7366 &states.ResourceInstanceObjectSrc{ 7367 Status: states.ObjectReady, 7368 AttrsJSON: []byte(`{"type":"aws_instance"}`), 7369 }, 7370 mustProviderConfig(`provider["registry.terraform.io/hashicorp/aws"]`), 7371 ) 7372 7373 ctx := testContext2(t, &ContextOpts{ 7374 Providers: map[addrs.Provider]providers.Factory{ 7375 addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p), 7376 }, 7377 }) 7378 7379 plan, diags := ctx.Plan(m, state, &PlanOpts{ 7380 Mode: plans.NormalMode, 7381 Targets: []addrs.Targetable{ 7382 addrs.RootModuleInstance.Resource( 7383 addrs.ManagedResourceMode, "aws_instance", "foo", 7384 ), 7385 }, 7386 }) 7387 assertNoErrors(t, diags) 7388 7389 if _, diags := ctx.Apply(plan, m); diags.HasErrors() { 7390 t.Fatalf("apply errors: %s", diags.Err()) 7391 } 7392 } 7393 7394 func TestContext2Apply_unknownAttribute(t *testing.T) { 7395 m := testModule(t, "apply-unknown") 7396 p := testProvider("aws") 7397 p.PlanResourceChangeFn = func(req providers.PlanResourceChangeRequest) (resp providers.PlanResourceChangeResponse) { 7398 resp = testDiffFn(req) 7399 planned := resp.PlannedState.AsValueMap() 7400 planned["unknown"] = cty.UnknownVal(cty.String) 7401 resp.PlannedState = cty.ObjectVal(planned) 7402 return resp 7403 } 7404 p.ApplyResourceChangeFn = testApplyFn 7405 7406 p.GetProviderSchemaResponse = getProviderSchemaResponseFromProviderSchema(&ProviderSchema{ 7407 ResourceTypes: map[string]*configschema.Block{ 7408 "aws_instance": { 7409 Attributes: map[string]*configschema.Attribute{ 7410 "id": {Type: cty.String, Computed: true}, 7411 "num": {Type: cty.Number, Optional: true}, 7412 "unknown": {Type: cty.String, Computed: true}, 7413 "type": {Type: cty.String, Computed: true}, 7414 }, 7415 }, 7416 }, 7417 }) 7418 7419 ctx := testContext2(t, &ContextOpts{ 7420 Providers: map[addrs.Provider]providers.Factory{ 7421 addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p), 7422 }, 7423 }) 7424 7425 plan, diags := ctx.Plan(m, states.NewState(), DefaultPlanOpts) 7426 assertNoErrors(t, diags) 7427 7428 state, diags := ctx.Apply(plan, m) 7429 if !diags.HasErrors() { 7430 t.Error("should error, because attribute 'unknown' is still unknown after apply") 7431 } 7432 7433 actual := strings.TrimSpace(state.String()) 7434 expected := strings.TrimSpace(testTerraformApplyUnknownAttrStr) 7435 if actual != expected { 7436 t.Fatalf("wrong result\n\ngot:\n%s\n\nwant:\n%s", actual, expected) 7437 } 7438 } 7439 7440 func TestContext2Apply_unknownAttributeInterpolate(t *testing.T) { 7441 m := testModule(t, "apply-unknown-interpolate") 7442 p := testProvider("aws") 7443 p.PlanResourceChangeFn = testDiffFn 7444 ctx := testContext2(t, &ContextOpts{ 7445 Providers: map[addrs.Provider]providers.Factory{ 7446 addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p), 7447 }, 7448 }) 7449 7450 if _, diags := ctx.Plan(m, states.NewState(), DefaultPlanOpts); diags == nil { 7451 t.Fatal("should error") 7452 } 7453 } 7454 7455 func TestContext2Apply_vars(t *testing.T) { 7456 fixture := contextFixtureApplyVars(t) 7457 opts := fixture.ContextOpts() 7458 ctx := testContext2(t, opts) 7459 m := fixture.Config 7460 7461 diags := ctx.Validate(m) 7462 if len(diags) != 0 { 7463 t.Fatalf("bad: %s", diags.ErrWithWarnings()) 7464 } 7465 7466 variables := InputValues{ 7467 "foo": &InputValue{ 7468 Value: cty.StringVal("us-east-1"), 7469 SourceType: ValueFromCaller, 7470 }, 7471 "test_list": &InputValue{ 7472 Value: cty.ListVal([]cty.Value{ 7473 cty.StringVal("Hello"), 7474 cty.StringVal("World"), 7475 }), 7476 SourceType: ValueFromCaller, 7477 }, 7478 "test_map": &InputValue{ 7479 Value: cty.MapVal(map[string]cty.Value{ 7480 "Hello": cty.StringVal("World"), 7481 "Foo": cty.StringVal("Bar"), 7482 "Baz": cty.StringVal("Foo"), 7483 }), 7484 SourceType: ValueFromCaller, 7485 }, 7486 "amis": &InputValue{ 7487 Value: cty.MapVal(map[string]cty.Value{ 7488 "us-east-1": cty.StringVal("override"), 7489 }), 7490 SourceType: ValueFromCaller, 7491 }, 7492 } 7493 7494 plan, diags := ctx.Plan(m, states.NewState(), &PlanOpts{ 7495 Mode: plans.NormalMode, 7496 SetVariables: variables, 7497 }) 7498 assertNoErrors(t, diags) 7499 7500 state, diags := ctx.Apply(plan, m) 7501 if diags.HasErrors() { 7502 t.Fatalf("err: %s", diags.Err()) 7503 } 7504 7505 got := strings.TrimSpace(state.String()) 7506 want := strings.TrimSpace(testTerraformApplyVarsStr) 7507 if got != want { 7508 t.Errorf("wrong result\n\ngot:\n%s\n\nwant:\n%s", got, want) 7509 } 7510 } 7511 7512 func TestContext2Apply_varsEnv(t *testing.T) { 7513 fixture := contextFixtureApplyVarsEnv(t) 7514 opts := fixture.ContextOpts() 7515 ctx := testContext2(t, opts) 7516 m := fixture.Config 7517 7518 diags := ctx.Validate(m) 7519 if len(diags) != 0 { 7520 t.Fatalf("bad: %s", diags.ErrWithWarnings()) 7521 } 7522 7523 variables := InputValues{ 7524 "string": &InputValue{ 7525 Value: cty.StringVal("baz"), 7526 SourceType: ValueFromEnvVar, 7527 }, 7528 "list": &InputValue{ 7529 Value: cty.ListVal([]cty.Value{ 7530 cty.StringVal("Hello"), 7531 cty.StringVal("World"), 7532 }), 7533 SourceType: ValueFromEnvVar, 7534 }, 7535 "map": &InputValue{ 7536 Value: cty.MapVal(map[string]cty.Value{ 7537 "Hello": cty.StringVal("World"), 7538 "Foo": cty.StringVal("Bar"), 7539 "Baz": cty.StringVal("Foo"), 7540 }), 7541 SourceType: ValueFromEnvVar, 7542 }, 7543 } 7544 7545 plan, diags := ctx.Plan(m, states.NewState(), &PlanOpts{ 7546 Mode: plans.NormalMode, 7547 SetVariables: variables, 7548 }) 7549 assertNoErrors(t, diags) 7550 7551 state, diags := ctx.Apply(plan, m) 7552 if diags.HasErrors() { 7553 t.Fatalf("err: %s", diags.Err()) 7554 } 7555 7556 actual := strings.TrimSpace(state.String()) 7557 expected := strings.TrimSpace(testTerraformApplyVarsEnvStr) 7558 if actual != expected { 7559 t.Errorf("wrong result\n\ngot:\n%s\n\nwant:\n%s", actual, expected) 7560 } 7561 } 7562 7563 func TestContext2Apply_createBefore_depends(t *testing.T) { 7564 m := testModule(t, "apply-depends-create-before") 7565 h := new(HookRecordApplyOrder) 7566 p := testProvider("aws") 7567 p.PlanResourceChangeFn = testDiffFn 7568 p.ApplyResourceChangeFn = testApplyFn 7569 state := states.NewState() 7570 root := state.EnsureModule(addrs.RootModuleInstance) 7571 root.SetResourceInstanceCurrent( 7572 addrs.Resource{ 7573 Mode: addrs.ManagedResourceMode, 7574 Type: "aws_instance", 7575 Name: "web", 7576 }.Instance(addrs.NoKey), 7577 &states.ResourceInstanceObjectSrc{ 7578 Status: states.ObjectReady, 7579 AttrsJSON: []byte(`{"id":"bar","require_new":"ami-old"}`), 7580 }, 7581 addrs.AbsProviderConfig{ 7582 Provider: addrs.NewDefaultProvider("aws"), 7583 Module: addrs.RootModule, 7584 }, 7585 ) 7586 7587 root.SetResourceInstanceCurrent( 7588 addrs.Resource{ 7589 Mode: addrs.ManagedResourceMode, 7590 Type: "aws_instance", 7591 Name: "lb", 7592 }.Instance(addrs.NoKey), 7593 &states.ResourceInstanceObjectSrc{ 7594 Status: states.ObjectReady, 7595 AttrsJSON: []byte(`{"id":"baz","instance":"bar"}`), 7596 Dependencies: []addrs.ConfigResource{ 7597 { 7598 Resource: addrs.Resource{ 7599 Mode: addrs.ManagedResourceMode, 7600 Type: "aws_instance", 7601 Name: "web", 7602 }, 7603 Module: addrs.RootModule, 7604 }, 7605 }, 7606 }, 7607 addrs.AbsProviderConfig{ 7608 Provider: addrs.NewDefaultProvider("aws"), 7609 Module: addrs.RootModule, 7610 }, 7611 ) 7612 7613 ctx := testContext2(t, &ContextOpts{ 7614 Hooks: []Hook{h}, 7615 Providers: map[addrs.Provider]providers.Factory{ 7616 addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p), 7617 }, 7618 }) 7619 7620 plan, diags := ctx.Plan(m, state, DefaultPlanOpts) 7621 if diags.HasErrors() { 7622 logDiagnostics(t, diags) 7623 t.Fatal("plan failed") 7624 } else { 7625 t.Logf("plan:\n%s", legacyDiffComparisonString(plan.Changes)) 7626 } 7627 7628 h.Active = true 7629 state, diags = ctx.Apply(plan, m) 7630 if diags.HasErrors() { 7631 logDiagnostics(t, diags) 7632 t.Fatal("apply failed") 7633 } 7634 7635 mod := state.RootModule() 7636 if len(mod.Resources) < 2 { 7637 t.Logf("state after apply:\n%s", state.String()) 7638 t.Fatalf("only %d resources in root module; want at least 2", len(mod.Resources)) 7639 } 7640 7641 got := strings.TrimSpace(state.String()) 7642 want := strings.TrimSpace(testTerraformApplyDependsCreateBeforeStr) 7643 if got != want { 7644 t.Fatalf("wrong final state\ngot:\n%s\n\nwant:\n%s", got, want) 7645 } 7646 7647 // Test that things were managed _in the right order_ 7648 order := h.States 7649 7650 diffs := h.Diffs 7651 if !order[0].IsNull() || diffs[0].Action == plans.Delete { 7652 t.Fatalf("should create new instance first: %#v", order) 7653 } 7654 7655 if order[1].GetAttr("id").AsString() != "baz" { 7656 t.Fatalf("update must happen after create: %#v", order[1]) 7657 } 7658 7659 if order[2].GetAttr("id").AsString() != "bar" || diffs[2].Action != plans.Delete { 7660 t.Fatalf("destroy must happen after update: %#v", order[2]) 7661 } 7662 } 7663 7664 func TestContext2Apply_singleDestroy(t *testing.T) { 7665 m := testModule(t, "apply-depends-create-before") 7666 h := new(HookRecordApplyOrder) 7667 p := testProvider("aws") 7668 invokeCount := 0 7669 p.ApplyResourceChangeFn = func(req providers.ApplyResourceChangeRequest) providers.ApplyResourceChangeResponse { 7670 invokeCount++ 7671 switch invokeCount { 7672 case 1: 7673 if req.PlannedState.IsNull() { 7674 t.Fatalf("should not destroy") 7675 } 7676 if id := req.PlannedState.GetAttr("id"); id.IsKnown() { 7677 t.Fatalf("should not have ID") 7678 } 7679 case 2: 7680 if req.PlannedState.IsNull() { 7681 t.Fatalf("should not destroy") 7682 } 7683 if id := req.PlannedState.GetAttr("id"); id.AsString() != "baz" { 7684 t.Fatalf("should have id") 7685 } 7686 case 3: 7687 if !req.PlannedState.IsNull() { 7688 t.Fatalf("should destroy") 7689 } 7690 default: 7691 t.Fatalf("bad invoke count %d", invokeCount) 7692 } 7693 return testApplyFn(req) 7694 } 7695 7696 p.PlanResourceChangeFn = testDiffFn 7697 state := states.NewState() 7698 root := state.EnsureModule(addrs.RootModuleInstance) 7699 root.SetResourceInstanceCurrent( 7700 addrs.Resource{ 7701 Mode: addrs.ManagedResourceMode, 7702 Type: "aws_instance", 7703 Name: "web", 7704 }.Instance(addrs.NoKey), 7705 &states.ResourceInstanceObjectSrc{ 7706 Status: states.ObjectReady, 7707 AttrsJSON: []byte(`{"id":"bar","require_new":"ami-old"}`), 7708 }, 7709 addrs.AbsProviderConfig{ 7710 Provider: addrs.NewDefaultProvider("aws"), 7711 Module: addrs.RootModule, 7712 }, 7713 ) 7714 7715 root.SetResourceInstanceCurrent( 7716 addrs.Resource{ 7717 Mode: addrs.ManagedResourceMode, 7718 Type: "aws_instance", 7719 Name: "lb", 7720 }.Instance(addrs.NoKey), 7721 &states.ResourceInstanceObjectSrc{ 7722 Status: states.ObjectReady, 7723 AttrsJSON: []byte(`{"id":"baz","instance":"bar"}`), 7724 Dependencies: []addrs.ConfigResource{ 7725 { 7726 Resource: addrs.Resource{ 7727 Mode: addrs.ManagedResourceMode, 7728 Type: "aws_instance", 7729 Name: "web", 7730 }, 7731 Module: addrs.RootModule, 7732 }, 7733 }, 7734 }, 7735 addrs.AbsProviderConfig{ 7736 Provider: addrs.NewDefaultProvider("aws"), 7737 Module: addrs.RootModule, 7738 }, 7739 ) 7740 7741 ctx := testContext2(t, &ContextOpts{ 7742 Hooks: []Hook{h}, 7743 Providers: map[addrs.Provider]providers.Factory{ 7744 addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p), 7745 }, 7746 }) 7747 7748 plan, diags := ctx.Plan(m, state, DefaultPlanOpts) 7749 assertNoErrors(t, diags) 7750 7751 h.Active = true 7752 _, diags = ctx.Apply(plan, m) 7753 if diags.HasErrors() { 7754 t.Fatalf("diags: %s", diags.Err()) 7755 } 7756 7757 if invokeCount != 3 { 7758 t.Fatalf("bad: %d", invokeCount) 7759 } 7760 } 7761 7762 // GH-7824 7763 func TestContext2Apply_issue7824(t *testing.T) { 7764 p := testProvider("template") 7765 p.PlanResourceChangeFn = testDiffFn 7766 p.GetProviderSchemaResponse = getProviderSchemaResponseFromProviderSchema(&ProviderSchema{ 7767 ResourceTypes: map[string]*configschema.Block{ 7768 "template_file": { 7769 Attributes: map[string]*configschema.Attribute{ 7770 "template": {Type: cty.String, Optional: true}, 7771 "__template_requires_new": {Type: cty.Bool, Optional: true}, 7772 }, 7773 }, 7774 }, 7775 }) 7776 7777 m, snap := testModuleWithSnapshot(t, "issue-7824") 7778 7779 // Apply cleanly step 0 7780 ctx := testContext2(t, &ContextOpts{ 7781 Providers: map[addrs.Provider]providers.Factory{ 7782 addrs.NewDefaultProvider("template"): testProviderFuncFixed(p), 7783 }, 7784 }) 7785 7786 plan, diags := ctx.Plan(m, states.NewState(), DefaultPlanOpts) 7787 if diags.HasErrors() { 7788 t.Fatalf("err: %s", diags.Err()) 7789 } 7790 7791 // Write / Read plan to simulate running it through a Plan file 7792 ctxOpts, m, plan, err := contextOptsForPlanViaFile(snap, plan) 7793 if err != nil { 7794 t.Fatalf("failed to round-trip through planfile: %s", err) 7795 } 7796 7797 ctxOpts.Providers = 7798 map[addrs.Provider]providers.Factory{ 7799 addrs.NewDefaultProvider("template"): testProviderFuncFixed(p), 7800 } 7801 7802 ctx, diags = NewContext(ctxOpts) 7803 if diags.HasErrors() { 7804 t.Fatalf("err: %s", diags.Err()) 7805 } 7806 7807 _, diags = ctx.Apply(plan, m) 7808 if diags.HasErrors() { 7809 t.Fatalf("err: %s", diags.Err()) 7810 } 7811 } 7812 7813 // This deals with the situation where a splat expression is used referring 7814 // to another resource whose count is non-constant. 7815 func TestContext2Apply_issue5254(t *testing.T) { 7816 // Create a provider. We use "template" here just to match the repro 7817 // we got from the issue itself. 7818 p := testProvider("template") 7819 p.PlanResourceChangeFn = testDiffFn 7820 p.ApplyResourceChangeFn = testApplyFn 7821 p.GetProviderSchemaResponse = getProviderSchemaResponseFromProviderSchema(&ProviderSchema{ 7822 ResourceTypes: map[string]*configschema.Block{ 7823 "template_file": { 7824 Attributes: map[string]*configschema.Attribute{ 7825 "template": {Type: cty.String, Optional: true}, 7826 "__template_requires_new": {Type: cty.Bool, Optional: true}, 7827 "id": {Type: cty.String, Computed: true}, 7828 "type": {Type: cty.String, Computed: true}, 7829 }, 7830 }, 7831 }, 7832 }) 7833 7834 // Apply cleanly step 0 7835 m := testModule(t, "issue-5254/step-0") 7836 ctx := testContext2(t, &ContextOpts{ 7837 Providers: map[addrs.Provider]providers.Factory{ 7838 addrs.NewDefaultProvider("template"): testProviderFuncFixed(p), 7839 }, 7840 }) 7841 7842 plan, diags := ctx.Plan(m, states.NewState(), DefaultPlanOpts) 7843 if diags.HasErrors() { 7844 t.Fatalf("err: %s", diags.Err()) 7845 } 7846 7847 state, diags := ctx.Apply(plan, m) 7848 if diags.HasErrors() { 7849 t.Fatalf("err: %s", diags.Err()) 7850 } 7851 7852 m, snap := testModuleWithSnapshot(t, "issue-5254/step-1") 7853 7854 // Application success. Now make the modification and store a plan 7855 ctx = testContext2(t, &ContextOpts{ 7856 Providers: map[addrs.Provider]providers.Factory{ 7857 addrs.NewDefaultProvider("template"): testProviderFuncFixed(p), 7858 }, 7859 }) 7860 7861 plan, diags = ctx.Plan(m, state, DefaultPlanOpts) 7862 if diags.HasErrors() { 7863 t.Fatalf("err: %s", diags.Err()) 7864 } 7865 7866 // Write / Read plan to simulate running it through a Plan file 7867 ctxOpts, m, plan, err := contextOptsForPlanViaFile(snap, plan) 7868 if err != nil { 7869 t.Fatalf("failed to round-trip through planfile: %s", err) 7870 } 7871 7872 ctxOpts.Providers = map[addrs.Provider]providers.Factory{ 7873 addrs.NewDefaultProvider("template"): testProviderFuncFixed(p), 7874 } 7875 7876 ctx, diags = NewContext(ctxOpts) 7877 if diags.HasErrors() { 7878 t.Fatalf("err: %s", diags.Err()) 7879 } 7880 7881 state, diags = ctx.Apply(plan, m) 7882 if diags.HasErrors() { 7883 t.Fatalf("err: %s", diags.Err()) 7884 } 7885 7886 actual := strings.TrimSpace(state.String()) 7887 expected := strings.TrimSpace(` 7888 template_file.child: 7889 ID = foo 7890 provider = provider["registry.terraform.io/hashicorp/template"] 7891 __template_requires_new = true 7892 template = Hi 7893 type = template_file 7894 7895 Dependencies: 7896 template_file.parent 7897 template_file.parent.0: 7898 ID = foo 7899 provider = provider["registry.terraform.io/hashicorp/template"] 7900 template = Hi 7901 type = template_file 7902 `) 7903 if actual != expected { 7904 t.Fatalf("wrong final state\ngot:\n%s\n\nwant:\n%s", actual, expected) 7905 } 7906 } 7907 7908 func TestContext2Apply_targetedWithTaintedInState(t *testing.T) { 7909 p := testProvider("aws") 7910 p.PlanResourceChangeFn = testDiffFn 7911 p.ApplyResourceChangeFn = testApplyFn 7912 m, snap := testModuleWithSnapshot(t, "apply-tainted-targets") 7913 7914 state := states.NewState() 7915 root := state.EnsureModule(addrs.RootModuleInstance) 7916 root.SetResourceInstanceCurrent( 7917 mustResourceInstanceAddr("aws_instance.ifailedprovisioners").Resource, 7918 &states.ResourceInstanceObjectSrc{ 7919 Status: states.ObjectTainted, 7920 AttrsJSON: []byte(`{"id":"ifailedprovisioners"}`), 7921 }, 7922 mustProviderConfig(`provider["registry.terraform.io/hashicorp/aws"]`), 7923 ) 7924 7925 ctx := testContext2(t, &ContextOpts{ 7926 Providers: map[addrs.Provider]providers.Factory{ 7927 addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p), 7928 }, 7929 }) 7930 7931 plan, diags := ctx.Plan(m, state, &PlanOpts{ 7932 Mode: plans.NormalMode, 7933 Targets: []addrs.Targetable{ 7934 addrs.RootModuleInstance.Resource( 7935 addrs.ManagedResourceMode, "aws_instance", "iambeingadded", 7936 ), 7937 }, 7938 }) 7939 if diags.HasErrors() { 7940 t.Fatalf("err: %s", diags.Err()) 7941 } 7942 7943 // Write / Read plan to simulate running it through a Plan file 7944 ctxOpts, m, plan, err := contextOptsForPlanViaFile(snap, plan) 7945 if err != nil { 7946 t.Fatalf("failed to round-trip through planfile: %s", err) 7947 } 7948 7949 ctxOpts.Providers = map[addrs.Provider]providers.Factory{ 7950 addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p), 7951 } 7952 7953 ctx, diags = NewContext(ctxOpts) 7954 if diags.HasErrors() { 7955 t.Fatalf("err: %s", diags.Err()) 7956 } 7957 7958 s, diags := ctx.Apply(plan, m) 7959 if diags.HasErrors() { 7960 t.Fatalf("err: %s", diags.Err()) 7961 } 7962 7963 actual := strings.TrimSpace(s.String()) 7964 expected := strings.TrimSpace(` 7965 aws_instance.iambeingadded: 7966 ID = foo 7967 provider = provider["registry.terraform.io/hashicorp/aws"] 7968 type = aws_instance 7969 aws_instance.ifailedprovisioners: (tainted) 7970 ID = ifailedprovisioners 7971 provider = provider["registry.terraform.io/hashicorp/aws"] 7972 `) 7973 if actual != expected { 7974 t.Fatalf("expected state: \n%s\ngot: \n%s", expected, actual) 7975 } 7976 } 7977 7978 // Higher level test exposing the bug this covers in 7979 // TestResource_ignoreChangesRequired 7980 func TestContext2Apply_ignoreChangesCreate(t *testing.T) { 7981 m := testModule(t, "apply-ignore-changes-create") 7982 p := testProvider("aws") 7983 p.PlanResourceChangeFn = testDiffFn 7984 p.ApplyResourceChangeFn = testApplyFn 7985 7986 instanceSchema := p.GetProviderSchemaResponse.ResourceTypes["aws_instance"].Block 7987 instanceSchema.Attributes["required_field"] = &configschema.Attribute{ 7988 Type: cty.String, 7989 Required: true, 7990 } 7991 7992 ctx := testContext2(t, &ContextOpts{ 7993 Providers: map[addrs.Provider]providers.Factory{ 7994 addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p), 7995 }, 7996 }) 7997 7998 plan, diags := ctx.Plan(m, states.NewState(), DefaultPlanOpts) 7999 if diags.HasErrors() { 8000 t.Fatalf("diags: %s", diags.Err()) 8001 } else { 8002 t.Logf(legacyDiffComparisonString(plan.Changes)) 8003 } 8004 8005 state, diags := ctx.Apply(plan, m) 8006 if diags.HasErrors() { 8007 t.Fatalf("diags: %s", diags.Err()) 8008 } 8009 8010 mod := state.RootModule() 8011 if len(mod.Resources) != 1 { 8012 t.Fatalf("bad: %s", state) 8013 } 8014 8015 actual := strings.TrimSpace(state.String()) 8016 // Expect no changes from original state 8017 expected := strings.TrimSpace(` 8018 aws_instance.foo: 8019 ID = foo 8020 provider = provider["registry.terraform.io/hashicorp/aws"] 8021 required_field = set 8022 type = aws_instance 8023 `) 8024 if actual != expected { 8025 t.Fatalf("expected:\n%s\ngot:\n%s", expected, actual) 8026 } 8027 } 8028 8029 func TestContext2Apply_ignoreChangesWithDep(t *testing.T) { 8030 m := testModule(t, "apply-ignore-changes-dep") 8031 p := testProvider("aws") 8032 8033 p.PlanResourceChangeFn = func(req providers.PlanResourceChangeRequest) (resp providers.PlanResourceChangeResponse) { 8034 resp.PlannedState = req.ProposedNewState 8035 8036 switch req.TypeName { 8037 case "aws_instance": 8038 resp.RequiresReplace = append(resp.RequiresReplace, cty.Path{cty.GetAttrStep{Name: "ami"}}) 8039 case "aws_eip": 8040 return testDiffFn(req) 8041 default: 8042 t.Fatalf("Unexpected type: %s", req.TypeName) 8043 } 8044 return 8045 } 8046 8047 state := states.NewState() 8048 root := state.EnsureModule(addrs.RootModuleInstance) 8049 root.SetResourceInstanceCurrent( 8050 mustResourceInstanceAddr("aws_instance.foo[0]").Resource, 8051 &states.ResourceInstanceObjectSrc{ 8052 Status: states.ObjectReady, 8053 AttrsJSON: []byte(`{"id":"i-abc123","ami":"ami-abcd1234"}`), 8054 }, 8055 mustProviderConfig(`provider["registry.terraform.io/hashicorp/aws"]`), 8056 ) 8057 root.SetResourceInstanceCurrent( 8058 mustResourceInstanceAddr("aws_instance.foo[1]").Resource, 8059 &states.ResourceInstanceObjectSrc{ 8060 Status: states.ObjectReady, 8061 AttrsJSON: []byte(`{"id":"i-bcd234","ami":"i-bcd234"}`), 8062 }, 8063 mustProviderConfig(`provider["registry.terraform.io/hashicorp/aws"]`), 8064 ) 8065 root.SetResourceInstanceCurrent( 8066 mustResourceInstanceAddr("aws_eip.foo[0]").Resource, 8067 &states.ResourceInstanceObjectSrc{ 8068 Status: states.ObjectReady, 8069 AttrsJSON: []byte(`{"id":"eip-abc123","instance":"i-abc123"}`), 8070 Dependencies: []addrs.ConfigResource{ 8071 { 8072 Resource: addrs.Resource{ 8073 Mode: addrs.ManagedResourceMode, 8074 Type: "aws_instance", 8075 Name: "foo", 8076 }, 8077 Module: addrs.RootModule, 8078 }, 8079 }, 8080 }, 8081 mustProviderConfig(`provider["registry.terraform.io/hashicorp/aws"]`), 8082 ) 8083 root.SetResourceInstanceCurrent( 8084 mustResourceInstanceAddr("aws_eip.foo[1]").Resource, 8085 &states.ResourceInstanceObjectSrc{ 8086 Status: states.ObjectReady, 8087 AttrsJSON: []byte(`{"id":"eip-bcd234","instance":"i-bcd234"}`), 8088 Dependencies: []addrs.ConfigResource{ 8089 { 8090 Resource: addrs.Resource{ 8091 Mode: addrs.ManagedResourceMode, 8092 Type: "aws_instance", 8093 Name: "foo", 8094 }, 8095 Module: addrs.RootModule, 8096 }, 8097 }, 8098 }, 8099 mustProviderConfig(`provider["registry.terraform.io/hashicorp/aws"]`), 8100 ) 8101 8102 ctx := testContext2(t, &ContextOpts{ 8103 Providers: map[addrs.Provider]providers.Factory{ 8104 addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p), 8105 }, 8106 }) 8107 8108 plan, diags := ctx.Plan(m, state.DeepCopy(), DefaultPlanOpts) 8109 assertNoErrors(t, diags) 8110 8111 s, diags := ctx.Apply(plan, m) 8112 assertNoErrors(t, diags) 8113 8114 actual := strings.TrimSpace(s.String()) 8115 expected := strings.TrimSpace(state.String()) 8116 if actual != expected { 8117 t.Fatalf("expected:\n%s\n\ngot:\n%s", expected, actual) 8118 } 8119 } 8120 8121 func TestContext2Apply_ignoreChangesAll(t *testing.T) { 8122 m := testModule(t, "apply-ignore-changes-all") 8123 p := testProvider("aws") 8124 p.PlanResourceChangeFn = testDiffFn 8125 p.ApplyResourceChangeFn = testApplyFn 8126 8127 instanceSchema := p.GetProviderSchemaResponse.ResourceTypes["aws_instance"].Block 8128 instanceSchema.Attributes["required_field"] = &configschema.Attribute{ 8129 Type: cty.String, 8130 Required: true, 8131 } 8132 8133 ctx := testContext2(t, &ContextOpts{ 8134 Providers: map[addrs.Provider]providers.Factory{ 8135 addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p), 8136 }, 8137 }) 8138 8139 plan, diags := ctx.Plan(m, states.NewState(), DefaultPlanOpts) 8140 if diags.HasErrors() { 8141 logDiagnostics(t, diags) 8142 t.Fatal("plan failed") 8143 } else { 8144 t.Logf(legacyDiffComparisonString(plan.Changes)) 8145 } 8146 8147 state, diags := ctx.Apply(plan, m) 8148 assertNoErrors(t, diags) 8149 8150 mod := state.RootModule() 8151 if len(mod.Resources) != 1 { 8152 t.Fatalf("bad: %s", state) 8153 } 8154 8155 actual := strings.TrimSpace(state.String()) 8156 // Expect no changes from original state 8157 expected := strings.TrimSpace(` 8158 aws_instance.foo: 8159 ID = foo 8160 provider = provider["registry.terraform.io/hashicorp/aws"] 8161 required_field = set 8162 type = aws_instance 8163 `) 8164 if actual != expected { 8165 t.Fatalf("expected:\n%s\ngot:\n%s", expected, actual) 8166 } 8167 } 8168 8169 // https://github.com/hashicorp/terraform/issues/7378 8170 func TestContext2Apply_destroyNestedModuleWithAttrsReferencingResource(t *testing.T) { 8171 m, snap := testModuleWithSnapshot(t, "apply-destroy-nested-module-with-attrs") 8172 p := testProvider("null") 8173 p.PlanResourceChangeFn = testDiffFn 8174 8175 var state *states.State 8176 { 8177 ctx := testContext2(t, &ContextOpts{ 8178 Providers: map[addrs.Provider]providers.Factory{ 8179 addrs.NewDefaultProvider("null"): testProviderFuncFixed(p), 8180 }, 8181 }) 8182 8183 // First plan and apply a create operation 8184 plan, diags := ctx.Plan(m, states.NewState(), DefaultPlanOpts) 8185 assertNoErrors(t, diags) 8186 8187 state, diags = ctx.Apply(plan, m) 8188 if diags.HasErrors() { 8189 t.Fatalf("apply err: %s", diags.Err()) 8190 } 8191 } 8192 8193 { 8194 ctx := testContext2(t, &ContextOpts{ 8195 Providers: map[addrs.Provider]providers.Factory{ 8196 addrs.NewDefaultProvider("null"): testProviderFuncFixed(p), 8197 }, 8198 }) 8199 8200 plan, diags := ctx.Plan(m, state, &PlanOpts{ 8201 Mode: plans.DestroyMode, 8202 }) 8203 if diags.HasErrors() { 8204 t.Fatalf("destroy plan err: %s", diags.Err()) 8205 } 8206 8207 ctxOpts, m, plan, err := contextOptsForPlanViaFile(snap, plan) 8208 if err != nil { 8209 t.Fatalf("failed to round-trip through planfile: %s", err) 8210 } 8211 8212 ctxOpts.Providers = map[addrs.Provider]providers.Factory{ 8213 addrs.NewDefaultProvider("null"): testProviderFuncFixed(p), 8214 } 8215 8216 ctx, diags = NewContext(ctxOpts) 8217 if diags.HasErrors() { 8218 t.Fatalf("err: %s", diags.Err()) 8219 } 8220 8221 state, diags = ctx.Apply(plan, m) 8222 if diags.HasErrors() { 8223 t.Fatalf("destroy apply err: %s", diags.Err()) 8224 } 8225 } 8226 8227 if !state.Empty() { 8228 t.Fatalf("state after apply: %s\nwant empty state", spew.Sdump(state)) 8229 } 8230 } 8231 8232 // If a data source explicitly depends on another resource, it's because we need 8233 // that resource to be applied first. 8234 func TestContext2Apply_dataDependsOn(t *testing.T) { 8235 p := testProvider("null") 8236 m := testModuleInline(t, map[string]string{ 8237 "main.tf": ` 8238 resource "null_instance" "write" { 8239 foo = "attribute" 8240 } 8241 8242 data "null_data_source" "read" { 8243 count = 1 8244 depends_on = ["null_instance.write"] 8245 } 8246 8247 resource "null_instance" "depends" { 8248 foo = data.null_data_source.read[0].foo 8249 } 8250 `}) 8251 8252 ctx := testContext2(t, &ContextOpts{ 8253 Providers: map[addrs.Provider]providers.Factory{ 8254 addrs.NewDefaultProvider("null"): testProviderFuncFixed(p), 8255 }, 8256 }) 8257 8258 // the "provisioner" here writes to this variable, because the intent is to 8259 // create a dependency which can't be viewed through the graph, and depends 8260 // solely on the configuration providing "depends_on" 8261 provisionerOutput := "" 8262 8263 p.ApplyResourceChangeFn = func(req providers.ApplyResourceChangeRequest) providers.ApplyResourceChangeResponse { 8264 // the side effect of the resource being applied 8265 provisionerOutput = "APPLIED" 8266 return testApplyFn(req) 8267 } 8268 8269 p.PlanResourceChangeFn = testDiffFn 8270 p.ReadDataSourceFn = func(req providers.ReadDataSourceRequest) providers.ReadDataSourceResponse { 8271 return providers.ReadDataSourceResponse{ 8272 State: cty.ObjectVal(map[string]cty.Value{ 8273 "id": cty.StringVal("boop"), 8274 "foo": cty.StringVal(provisionerOutput), 8275 }), 8276 } 8277 } 8278 8279 plan, diags := ctx.Plan(m, states.NewState(), DefaultPlanOpts) 8280 assertNoErrors(t, diags) 8281 8282 state, diags := ctx.Apply(plan, m) 8283 assertNoErrors(t, diags) 8284 8285 root := state.Module(addrs.RootModuleInstance) 8286 is := root.ResourceInstance(addrs.Resource{ 8287 Mode: addrs.DataResourceMode, 8288 Type: "null_data_source", 8289 Name: "read", 8290 }.Instance(addrs.IntKey(0))) 8291 if is == nil { 8292 t.Fatal("data resource instance is not present in state; should be") 8293 } 8294 var attrs map[string]interface{} 8295 err := json.Unmarshal(is.Current.AttrsJSON, &attrs) 8296 if err != nil { 8297 t.Fatal(err) 8298 } 8299 actual := attrs["foo"] 8300 expected := "APPLIED" 8301 if actual != expected { 8302 t.Fatalf("bad:\n%s", strings.TrimSpace(state.String())) 8303 } 8304 8305 // run another plan to make sure the data source doesn't show as a change 8306 plan, diags = ctx.Plan(m, state, DefaultPlanOpts) 8307 assertNoErrors(t, diags) 8308 8309 for _, c := range plan.Changes.Resources { 8310 if c.Action != plans.NoOp { 8311 t.Fatalf("unexpected change for %s", c.Addr) 8312 } 8313 } 8314 8315 // now we cause a change in the first resource, which should trigger a plan 8316 // in the data source, and the resource that depends on the data source 8317 // must plan a change as well. 8318 m = testModuleInline(t, map[string]string{ 8319 "main.tf": ` 8320 resource "null_instance" "write" { 8321 foo = "new" 8322 } 8323 8324 data "null_data_source" "read" { 8325 depends_on = ["null_instance.write"] 8326 } 8327 8328 resource "null_instance" "depends" { 8329 foo = data.null_data_source.read.foo 8330 } 8331 `}) 8332 8333 p.ApplyResourceChangeFn = func(req providers.ApplyResourceChangeRequest) providers.ApplyResourceChangeResponse { 8334 // the side effect of the resource being applied 8335 provisionerOutput = "APPLIED_AGAIN" 8336 return testApplyFn(req) 8337 } 8338 8339 ctx = testContext2(t, &ContextOpts{ 8340 Providers: map[addrs.Provider]providers.Factory{ 8341 addrs.NewDefaultProvider("null"): testProviderFuncFixed(p), 8342 }, 8343 }) 8344 8345 plan, diags = ctx.Plan(m, state, DefaultPlanOpts) 8346 assertNoErrors(t, diags) 8347 8348 expectedChanges := map[string]plans.Action{ 8349 "null_instance.write": plans.Update, 8350 "data.null_data_source.read": plans.Read, 8351 "null_instance.depends": plans.Update, 8352 } 8353 8354 for _, c := range plan.Changes.Resources { 8355 if c.Action != expectedChanges[c.Addr.String()] { 8356 t.Errorf("unexpected %s for %s", c.Action, c.Addr) 8357 } 8358 } 8359 } 8360 8361 func TestContext2Apply_terraformWorkspace(t *testing.T) { 8362 m := testModule(t, "apply-terraform-workspace") 8363 p := testProvider("aws") 8364 p.PlanResourceChangeFn = testDiffFn 8365 8366 ctx := testContext2(t, &ContextOpts{ 8367 Meta: &ContextMeta{Env: "foo"}, 8368 Providers: map[addrs.Provider]providers.Factory{ 8369 addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p), 8370 }, 8371 }) 8372 8373 plan, diags := ctx.Plan(m, states.NewState(), DefaultPlanOpts) 8374 assertNoErrors(t, diags) 8375 8376 state, diags := ctx.Apply(plan, m) 8377 if diags.HasErrors() { 8378 t.Fatalf("diags: %s", diags.Err()) 8379 } 8380 8381 actual := state.RootModule().OutputValues["output"] 8382 expected := cty.StringVal("foo") 8383 if actual == nil || actual.Value != expected { 8384 t.Fatalf("wrong value\ngot: %#v\nwant: %#v", actual.Value, expected) 8385 } 8386 } 8387 8388 // verify that multiple config references only create a single depends_on entry 8389 func TestContext2Apply_multiRef(t *testing.T) { 8390 m := testModule(t, "apply-multi-ref") 8391 p := testProvider("aws") 8392 p.PlanResourceChangeFn = testDiffFn 8393 ctx := testContext2(t, &ContextOpts{ 8394 Providers: map[addrs.Provider]providers.Factory{ 8395 addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p), 8396 }, 8397 }) 8398 8399 plan, diags := ctx.Plan(m, states.NewState(), DefaultPlanOpts) 8400 assertNoErrors(t, diags) 8401 8402 state, diags := ctx.Apply(plan, m) 8403 if diags.HasErrors() { 8404 t.Fatalf("err: %s", diags.Err()) 8405 } 8406 8407 deps := state.Modules[""].Resources["aws_instance.other"].Instances[addrs.NoKey].Current.Dependencies 8408 if len(deps) != 1 || deps[0].String() != "aws_instance.create" { 8409 t.Fatalf("expected 1 depends_on entry for aws_instance.create, got %q", deps) 8410 } 8411 } 8412 8413 func TestContext2Apply_targetedModuleRecursive(t *testing.T) { 8414 m := testModule(t, "apply-targeted-module-recursive") 8415 p := testProvider("aws") 8416 p.PlanResourceChangeFn = testDiffFn 8417 p.ApplyResourceChangeFn = testApplyFn 8418 ctx := testContext2(t, &ContextOpts{ 8419 Providers: map[addrs.Provider]providers.Factory{ 8420 addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p), 8421 }, 8422 }) 8423 8424 plan, diags := ctx.Plan(m, states.NewState(), &PlanOpts{ 8425 Mode: plans.NormalMode, 8426 Targets: []addrs.Targetable{ 8427 addrs.RootModuleInstance.Child("child", addrs.NoKey), 8428 }, 8429 }) 8430 assertNoErrors(t, diags) 8431 8432 state, diags := ctx.Apply(plan, m) 8433 if diags.HasErrors() { 8434 t.Fatalf("err: %s", diags.Err()) 8435 } 8436 8437 mod := state.Module( 8438 addrs.RootModuleInstance.Child("child", addrs.NoKey).Child("subchild", addrs.NoKey), 8439 ) 8440 if mod == nil { 8441 t.Fatalf("no subchild module found in the state!\n\n%#v", state) 8442 } 8443 if len(mod.Resources) != 1 { 8444 t.Fatalf("expected 1 resources, got: %#v", mod.Resources) 8445 } 8446 8447 checkStateString(t, state, ` 8448 <no state> 8449 module.child.subchild: 8450 aws_instance.foo: 8451 ID = foo 8452 provider = provider["registry.terraform.io/hashicorp/aws"] 8453 num = 2 8454 type = aws_instance 8455 `) 8456 } 8457 8458 func TestContext2Apply_localVal(t *testing.T) { 8459 m := testModule(t, "apply-local-val") 8460 ctx := testContext2(t, &ContextOpts{ 8461 Providers: map[addrs.Provider]providers.Factory{}, 8462 }) 8463 8464 plan, diags := ctx.Plan(m, states.NewState(), DefaultPlanOpts) 8465 assertNoErrors(t, diags) 8466 8467 state, diags := ctx.Apply(plan, m) 8468 if diags.HasErrors() { 8469 t.Fatalf("error during apply: %s", diags.Err()) 8470 } 8471 8472 got := strings.TrimSpace(state.String()) 8473 want := strings.TrimSpace(` 8474 <no state> 8475 Outputs: 8476 8477 result_1 = hello 8478 result_3 = hello world 8479 `) 8480 if got != want { 8481 t.Fatalf("wrong final state\ngot:\n%s\nwant:\n%s", got, want) 8482 } 8483 } 8484 8485 func TestContext2Apply_destroyWithLocals(t *testing.T) { 8486 m := testModule(t, "apply-destroy-with-locals") 8487 p := testProvider("aws") 8488 p.PlanResourceChangeFn = testDiffFn 8489 8490 state := states.NewState() 8491 root := state.EnsureModule(addrs.RootModuleInstance) 8492 root.SetResourceInstanceCurrent( 8493 mustResourceInstanceAddr("aws_instance.foo").Resource, 8494 &states.ResourceInstanceObjectSrc{ 8495 Status: states.ObjectReady, 8496 AttrsJSON: []byte(`{"id":"foo"}`), 8497 }, 8498 mustProviderConfig(`provider["registry.terraform.io/hashicorp/aws"]`), 8499 ) 8500 root.SetOutputValue("name", cty.StringVal("test-bar"), false) 8501 8502 ctx := testContext2(t, &ContextOpts{ 8503 Providers: map[addrs.Provider]providers.Factory{ 8504 addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p), 8505 }, 8506 }) 8507 8508 plan, diags := ctx.Plan(m, state, &PlanOpts{ 8509 Mode: plans.DestroyMode, 8510 }) 8511 assertNoErrors(t, diags) 8512 8513 s, diags := ctx.Apply(plan, m) 8514 if diags.HasErrors() { 8515 t.Fatalf("error during apply: %s", diags.Err()) 8516 } 8517 8518 got := strings.TrimSpace(s.String()) 8519 want := strings.TrimSpace(`<no state>`) 8520 if got != want { 8521 t.Fatalf("wrong final state\ngot:\n%s\nwant:\n%s", got, want) 8522 } 8523 } 8524 8525 func TestContext2Apply_providerWithLocals(t *testing.T) { 8526 m := testModule(t, "provider-with-locals") 8527 p := testProvider("aws") 8528 8529 providerRegion := "" 8530 // this should not be overridden during destroy 8531 p.ConfigureProviderFn = func(req providers.ConfigureProviderRequest) (resp providers.ConfigureProviderResponse) { 8532 val := req.Config.GetAttr("region") 8533 if !val.IsNull() { 8534 providerRegion = val.AsString() 8535 } 8536 8537 return 8538 } 8539 8540 p.PlanResourceChangeFn = testDiffFn 8541 ctx := testContext2(t, &ContextOpts{ 8542 Providers: map[addrs.Provider]providers.Factory{ 8543 addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p), 8544 }, 8545 }) 8546 8547 plan, diags := ctx.Plan(m, states.NewState(), DefaultPlanOpts) 8548 assertNoErrors(t, diags) 8549 8550 state, diags := ctx.Apply(plan, m) 8551 if diags.HasErrors() { 8552 t.Fatalf("err: %s", diags.Err()) 8553 } 8554 8555 ctx = testContext2(t, &ContextOpts{ 8556 Providers: map[addrs.Provider]providers.Factory{ 8557 addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p), 8558 }, 8559 }) 8560 8561 plan, diags = ctx.Plan(m, state, &PlanOpts{ 8562 Mode: plans.DestroyMode, 8563 }) 8564 assertNoErrors(t, diags) 8565 8566 state, diags = ctx.Apply(plan, m) 8567 if diags.HasErrors() { 8568 t.Fatalf("err: %s", diags.Err()) 8569 } 8570 8571 if state.HasResources() { 8572 t.Fatal("expected no state, got:", state) 8573 } 8574 8575 if providerRegion != "bar" { 8576 t.Fatalf("expected region %q, got: %q", "bar", providerRegion) 8577 } 8578 } 8579 8580 func TestContext2Apply_destroyWithProviders(t *testing.T) { 8581 m := testModule(t, "destroy-module-with-provider") 8582 p := testProvider("aws") 8583 p.PlanResourceChangeFn = testDiffFn 8584 8585 state := states.NewState() 8586 removed := state.EnsureModule(addrs.RootModuleInstance.Child("mod", addrs.NoKey).Child("removed", addrs.NoKey)) 8587 removed.SetResourceInstanceCurrent( 8588 mustResourceInstanceAddr("aws_instance.child").Resource, 8589 &states.ResourceInstanceObjectSrc{ 8590 Status: states.ObjectReady, 8591 AttrsJSON: []byte(`{"id":"bar"}`), 8592 }, 8593 mustProviderConfig(`provider["registry.terraform.io/hashicorp/aws"].baz`), 8594 ) 8595 8596 ctx := testContext2(t, &ContextOpts{ 8597 Providers: map[addrs.Provider]providers.Factory{ 8598 addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p), 8599 }, 8600 }) 8601 8602 // test that we can't destroy if the provider is missing 8603 if _, diags := ctx.Plan(m, state, &PlanOpts{Mode: plans.DestroyMode}); diags == nil { 8604 t.Fatal("expected plan error, provider.aws.baz doesn't exist") 8605 } 8606 8607 // correct the state 8608 state.Modules["module.mod.module.removed"].Resources["aws_instance.child"].ProviderConfig = mustProviderConfig(`provider["registry.terraform.io/hashicorp/aws"].bar`) 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 state, diags = ctx.Apply(plan, m) 8622 if diags.HasErrors() { 8623 t.Fatalf("error during apply: %s", diags.Err()) 8624 } 8625 8626 got := strings.TrimSpace(state.String()) 8627 8628 want := strings.TrimSpace("<no state>") 8629 if got != want { 8630 t.Fatalf("wrong final state\ngot:\n%s\nwant:\n%s", got, want) 8631 } 8632 } 8633 8634 func TestContext2Apply_providersFromState(t *testing.T) { 8635 m := configs.NewEmptyConfig() 8636 p := testProvider("aws") 8637 p.PlanResourceChangeFn = testDiffFn 8638 8639 implicitProviderState := states.NewState() 8640 impRoot := implicitProviderState.EnsureModule(addrs.RootModuleInstance) 8641 impRoot.SetResourceInstanceCurrent( 8642 mustResourceInstanceAddr("aws_instance.a").Resource, 8643 &states.ResourceInstanceObjectSrc{ 8644 Status: states.ObjectReady, 8645 AttrsJSON: []byte(`{"id":"bar"}`), 8646 }, 8647 mustProviderConfig(`provider["registry.terraform.io/hashicorp/aws"]`), 8648 ) 8649 8650 aliasedProviderState := states.NewState() 8651 aliasRoot := aliasedProviderState.EnsureModule(addrs.RootModuleInstance) 8652 aliasRoot.SetResourceInstanceCurrent( 8653 mustResourceInstanceAddr("aws_instance.a").Resource, 8654 &states.ResourceInstanceObjectSrc{ 8655 Status: states.ObjectReady, 8656 AttrsJSON: []byte(`{"id":"bar"}`), 8657 }, 8658 mustProviderConfig(`provider["registry.terraform.io/hashicorp/aws"].bar`), 8659 ) 8660 8661 moduleProviderState := states.NewState() 8662 moduleProviderRoot := moduleProviderState.EnsureModule(addrs.RootModuleInstance) 8663 moduleProviderRoot.SetResourceInstanceCurrent( 8664 mustResourceInstanceAddr("aws_instance.a").Resource, 8665 &states.ResourceInstanceObjectSrc{ 8666 Status: states.ObjectReady, 8667 AttrsJSON: []byte(`{"id":"bar"}`), 8668 }, 8669 mustProviderConfig(`module.child.provider["registry.terraform.io/hashicorp/aws"]`), 8670 ) 8671 8672 for _, tc := range []struct { 8673 name string 8674 state *states.State 8675 output string 8676 err bool 8677 }{ 8678 { 8679 name: "add implicit provider", 8680 state: implicitProviderState, 8681 err: false, 8682 output: "<no state>", 8683 }, 8684 8685 // an aliased provider must be in the config to remove a resource 8686 { 8687 name: "add aliased provider", 8688 state: aliasedProviderState, 8689 err: true, 8690 }, 8691 8692 // a provider in a module implies some sort of config, so this isn't 8693 // allowed even without an alias 8694 { 8695 name: "add unaliased module provider", 8696 state: moduleProviderState, 8697 err: true, 8698 }, 8699 } { 8700 t.Run(tc.name, func(t *testing.T) { 8701 ctx := testContext2(t, &ContextOpts{ 8702 Providers: map[addrs.Provider]providers.Factory{ 8703 addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p), 8704 }, 8705 }) 8706 8707 plan, diags := ctx.Plan(m, tc.state, DefaultPlanOpts) 8708 if tc.err { 8709 if diags == nil { 8710 t.Fatal("expected error") 8711 } else { 8712 return 8713 } 8714 } 8715 if !tc.err && diags.HasErrors() { 8716 t.Fatal(diags.Err()) 8717 } 8718 8719 state, diags := ctx.Apply(plan, m) 8720 if diags.HasErrors() { 8721 t.Fatalf("diags: %s", diags.Err()) 8722 } 8723 8724 checkStateString(t, state, "<no state>") 8725 8726 }) 8727 } 8728 } 8729 8730 func TestContext2Apply_plannedInterpolatedCount(t *testing.T) { 8731 m, snap := testModuleWithSnapshot(t, "apply-interpolated-count") 8732 8733 p := testProvider("aws") 8734 p.PlanResourceChangeFn = testDiffFn 8735 8736 Providers := map[addrs.Provider]providers.Factory{ 8737 addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p), 8738 } 8739 8740 state := states.NewState() 8741 root := state.EnsureModule(addrs.RootModuleInstance) 8742 root.SetResourceInstanceCurrent( 8743 mustResourceInstanceAddr("aws_instance.test").Resource, 8744 &states.ResourceInstanceObjectSrc{ 8745 Status: states.ObjectReady, 8746 AttrsJSON: []byte(`{"id":"foo"}`), 8747 }, 8748 mustProviderConfig(`provider["registry.terraform.io/hashicorp/aws"]`), 8749 ) 8750 8751 ctx := testContext2(t, &ContextOpts{ 8752 Providers: Providers, 8753 }) 8754 8755 plan, diags := ctx.Plan(m, state, DefaultPlanOpts) 8756 if diags.HasErrors() { 8757 t.Fatalf("plan failed: %s", diags.Err()) 8758 } 8759 8760 // We'll marshal and unmarshal the plan here, to ensure that we have 8761 // a clean new context as would be created if we separately ran 8762 // terraform plan -out=tfplan && terraform apply tfplan 8763 ctxOpts, m, plan, err := contextOptsForPlanViaFile(snap, plan) 8764 if err != nil { 8765 t.Fatalf("failed to round-trip through planfile: %s", err) 8766 } 8767 8768 ctxOpts.Providers = Providers 8769 ctx, diags = NewContext(ctxOpts) 8770 if diags.HasErrors() { 8771 t.Fatalf("err: %s", diags.Err()) 8772 } 8773 8774 // Applying the plan should now succeed 8775 _, diags = ctx.Apply(plan, m) 8776 if diags.HasErrors() { 8777 t.Fatalf("apply failed: %s", diags.Err()) 8778 } 8779 } 8780 8781 func TestContext2Apply_plannedDestroyInterpolatedCount(t *testing.T) { 8782 m, snap := testModuleWithSnapshot(t, "plan-destroy-interpolated-count") 8783 8784 p := testProvider("aws") 8785 p.PlanResourceChangeFn = testDiffFn 8786 providers := map[addrs.Provider]providers.Factory{ 8787 addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p), 8788 } 8789 8790 state := states.NewState() 8791 root := state.EnsureModule(addrs.RootModuleInstance) 8792 root.SetResourceInstanceCurrent( 8793 mustResourceInstanceAddr("aws_instance.a[0]").Resource, 8794 &states.ResourceInstanceObjectSrc{ 8795 Status: states.ObjectReady, 8796 AttrsJSON: []byte(`{"id":"foo"}`), 8797 }, 8798 mustProviderConfig(`provider["registry.terraform.io/hashicorp/aws"]`), 8799 ) 8800 root.SetResourceInstanceCurrent( 8801 mustResourceInstanceAddr("aws_instance.a[1]").Resource, 8802 &states.ResourceInstanceObjectSrc{ 8803 Status: states.ObjectReady, 8804 AttrsJSON: []byte(`{"id":"foo"}`), 8805 }, 8806 mustProviderConfig(`provider["registry.terraform.io/hashicorp/aws"]`), 8807 ) 8808 root.SetOutputValue("out", cty.ListVal([]cty.Value{cty.StringVal("foo"), cty.StringVal("foo")}), false) 8809 8810 ctx := testContext2(t, &ContextOpts{ 8811 Providers: providers, 8812 }) 8813 8814 plan, diags := ctx.Plan(m, state, &PlanOpts{ 8815 Mode: plans.DestroyMode, 8816 }) 8817 if diags.HasErrors() { 8818 t.Fatalf("plan failed: %s", diags.Err()) 8819 } 8820 8821 // We'll marshal and unmarshal the plan here, to ensure that we have 8822 // a clean new context as would be created if we separately ran 8823 // terraform plan -out=tfplan && terraform apply tfplan 8824 ctxOpts, m, plan, err := contextOptsForPlanViaFile(snap, plan) 8825 if err != nil { 8826 t.Fatalf("failed to round-trip through planfile: %s", err) 8827 } 8828 8829 ctxOpts.Providers = providers 8830 ctx, diags = NewContext(ctxOpts) 8831 if diags.HasErrors() { 8832 t.Fatalf("err: %s", diags.Err()) 8833 } 8834 8835 // Applying the plan should now succeed 8836 state, diags = ctx.Apply(plan, m) 8837 if diags.HasErrors() { 8838 t.Fatalf("apply failed: %s", diags.Err()) 8839 } 8840 if !state.Empty() { 8841 t.Fatalf("state not empty: %s\n", state) 8842 } 8843 } 8844 8845 func TestContext2Apply_scaleInMultivarRef(t *testing.T) { 8846 m := testModule(t, "apply-resource-scale-in") 8847 8848 p := testProvider("aws") 8849 p.PlanResourceChangeFn = testDiffFn 8850 8851 Providers := map[addrs.Provider]providers.Factory{ 8852 addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p), 8853 } 8854 8855 state := states.NewState() 8856 root := state.EnsureModule(addrs.RootModuleInstance) 8857 root.SetResourceInstanceCurrent( 8858 mustResourceInstanceAddr("aws_instance.one").Resource, 8859 &states.ResourceInstanceObjectSrc{ 8860 Status: states.ObjectReady, 8861 AttrsJSON: []byte(`{"id":"foo"}`), 8862 }, 8863 mustProviderConfig(`provider["registry.terraform.io/hashicorp/aws"]`), 8864 ) 8865 root.SetResourceInstanceCurrent( 8866 mustResourceInstanceAddr("aws_instance.two").Resource, 8867 &states.ResourceInstanceObjectSrc{ 8868 Status: states.ObjectReady, 8869 AttrsJSON: []byte(`{"id":"foo"}`), 8870 }, 8871 mustProviderConfig(`provider["registry.terraform.io/hashicorp/aws"]`), 8872 ) 8873 8874 ctx := testContext2(t, &ContextOpts{ 8875 Providers: Providers, 8876 }) 8877 8878 plan, diags := ctx.Plan(m, state, &PlanOpts{ 8879 Mode: plans.NormalMode, 8880 SetVariables: InputValues{ 8881 "instance_count": { 8882 Value: cty.NumberIntVal(0), 8883 SourceType: ValueFromCaller, 8884 }, 8885 }, 8886 }) 8887 assertNoErrors(t, diags) 8888 8889 // Applying the plan should now succeed 8890 _, diags = ctx.Apply(plan, m) 8891 assertNoErrors(t, diags) 8892 } 8893 8894 func TestContext2Apply_inconsistentWithPlan(t *testing.T) { 8895 m := testModule(t, "apply-inconsistent-with-plan") 8896 p := testProvider("test") 8897 p.GetProviderSchemaResponse = getProviderSchemaResponseFromProviderSchema(&ProviderSchema{ 8898 ResourceTypes: map[string]*configschema.Block{ 8899 "test": { 8900 Attributes: map[string]*configschema.Attribute{ 8901 "id": {Type: cty.String, Computed: true}, 8902 }, 8903 }, 8904 }, 8905 }) 8906 p.PlanResourceChangeFn = func(req providers.PlanResourceChangeRequest) providers.PlanResourceChangeResponse { 8907 return providers.PlanResourceChangeResponse{ 8908 PlannedState: cty.ObjectVal(map[string]cty.Value{ 8909 "id": cty.StringVal("before"), 8910 }), 8911 } 8912 } 8913 p.ApplyResourceChangeFn = func(req providers.ApplyResourceChangeRequest) providers.ApplyResourceChangeResponse { 8914 return providers.ApplyResourceChangeResponse{ 8915 NewState: cty.ObjectVal(map[string]cty.Value{ 8916 // This is intentionally incorrect: because id was fixed at "before" 8917 // during plan, it must not change during apply. 8918 "id": cty.StringVal("after"), 8919 }), 8920 } 8921 } 8922 ctx := testContext2(t, &ContextOpts{ 8923 Providers: map[addrs.Provider]providers.Factory{ 8924 addrs.NewDefaultProvider("test"): testProviderFuncFixed(p), 8925 }, 8926 }) 8927 8928 plan, diags := ctx.Plan(m, states.NewState(), DefaultPlanOpts) 8929 assertNoErrors(t, diags) 8930 8931 _, diags = ctx.Apply(plan, m) 8932 if !diags.HasErrors() { 8933 t.Fatalf("apply succeeded; want error") 8934 } 8935 if got, want := diags.Err().Error(), "Provider produced inconsistent result after apply"; !strings.Contains(got, want) { 8936 t.Fatalf("wrong error\ngot: %s\nshould contain: %s", got, want) 8937 } 8938 } 8939 8940 // Issue 19908 was about retaining an existing object in the state when an 8941 // update to it fails and the provider does not return a partially-updated 8942 // value for it. Previously we were incorrectly removing it from the state 8943 // in that case, but instead it should be retained so the update can be 8944 // retried. 8945 func TestContext2Apply_issue19908(t *testing.T) { 8946 m := testModule(t, "apply-issue19908") 8947 p := testProvider("test") 8948 p.GetProviderSchemaResponse = getProviderSchemaResponseFromProviderSchema(&ProviderSchema{ 8949 ResourceTypes: map[string]*configschema.Block{ 8950 "test": { 8951 Attributes: map[string]*configschema.Attribute{ 8952 "baz": {Type: cty.String, Required: true}, 8953 }, 8954 }, 8955 }, 8956 }) 8957 p.PlanResourceChangeFn = func(req providers.PlanResourceChangeRequest) providers.PlanResourceChangeResponse { 8958 return providers.PlanResourceChangeResponse{ 8959 PlannedState: req.ProposedNewState, 8960 } 8961 } 8962 p.ApplyResourceChangeFn = func(req providers.ApplyResourceChangeRequest) providers.ApplyResourceChangeResponse { 8963 var diags tfdiags.Diagnostics 8964 diags = diags.Append(fmt.Errorf("update failed")) 8965 return providers.ApplyResourceChangeResponse{ 8966 Diagnostics: diags, 8967 } 8968 } 8969 ctx := testContext2(t, &ContextOpts{ 8970 Providers: map[addrs.Provider]providers.Factory{ 8971 addrs.NewDefaultProvider("test"): testProviderFuncFixed(p), 8972 }, 8973 }) 8974 8975 state := states.BuildState(func(s *states.SyncState) { 8976 s.SetResourceInstanceCurrent( 8977 addrs.Resource{ 8978 Mode: addrs.ManagedResourceMode, 8979 Type: "test", 8980 Name: "foo", 8981 }.Instance(addrs.NoKey).Absolute(addrs.RootModuleInstance), 8982 &states.ResourceInstanceObjectSrc{ 8983 AttrsJSON: []byte(`{"baz":"old"}`), 8984 Status: states.ObjectReady, 8985 }, 8986 addrs.AbsProviderConfig{ 8987 Provider: addrs.NewDefaultProvider("test"), 8988 Module: addrs.RootModule, 8989 }, 8990 ) 8991 }) 8992 8993 plan, diags := ctx.Plan(m, state, DefaultPlanOpts) 8994 assertNoErrors(t, diags) 8995 8996 state, diags = ctx.Apply(plan, m) 8997 if !diags.HasErrors() { 8998 t.Fatalf("apply succeeded; want error") 8999 } 9000 if got, want := diags.Err().Error(), "update failed"; !strings.Contains(got, want) { 9001 t.Fatalf("wrong error\ngot: %s\nshould contain: %s", got, want) 9002 } 9003 9004 mod := state.RootModule() 9005 rs := mod.Resources["test.foo"] 9006 if rs == nil { 9007 t.Fatalf("test.foo not in state after apply, but should be") 9008 } 9009 is := rs.Instances[addrs.NoKey] 9010 if is == nil { 9011 t.Fatalf("test.foo not in state after apply, but should be") 9012 } 9013 obj := is.Current 9014 if obj == nil { 9015 t.Fatalf("test.foo has no current object in state after apply, but should do") 9016 } 9017 9018 if got, want := obj.Status, states.ObjectReady; got != want { 9019 t.Errorf("test.foo has wrong status %s after apply; want %s", got, want) 9020 } 9021 if got, want := obj.AttrsJSON, []byte(`"old"`); !bytes.Contains(got, want) { 9022 t.Errorf("test.foo attributes JSON doesn't contain %s after apply\ngot: %s", want, got) 9023 } 9024 } 9025 9026 func TestContext2Apply_invalidIndexRef(t *testing.T) { 9027 p := testProvider("test") 9028 p.GetProviderSchemaResponse = getProviderSchemaResponseFromProviderSchema(&ProviderSchema{ 9029 ResourceTypes: map[string]*configschema.Block{ 9030 "test_instance": { 9031 Attributes: map[string]*configschema.Attribute{ 9032 "value": {Type: cty.String, Optional: true, Computed: true}, 9033 }, 9034 }, 9035 }, 9036 }) 9037 p.PlanResourceChangeFn = testDiffFn 9038 9039 m := testModule(t, "apply-invalid-index") 9040 c := testContext2(t, &ContextOpts{ 9041 Providers: map[addrs.Provider]providers.Factory{ 9042 addrs.NewDefaultProvider("test"): testProviderFuncFixed(p), 9043 }, 9044 }) 9045 diags := c.Validate(m) 9046 if diags.HasErrors() { 9047 t.Fatalf("unexpected validation failure: %s", diags.Err()) 9048 } 9049 9050 wantErr := `The given key does not identify an element in this collection value` 9051 _, diags = c.Plan(m, states.NewState(), DefaultPlanOpts) 9052 9053 if !diags.HasErrors() { 9054 t.Fatalf("plan succeeded; want error") 9055 } 9056 gotErr := diags.Err().Error() 9057 9058 if !strings.Contains(gotErr, wantErr) { 9059 t.Fatalf("missing expected error\ngot: %s\n\nwant: error containing %q", gotErr, wantErr) 9060 } 9061 } 9062 9063 func TestContext2Apply_moduleReplaceCycle(t *testing.T) { 9064 for _, mode := range []string{"normal", "cbd"} { 9065 var m *configs.Config 9066 9067 switch mode { 9068 case "normal": 9069 m = testModule(t, "apply-module-replace-cycle") 9070 case "cbd": 9071 m = testModule(t, "apply-module-replace-cycle-cbd") 9072 } 9073 9074 p := testProvider("aws") 9075 p.PlanResourceChangeFn = testDiffFn 9076 9077 instanceSchema := &configschema.Block{ 9078 Attributes: map[string]*configschema.Attribute{ 9079 "id": {Type: cty.String, Computed: true}, 9080 "require_new": {Type: cty.String, Optional: true}, 9081 }, 9082 } 9083 9084 p.GetProviderSchemaResponse = getProviderSchemaResponseFromProviderSchema(&ProviderSchema{ 9085 ResourceTypes: map[string]*configschema.Block{ 9086 "aws_instance": instanceSchema, 9087 }, 9088 }) 9089 9090 state := states.NewState() 9091 modA := state.EnsureModule(addrs.RootModuleInstance.Child("a", addrs.NoKey)) 9092 modA.SetResourceInstanceCurrent( 9093 addrs.Resource{ 9094 Mode: addrs.ManagedResourceMode, 9095 Type: "aws_instance", 9096 Name: "a", 9097 }.Instance(addrs.NoKey), 9098 &states.ResourceInstanceObjectSrc{ 9099 Status: states.ObjectReady, 9100 AttrsJSON: []byte(`{"id":"a","require_new":"old"}`), 9101 CreateBeforeDestroy: mode == "cbd", 9102 }, 9103 addrs.AbsProviderConfig{ 9104 Provider: addrs.NewDefaultProvider("aws"), 9105 Module: addrs.RootModule, 9106 }, 9107 ) 9108 9109 modB := state.EnsureModule(addrs.RootModuleInstance.Child("b", addrs.NoKey)) 9110 modB.SetResourceInstanceCurrent( 9111 addrs.Resource{ 9112 Mode: addrs.ManagedResourceMode, 9113 Type: "aws_instance", 9114 Name: "b", 9115 }.Instance(addrs.IntKey(0)), 9116 &states.ResourceInstanceObjectSrc{ 9117 Status: states.ObjectReady, 9118 AttrsJSON: []byte(`{"id":"b","require_new":"old"}`), 9119 }, 9120 addrs.AbsProviderConfig{ 9121 Provider: addrs.NewDefaultProvider("aws"), 9122 Module: addrs.RootModule, 9123 }, 9124 ) 9125 9126 aBefore, _ := plans.NewDynamicValue( 9127 cty.ObjectVal(map[string]cty.Value{ 9128 "id": cty.StringVal("a"), 9129 "require_new": cty.StringVal("old"), 9130 }), instanceSchema.ImpliedType()) 9131 aAfter, _ := plans.NewDynamicValue( 9132 cty.ObjectVal(map[string]cty.Value{ 9133 "id": cty.UnknownVal(cty.String), 9134 "require_new": cty.StringVal("new"), 9135 }), instanceSchema.ImpliedType()) 9136 bBefore, _ := plans.NewDynamicValue( 9137 cty.ObjectVal(map[string]cty.Value{ 9138 "id": cty.StringVal("b"), 9139 "require_new": cty.StringVal("old"), 9140 }), instanceSchema.ImpliedType()) 9141 bAfter, _ := plans.NewDynamicValue( 9142 cty.ObjectVal(map[string]cty.Value{ 9143 "id": cty.UnknownVal(cty.String), 9144 "require_new": cty.UnknownVal(cty.String), 9145 }), instanceSchema.ImpliedType()) 9146 9147 var aAction plans.Action 9148 switch mode { 9149 case "normal": 9150 aAction = plans.DeleteThenCreate 9151 case "cbd": 9152 aAction = plans.CreateThenDelete 9153 } 9154 9155 ctx := testContext2(t, &ContextOpts{ 9156 Providers: map[addrs.Provider]providers.Factory{ 9157 addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p), 9158 }, 9159 }) 9160 9161 changes := &plans.Changes{ 9162 Resources: []*plans.ResourceInstanceChangeSrc{ 9163 { 9164 Addr: addrs.Resource{ 9165 Mode: addrs.ManagedResourceMode, 9166 Type: "aws_instance", 9167 Name: "a", 9168 }.Instance(addrs.NoKey).Absolute(addrs.RootModuleInstance.Child("a", addrs.NoKey)), 9169 ProviderAddr: addrs.AbsProviderConfig{ 9170 Provider: addrs.NewDefaultProvider("aws"), 9171 Module: addrs.RootModule, 9172 }, 9173 ChangeSrc: plans.ChangeSrc{ 9174 Action: aAction, 9175 Before: aBefore, 9176 After: aAfter, 9177 }, 9178 }, 9179 { 9180 Addr: addrs.Resource{ 9181 Mode: addrs.ManagedResourceMode, 9182 Type: "aws_instance", 9183 Name: "b", 9184 }.Instance(addrs.IntKey(0)).Absolute(addrs.RootModuleInstance.Child("b", addrs.NoKey)), 9185 ProviderAddr: addrs.AbsProviderConfig{ 9186 Provider: addrs.NewDefaultProvider("aws"), 9187 Module: addrs.RootModule, 9188 }, 9189 ChangeSrc: plans.ChangeSrc{ 9190 Action: plans.DeleteThenCreate, 9191 Before: bBefore, 9192 After: bAfter, 9193 }, 9194 }, 9195 }, 9196 } 9197 9198 plan := &plans.Plan{ 9199 UIMode: plans.NormalMode, 9200 Changes: changes, 9201 PriorState: state.DeepCopy(), 9202 PrevRunState: state.DeepCopy(), 9203 } 9204 9205 t.Run(mode, func(t *testing.T) { 9206 _, diags := ctx.Apply(plan, m) 9207 if diags.HasErrors() { 9208 t.Fatal(diags.Err()) 9209 } 9210 }) 9211 } 9212 } 9213 9214 func TestContext2Apply_destroyDataCycle(t *testing.T) { 9215 m, snap := testModuleWithSnapshot(t, "apply-destroy-data-cycle") 9216 p := testProvider("null") 9217 p.PlanResourceChangeFn = testDiffFn 9218 p.ReadDataSourceFn = func(req providers.ReadDataSourceRequest) providers.ReadDataSourceResponse { 9219 return providers.ReadDataSourceResponse{ 9220 State: cty.ObjectVal(map[string]cty.Value{ 9221 "id": cty.StringVal("new"), 9222 "foo": cty.NullVal(cty.String), 9223 }), 9224 } 9225 } 9226 9227 tp := testProvider("test") 9228 tp.PlanResourceChangeFn = testDiffFn 9229 9230 state := states.NewState() 9231 root := state.EnsureModule(addrs.RootModuleInstance) 9232 root.SetResourceInstanceCurrent( 9233 addrs.Resource{ 9234 Mode: addrs.ManagedResourceMode, 9235 Type: "null_resource", 9236 Name: "a", 9237 }.Instance(addrs.IntKey(0)), 9238 &states.ResourceInstanceObjectSrc{ 9239 Status: states.ObjectReady, 9240 AttrsJSON: []byte(`{"id":"a"}`), 9241 }, 9242 addrs.AbsProviderConfig{ 9243 Provider: addrs.NewDefaultProvider("null"), 9244 Module: addrs.RootModule, 9245 }, 9246 ) 9247 root.SetResourceInstanceCurrent( 9248 addrs.Resource{ 9249 Mode: addrs.ManagedResourceMode, 9250 Type: "test_resource", 9251 Name: "a", 9252 }.Instance(addrs.IntKey(0)), 9253 &states.ResourceInstanceObjectSrc{ 9254 Status: states.ObjectReady, 9255 AttrsJSON: []byte(`{"id":"a"}`), 9256 Dependencies: []addrs.ConfigResource{ 9257 { 9258 Resource: addrs.Resource{ 9259 Mode: addrs.DataResourceMode, 9260 Type: "null_data_source", 9261 Name: "d", 9262 }, 9263 Module: addrs.RootModule, 9264 }, 9265 }, 9266 }, 9267 addrs.AbsProviderConfig{ 9268 Provider: addrs.NewDefaultProvider("test"), 9269 Module: addrs.RootModule, 9270 }, 9271 ) 9272 root.SetResourceInstanceCurrent( 9273 addrs.Resource{ 9274 Mode: addrs.DataResourceMode, 9275 Type: "null_data_source", 9276 Name: "d", 9277 }.Instance(addrs.NoKey), 9278 &states.ResourceInstanceObjectSrc{ 9279 Status: states.ObjectReady, 9280 AttrsJSON: []byte(`{"id":"old"}`), 9281 }, 9282 addrs.AbsProviderConfig{ 9283 Provider: addrs.NewDefaultProvider("null"), 9284 Module: addrs.RootModule, 9285 }, 9286 ) 9287 9288 Providers := map[addrs.Provider]providers.Factory{ 9289 addrs.NewDefaultProvider("null"): testProviderFuncFixed(p), 9290 addrs.NewDefaultProvider("test"): testProviderFuncFixed(tp), 9291 } 9292 9293 ctx := testContext2(t, &ContextOpts{ 9294 Providers: Providers, 9295 }) 9296 9297 plan, diags := ctx.Plan(m, state, &PlanOpts{ 9298 Mode: plans.DestroyMode, 9299 }) 9300 diags.HasErrors() 9301 if diags.HasErrors() { 9302 t.Fatalf("diags: %s", diags.Err()) 9303 } 9304 9305 // We'll marshal and unmarshal the plan here, to ensure that we have 9306 // a clean new context as would be created if we separately ran 9307 // terraform plan -out=tfplan && terraform apply tfplan 9308 ctxOpts, m, plan, err := contextOptsForPlanViaFile(snap, plan) 9309 if err != nil { 9310 t.Fatal(err) 9311 } 9312 ctxOpts.Providers = Providers 9313 ctx, diags = NewContext(ctxOpts) 9314 if diags.HasErrors() { 9315 t.Fatalf("failed to create context for plan: %s", diags.Err()) 9316 } 9317 9318 tp.ConfigureProviderFn = func(req providers.ConfigureProviderRequest) (resp providers.ConfigureProviderResponse) { 9319 foo := req.Config.GetAttr("foo") 9320 if !foo.IsKnown() { 9321 resp.Diagnostics = resp.Diagnostics.Append(fmt.Errorf("unknown config value foo")) 9322 return resp 9323 } 9324 9325 if foo.AsString() != "new" { 9326 resp.Diagnostics = resp.Diagnostics.Append(fmt.Errorf("wrong config value: %q", foo.AsString())) 9327 } 9328 return resp 9329 } 9330 9331 _, diags = ctx.Apply(plan, m) 9332 if diags.HasErrors() { 9333 t.Fatalf("diags: %s", diags.Err()) 9334 } 9335 } 9336 9337 func TestContext2Apply_taintedDestroyFailure(t *testing.T) { 9338 m := testModule(t, "apply-destroy-tainted") 9339 p := testProvider("test") 9340 p.PlanResourceChangeFn = testDiffFn 9341 p.ApplyResourceChangeFn = func(req providers.ApplyResourceChangeRequest) (resp providers.ApplyResourceChangeResponse) { 9342 // All destroys fail. 9343 if req.PlannedState.IsNull() { 9344 resp.Diagnostics = resp.Diagnostics.Append(errors.New("failure")) 9345 return 9346 } 9347 9348 // c will also fail to create, meaning the existing tainted instance 9349 // becomes deposed, ans is then promoted back to current. 9350 // only C has a foo attribute 9351 planned := req.PlannedState.AsValueMap() 9352 foo, ok := planned["foo"] 9353 if ok && !foo.IsNull() && foo.AsString() == "c" { 9354 resp.Diagnostics = resp.Diagnostics.Append(errors.New("failure")) 9355 return 9356 } 9357 9358 return testApplyFn(req) 9359 } 9360 p.GetProviderSchemaResponse = getProviderSchemaResponseFromProviderSchema(&ProviderSchema{ 9361 ResourceTypes: map[string]*configschema.Block{ 9362 "test_instance": { 9363 Attributes: map[string]*configschema.Attribute{ 9364 "id": { 9365 Type: cty.String, 9366 Computed: true, 9367 }, 9368 "foo": { 9369 Type: cty.String, 9370 Optional: true, 9371 }, 9372 }, 9373 }, 9374 }, 9375 }) 9376 9377 state := states.NewState() 9378 root := state.EnsureModule(addrs.RootModuleInstance) 9379 root.SetResourceInstanceCurrent( 9380 addrs.Resource{ 9381 Mode: addrs.ManagedResourceMode, 9382 Type: "test_instance", 9383 Name: "a", 9384 }.Instance(addrs.NoKey), 9385 &states.ResourceInstanceObjectSrc{ 9386 Status: states.ObjectTainted, 9387 AttrsJSON: []byte(`{"id":"a","foo":"a"}`), 9388 }, 9389 addrs.AbsProviderConfig{ 9390 Provider: addrs.NewDefaultProvider("test"), 9391 Module: addrs.RootModule, 9392 }, 9393 ) 9394 root.SetResourceInstanceCurrent( 9395 addrs.Resource{ 9396 Mode: addrs.ManagedResourceMode, 9397 Type: "test_instance", 9398 Name: "b", 9399 }.Instance(addrs.NoKey), 9400 &states.ResourceInstanceObjectSrc{ 9401 Status: states.ObjectTainted, 9402 AttrsJSON: []byte(`{"id":"b","foo":"b"}`), 9403 }, 9404 addrs.AbsProviderConfig{ 9405 Provider: addrs.NewDefaultProvider("test"), 9406 Module: addrs.RootModule, 9407 }, 9408 ) 9409 root.SetResourceInstanceCurrent( 9410 addrs.Resource{ 9411 Mode: addrs.ManagedResourceMode, 9412 Type: "test_instance", 9413 Name: "c", 9414 }.Instance(addrs.NoKey), 9415 &states.ResourceInstanceObjectSrc{ 9416 Status: states.ObjectTainted, 9417 AttrsJSON: []byte(`{"id":"c","foo":"old"}`), 9418 }, 9419 addrs.AbsProviderConfig{ 9420 Provider: addrs.NewDefaultProvider("test"), 9421 Module: addrs.RootModule, 9422 }, 9423 ) 9424 9425 Providers := map[addrs.Provider]providers.Factory{ 9426 addrs.NewDefaultProvider("test"): testProviderFuncFixed(p), 9427 } 9428 9429 ctx := testContext2(t, &ContextOpts{ 9430 Providers: Providers, 9431 Hooks: []Hook{&testHook{}}, 9432 }) 9433 9434 plan, diags := ctx.Plan(m, state, DefaultPlanOpts) 9435 diags.HasErrors() 9436 if diags.HasErrors() { 9437 t.Fatalf("diags: %s", diags.Err()) 9438 } 9439 9440 state, diags = ctx.Apply(plan, m) 9441 if !diags.HasErrors() { 9442 t.Fatal("expected error") 9443 } 9444 9445 root = state.Module(addrs.RootModuleInstance) 9446 9447 // the instance that failed to destroy should remain tainted 9448 a := root.ResourceInstance(addrs.Resource{ 9449 Mode: addrs.ManagedResourceMode, 9450 Type: "test_instance", 9451 Name: "a", 9452 }.Instance(addrs.NoKey)) 9453 9454 if a.Current.Status != states.ObjectTainted { 9455 t.Fatal("test_instance.a should be tainted") 9456 } 9457 9458 // b is create_before_destroy, and the destroy failed, so there should be 1 9459 // deposed instance. 9460 b := root.ResourceInstance(addrs.Resource{ 9461 Mode: addrs.ManagedResourceMode, 9462 Type: "test_instance", 9463 Name: "b", 9464 }.Instance(addrs.NoKey)) 9465 9466 if b.Current.Status != states.ObjectReady { 9467 t.Fatal("test_instance.b should be Ready") 9468 } 9469 9470 if len(b.Deposed) != 1 { 9471 t.Fatal("test_instance.b failed to keep deposed instance") 9472 } 9473 9474 // the desposed c instance should be promoted back to Current, and remain 9475 // tainted 9476 c := root.ResourceInstance(addrs.Resource{ 9477 Mode: addrs.ManagedResourceMode, 9478 Type: "test_instance", 9479 Name: "c", 9480 }.Instance(addrs.NoKey)) 9481 9482 if c.Current == nil { 9483 t.Fatal("test_instance.c has no current instance, but it should") 9484 } 9485 9486 if c.Current.Status != states.ObjectTainted { 9487 t.Fatal("test_instance.c should be tainted") 9488 } 9489 9490 if len(c.Deposed) != 0 { 9491 t.Fatal("test_instance.c should have no deposed instances") 9492 } 9493 9494 if string(c.Current.AttrsJSON) != `{"foo":"old","id":"c"}` { 9495 t.Fatalf("unexpected attrs for c: %q\n", c.Current.AttrsJSON) 9496 } 9497 } 9498 9499 func TestContext2Apply_plannedConnectionRefs(t *testing.T) { 9500 m := testModule(t, "apply-plan-connection-refs") 9501 p := testProvider("test") 9502 p.PlanResourceChangeFn = testDiffFn 9503 p.ApplyResourceChangeFn = func(req providers.ApplyResourceChangeRequest) (resp providers.ApplyResourceChangeResponse) { 9504 s := req.PlannedState.AsValueMap() 9505 // delay "a" slightly, so if the reference edge is missing the "b" 9506 // provisioner will see an unknown value. 9507 if s["foo"].AsString() == "a" { 9508 time.Sleep(500 * time.Millisecond) 9509 } 9510 9511 s["id"] = cty.StringVal("ID") 9512 if ty, ok := s["type"]; ok && !ty.IsKnown() { 9513 s["type"] = cty.StringVal(req.TypeName) 9514 } 9515 resp.NewState = cty.ObjectVal(s) 9516 return resp 9517 } 9518 9519 provisionerFactory := func() (provisioners.Interface, error) { 9520 pr := testProvisioner() 9521 pr.ProvisionResourceFn = func(req provisioners.ProvisionResourceRequest) (resp provisioners.ProvisionResourceResponse) { 9522 host := req.Connection.GetAttr("host") 9523 if host.IsNull() || !host.IsKnown() { 9524 resp.Diagnostics = resp.Diagnostics.Append(fmt.Errorf("invalid host value: %#v", host)) 9525 } 9526 9527 return resp 9528 } 9529 return pr, nil 9530 } 9531 9532 Providers := map[addrs.Provider]providers.Factory{ 9533 addrs.NewDefaultProvider("test"): testProviderFuncFixed(p), 9534 } 9535 9536 provisioners := map[string]provisioners.Factory{ 9537 "shell": provisionerFactory, 9538 } 9539 9540 hook := &testHook{} 9541 ctx := testContext2(t, &ContextOpts{ 9542 Providers: Providers, 9543 Provisioners: provisioners, 9544 Hooks: []Hook{hook}, 9545 }) 9546 9547 plan, diags := ctx.Plan(m, states.NewState(), DefaultPlanOpts) 9548 diags.HasErrors() 9549 if diags.HasErrors() { 9550 t.Fatalf("diags: %s", diags.Err()) 9551 } 9552 9553 _, diags = ctx.Apply(plan, m) 9554 if diags.HasErrors() { 9555 t.Fatalf("diags: %s", diags.Err()) 9556 } 9557 } 9558 9559 func TestContext2Apply_cbdCycle(t *testing.T) { 9560 m, snap := testModuleWithSnapshot(t, "apply-cbd-cycle") 9561 p := testProvider("test") 9562 p.PlanResourceChangeFn = testDiffFn 9563 9564 state := states.NewState() 9565 root := state.EnsureModule(addrs.RootModuleInstance) 9566 root.SetResourceInstanceCurrent( 9567 addrs.Resource{ 9568 Mode: addrs.ManagedResourceMode, 9569 Type: "test_instance", 9570 Name: "a", 9571 }.Instance(addrs.NoKey), 9572 &states.ResourceInstanceObjectSrc{ 9573 Status: states.ObjectReady, 9574 AttrsJSON: []byte(`{"id":"a","require_new":"old","foo":"b"}`), 9575 Dependencies: []addrs.ConfigResource{ 9576 { 9577 Resource: addrs.Resource{ 9578 Mode: addrs.ManagedResourceMode, 9579 Type: "test_instance", 9580 Name: "b", 9581 }, 9582 Module: addrs.RootModule, 9583 }, 9584 { 9585 Resource: addrs.Resource{ 9586 Mode: addrs.ManagedResourceMode, 9587 Type: "test_instance", 9588 Name: "c", 9589 }, 9590 Module: addrs.RootModule, 9591 }, 9592 }, 9593 }, 9594 addrs.AbsProviderConfig{ 9595 Provider: addrs.NewDefaultProvider("test"), 9596 Module: addrs.RootModule, 9597 }, 9598 ) 9599 root.SetResourceInstanceCurrent( 9600 addrs.Resource{ 9601 Mode: addrs.ManagedResourceMode, 9602 Type: "test_instance", 9603 Name: "b", 9604 }.Instance(addrs.NoKey), 9605 &states.ResourceInstanceObjectSrc{ 9606 Status: states.ObjectReady, 9607 AttrsJSON: []byte(`{"id":"b","require_new":"old","foo":"c"}`), 9608 Dependencies: []addrs.ConfigResource{ 9609 { 9610 Resource: addrs.Resource{ 9611 Mode: addrs.ManagedResourceMode, 9612 Type: "test_instance", 9613 Name: "c", 9614 }, 9615 Module: addrs.RootModule, 9616 }, 9617 }, 9618 }, 9619 addrs.AbsProviderConfig{ 9620 Provider: addrs.NewDefaultProvider("test"), 9621 Module: addrs.RootModule, 9622 }, 9623 ) 9624 root.SetResourceInstanceCurrent( 9625 addrs.Resource{ 9626 Mode: addrs.ManagedResourceMode, 9627 Type: "test_instance", 9628 Name: "c", 9629 }.Instance(addrs.NoKey), 9630 &states.ResourceInstanceObjectSrc{ 9631 Status: states.ObjectReady, 9632 AttrsJSON: []byte(`{"id":"c","require_new":"old"}`), 9633 }, 9634 addrs.AbsProviderConfig{ 9635 Provider: addrs.NewDefaultProvider("test"), 9636 Module: addrs.RootModule, 9637 }, 9638 ) 9639 9640 Providers := map[addrs.Provider]providers.Factory{ 9641 addrs.NewDefaultProvider("test"): testProviderFuncFixed(p), 9642 } 9643 9644 hook := &testHook{} 9645 ctx := testContext2(t, &ContextOpts{ 9646 Providers: Providers, 9647 Hooks: []Hook{hook}, 9648 }) 9649 9650 plan, diags := ctx.Plan(m, state, DefaultPlanOpts) 9651 diags.HasErrors() 9652 if diags.HasErrors() { 9653 t.Fatalf("diags: %s", diags.Err()) 9654 } 9655 9656 // We'll marshal and unmarshal the plan here, to ensure that we have 9657 // a clean new context as would be created if we separately ran 9658 // terraform plan -out=tfplan && terraform apply tfplan 9659 ctxOpts, m, plan, err := contextOptsForPlanViaFile(snap, plan) 9660 if err != nil { 9661 t.Fatal(err) 9662 } 9663 ctxOpts.Providers = Providers 9664 ctx, diags = NewContext(ctxOpts) 9665 if diags.HasErrors() { 9666 t.Fatalf("failed to create context for plan: %s", diags.Err()) 9667 } 9668 9669 _, diags = ctx.Apply(plan, m) 9670 if diags.HasErrors() { 9671 t.Fatalf("diags: %s", diags.Err()) 9672 } 9673 } 9674 9675 func TestContext2Apply_ProviderMeta_apply_set(t *testing.T) { 9676 m := testModule(t, "provider-meta-set") 9677 p := testProvider("test") 9678 p.PlanResourceChangeFn = testDiffFn 9679 schema := p.ProviderSchema() 9680 schema.ProviderMeta = &configschema.Block{ 9681 Attributes: map[string]*configschema.Attribute{ 9682 "baz": { 9683 Type: cty.String, 9684 Required: true, 9685 }, 9686 }, 9687 } 9688 9689 var pmMu sync.Mutex 9690 arcPMs := map[string]cty.Value{} 9691 9692 p.ApplyResourceChangeFn = func(req providers.ApplyResourceChangeRequest) providers.ApplyResourceChangeResponse { 9693 pmMu.Lock() 9694 defer pmMu.Unlock() 9695 arcPMs[req.TypeName] = req.ProviderMeta 9696 9697 s := req.PlannedState.AsValueMap() 9698 s["id"] = cty.StringVal("ID") 9699 if ty, ok := s["type"]; ok && !ty.IsKnown() { 9700 s["type"] = cty.StringVal(req.TypeName) 9701 } 9702 return providers.ApplyResourceChangeResponse{ 9703 NewState: cty.ObjectVal(s), 9704 } 9705 } 9706 p.GetProviderSchemaResponse = getProviderSchemaResponseFromProviderSchema(schema) 9707 ctx := testContext2(t, &ContextOpts{ 9708 Providers: map[addrs.Provider]providers.Factory{ 9709 addrs.NewDefaultProvider("test"): testProviderFuncFixed(p), 9710 }, 9711 }) 9712 9713 plan, diags := ctx.Plan(m, states.NewState(), DefaultPlanOpts) 9714 assertNoErrors(t, diags) 9715 9716 _, diags = ctx.Apply(plan, m) 9717 assertNoErrors(t, diags) 9718 9719 if !p.ApplyResourceChangeCalled { 9720 t.Fatalf("ApplyResourceChange not called") 9721 } 9722 9723 expectations := map[string]cty.Value{} 9724 9725 if pm, ok := arcPMs["test_resource"]; !ok { 9726 t.Fatalf("sub-module ApplyResourceChange not called") 9727 } else if pm.IsNull() { 9728 t.Fatalf("null ProviderMeta in sub-module ApplyResourceChange") 9729 } else { 9730 expectations["quux-submodule"] = pm 9731 } 9732 9733 if pm, ok := arcPMs["test_instance"]; !ok { 9734 t.Fatalf("root module ApplyResourceChange not called") 9735 } else if pm.IsNull() { 9736 t.Fatalf("null ProviderMeta in root module ApplyResourceChange") 9737 } else { 9738 expectations["quux"] = pm 9739 } 9740 9741 type metaStruct struct { 9742 Baz string `cty:"baz"` 9743 } 9744 9745 for expected, v := range expectations { 9746 var meta metaStruct 9747 err := gocty.FromCtyValue(v, &meta) 9748 if err != nil { 9749 t.Fatalf("Error parsing cty value: %s", err) 9750 } 9751 if meta.Baz != expected { 9752 t.Fatalf("Expected meta.Baz to be %q, got %q", expected, meta.Baz) 9753 } 9754 } 9755 } 9756 9757 func TestContext2Apply_ProviderMeta_apply_unset(t *testing.T) { 9758 m := testModule(t, "provider-meta-unset") 9759 p := testProvider("test") 9760 p.PlanResourceChangeFn = testDiffFn 9761 schema := p.ProviderSchema() 9762 schema.ProviderMeta = &configschema.Block{ 9763 Attributes: map[string]*configschema.Attribute{ 9764 "baz": { 9765 Type: cty.String, 9766 Required: true, 9767 }, 9768 }, 9769 } 9770 var pmMu sync.Mutex 9771 arcPMs := map[string]cty.Value{} 9772 p.ApplyResourceChangeFn = func(req providers.ApplyResourceChangeRequest) providers.ApplyResourceChangeResponse { 9773 pmMu.Lock() 9774 defer pmMu.Unlock() 9775 arcPMs[req.TypeName] = req.ProviderMeta 9776 9777 s := req.PlannedState.AsValueMap() 9778 s["id"] = cty.StringVal("ID") 9779 if ty, ok := s["type"]; ok && !ty.IsKnown() { 9780 s["type"] = cty.StringVal(req.TypeName) 9781 } 9782 return providers.ApplyResourceChangeResponse{ 9783 NewState: cty.ObjectVal(s), 9784 } 9785 } 9786 p.GetProviderSchemaResponse = getProviderSchemaResponseFromProviderSchema(schema) 9787 ctx := testContext2(t, &ContextOpts{ 9788 Providers: map[addrs.Provider]providers.Factory{ 9789 addrs.NewDefaultProvider("test"): testProviderFuncFixed(p), 9790 }, 9791 }) 9792 9793 plan, diags := ctx.Plan(m, states.NewState(), DefaultPlanOpts) 9794 assertNoErrors(t, diags) 9795 9796 _, diags = ctx.Apply(plan, m) 9797 assertNoErrors(t, diags) 9798 9799 if !p.ApplyResourceChangeCalled { 9800 t.Fatalf("ApplyResourceChange not called") 9801 } 9802 9803 if pm, ok := arcPMs["test_resource"]; !ok { 9804 t.Fatalf("sub-module ApplyResourceChange not called") 9805 } else if !pm.IsNull() { 9806 t.Fatalf("non-null ProviderMeta in sub-module ApplyResourceChange: %+v", pm) 9807 } 9808 9809 if pm, ok := arcPMs["test_instance"]; !ok { 9810 t.Fatalf("root module ApplyResourceChange not called") 9811 } else if !pm.IsNull() { 9812 t.Fatalf("non-null ProviderMeta in root module ApplyResourceChange: %+v", pm) 9813 } 9814 } 9815 9816 func TestContext2Apply_ProviderMeta_plan_set(t *testing.T) { 9817 m := testModule(t, "provider-meta-set") 9818 p := testProvider("test") 9819 schema := p.ProviderSchema() 9820 schema.ProviderMeta = &configschema.Block{ 9821 Attributes: map[string]*configschema.Attribute{ 9822 "baz": { 9823 Type: cty.String, 9824 Required: true, 9825 }, 9826 }, 9827 } 9828 prcPMs := map[string]cty.Value{} 9829 p.PlanResourceChangeFn = func(req providers.PlanResourceChangeRequest) providers.PlanResourceChangeResponse { 9830 prcPMs[req.TypeName] = req.ProviderMeta 9831 return providers.PlanResourceChangeResponse{ 9832 PlannedState: req.ProposedNewState, 9833 } 9834 } 9835 p.GetProviderSchemaResponse = getProviderSchemaResponseFromProviderSchema(schema) 9836 ctx := testContext2(t, &ContextOpts{ 9837 Providers: map[addrs.Provider]providers.Factory{ 9838 addrs.NewDefaultProvider("test"): testProviderFuncFixed(p), 9839 }, 9840 }) 9841 9842 _, diags := ctx.Plan(m, states.NewState(), DefaultPlanOpts) 9843 assertNoErrors(t, diags) 9844 9845 if !p.PlanResourceChangeCalled { 9846 t.Fatalf("PlanResourceChange not called") 9847 } 9848 9849 expectations := map[string]cty.Value{} 9850 9851 if pm, ok := prcPMs["test_resource"]; !ok { 9852 t.Fatalf("sub-module PlanResourceChange not called") 9853 } else if pm.IsNull() { 9854 t.Fatalf("null ProviderMeta in sub-module PlanResourceChange") 9855 } else { 9856 expectations["quux-submodule"] = pm 9857 } 9858 9859 if pm, ok := prcPMs["test_instance"]; !ok { 9860 t.Fatalf("root module PlanResourceChange not called") 9861 } else if pm.IsNull() { 9862 t.Fatalf("null ProviderMeta in root module PlanResourceChange") 9863 } else { 9864 expectations["quux"] = pm 9865 } 9866 9867 type metaStruct struct { 9868 Baz string `cty:"baz"` 9869 } 9870 9871 for expected, v := range expectations { 9872 var meta metaStruct 9873 err := gocty.FromCtyValue(v, &meta) 9874 if err != nil { 9875 t.Fatalf("Error parsing cty value: %s", err) 9876 } 9877 if meta.Baz != expected { 9878 t.Fatalf("Expected meta.Baz to be %q, got %q", expected, meta.Baz) 9879 } 9880 } 9881 } 9882 9883 func TestContext2Apply_ProviderMeta_plan_unset(t *testing.T) { 9884 m := testModule(t, "provider-meta-unset") 9885 p := testProvider("test") 9886 schema := p.ProviderSchema() 9887 schema.ProviderMeta = &configschema.Block{ 9888 Attributes: map[string]*configschema.Attribute{ 9889 "baz": { 9890 Type: cty.String, 9891 Required: true, 9892 }, 9893 }, 9894 } 9895 prcPMs := map[string]cty.Value{} 9896 p.PlanResourceChangeFn = func(req providers.PlanResourceChangeRequest) providers.PlanResourceChangeResponse { 9897 prcPMs[req.TypeName] = req.ProviderMeta 9898 return providers.PlanResourceChangeResponse{ 9899 PlannedState: req.ProposedNewState, 9900 } 9901 } 9902 p.GetProviderSchemaResponse = getProviderSchemaResponseFromProviderSchema(schema) 9903 ctx := testContext2(t, &ContextOpts{ 9904 Providers: map[addrs.Provider]providers.Factory{ 9905 addrs.NewDefaultProvider("test"): testProviderFuncFixed(p), 9906 }, 9907 }) 9908 9909 _, diags := ctx.Plan(m, states.NewState(), DefaultPlanOpts) 9910 assertNoErrors(t, diags) 9911 9912 if !p.PlanResourceChangeCalled { 9913 t.Fatalf("PlanResourceChange not called") 9914 } 9915 9916 if pm, ok := prcPMs["test_resource"]; !ok { 9917 t.Fatalf("sub-module PlanResourceChange not called") 9918 } else if !pm.IsNull() { 9919 t.Fatalf("non-null ProviderMeta in sub-module PlanResourceChange: %+v", pm) 9920 } 9921 9922 if pm, ok := prcPMs["test_instance"]; !ok { 9923 t.Fatalf("root module PlanResourceChange not called") 9924 } else if !pm.IsNull() { 9925 t.Fatalf("non-null ProviderMeta in root module PlanResourceChange: %+v", pm) 9926 } 9927 } 9928 9929 func TestContext2Apply_ProviderMeta_plan_setNoSchema(t *testing.T) { 9930 m := testModule(t, "provider-meta-set") 9931 p := testProvider("test") 9932 p.PlanResourceChangeFn = testDiffFn 9933 ctx := testContext2(t, &ContextOpts{ 9934 Providers: map[addrs.Provider]providers.Factory{ 9935 addrs.NewDefaultProvider("test"): testProviderFuncFixed(p), 9936 }, 9937 }) 9938 9939 _, diags := ctx.Plan(m, states.NewState(), DefaultPlanOpts) 9940 if !diags.HasErrors() { 9941 t.Fatalf("plan supposed to error, has no errors") 9942 } 9943 9944 var rootErr, subErr bool 9945 errorSummary := "The resource test_%s.bar belongs to a provider that doesn't support provider_meta blocks" 9946 for _, diag := range diags { 9947 if diag.Description().Summary != "Provider registry.terraform.io/hashicorp/test doesn't support provider_meta" { 9948 t.Errorf("Unexpected error: %+v", diag.Description()) 9949 } 9950 switch diag.Description().Detail { 9951 case fmt.Sprintf(errorSummary, "instance"): 9952 rootErr = true 9953 case fmt.Sprintf(errorSummary, "resource"): 9954 subErr = true 9955 default: 9956 t.Errorf("Unexpected error: %s", diag.Description()) 9957 } 9958 } 9959 if !rootErr { 9960 t.Errorf("Expected unsupported provider_meta block error for root module, none received") 9961 } 9962 if !subErr { 9963 t.Errorf("Expected unsupported provider_meta block error for sub-module, none received") 9964 } 9965 } 9966 9967 func TestContext2Apply_ProviderMeta_plan_setInvalid(t *testing.T) { 9968 m := testModule(t, "provider-meta-set") 9969 p := testProvider("test") 9970 p.PlanResourceChangeFn = testDiffFn 9971 schema := p.ProviderSchema() 9972 schema.ProviderMeta = &configschema.Block{ 9973 Attributes: map[string]*configschema.Attribute{ 9974 "quux": { 9975 Type: cty.String, 9976 Required: true, 9977 }, 9978 }, 9979 } 9980 p.GetProviderSchemaResponse = getProviderSchemaResponseFromProviderSchema(schema) 9981 ctx := testContext2(t, &ContextOpts{ 9982 Providers: map[addrs.Provider]providers.Factory{ 9983 addrs.NewDefaultProvider("test"): testProviderFuncFixed(p), 9984 }, 9985 }) 9986 9987 _, diags := ctx.Plan(m, states.NewState(), DefaultPlanOpts) 9988 if !diags.HasErrors() { 9989 t.Fatalf("plan supposed to error, has no errors") 9990 } 9991 9992 var reqErr, invalidErr bool 9993 for _, diag := range diags { 9994 switch diag.Description().Summary { 9995 case "Missing required argument": 9996 if diag.Description().Detail == `The argument "quux" is required, but no definition was found.` { 9997 reqErr = true 9998 } else { 9999 t.Errorf("Unexpected error %+v", diag.Description()) 10000 } 10001 case "Unsupported argument": 10002 if diag.Description().Detail == `An argument named "baz" is not expected here.` { 10003 invalidErr = true 10004 } else { 10005 t.Errorf("Unexpected error %+v", diag.Description()) 10006 } 10007 default: 10008 t.Errorf("Unexpected error %+v", diag.Description()) 10009 } 10010 } 10011 if !reqErr { 10012 t.Errorf("Expected missing required argument error, none received") 10013 } 10014 if !invalidErr { 10015 t.Errorf("Expected unsupported argument error, none received") 10016 } 10017 } 10018 10019 func TestContext2Apply_ProviderMeta_refresh_set(t *testing.T) { 10020 m := testModule(t, "provider-meta-set") 10021 p := testProvider("test") 10022 p.PlanResourceChangeFn = testDiffFn 10023 schema := p.ProviderSchema() 10024 schema.ProviderMeta = &configschema.Block{ 10025 Attributes: map[string]*configschema.Attribute{ 10026 "baz": { 10027 Type: cty.String, 10028 Required: true, 10029 }, 10030 }, 10031 } 10032 rrcPMs := map[string]cty.Value{} 10033 p.ReadResourceFn = func(req providers.ReadResourceRequest) (resp providers.ReadResourceResponse) { 10034 rrcPMs[req.TypeName] = req.ProviderMeta 10035 newState, err := p.GetProviderSchemaResponse.ResourceTypes[req.TypeName].Block.CoerceValue(req.PriorState) 10036 if err != nil { 10037 panic(err) 10038 } 10039 resp.NewState = newState 10040 return resp 10041 } 10042 p.GetProviderSchemaResponse = getProviderSchemaResponseFromProviderSchema(schema) 10043 ctx := testContext2(t, &ContextOpts{ 10044 Providers: map[addrs.Provider]providers.Factory{ 10045 addrs.NewDefaultProvider("test"): testProviderFuncFixed(p), 10046 }, 10047 }) 10048 10049 plan, diags := ctx.Plan(m, states.NewState(), DefaultPlanOpts) 10050 assertNoErrors(t, diags) 10051 10052 state, diags := ctx.Apply(plan, m) 10053 assertNoErrors(t, diags) 10054 10055 _, diags = ctx.Refresh(m, state, DefaultPlanOpts) 10056 assertNoErrors(t, diags) 10057 10058 if !p.ReadResourceCalled { 10059 t.Fatalf("ReadResource not called") 10060 } 10061 10062 expectations := map[string]cty.Value{} 10063 10064 if pm, ok := rrcPMs["test_resource"]; !ok { 10065 t.Fatalf("sub-module ReadResource not called") 10066 } else if pm.IsNull() { 10067 t.Fatalf("null ProviderMeta in sub-module ReadResource") 10068 } else { 10069 expectations["quux-submodule"] = pm 10070 } 10071 10072 if pm, ok := rrcPMs["test_instance"]; !ok { 10073 t.Fatalf("root module ReadResource not called") 10074 } else if pm.IsNull() { 10075 t.Fatalf("null ProviderMeta in root module ReadResource") 10076 } else { 10077 expectations["quux"] = pm 10078 } 10079 10080 type metaStruct struct { 10081 Baz string `cty:"baz"` 10082 } 10083 10084 for expected, v := range expectations { 10085 var meta metaStruct 10086 err := gocty.FromCtyValue(v, &meta) 10087 if err != nil { 10088 t.Fatalf("Error parsing cty value: %s", err) 10089 } 10090 if meta.Baz != expected { 10091 t.Fatalf("Expected meta.Baz to be %q, got %q", expected, meta.Baz) 10092 } 10093 } 10094 } 10095 10096 func TestContext2Apply_ProviderMeta_refresh_setNoSchema(t *testing.T) { 10097 m := testModule(t, "provider-meta-set") 10098 p := testProvider("test") 10099 p.PlanResourceChangeFn = testDiffFn 10100 10101 // we need a schema for plan/apply so they don't error 10102 schema := p.ProviderSchema() 10103 schema.ProviderMeta = &configschema.Block{ 10104 Attributes: map[string]*configschema.Attribute{ 10105 "baz": { 10106 Type: cty.String, 10107 Required: true, 10108 }, 10109 }, 10110 } 10111 p.GetProviderSchemaResponse = getProviderSchemaResponseFromProviderSchema(schema) 10112 ctx := testContext2(t, &ContextOpts{ 10113 Providers: map[addrs.Provider]providers.Factory{ 10114 addrs.NewDefaultProvider("test"): testProviderFuncFixed(p), 10115 }, 10116 }) 10117 10118 plan, diags := ctx.Plan(m, states.NewState(), DefaultPlanOpts) 10119 assertNoErrors(t, diags) 10120 10121 state, diags := ctx.Apply(plan, m) 10122 assertNoErrors(t, diags) 10123 10124 // drop the schema before refresh, to test that it errors 10125 schema.ProviderMeta = nil 10126 p.GetProviderSchemaResponse = getProviderSchemaResponseFromProviderSchema(schema) 10127 ctx = testContext2(t, &ContextOpts{ 10128 Providers: map[addrs.Provider]providers.Factory{ 10129 addrs.NewDefaultProvider("test"): testProviderFuncFixed(p), 10130 }, 10131 }) 10132 10133 _, diags = ctx.Refresh(m, state, DefaultPlanOpts) 10134 if !diags.HasErrors() { 10135 t.Fatalf("refresh supposed to error, has no errors") 10136 } 10137 10138 var rootErr, subErr bool 10139 errorSummary := "The resource test_%s.bar belongs to a provider that doesn't support provider_meta blocks" 10140 for _, diag := range diags { 10141 if diag.Description().Summary != "Provider registry.terraform.io/hashicorp/test doesn't support provider_meta" { 10142 t.Errorf("Unexpected error: %+v", diag.Description()) 10143 } 10144 switch diag.Description().Detail { 10145 case fmt.Sprintf(errorSummary, "instance"): 10146 rootErr = true 10147 case fmt.Sprintf(errorSummary, "resource"): 10148 subErr = true 10149 default: 10150 t.Errorf("Unexpected error: %s", diag.Description()) 10151 } 10152 } 10153 if !rootErr { 10154 t.Errorf("Expected unsupported provider_meta block error for root module, none received") 10155 } 10156 if !subErr { 10157 t.Errorf("Expected unsupported provider_meta block error for sub-module, none received") 10158 } 10159 } 10160 10161 func TestContext2Apply_ProviderMeta_refresh_setInvalid(t *testing.T) { 10162 m := testModule(t, "provider-meta-set") 10163 p := testProvider("test") 10164 p.PlanResourceChangeFn = testDiffFn 10165 10166 // we need a matching schema for plan/apply so they don't error 10167 schema := p.ProviderSchema() 10168 schema.ProviderMeta = &configschema.Block{ 10169 Attributes: map[string]*configschema.Attribute{ 10170 "baz": { 10171 Type: cty.String, 10172 Required: true, 10173 }, 10174 }, 10175 } 10176 p.GetProviderSchemaResponse = getProviderSchemaResponseFromProviderSchema(schema) 10177 ctx := testContext2(t, &ContextOpts{ 10178 Providers: map[addrs.Provider]providers.Factory{ 10179 addrs.NewDefaultProvider("test"): testProviderFuncFixed(p), 10180 }, 10181 }) 10182 10183 plan, diags := ctx.Plan(m, states.NewState(), DefaultPlanOpts) 10184 assertNoErrors(t, diags) 10185 10186 state, diags := ctx.Apply(plan, m) 10187 assertNoErrors(t, diags) 10188 10189 // change the schema before refresh, to test that it errors 10190 schema.ProviderMeta = &configschema.Block{ 10191 Attributes: map[string]*configschema.Attribute{ 10192 "quux": { 10193 Type: cty.String, 10194 Required: true, 10195 }, 10196 }, 10197 } 10198 p.GetProviderSchemaResponse = getProviderSchemaResponseFromProviderSchema(schema) 10199 ctx = testContext2(t, &ContextOpts{ 10200 Providers: map[addrs.Provider]providers.Factory{ 10201 addrs.NewDefaultProvider("test"): testProviderFuncFixed(p), 10202 }, 10203 }) 10204 10205 _, diags = ctx.Refresh(m, state, DefaultPlanOpts) 10206 if !diags.HasErrors() { 10207 t.Fatalf("refresh supposed to error, has no errors") 10208 } 10209 10210 var reqErr, invalidErr bool 10211 for _, diag := range diags { 10212 switch diag.Description().Summary { 10213 case "Missing required argument": 10214 if diag.Description().Detail == `The argument "quux" is required, but no definition was found.` { 10215 reqErr = true 10216 } else { 10217 t.Errorf("Unexpected error %+v", diag.Description()) 10218 } 10219 case "Unsupported argument": 10220 if diag.Description().Detail == `An argument named "baz" is not expected here.` { 10221 invalidErr = true 10222 } else { 10223 t.Errorf("Unexpected error %+v", diag.Description()) 10224 } 10225 default: 10226 t.Errorf("Unexpected error %+v", diag.Description()) 10227 } 10228 } 10229 if !reqErr { 10230 t.Errorf("Expected missing required argument error, none received") 10231 } 10232 if !invalidErr { 10233 t.Errorf("Expected unsupported argument error, none received") 10234 } 10235 } 10236 10237 func TestContext2Apply_ProviderMeta_refreshdata_set(t *testing.T) { 10238 m := testModule(t, "provider-meta-data-set") 10239 p := testProvider("test") 10240 p.PlanResourceChangeFn = testDiffFn 10241 schema := p.ProviderSchema() 10242 schema.ProviderMeta = &configschema.Block{ 10243 Attributes: map[string]*configschema.Attribute{ 10244 "baz": { 10245 Type: cty.String, 10246 Required: true, 10247 }, 10248 }, 10249 } 10250 p.GetProviderSchemaResponse = getProviderSchemaResponseFromProviderSchema(schema) 10251 ctx := testContext2(t, &ContextOpts{ 10252 Providers: map[addrs.Provider]providers.Factory{ 10253 addrs.NewDefaultProvider("test"): testProviderFuncFixed(p), 10254 }, 10255 }) 10256 rdsPMs := map[string]cty.Value{} 10257 p.ReadDataSourceFn = func(req providers.ReadDataSourceRequest) providers.ReadDataSourceResponse { 10258 rdsPMs[req.TypeName] = req.ProviderMeta 10259 switch req.TypeName { 10260 case "test_data_source": 10261 log.Printf("[TRACE] test_data_source RDSR returning") 10262 return providers.ReadDataSourceResponse{ 10263 State: cty.ObjectVal(map[string]cty.Value{ 10264 "id": cty.StringVal("yo"), 10265 "foo": cty.StringVal("bar"), 10266 }), 10267 } 10268 case "test_file": 10269 log.Printf("[TRACE] test_file RDSR returning") 10270 return providers.ReadDataSourceResponse{ 10271 State: cty.ObjectVal(map[string]cty.Value{ 10272 "id": cty.StringVal("bar"), 10273 "rendered": cty.StringVal("baz"), 10274 "template": cty.StringVal(""), 10275 }), 10276 } 10277 default: 10278 // config drift, oops 10279 log.Printf("[TRACE] unknown request TypeName: %q", req.TypeName) 10280 return providers.ReadDataSourceResponse{} 10281 } 10282 } 10283 10284 plan, diags := ctx.Plan(m, states.NewState(), DefaultPlanOpts) 10285 assertNoErrors(t, diags) 10286 10287 state, diags := ctx.Apply(plan, m) 10288 assertNoErrors(t, diags) 10289 10290 _, diags = ctx.Refresh(m, state, DefaultPlanOpts) 10291 assertNoErrors(t, diags) 10292 10293 if !p.ReadDataSourceCalled { 10294 t.Fatalf("ReadDataSource not called") 10295 } 10296 10297 expectations := map[string]cty.Value{} 10298 10299 if pm, ok := rdsPMs["test_file"]; !ok { 10300 t.Fatalf("sub-module ReadDataSource not called") 10301 } else if pm.IsNull() { 10302 t.Fatalf("null ProviderMeta in sub-module ReadDataSource") 10303 } else { 10304 expectations["quux-submodule"] = pm 10305 } 10306 10307 if pm, ok := rdsPMs["test_data_source"]; !ok { 10308 t.Fatalf("root module ReadDataSource not called") 10309 } else if pm.IsNull() { 10310 t.Fatalf("null ProviderMeta in root module ReadDataSource") 10311 } else { 10312 expectations["quux"] = pm 10313 } 10314 10315 type metaStruct struct { 10316 Baz string `cty:"baz"` 10317 } 10318 10319 for expected, v := range expectations { 10320 var meta metaStruct 10321 err := gocty.FromCtyValue(v, &meta) 10322 if err != nil { 10323 t.Fatalf("Error parsing cty value: %s", err) 10324 } 10325 if meta.Baz != expected { 10326 t.Fatalf("Expected meta.Baz to be %q, got %q", expected, meta.Baz) 10327 } 10328 } 10329 } 10330 10331 func TestContext2Apply_ProviderMeta_refreshdata_unset(t *testing.T) { 10332 m := testModule(t, "provider-meta-data-unset") 10333 p := testProvider("test") 10334 p.PlanResourceChangeFn = testDiffFn 10335 schema := p.ProviderSchema() 10336 schema.ProviderMeta = &configschema.Block{ 10337 Attributes: map[string]*configschema.Attribute{ 10338 "baz": { 10339 Type: cty.String, 10340 Required: true, 10341 }, 10342 }, 10343 } 10344 p.GetProviderSchemaResponse = getProviderSchemaResponseFromProviderSchema(schema) 10345 ctx := testContext2(t, &ContextOpts{ 10346 Providers: map[addrs.Provider]providers.Factory{ 10347 addrs.NewDefaultProvider("test"): testProviderFuncFixed(p), 10348 }, 10349 }) 10350 rdsPMs := map[string]cty.Value{} 10351 p.ReadDataSourceFn = func(req providers.ReadDataSourceRequest) providers.ReadDataSourceResponse { 10352 rdsPMs[req.TypeName] = req.ProviderMeta 10353 switch req.TypeName { 10354 case "test_data_source": 10355 return providers.ReadDataSourceResponse{ 10356 State: cty.ObjectVal(map[string]cty.Value{ 10357 "id": cty.StringVal("yo"), 10358 "foo": cty.StringVal("bar"), 10359 }), 10360 } 10361 case "test_file": 10362 return providers.ReadDataSourceResponse{ 10363 State: cty.ObjectVal(map[string]cty.Value{ 10364 "id": cty.StringVal("bar"), 10365 "rendered": cty.StringVal("baz"), 10366 "template": cty.StringVal(""), 10367 }), 10368 } 10369 default: 10370 // config drift, oops 10371 return providers.ReadDataSourceResponse{} 10372 } 10373 } 10374 10375 plan, diags := ctx.Plan(m, states.NewState(), DefaultPlanOpts) 10376 assertNoErrors(t, diags) 10377 10378 _, diags = ctx.Apply(plan, m) 10379 assertNoErrors(t, diags) 10380 10381 if !p.ReadDataSourceCalled { 10382 t.Fatalf("ReadDataSource not called") 10383 } 10384 10385 if pm, ok := rdsPMs["test_file"]; !ok { 10386 t.Fatalf("sub-module ReadDataSource not called") 10387 } else if !pm.IsNull() { 10388 t.Fatalf("non-null ProviderMeta in sub-module ReadDataSource") 10389 } 10390 10391 if pm, ok := rdsPMs["test_data_source"]; !ok { 10392 t.Fatalf("root module ReadDataSource not called") 10393 } else if !pm.IsNull() { 10394 t.Fatalf("non-null ProviderMeta in root module ReadDataSource") 10395 } 10396 } 10397 10398 func TestContext2Apply_ProviderMeta_refreshdata_setNoSchema(t *testing.T) { 10399 m := testModule(t, "provider-meta-data-set") 10400 p := testProvider("test") 10401 p.PlanResourceChangeFn = testDiffFn 10402 ctx := testContext2(t, &ContextOpts{ 10403 Providers: map[addrs.Provider]providers.Factory{ 10404 addrs.NewDefaultProvider("test"): testProviderFuncFixed(p), 10405 }, 10406 }) 10407 p.ReadDataSourceResponse = &providers.ReadDataSourceResponse{ 10408 State: cty.ObjectVal(map[string]cty.Value{ 10409 "id": cty.StringVal("yo"), 10410 "foo": cty.StringVal("bar"), 10411 }), 10412 } 10413 10414 _, diags := ctx.Refresh(m, states.NewState(), DefaultPlanOpts) 10415 if !diags.HasErrors() { 10416 t.Fatalf("refresh supposed to error, has no errors") 10417 } 10418 10419 var rootErr, subErr bool 10420 errorSummary := "The resource data.test_%s.foo belongs to a provider that doesn't support provider_meta blocks" 10421 for _, diag := range diags { 10422 if diag.Description().Summary != "Provider registry.terraform.io/hashicorp/test doesn't support provider_meta" { 10423 t.Errorf("Unexpected error: %+v", diag.Description()) 10424 } 10425 switch diag.Description().Detail { 10426 case fmt.Sprintf(errorSummary, "data_source"): 10427 rootErr = true 10428 case fmt.Sprintf(errorSummary, "file"): 10429 subErr = true 10430 default: 10431 t.Errorf("Unexpected error: %s", diag.Description()) 10432 } 10433 } 10434 if !rootErr { 10435 t.Errorf("Expected unsupported provider_meta block error for root module, none received") 10436 } 10437 if !subErr { 10438 t.Errorf("Expected unsupported provider_meta block error for sub-module, none received") 10439 } 10440 } 10441 10442 func TestContext2Apply_ProviderMeta_refreshdata_setInvalid(t *testing.T) { 10443 m := testModule(t, "provider-meta-data-set") 10444 p := testProvider("test") 10445 p.PlanResourceChangeFn = testDiffFn 10446 schema := p.ProviderSchema() 10447 schema.ProviderMeta = &configschema.Block{ 10448 Attributes: map[string]*configschema.Attribute{ 10449 "quux": { 10450 Type: cty.String, 10451 Required: true, 10452 }, 10453 }, 10454 } 10455 p.GetProviderSchemaResponse = getProviderSchemaResponseFromProviderSchema(schema) 10456 ctx := testContext2(t, &ContextOpts{ 10457 Providers: map[addrs.Provider]providers.Factory{ 10458 addrs.NewDefaultProvider("test"): testProviderFuncFixed(p), 10459 }, 10460 }) 10461 p.ReadDataSourceResponse = &providers.ReadDataSourceResponse{ 10462 State: cty.ObjectVal(map[string]cty.Value{ 10463 "id": cty.StringVal("yo"), 10464 "foo": cty.StringVal("bar"), 10465 }), 10466 } 10467 10468 _, diags := ctx.Refresh(m, states.NewState(), DefaultPlanOpts) 10469 if !diags.HasErrors() { 10470 t.Fatalf("refresh supposed to error, has no errors") 10471 } 10472 10473 var reqErr, invalidErr bool 10474 for _, diag := range diags { 10475 switch diag.Description().Summary { 10476 case "Missing required argument": 10477 if diag.Description().Detail == `The argument "quux" is required, but no definition was found.` { 10478 reqErr = true 10479 } else { 10480 t.Errorf("Unexpected error %+v", diag.Description()) 10481 } 10482 case "Unsupported argument": 10483 if diag.Description().Detail == `An argument named "baz" is not expected here.` { 10484 invalidErr = true 10485 } else { 10486 t.Errorf("Unexpected error %+v", diag.Description()) 10487 } 10488 default: 10489 t.Errorf("Unexpected error %+v", diag.Description()) 10490 } 10491 } 10492 if !reqErr { 10493 t.Errorf("Expected missing required argument error, none received") 10494 } 10495 if !invalidErr { 10496 t.Errorf("Expected unsupported argument error, none received") 10497 } 10498 } 10499 10500 func TestContext2Apply_expandModuleVariables(t *testing.T) { 10501 m := testModuleInline(t, map[string]string{ 10502 "main.tf": ` 10503 module "mod1" { 10504 for_each = toset(["a"]) 10505 source = "./mod" 10506 } 10507 10508 module "mod2" { 10509 source = "./mod" 10510 in = module.mod1["a"].out 10511 } 10512 `, 10513 "mod/main.tf": ` 10514 resource "aws_instance" "foo" { 10515 foo = var.in 10516 } 10517 10518 variable "in" { 10519 type = string 10520 default = "default" 10521 } 10522 10523 output "out" { 10524 value = aws_instance.foo.id 10525 } 10526 `, 10527 }) 10528 10529 p := testProvider("aws") 10530 p.PlanResourceChangeFn = testDiffFn 10531 p.ApplyResourceChangeFn = testApplyFn 10532 ctx := testContext2(t, &ContextOpts{ 10533 Providers: map[addrs.Provider]providers.Factory{ 10534 addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p), 10535 }, 10536 }) 10537 10538 plan, diags := ctx.Plan(m, states.NewState(), DefaultPlanOpts) 10539 if diags.HasErrors() { 10540 t.Fatal(diags.ErrWithWarnings()) 10541 } 10542 10543 state, diags := ctx.Apply(plan, m) 10544 if diags.HasErrors() { 10545 t.Fatal(diags.ErrWithWarnings()) 10546 } 10547 10548 expected := `<no state> 10549 module.mod1["a"]: 10550 aws_instance.foo: 10551 ID = foo 10552 provider = provider["registry.terraform.io/hashicorp/aws"] 10553 foo = default 10554 type = aws_instance 10555 10556 Outputs: 10557 10558 out = foo 10559 module.mod2: 10560 aws_instance.foo: 10561 ID = foo 10562 provider = provider["registry.terraform.io/hashicorp/aws"] 10563 foo = foo 10564 type = aws_instance 10565 10566 Dependencies: 10567 module.mod1.aws_instance.foo` 10568 10569 if state.String() != expected { 10570 t.Fatalf("expected:\n%s\ngot:\n%s\n", expected, state) 10571 } 10572 } 10573 10574 func TestContext2Apply_inheritAndStoreCBD(t *testing.T) { 10575 m := testModuleInline(t, map[string]string{ 10576 "main.tf": ` 10577 resource "aws_instance" "foo" { 10578 } 10579 10580 resource "aws_instance" "cbd" { 10581 foo = aws_instance.foo.id 10582 lifecycle { 10583 create_before_destroy = true 10584 } 10585 } 10586 `, 10587 }) 10588 10589 p := testProvider("aws") 10590 p.PlanResourceChangeFn = testDiffFn 10591 ctx := testContext2(t, &ContextOpts{ 10592 Providers: map[addrs.Provider]providers.Factory{ 10593 addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p), 10594 }, 10595 }) 10596 10597 plan, diags := ctx.Plan(m, states.NewState(), DefaultPlanOpts) 10598 if diags.HasErrors() { 10599 t.Fatal(diags.ErrWithWarnings()) 10600 } 10601 10602 state, diags := ctx.Apply(plan, m) 10603 if diags.HasErrors() { 10604 t.Fatal(diags.ErrWithWarnings()) 10605 } 10606 10607 foo := state.ResourceInstance(mustResourceInstanceAddr("aws_instance.foo")) 10608 if !foo.Current.CreateBeforeDestroy { 10609 t.Fatal("aws_instance.foo should also be create_before_destroy") 10610 } 10611 } 10612 10613 func TestContext2Apply_moduleDependsOn(t *testing.T) { 10614 m := testModule(t, "apply-module-depends-on") 10615 10616 p := testProvider("test") 10617 10618 // each instance being applied should happen in sequential order 10619 applied := int64(0) 10620 10621 p.ReadDataSourceFn = func(req providers.ReadDataSourceRequest) providers.ReadDataSourceResponse { 10622 cfg := req.Config.AsValueMap() 10623 foo := cfg["foo"].AsString() 10624 ord := atomic.LoadInt64(&applied) 10625 10626 resp := providers.ReadDataSourceResponse{ 10627 State: cty.ObjectVal(map[string]cty.Value{ 10628 "id": cty.StringVal("data"), 10629 "foo": cfg["foo"], 10630 }), 10631 } 10632 10633 if foo == "a" && ord < 4 { 10634 // due to data source "a"'s module depending on instance 4, this 10635 // should not be less than 4 10636 resp.Diagnostics = resp.Diagnostics.Append(fmt.Errorf("data source a read too early")) 10637 } 10638 if foo == "b" && ord < 1 { 10639 // due to data source "b"'s module depending on instance 1, this 10640 // should not be less than 1 10641 resp.Diagnostics = resp.Diagnostics.Append(fmt.Errorf("data source b read too early")) 10642 } 10643 return resp 10644 } 10645 p.PlanResourceChangeFn = testDiffFn 10646 10647 p.ApplyResourceChangeFn = func(req providers.ApplyResourceChangeRequest) (resp providers.ApplyResourceChangeResponse) { 10648 state := req.PlannedState.AsValueMap() 10649 num, _ := state["num"].AsBigFloat().Float64() 10650 ord := int64(num) 10651 if !atomic.CompareAndSwapInt64(&applied, ord-1, ord) { 10652 actual := atomic.LoadInt64(&applied) 10653 resp.Diagnostics = resp.Diagnostics.Append(fmt.Errorf("instance %d was applied after %d", ord, actual)) 10654 } 10655 10656 state["id"] = cty.StringVal(fmt.Sprintf("test_%d", ord)) 10657 state["type"] = cty.StringVal("test_instance") 10658 resp.NewState = cty.ObjectVal(state) 10659 10660 return resp 10661 } 10662 10663 ctx := testContext2(t, &ContextOpts{ 10664 Providers: map[addrs.Provider]providers.Factory{ 10665 addrs.NewDefaultProvider("test"): testProviderFuncFixed(p), 10666 }, 10667 }) 10668 10669 plan, diags := ctx.Plan(m, states.NewState(), DefaultPlanOpts) 10670 if diags.HasErrors() { 10671 t.Fatal(diags.ErrWithWarnings()) 10672 } 10673 10674 state, diags := ctx.Apply(plan, m) 10675 if diags.HasErrors() { 10676 t.Fatal(diags.ErrWithWarnings()) 10677 } 10678 10679 plan, diags = ctx.Plan(m, state, DefaultPlanOpts) 10680 if diags.HasErrors() { 10681 t.Fatal(diags.ErrWithWarnings()) 10682 } 10683 10684 for _, res := range plan.Changes.Resources { 10685 if res.Action != plans.NoOp { 10686 t.Fatalf("expected NoOp, got %s for %s", res.Action, res.Addr) 10687 } 10688 } 10689 } 10690 10691 func TestContext2Apply_moduleSelfReference(t *testing.T) { 10692 m := testModuleInline(t, map[string]string{ 10693 "main.tf": ` 10694 module "test" { 10695 source = "./test" 10696 10697 a = module.test.b 10698 } 10699 10700 output "c" { 10701 value = module.test.c 10702 } 10703 `, 10704 "test/main.tf": ` 10705 variable "a" {} 10706 10707 resource "test_instance" "test" { 10708 } 10709 10710 output "b" { 10711 value = test_instance.test.id 10712 } 10713 10714 output "c" { 10715 value = var.a 10716 }`}) 10717 10718 p := testProvider("test") 10719 p.PlanResourceChangeFn = testDiffFn 10720 ctx := testContext2(t, &ContextOpts{ 10721 Providers: map[addrs.Provider]providers.Factory{ 10722 addrs.NewDefaultProvider("test"): testProviderFuncFixed(p), 10723 }, 10724 }) 10725 10726 plan, diags := ctx.Plan(m, states.NewState(), DefaultPlanOpts) 10727 if diags.HasErrors() { 10728 t.Fatal(diags.ErrWithWarnings()) 10729 } 10730 10731 state, diags := ctx.Apply(plan, m) 10732 if diags.HasErrors() { 10733 t.Fatal(diags.ErrWithWarnings()) 10734 } 10735 10736 ctx = testContext2(t, &ContextOpts{ 10737 Providers: map[addrs.Provider]providers.Factory{ 10738 addrs.NewDefaultProvider("test"): testProviderFuncFixed(p), 10739 }, 10740 }) 10741 10742 plan, diags = ctx.Plan(m, state, &PlanOpts{ 10743 Mode: plans.DestroyMode, 10744 }) 10745 if diags.HasErrors() { 10746 t.Fatal(diags.ErrWithWarnings()) 10747 } 10748 10749 state, diags = ctx.Apply(plan, m) 10750 if diags.HasErrors() { 10751 t.Fatal(diags.ErrWithWarnings()) 10752 } 10753 10754 if !state.Empty() { 10755 t.Fatal("expected empty state, got:", state) 10756 } 10757 } 10758 10759 func TestContext2Apply_moduleExpandDependsOn(t *testing.T) { 10760 m := testModuleInline(t, map[string]string{ 10761 "main.tf": ` 10762 module "child" { 10763 count = 1 10764 source = "./child" 10765 10766 depends_on = [test_instance.a, test_instance.b] 10767 } 10768 10769 resource "test_instance" "a" { 10770 } 10771 10772 10773 resource "test_instance" "b" { 10774 } 10775 `, 10776 "child/main.tf": ` 10777 resource "test_instance" "foo" { 10778 } 10779 10780 output "myoutput" { 10781 value = "literal string" 10782 } 10783 `}) 10784 10785 p := testProvider("test") 10786 p.PlanResourceChangeFn = testDiffFn 10787 ctx := testContext2(t, &ContextOpts{ 10788 Providers: map[addrs.Provider]providers.Factory{ 10789 addrs.NewDefaultProvider("test"): testProviderFuncFixed(p), 10790 }, 10791 }) 10792 10793 plan, diags := ctx.Plan(m, states.NewState(), DefaultPlanOpts) 10794 if diags.HasErrors() { 10795 t.Fatal(diags.ErrWithWarnings()) 10796 } 10797 10798 state, diags := ctx.Apply(plan, m) 10799 if diags.HasErrors() { 10800 t.Fatal(diags.ErrWithWarnings()) 10801 } 10802 10803 ctx = testContext2(t, &ContextOpts{ 10804 Providers: map[addrs.Provider]providers.Factory{ 10805 addrs.NewDefaultProvider("test"): testProviderFuncFixed(p), 10806 }, 10807 }) 10808 10809 plan, diags = ctx.Plan(m, state, &PlanOpts{ 10810 Mode: plans.DestroyMode, 10811 }) 10812 if diags.HasErrors() { 10813 t.Fatal(diags.ErrWithWarnings()) 10814 } 10815 10816 state, diags = ctx.Apply(plan, m) 10817 if diags.HasErrors() { 10818 t.Fatal(diags.ErrWithWarnings()) 10819 } 10820 10821 if !state.Empty() { 10822 t.Fatal("expected empty state, got:", state) 10823 } 10824 } 10825 10826 func TestContext2Apply_scaleInCBD(t *testing.T) { 10827 m := testModuleInline(t, map[string]string{ 10828 "main.tf": ` 10829 variable "ct" { 10830 type = number 10831 } 10832 10833 resource "test_instance" "a" { 10834 count = var.ct 10835 } 10836 10837 resource "test_instance" "b" { 10838 require_new = local.removable 10839 lifecycle { 10840 create_before_destroy = true 10841 } 10842 } 10843 10844 resource "test_instance" "c" { 10845 require_new = test_instance.b.id 10846 lifecycle { 10847 create_before_destroy = true 10848 } 10849 } 10850 10851 output "out" { 10852 value = join(".", test_instance.a[*].id) 10853 } 10854 10855 locals { 10856 removable = join(".", test_instance.a[*].id) 10857 } 10858 `}) 10859 10860 state := states.NewState() 10861 root := state.EnsureModule(addrs.RootModuleInstance) 10862 root.SetResourceInstanceCurrent( 10863 mustResourceInstanceAddr("test_instance.a[0]").Resource, 10864 &states.ResourceInstanceObjectSrc{ 10865 Status: states.ObjectReady, 10866 AttrsJSON: []byte(`{"id":"a0"}`), 10867 Dependencies: []addrs.ConfigResource{}, 10868 CreateBeforeDestroy: true, 10869 }, 10870 mustProviderConfig(`provider["registry.terraform.io/hashicorp/test"]`), 10871 ) 10872 root.SetResourceInstanceCurrent( 10873 mustResourceInstanceAddr("test_instance.a[1]").Resource, 10874 &states.ResourceInstanceObjectSrc{ 10875 Status: states.ObjectReady, 10876 AttrsJSON: []byte(`{"id":"a1"}`), 10877 Dependencies: []addrs.ConfigResource{}, 10878 CreateBeforeDestroy: true, 10879 }, 10880 mustProviderConfig(`provider["registry.terraform.io/hashicorp/test"]`), 10881 ) 10882 root.SetResourceInstanceCurrent( 10883 mustResourceInstanceAddr("test_instance.b").Resource, 10884 &states.ResourceInstanceObjectSrc{ 10885 Status: states.ObjectReady, 10886 AttrsJSON: []byte(`{"id":"b", "require_new":"old.old"}`), 10887 Dependencies: []addrs.ConfigResource{mustConfigResourceAddr("test_instance.a")}, 10888 CreateBeforeDestroy: true, 10889 }, 10890 mustProviderConfig(`provider["registry.terraform.io/hashicorp/test"]`), 10891 ) 10892 root.SetResourceInstanceCurrent( 10893 mustResourceInstanceAddr("test_instance.c").Resource, 10894 &states.ResourceInstanceObjectSrc{ 10895 Status: states.ObjectReady, 10896 AttrsJSON: []byte(`{"id":"c", "require_new":"b"}`), 10897 Dependencies: []addrs.ConfigResource{ 10898 mustConfigResourceAddr("test_instance.a"), 10899 mustConfigResourceAddr("test_instance.b"), 10900 }, 10901 CreateBeforeDestroy: true, 10902 }, 10903 mustProviderConfig(`provider["registry.terraform.io/hashicorp/test"]`), 10904 ) 10905 10906 p := testProvider("test") 10907 10908 p.PlanResourceChangeFn = func(r providers.PlanResourceChangeRequest) (resp providers.PlanResourceChangeResponse) { 10909 n := r.ProposedNewState.AsValueMap() 10910 10911 if r.PriorState.IsNull() { 10912 n["id"] = cty.UnknownVal(cty.String) 10913 resp.PlannedState = cty.ObjectVal(n) 10914 return resp 10915 } 10916 10917 p := r.PriorState.AsValueMap() 10918 10919 priorRN := p["require_new"] 10920 newRN := n["require_new"] 10921 10922 if eq := priorRN.Equals(newRN); !eq.IsKnown() || eq.False() { 10923 resp.RequiresReplace = []cty.Path{{cty.GetAttrStep{Name: "require_new"}}} 10924 n["id"] = cty.UnknownVal(cty.String) 10925 } 10926 10927 resp.PlannedState = cty.ObjectVal(n) 10928 return resp 10929 } 10930 10931 // reduce the count to 1 10932 ctx := testContext2(t, &ContextOpts{ 10933 Providers: map[addrs.Provider]providers.Factory{ 10934 addrs.NewDefaultProvider("test"): testProviderFuncFixed(p), 10935 }, 10936 }) 10937 10938 plan, diags := ctx.Plan(m, state, &PlanOpts{ 10939 Mode: plans.NormalMode, 10940 SetVariables: InputValues{ 10941 "ct": &InputValue{ 10942 Value: cty.NumberIntVal(1), 10943 SourceType: ValueFromCaller, 10944 }, 10945 }, 10946 }) 10947 if diags.HasErrors() { 10948 t.Fatal(diags.ErrWithWarnings()) 10949 } 10950 10951 state, diags = ctx.Apply(plan, m) 10952 if diags.HasErrors() { 10953 log.Fatal(diags.ErrWithWarnings()) 10954 } 10955 10956 // check the output, as those can't cause an error planning the value 10957 out := state.RootModule().OutputValues["out"].Value.AsString() 10958 if out != "a0" { 10959 t.Fatalf(`expected output "a0", got: %q`, out) 10960 } 10961 10962 // reduce the count to 0 10963 ctx = testContext2(t, &ContextOpts{ 10964 Providers: map[addrs.Provider]providers.Factory{ 10965 addrs.NewDefaultProvider("test"): testProviderFuncFixed(p), 10966 }, 10967 }) 10968 10969 plan, diags = ctx.Plan(m, state, &PlanOpts{ 10970 Mode: plans.NormalMode, 10971 SetVariables: InputValues{ 10972 "ct": &InputValue{ 10973 Value: cty.NumberIntVal(0), 10974 SourceType: ValueFromCaller, 10975 }, 10976 }, 10977 }) 10978 if diags.HasErrors() { 10979 t.Fatal(diags.ErrWithWarnings()) 10980 } 10981 10982 state, diags = ctx.Apply(plan, m) 10983 if diags.HasErrors() { 10984 t.Fatal(diags.ErrWithWarnings()) 10985 } 10986 10987 // check the output, as those can't cause an error planning the value 10988 out = state.RootModule().OutputValues["out"].Value.AsString() 10989 if out != "" { 10990 t.Fatalf(`expected output "", got: %q`, out) 10991 } 10992 } 10993 10994 // Ensure that we can destroy when a provider references a resource that will 10995 // also be destroyed 10996 func TestContext2Apply_destroyProviderReference(t *testing.T) { 10997 m := testModuleInline(t, map[string]string{ 10998 "main.tf": ` 10999 provider "null" { 11000 value = "" 11001 } 11002 11003 module "mod" { 11004 source = "./mod" 11005 } 11006 11007 provider "test" { 11008 value = module.mod.output 11009 } 11010 11011 resource "test_instance" "bar" { 11012 } 11013 `, 11014 "mod/main.tf": ` 11015 data "null_data_source" "foo" { 11016 count = 1 11017 } 11018 11019 11020 output "output" { 11021 value = data.null_data_source.foo[0].output 11022 } 11023 `}) 11024 11025 schemaFn := func(name string) *ProviderSchema { 11026 return &ProviderSchema{ 11027 Provider: &configschema.Block{ 11028 Attributes: map[string]*configschema.Attribute{ 11029 "value": { 11030 Type: cty.String, 11031 Required: true, 11032 }, 11033 }, 11034 }, 11035 ResourceTypes: map[string]*configschema.Block{ 11036 name + "_instance": { 11037 Attributes: map[string]*configschema.Attribute{ 11038 "id": { 11039 Type: cty.String, 11040 Computed: true, 11041 }, 11042 "foo": { 11043 Type: cty.String, 11044 Optional: true, 11045 }, 11046 }, 11047 }, 11048 }, 11049 DataSources: map[string]*configschema.Block{ 11050 name + "_data_source": { 11051 Attributes: map[string]*configschema.Attribute{ 11052 "id": { 11053 Type: cty.String, 11054 Computed: true, 11055 }, 11056 "output": { 11057 Type: cty.String, 11058 Computed: true, 11059 }, 11060 }, 11061 }, 11062 }, 11063 } 11064 } 11065 11066 testP := new(MockProvider) 11067 testP.ReadResourceFn = func(req providers.ReadResourceRequest) providers.ReadResourceResponse { 11068 return providers.ReadResourceResponse{NewState: req.PriorState} 11069 } 11070 testP.GetProviderSchemaResponse = getProviderSchemaResponseFromProviderSchema(schemaFn("test")) 11071 11072 providerConfig := "" 11073 testP.ConfigureProviderFn = func(req providers.ConfigureProviderRequest) (resp providers.ConfigureProviderResponse) { 11074 value := req.Config.GetAttr("value") 11075 if value.IsKnown() && !value.IsNull() { 11076 providerConfig = value.AsString() 11077 } else { 11078 providerConfig = "" 11079 } 11080 return resp 11081 } 11082 testP.ApplyResourceChangeFn = func(req providers.ApplyResourceChangeRequest) (resp providers.ApplyResourceChangeResponse) { 11083 if providerConfig != "valid" { 11084 resp.Diagnostics = resp.Diagnostics.Append(fmt.Errorf("provider config is %q", providerConfig)) 11085 return 11086 } 11087 return testApplyFn(req) 11088 } 11089 testP.PlanResourceChangeFn = testDiffFn 11090 11091 nullP := new(MockProvider) 11092 nullP.ReadResourceFn = func(req providers.ReadResourceRequest) providers.ReadResourceResponse { 11093 return providers.ReadResourceResponse{NewState: req.PriorState} 11094 } 11095 nullP.GetProviderSchemaResponse = getProviderSchemaResponseFromProviderSchema(schemaFn("null")) 11096 11097 nullP.ApplyResourceChangeFn = testApplyFn 11098 nullP.PlanResourceChangeFn = testDiffFn 11099 11100 nullP.ReadDataSourceResponse = &providers.ReadDataSourceResponse{ 11101 State: cty.ObjectVal(map[string]cty.Value{ 11102 "id": cty.StringVal("ID"), 11103 "output": cty.StringVal("valid"), 11104 }), 11105 } 11106 11107 ctx := testContext2(t, &ContextOpts{ 11108 Providers: map[addrs.Provider]providers.Factory{ 11109 addrs.NewDefaultProvider("test"): testProviderFuncFixed(testP), 11110 addrs.NewDefaultProvider("null"): testProviderFuncFixed(nullP), 11111 }, 11112 }) 11113 11114 plan, diags := ctx.Plan(m, states.NewState(), DefaultPlanOpts) 11115 assertNoErrors(t, diags) 11116 11117 state, diags := ctx.Apply(plan, m) 11118 if diags.HasErrors() { 11119 t.Fatalf("apply errors: %s", diags.Err()) 11120 } 11121 11122 ctx = testContext2(t, &ContextOpts{ 11123 Providers: map[addrs.Provider]providers.Factory{ 11124 addrs.NewDefaultProvider("test"): testProviderFuncFixed(testP), 11125 addrs.NewDefaultProvider("null"): testProviderFuncFixed(nullP), 11126 }, 11127 }) 11128 11129 plan, diags = ctx.Plan(m, state, &PlanOpts{ 11130 Mode: plans.DestroyMode, 11131 }) 11132 assertNoErrors(t, diags) 11133 11134 if _, diags := ctx.Apply(plan, m); diags.HasErrors() { 11135 t.Fatalf("destroy apply errors: %s", diags.Err()) 11136 } 11137 } 11138 11139 // Destroying properly requires pruning out all unneeded config nodes to 11140 // prevent incorrect expansion evaluation. 11141 func TestContext2Apply_destroyInterModuleExpansion(t *testing.T) { 11142 m := testModuleInline(t, map[string]string{ 11143 "main.tf": ` 11144 data "test_data_source" "a" { 11145 for_each = { 11146 one = "thing" 11147 } 11148 } 11149 11150 locals { 11151 module_input = { 11152 for k, v in data.test_data_source.a : k => v.id 11153 } 11154 } 11155 11156 module "mod1" { 11157 source = "./mod" 11158 input = local.module_input 11159 } 11160 11161 module "mod2" { 11162 source = "./mod" 11163 input = module.mod1.outputs 11164 } 11165 11166 resource "test_instance" "bar" { 11167 for_each = module.mod2.outputs 11168 } 11169 11170 output "module_output" { 11171 value = module.mod2.outputs 11172 } 11173 output "test_instances" { 11174 value = test_instance.bar 11175 } 11176 `, 11177 "mod/main.tf": ` 11178 variable "input" { 11179 } 11180 11181 data "test_data_source" "foo" { 11182 for_each = var.input 11183 } 11184 11185 output "outputs" { 11186 value = data.test_data_source.foo 11187 } 11188 `}) 11189 11190 p := testProvider("test") 11191 p.PlanResourceChangeFn = testDiffFn 11192 p.ReadDataSourceFn = func(req providers.ReadDataSourceRequest) providers.ReadDataSourceResponse { 11193 return providers.ReadDataSourceResponse{ 11194 State: cty.ObjectVal(map[string]cty.Value{ 11195 "id": cty.StringVal("data_source"), 11196 "foo": cty.StringVal("output"), 11197 }), 11198 } 11199 } 11200 11201 ctx := testContext2(t, &ContextOpts{ 11202 Providers: map[addrs.Provider]providers.Factory{ 11203 addrs.NewDefaultProvider("test"): testProviderFuncFixed(p), 11204 }, 11205 }) 11206 11207 plan, diags := ctx.Plan(m, states.NewState(), DefaultPlanOpts) 11208 assertNoErrors(t, diags) 11209 11210 state, diags := ctx.Apply(plan, m) 11211 if diags.HasErrors() { 11212 t.Fatalf("apply errors: %s", diags.Err()) 11213 } 11214 11215 destroy := func() { 11216 ctx = testContext2(t, &ContextOpts{ 11217 Providers: map[addrs.Provider]providers.Factory{ 11218 addrs.NewDefaultProvider("test"): testProviderFuncFixed(p), 11219 }, 11220 }) 11221 11222 plan, diags = ctx.Plan(m, state, &PlanOpts{ 11223 Mode: plans.DestroyMode, 11224 }) 11225 assertNoErrors(t, diags) 11226 11227 state, diags = ctx.Apply(plan, m) 11228 if diags.HasErrors() { 11229 t.Fatalf("destroy apply errors: %s", diags.Err()) 11230 } 11231 } 11232 11233 destroy() 11234 // Destroying again from the empty state should not cause any errors either 11235 destroy() 11236 } 11237 11238 func TestContext2Apply_createBeforeDestroyWithModule(t *testing.T) { 11239 m := testModuleInline(t, map[string]string{ 11240 "main.tf": ` 11241 variable "v" {} 11242 11243 module "mod" { 11244 source = "./mod" 11245 in = var.v 11246 } 11247 11248 resource "test_resource" "a" { 11249 value = var.v 11250 depends_on = [module.mod] 11251 lifecycle { 11252 create_before_destroy = true 11253 } 11254 } 11255 `, 11256 "mod/main.tf": ` 11257 variable "in" {} 11258 11259 resource "test_resource" "a" { 11260 value = var.in 11261 } 11262 `}) 11263 11264 p := testProvider("test") 11265 p.PlanResourceChangeFn = func(req providers.PlanResourceChangeRequest) providers.PlanResourceChangeResponse { 11266 proposed := req.ProposedNewState.AsValueMap() 11267 proposed["id"] = cty.UnknownVal(cty.String) 11268 return providers.PlanResourceChangeResponse{ 11269 PlannedState: cty.ObjectVal(proposed), 11270 RequiresReplace: []cty.Path{{cty.GetAttrStep{Name: "value"}}}, 11271 } 11272 } 11273 11274 ctx := testContext2(t, &ContextOpts{ 11275 Providers: map[addrs.Provider]providers.Factory{ 11276 addrs.NewDefaultProvider("test"): testProviderFuncFixed(p), 11277 }, 11278 }) 11279 11280 plan, diags := ctx.Plan(m, states.NewState(), &PlanOpts{ 11281 Mode: plans.NormalMode, 11282 SetVariables: InputValues{ 11283 "v": &InputValue{ 11284 Value: cty.StringVal("A"), 11285 }, 11286 }, 11287 }) 11288 assertNoErrors(t, diags) 11289 11290 state, diags := ctx.Apply(plan, m) 11291 if diags.HasErrors() { 11292 t.Fatalf("apply errors: %s", diags.Err()) 11293 } 11294 11295 ctx = testContext2(t, &ContextOpts{ 11296 Providers: map[addrs.Provider]providers.Factory{ 11297 addrs.NewDefaultProvider("test"): testProviderFuncFixed(p), 11298 }, 11299 }) 11300 11301 plan, diags = ctx.Plan(m, state, &PlanOpts{ 11302 Mode: plans.NormalMode, 11303 SetVariables: InputValues{ 11304 "v": &InputValue{ 11305 Value: cty.StringVal("B"), 11306 }, 11307 }, 11308 }) 11309 assertNoErrors(t, diags) 11310 11311 _, diags = ctx.Apply(plan, m) 11312 if diags.HasErrors() { 11313 t.Fatalf("apply errors: %s", diags.Err()) 11314 } 11315 } 11316 11317 func TestContext2Apply_forcedCBD(t *testing.T) { 11318 m := testModuleInline(t, map[string]string{ 11319 "main.tf": ` 11320 variable "v" {} 11321 11322 resource "test_instance" "a" { 11323 require_new = var.v 11324 } 11325 11326 resource "test_instance" "b" { 11327 depends_on = [test_instance.a] 11328 lifecycle { 11329 create_before_destroy = true 11330 } 11331 } 11332 `}) 11333 11334 p := testProvider("test") 11335 p.PlanResourceChangeFn = testDiffFn 11336 11337 ctx := testContext2(t, &ContextOpts{ 11338 Providers: map[addrs.Provider]providers.Factory{ 11339 addrs.NewDefaultProvider("test"): testProviderFuncFixed(p), 11340 }, 11341 }) 11342 11343 plan, diags := ctx.Plan(m, states.NewState(), &PlanOpts{ 11344 Mode: plans.NormalMode, 11345 SetVariables: InputValues{ 11346 "v": &InputValue{ 11347 Value: cty.StringVal("A"), 11348 }, 11349 }, 11350 }) 11351 assertNoErrors(t, diags) 11352 11353 state, diags := ctx.Apply(plan, m) 11354 if diags.HasErrors() { 11355 t.Fatalf("apply errors: %s", diags.Err()) 11356 } 11357 11358 ctx = testContext2(t, &ContextOpts{ 11359 Providers: map[addrs.Provider]providers.Factory{ 11360 addrs.NewDefaultProvider("test"): testProviderFuncFixed(p), 11361 }, 11362 }) 11363 11364 plan, diags = ctx.Plan(m, state, &PlanOpts{ 11365 Mode: plans.NormalMode, 11366 SetVariables: InputValues{ 11367 "v": &InputValue{ 11368 Value: cty.StringVal("B"), 11369 }, 11370 }, 11371 }) 11372 assertNoErrors(t, diags) 11373 11374 _, diags = ctx.Apply(plan, m) 11375 if diags.HasErrors() { 11376 t.Fatalf("apply errors: %s", diags.Err()) 11377 } 11378 } 11379 11380 func TestContext2Apply_removeReferencedResource(t *testing.T) { 11381 m := testModuleInline(t, map[string]string{ 11382 "main.tf": ` 11383 variable "ct" { 11384 } 11385 11386 resource "test_resource" "to_remove" { 11387 count = var.ct 11388 } 11389 11390 resource "test_resource" "c" { 11391 value = join("", test_resource.to_remove[*].id) 11392 } 11393 `}) 11394 11395 p := testProvider("test") 11396 p.PlanResourceChangeFn = testDiffFn 11397 11398 ctx := testContext2(t, &ContextOpts{ 11399 Providers: map[addrs.Provider]providers.Factory{ 11400 addrs.NewDefaultProvider("test"): testProviderFuncFixed(p), 11401 }, 11402 }) 11403 11404 plan, diags := ctx.Plan(m, states.NewState(), &PlanOpts{ 11405 Mode: plans.NormalMode, 11406 SetVariables: InputValues{ 11407 "ct": &InputValue{ 11408 Value: cty.NumberIntVal(1), 11409 }, 11410 }, 11411 }) 11412 assertNoErrors(t, diags) 11413 11414 state, diags := ctx.Apply(plan, m) 11415 if diags.HasErrors() { 11416 t.Fatalf("apply errors: %s", diags.Err()) 11417 } 11418 11419 ctx = testContext2(t, &ContextOpts{ 11420 Providers: map[addrs.Provider]providers.Factory{ 11421 addrs.NewDefaultProvider("test"): testProviderFuncFixed(p), 11422 }, 11423 }) 11424 11425 plan, diags = ctx.Plan(m, state, &PlanOpts{ 11426 Mode: plans.NormalMode, 11427 SetVariables: InputValues{ 11428 "ct": &InputValue{ 11429 Value: cty.NumberIntVal(0), 11430 }, 11431 }, 11432 }) 11433 assertNoErrors(t, diags) 11434 11435 _, diags = ctx.Apply(plan, m) 11436 if diags.HasErrors() { 11437 t.Fatalf("apply errors: %s", diags.Err()) 11438 } 11439 } 11440 11441 func TestContext2Apply_variableSensitivity(t *testing.T) { 11442 m := testModuleInline(t, map[string]string{ 11443 "main.tf": ` 11444 variable "sensitive_var" { 11445 default = "foo" 11446 sensitive = true 11447 } 11448 11449 variable "sensitive_id" { 11450 default = "secret id" 11451 sensitive = true 11452 } 11453 11454 resource "test_resource" "foo" { 11455 value = var.sensitive_var 11456 11457 network_interface { 11458 network_interface_id = var.sensitive_id 11459 } 11460 }`, 11461 }) 11462 11463 p := new(MockProvider) 11464 p.ReadResourceFn = func(req providers.ReadResourceRequest) providers.ReadResourceResponse { 11465 return providers.ReadResourceResponse{NewState: req.PriorState} 11466 } 11467 p.GetProviderSchemaResponse = getProviderSchemaResponseFromProviderSchema(&ProviderSchema{ 11468 Provider: &configschema.Block{}, 11469 ResourceTypes: map[string]*configschema.Block{ 11470 "test_resource": { 11471 Attributes: map[string]*configschema.Attribute{ 11472 "id": { 11473 Type: cty.String, 11474 Computed: true, 11475 }, 11476 "value": { 11477 Type: cty.String, 11478 Optional: true, 11479 Computed: true, 11480 }, 11481 }, 11482 BlockTypes: map[string]*configschema.NestedBlock{ 11483 "network_interface": { 11484 Block: configschema.Block{ 11485 Attributes: map[string]*configschema.Attribute{ 11486 "network_interface_id": {Type: cty.String, Optional: true}, 11487 "device_index": {Type: cty.Number, Optional: true}, 11488 }, 11489 }, 11490 Nesting: configschema.NestingSet, 11491 }, 11492 }, 11493 }, 11494 }, 11495 }) 11496 p.PlanResourceChangeFn = testDiffFn 11497 11498 ctx := testContext2(t, &ContextOpts{ 11499 Providers: map[addrs.Provider]providers.Factory{ 11500 addrs.NewDefaultProvider("test"): testProviderFuncFixed(p), 11501 }, 11502 }) 11503 11504 plan, diags := ctx.Plan(m, states.NewState(), DefaultPlanOpts) 11505 assertNoErrors(t, diags) 11506 11507 state, diags := ctx.Apply(plan, m) 11508 if diags.HasErrors() { 11509 t.Fatalf("apply errors: %s", diags.Err()) 11510 } 11511 11512 // Run a second apply with no changes 11513 ctx = testContext2(t, &ContextOpts{ 11514 Providers: map[addrs.Provider]providers.Factory{ 11515 addrs.NewDefaultProvider("test"): testProviderFuncFixed(p), 11516 }, 11517 }) 11518 11519 plan, diags = ctx.Plan(m, state, DefaultPlanOpts) 11520 assertNoErrors(t, diags) 11521 11522 state, diags = ctx.Apply(plan, m) 11523 if diags.HasErrors() { 11524 t.Fatalf("apply errors: %s", diags.Err()) 11525 } 11526 11527 // Now change the variable value for sensitive_var 11528 ctx = testContext2(t, &ContextOpts{ 11529 Providers: map[addrs.Provider]providers.Factory{ 11530 addrs.NewDefaultProvider("test"): testProviderFuncFixed(p), 11531 }, 11532 }) 11533 11534 plan, diags = ctx.Plan(m, state, &PlanOpts{ 11535 Mode: plans.NormalMode, 11536 SetVariables: InputValues{ 11537 "sensitive_var": &InputValue{ 11538 Value: cty.StringVal("bar"), 11539 }, 11540 }, 11541 }) 11542 assertNoErrors(t, diags) 11543 11544 _, diags = ctx.Apply(plan, m) 11545 if diags.HasErrors() { 11546 t.Fatalf("apply errors: %s", diags.Err()) 11547 } 11548 } 11549 11550 func TestContext2Apply_variableSensitivityPropagation(t *testing.T) { 11551 m := testModuleInline(t, map[string]string{ 11552 "main.tf": ` 11553 variable "sensitive_map" { 11554 type = map(string) 11555 default = { 11556 "x" = "foo" 11557 } 11558 sensitive = true 11559 } 11560 11561 resource "test_resource" "foo" { 11562 value = var.sensitive_map.x 11563 } 11564 `, 11565 }) 11566 11567 p := testProvider("test") 11568 p.PlanResourceChangeFn = testDiffFn 11569 11570 ctx := testContext2(t, &ContextOpts{ 11571 Providers: map[addrs.Provider]providers.Factory{ 11572 addrs.NewDefaultProvider("test"): testProviderFuncFixed(p), 11573 }, 11574 }) 11575 11576 plan, diags := ctx.Plan(m, states.NewState(), DefaultPlanOpts) 11577 if diags.HasErrors() { 11578 t.Fatalf("plan errors: %s", diags.Err()) 11579 } 11580 11581 verifySensitiveValue := func(pvms []cty.PathValueMarks) { 11582 if len(pvms) != 1 { 11583 t.Fatalf("expected 1 sensitive path, got %d", len(pvms)) 11584 } 11585 pvm := pvms[0] 11586 if gotPath, wantPath := pvm.Path, cty.GetAttrPath("value"); !gotPath.Equals(wantPath) { 11587 t.Errorf("wrong path\n got: %#v\nwant: %#v", gotPath, wantPath) 11588 } 11589 if gotMarks, wantMarks := pvm.Marks, cty.NewValueMarks(marks.Sensitive); !gotMarks.Equal(wantMarks) { 11590 t.Errorf("wrong marks\n got: %#v\nwant: %#v", gotMarks, wantMarks) 11591 } 11592 } 11593 11594 addr := mustResourceInstanceAddr("test_resource.foo") 11595 fooChangeSrc := plan.Changes.ResourceInstance(addr) 11596 verifySensitiveValue(fooChangeSrc.AfterValMarks) 11597 11598 state, diags := ctx.Apply(plan, m) 11599 if diags.HasErrors() { 11600 t.Fatalf("apply errors: %s", diags.Err()) 11601 } 11602 11603 fooState := state.ResourceInstance(addr) 11604 verifySensitiveValue(fooState.Current.AttrSensitivePaths) 11605 } 11606 11607 func TestContext2Apply_variableSensitivityProviders(t *testing.T) { 11608 m := testModuleInline(t, map[string]string{ 11609 "main.tf": ` 11610 resource "test_resource" "foo" { 11611 sensitive_value = "should get marked" 11612 } 11613 11614 resource "test_resource" "bar" { 11615 value = test_resource.foo.sensitive_value 11616 random = test_resource.foo.id # not sensitive 11617 11618 nesting_single { 11619 value = "abc" 11620 sensitive_value = "xyz" 11621 } 11622 } 11623 11624 resource "test_resource" "baz" { 11625 value = test_resource.bar.nesting_single.sensitive_value 11626 } 11627 `, 11628 }) 11629 11630 p := testProvider("test") 11631 p.PlanResourceChangeFn = testDiffFn 11632 11633 ctx := testContext2(t, &ContextOpts{ 11634 Providers: map[addrs.Provider]providers.Factory{ 11635 addrs.NewDefaultProvider("test"): testProviderFuncFixed(p), 11636 }, 11637 }) 11638 11639 plan, diags := ctx.Plan(m, states.NewState(), DefaultPlanOpts) 11640 if diags.HasErrors() { 11641 t.Fatalf("plan errors: %s", diags.Err()) 11642 } 11643 11644 verifySensitiveValue := func(pvms []cty.PathValueMarks) { 11645 if len(pvms) != 1 { 11646 t.Fatalf("expected 1 sensitive path, got %d", len(pvms)) 11647 } 11648 pvm := pvms[0] 11649 if gotPath, wantPath := pvm.Path, cty.GetAttrPath("value"); !gotPath.Equals(wantPath) { 11650 t.Errorf("wrong path\n got: %#v\nwant: %#v", gotPath, wantPath) 11651 } 11652 if gotMarks, wantMarks := pvm.Marks, cty.NewValueMarks(marks.Sensitive); !gotMarks.Equal(wantMarks) { 11653 t.Errorf("wrong marks\n got: %#v\nwant: %#v", gotMarks, wantMarks) 11654 } 11655 } 11656 11657 // Sensitive attributes (defined by the provider) are marked 11658 // as sensitive when referenced from another resource 11659 // "bar" references sensitive resources in "foo" 11660 barAddr := mustResourceInstanceAddr("test_resource.bar") 11661 barChangeSrc := plan.Changes.ResourceInstance(barAddr) 11662 verifySensitiveValue(barChangeSrc.AfterValMarks) 11663 11664 bazAddr := mustResourceInstanceAddr("test_resource.baz") 11665 bazChangeSrc := plan.Changes.ResourceInstance(bazAddr) 11666 verifySensitiveValue(bazChangeSrc.AfterValMarks) 11667 11668 state, diags := ctx.Apply(plan, m) 11669 if diags.HasErrors() { 11670 t.Fatalf("apply errors: %s", diags.Err()) 11671 } 11672 11673 barState := state.ResourceInstance(barAddr) 11674 verifySensitiveValue(barState.Current.AttrSensitivePaths) 11675 11676 bazState := state.ResourceInstance(bazAddr) 11677 verifySensitiveValue(bazState.Current.AttrSensitivePaths) 11678 } 11679 11680 func TestContext2Apply_variableSensitivityChange(t *testing.T) { 11681 m := testModuleInline(t, map[string]string{ 11682 "main.tf": ` 11683 variable "sensitive_var" { 11684 default = "hello" 11685 sensitive = true 11686 } 11687 11688 resource "test_resource" "foo" { 11689 value = var.sensitive_var 11690 }`, 11691 }) 11692 11693 p := testProvider("test") 11694 p.PlanResourceChangeFn = testDiffFn 11695 11696 ctx := testContext2(t, &ContextOpts{ 11697 Providers: map[addrs.Provider]providers.Factory{ 11698 addrs.NewDefaultProvider("test"): testProviderFuncFixed(p), 11699 }, 11700 }) 11701 11702 state := states.BuildState(func(s *states.SyncState) { 11703 s.SetResourceInstanceCurrent( 11704 addrs.Resource{ 11705 Mode: addrs.ManagedResourceMode, 11706 Type: "test_resource", 11707 Name: "foo", 11708 }.Instance(addrs.NoKey).Absolute(addrs.RootModuleInstance), 11709 &states.ResourceInstanceObjectSrc{ 11710 Status: states.ObjectReady, 11711 AttrsJSON: []byte(`{"id":"foo", "value":"hello"}`), 11712 // No AttrSensitivePaths present 11713 }, 11714 addrs.AbsProviderConfig{ 11715 Provider: addrs.NewDefaultProvider("test"), 11716 Module: addrs.RootModule, 11717 }, 11718 ) 11719 }) 11720 11721 plan, diags := ctx.Plan(m, state, DefaultPlanOpts) 11722 assertNoErrors(t, diags) 11723 11724 addr := mustResourceInstanceAddr("test_resource.foo") 11725 11726 state, diags = ctx.Apply(plan, m) 11727 assertNoErrors(t, diags) 11728 11729 fooState := state.ResourceInstance(addr) 11730 11731 if len(fooState.Current.AttrSensitivePaths) != 1 { 11732 t.Fatalf("wrong number of sensitive paths, expected 1, got, %v", len(fooState.Current.AttrSensitivePaths)) 11733 } 11734 got := fooState.Current.AttrSensitivePaths[0] 11735 want := cty.PathValueMarks{ 11736 Path: cty.GetAttrPath("value"), 11737 Marks: cty.NewValueMarks(marks.Sensitive), 11738 } 11739 11740 if !got.Equal(want) { 11741 t.Fatalf("wrong value marks; got:\n%#v\n\nwant:\n%#v\n", got, want) 11742 } 11743 11744 m2 := testModuleInline(t, map[string]string{ 11745 "main.tf": ` 11746 variable "sensitive_var" { 11747 default = "hello" 11748 sensitive = false 11749 } 11750 11751 resource "test_resource" "foo" { 11752 value = var.sensitive_var 11753 }`, 11754 }) 11755 11756 ctx2 := testContext2(t, &ContextOpts{ 11757 Providers: map[addrs.Provider]providers.Factory{ 11758 addrs.NewDefaultProvider("test"): testProviderFuncFixed(p), 11759 }, 11760 }) 11761 11762 // NOTE: Prior to our refactoring to make the state an explicit argument 11763 // of Plan, as opposed to hidden state inside Context, this test was 11764 // calling ctx.Apply instead of ctx2.Apply and thus using the previous 11765 // plan instead of this new plan. "Fixing" it to use the new plan seems 11766 // to break the test, so we've preserved that oddity here by saving the 11767 // old plan as oldPlan and essentially discarding the new plan entirely, 11768 // but this seems rather suspicious and we should ideally figure out what 11769 // this test was originally intending to do and make it do that. 11770 oldPlan := plan 11771 _, diags = ctx2.Plan(m2, state, DefaultPlanOpts) 11772 assertNoErrors(t, diags) 11773 stateWithoutSensitive, diags := ctx.Apply(oldPlan, m) 11774 assertNoErrors(t, diags) 11775 11776 fooState2 := stateWithoutSensitive.ResourceInstance(addr) 11777 if len(fooState2.Current.AttrSensitivePaths) > 0 { 11778 t.Fatalf( 11779 "wrong number of sensitive paths, expected 0, got, %v\n%s", 11780 len(fooState2.Current.AttrSensitivePaths), 11781 spew.Sdump(fooState2.Current.AttrSensitivePaths), 11782 ) 11783 } 11784 } 11785 11786 func TestContext2Apply_moduleVariableOptionalAttributes(t *testing.T) { 11787 m := testModuleInline(t, map[string]string{ 11788 "main.tf": ` 11789 terraform { 11790 experiments = [module_variable_optional_attrs] 11791 } 11792 11793 variable "in" { 11794 type = object({ 11795 required = string 11796 optional = optional(string) 11797 }) 11798 } 11799 11800 output "out" { 11801 value = var.in 11802 } 11803 `}) 11804 11805 ctx := testContext2(t, &ContextOpts{}) 11806 11807 plan, diags := ctx.Plan(m, states.NewState(), &PlanOpts{ 11808 Mode: plans.NormalMode, 11809 SetVariables: InputValues{ 11810 "in": &InputValue{ 11811 Value: cty.MapVal(map[string]cty.Value{ 11812 "required": cty.StringVal("boop"), 11813 }), 11814 SourceType: ValueFromCaller, 11815 }, 11816 }, 11817 }) 11818 if diags.HasErrors() { 11819 t.Fatal(diags.ErrWithWarnings()) 11820 } 11821 11822 state, diags := ctx.Apply(plan, m) 11823 if diags.HasErrors() { 11824 t.Fatal(diags.ErrWithWarnings()) 11825 } 11826 11827 got := state.RootModule().OutputValues["out"].Value 11828 want := cty.ObjectVal(map[string]cty.Value{ 11829 "required": cty.StringVal("boop"), 11830 11831 // Because "optional" was marked as optional, it got silently filled 11832 // in as a null value of string type rather than returning an error. 11833 "optional": cty.NullVal(cty.String), 11834 }) 11835 if !want.RawEquals(got) { 11836 t.Fatalf("wrong result\ngot: %#v\nwant: %#v", got, want) 11837 } 11838 } 11839 11840 func TestContext2Apply_provisionerSensitive(t *testing.T) { 11841 m := testModule(t, "apply-provisioner-sensitive") 11842 p := testProvider("aws") 11843 11844 pr := testProvisioner() 11845 pr.ProvisionResourceFn = func(req provisioners.ProvisionResourceRequest) (resp provisioners.ProvisionResourceResponse) { 11846 if req.Config.ContainsMarked() { 11847 t.Fatalf("unexpectedly marked config value: %#v", req.Config) 11848 } 11849 command := req.Config.GetAttr("command") 11850 if command.IsMarked() { 11851 t.Fatalf("unexpectedly marked command argument: %#v", command.Marks()) 11852 } 11853 req.UIOutput.Output(fmt.Sprintf("Executing: %q", command.AsString())) 11854 return 11855 } 11856 p.PlanResourceChangeFn = testDiffFn 11857 p.ApplyResourceChangeFn = testApplyFn 11858 11859 h := new(MockHook) 11860 ctx := testContext2(t, &ContextOpts{ 11861 Hooks: []Hook{h}, 11862 Providers: map[addrs.Provider]providers.Factory{ 11863 addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p), 11864 }, 11865 Provisioners: map[string]provisioners.Factory{ 11866 "shell": testProvisionerFuncFixed(pr), 11867 }, 11868 }) 11869 11870 plan, diags := ctx.Plan(m, states.NewState(), &PlanOpts{ 11871 Mode: plans.NormalMode, 11872 SetVariables: InputValues{ 11873 "password": &InputValue{ 11874 Value: cty.StringVal("secret"), 11875 SourceType: ValueFromCaller, 11876 }, 11877 }, 11878 }) 11879 assertNoErrors(t, diags) 11880 11881 // "restart" provisioner 11882 pr.CloseCalled = false 11883 11884 state, diags := ctx.Apply(plan, m) 11885 if diags.HasErrors() { 11886 logDiagnostics(t, diags) 11887 t.Fatal("apply failed") 11888 } 11889 11890 actual := strings.TrimSpace(state.String()) 11891 expected := strings.TrimSpace(testTerraformApplyProvisionerSensitiveStr) 11892 if actual != expected { 11893 t.Fatalf("wrong result\n\ngot:\n%s\n\nwant:\n%s", actual, expected) 11894 } 11895 11896 // Verify apply was invoked 11897 if !pr.ProvisionResourceCalled { 11898 t.Fatalf("provisioner was not called on apply") 11899 } 11900 11901 // Verify output was suppressed 11902 if !h.ProvisionOutputCalled { 11903 t.Fatalf("ProvisionOutput hook not called") 11904 } 11905 if got, doNotWant := h.ProvisionOutputMessage, "secret"; strings.Contains(got, doNotWant) { 11906 t.Errorf("sensitive value %q included in output:\n%s", doNotWant, got) 11907 } 11908 if got, want := h.ProvisionOutputMessage, "output suppressed"; !strings.Contains(got, want) { 11909 t.Errorf("expected hook to be called with %q, but was:\n%s", want, got) 11910 } 11911 } 11912 11913 func TestContext2Apply_warnings(t *testing.T) { 11914 m := testModuleInline(t, map[string]string{ 11915 "main.tf": ` 11916 resource "test_resource" "foo" { 11917 }`, 11918 }) 11919 11920 p := testProvider("test") 11921 p.PlanResourceChangeFn = testDiffFn 11922 11923 p.ApplyResourceChangeFn = func(req providers.ApplyResourceChangeRequest) providers.ApplyResourceChangeResponse { 11924 resp := testApplyFn(req) 11925 11926 resp.Diagnostics = resp.Diagnostics.Append(tfdiags.SimpleWarning("warning")) 11927 return resp 11928 } 11929 11930 ctx := testContext2(t, &ContextOpts{ 11931 Providers: map[addrs.Provider]providers.Factory{ 11932 addrs.NewDefaultProvider("test"): testProviderFuncFixed(p), 11933 }, 11934 }) 11935 11936 plan, diags := ctx.Plan(m, states.NewState(), DefaultPlanOpts) 11937 assertNoErrors(t, diags) 11938 11939 state, diags := ctx.Apply(plan, m) 11940 if diags.HasErrors() { 11941 t.Fatalf("diags: %s", diags.Err()) 11942 } 11943 11944 inst := state.ResourceInstance(mustResourceInstanceAddr("test_resource.foo")) 11945 if inst == nil { 11946 t.Fatal("missing 'test_resource.foo' in state:", state) 11947 } 11948 } 11949 11950 func TestContext2Apply_rpcDiagnostics(t *testing.T) { 11951 m := testModuleInline(t, map[string]string{ 11952 "main.tf": ` 11953 resource "test_instance" "a" { 11954 } 11955 `, 11956 }) 11957 11958 p := testProvider("test") 11959 p.PlanResourceChangeFn = testDiffFn 11960 p.ApplyResourceChangeFn = func(req providers.ApplyResourceChangeRequest) (resp providers.ApplyResourceChangeResponse) { 11961 resp = testApplyFn(req) 11962 resp.Diagnostics = resp.Diagnostics.Append(tfdiags.SimpleWarning("don't frobble")) 11963 return resp 11964 } 11965 11966 p.GetProviderSchemaResponse = getProviderSchemaResponseFromProviderSchema(&ProviderSchema{ 11967 ResourceTypes: map[string]*configschema.Block{ 11968 "test_instance": { 11969 Attributes: map[string]*configschema.Attribute{ 11970 "id": {Type: cty.String, Computed: true}, 11971 }, 11972 }, 11973 }, 11974 }) 11975 11976 ctx := testContext2(t, &ContextOpts{ 11977 Providers: map[addrs.Provider]providers.Factory{ 11978 addrs.NewDefaultProvider("test"): testProviderFuncFixed(p), 11979 }, 11980 }) 11981 plan, diags := ctx.Plan(m, states.NewState(), DefaultPlanOpts) 11982 if diags.HasErrors() { 11983 t.Fatal(diags.Err()) 11984 } 11985 11986 _, diags = ctx.Apply(plan, m) 11987 if diags.HasErrors() { 11988 t.Fatal(diags.Err()) 11989 } 11990 11991 if len(diags) == 0 { 11992 t.Fatal("expected warnings") 11993 } 11994 11995 for _, d := range diags { 11996 des := d.Description().Summary 11997 if !strings.Contains(des, "frobble") { 11998 t.Fatalf(`expected frobble, got %q`, des) 11999 } 12000 } 12001 } 12002 12003 func TestContext2Apply_dataSensitive(t *testing.T) { 12004 m := testModule(t, "apply-data-sensitive") 12005 p := testProvider("null") 12006 p.PlanResourceChangeFn = testDiffFn 12007 p.ReadDataSourceFn = func(req providers.ReadDataSourceRequest) providers.ReadDataSourceResponse { 12008 // add the required id 12009 m := req.Config.AsValueMap() 12010 m["id"] = cty.StringVal("foo") 12011 12012 return providers.ReadDataSourceResponse{ 12013 State: cty.ObjectVal(m), 12014 } 12015 } 12016 12017 ctx := testContext2(t, &ContextOpts{ 12018 Providers: map[addrs.Provider]providers.Factory{ 12019 addrs.NewDefaultProvider("null"): testProviderFuncFixed(p), 12020 }, 12021 }) 12022 12023 plan, diags := ctx.Plan(m, states.NewState(), DefaultPlanOpts) 12024 if diags.HasErrors() { 12025 t.Fatalf("diags: %s", diags.Err()) 12026 } else { 12027 t.Logf(legacyDiffComparisonString(plan.Changes)) 12028 } 12029 12030 state, diags := ctx.Apply(plan, m) 12031 assertNoErrors(t, diags) 12032 12033 addr := mustResourceInstanceAddr("data.null_data_source.testing") 12034 12035 dataSourceState := state.ResourceInstance(addr) 12036 pvms := dataSourceState.Current.AttrSensitivePaths 12037 if len(pvms) != 1 { 12038 t.Fatalf("expected 1 sensitive path, got %d", len(pvms)) 12039 } 12040 pvm := pvms[0] 12041 if gotPath, wantPath := pvm.Path, cty.GetAttrPath("foo"); !gotPath.Equals(wantPath) { 12042 t.Errorf("wrong path\n got: %#v\nwant: %#v", gotPath, wantPath) 12043 } 12044 if gotMarks, wantMarks := pvm.Marks, cty.NewValueMarks(marks.Sensitive); !gotMarks.Equal(wantMarks) { 12045 t.Errorf("wrong marks\n got: %#v\nwant: %#v", gotMarks, wantMarks) 12046 } 12047 } 12048 12049 func TestContext2Apply_errorRestorePrivateData(t *testing.T) { 12050 // empty config to remove our resource 12051 m := testModuleInline(t, map[string]string{ 12052 "main.tf": "", 12053 }) 12054 12055 p := simpleMockProvider() 12056 p.ApplyResourceChangeResponse = &providers.ApplyResourceChangeResponse{ 12057 // we error during apply, which will trigger core to preserve the last 12058 // known state, including private data 12059 Diagnostics: tfdiags.Diagnostics(nil).Append(errors.New("oops")), 12060 } 12061 12062 addr := mustResourceInstanceAddr("test_object.a") 12063 12064 state := states.BuildState(func(s *states.SyncState) { 12065 s.SetResourceInstanceCurrent(addr, &states.ResourceInstanceObjectSrc{ 12066 Status: states.ObjectReady, 12067 AttrsJSON: []byte(`{"id":"foo"}`), 12068 Private: []byte("private"), 12069 }, mustProviderConfig(`provider["registry.terraform.io/hashicorp/test"]`)) 12070 }) 12071 12072 ctx := testContext2(t, &ContextOpts{ 12073 Providers: map[addrs.Provider]providers.Factory{ 12074 addrs.NewDefaultProvider("test"): testProviderFuncFixed(p), 12075 }, 12076 }) 12077 12078 plan, diags := ctx.Plan(m, state, DefaultPlanOpts) 12079 if diags.HasErrors() { 12080 t.Fatal(diags.Err()) 12081 } 12082 12083 state, _ = ctx.Apply(plan, m) 12084 if string(state.ResourceInstance(addr).Current.Private) != "private" { 12085 t.Fatal("missing private data in state") 12086 } 12087 } 12088 12089 func TestContext2Apply_errorRestoreStatus(t *testing.T) { 12090 // empty config to remove our resource 12091 m := testModuleInline(t, map[string]string{ 12092 "main.tf": "", 12093 }) 12094 12095 p := simpleMockProvider() 12096 p.ApplyResourceChangeFn = func(req providers.ApplyResourceChangeRequest) (resp providers.ApplyResourceChangeResponse) { 12097 // We error during apply, but return the current object state. 12098 resp.Diagnostics = resp.Diagnostics.Append(errors.New("oops")) 12099 // return a warning too to make sure it isn't dropped 12100 resp.Diagnostics = resp.Diagnostics.Append(tfdiags.SimpleWarning("warned")) 12101 resp.NewState = req.PriorState 12102 resp.Private = req.PlannedPrivate 12103 return resp 12104 } 12105 12106 addr := mustResourceInstanceAddr("test_object.a") 12107 12108 state := states.BuildState(func(s *states.SyncState) { 12109 s.SetResourceInstanceCurrent(addr, &states.ResourceInstanceObjectSrc{ 12110 Status: states.ObjectTainted, 12111 AttrsJSON: []byte(`{"test_string":"foo"}`), 12112 Private: []byte("private"), 12113 Dependencies: []addrs.ConfigResource{mustConfigResourceAddr("test_object.b")}, 12114 }, mustProviderConfig(`provider["registry.terraform.io/hashicorp/test"]`)) 12115 }) 12116 12117 ctx := testContext2(t, &ContextOpts{ 12118 Providers: map[addrs.Provider]providers.Factory{ 12119 addrs.NewDefaultProvider("test"): testProviderFuncFixed(p), 12120 }, 12121 }) 12122 12123 plan, diags := ctx.Plan(m, state, DefaultPlanOpts) 12124 if diags.HasErrors() { 12125 t.Fatal(diags.Err()) 12126 } 12127 12128 state, diags = ctx.Apply(plan, m) 12129 12130 errString := diags.ErrWithWarnings().Error() 12131 if !strings.Contains(errString, "oops") || !strings.Contains(errString, "warned") { 12132 t.Fatalf("error missing expected info: %q", errString) 12133 } 12134 12135 if len(diags) != 2 { 12136 t.Fatalf("expected 1 error and 1 warning, got: %q", errString) 12137 } 12138 12139 res := state.ResourceInstance(addr) 12140 if res == nil { 12141 t.Fatal("resource was removed from state") 12142 } 12143 12144 if res.Current.Status != states.ObjectTainted { 12145 t.Fatal("resource should still be tainted in the state") 12146 } 12147 12148 if len(res.Current.Dependencies) != 1 || !res.Current.Dependencies[0].Equal(mustConfigResourceAddr("test_object.b")) { 12149 t.Fatalf("incorrect dependencies, got %q", res.Current.Dependencies) 12150 } 12151 12152 if string(res.Current.Private) != "private" { 12153 t.Fatalf("incorrect private data, got %q", res.Current.Private) 12154 } 12155 } 12156 12157 func TestContext2Apply_nonConformingResponse(t *testing.T) { 12158 // empty config to remove our resource 12159 m := testModuleInline(t, map[string]string{ 12160 "main.tf": ` 12161 resource "test_object" "a" { 12162 test_string = "x" 12163 } 12164 `, 12165 }) 12166 12167 p := simpleMockProvider() 12168 respDiags := tfdiags.Diagnostics(nil).Append(tfdiags.SimpleWarning("warned")) 12169 respDiags = respDiags.Append(errors.New("oops")) 12170 p.ApplyResourceChangeResponse = &providers.ApplyResourceChangeResponse{ 12171 // Don't lose these diagnostics 12172 Diagnostics: respDiags, 12173 // This state is missing required attributes, and should produce an error 12174 NewState: cty.ObjectVal(map[string]cty.Value{ 12175 "test_string": cty.StringVal("x"), 12176 }), 12177 } 12178 12179 ctx := testContext2(t, &ContextOpts{ 12180 Providers: map[addrs.Provider]providers.Factory{ 12181 addrs.NewDefaultProvider("test"): testProviderFuncFixed(p), 12182 }, 12183 }) 12184 12185 plan, diags := ctx.Plan(m, states.NewState(), DefaultPlanOpts) 12186 if diags.HasErrors() { 12187 t.Fatal(diags.Err()) 12188 } 12189 12190 _, diags = ctx.Apply(plan, m) 12191 errString := diags.ErrWithWarnings().Error() 12192 if !strings.Contains(errString, "oops") || !strings.Contains(errString, "warned") { 12193 t.Fatalf("error missing expected info: %q", errString) 12194 } 12195 12196 // we should have more than the ones returned from the provider, and they 12197 // should not be coalesced into a single value 12198 if len(diags) < 3 { 12199 t.Fatalf("incorrect diagnostics, got %d values with %s", len(diags), diags.ErrWithWarnings()) 12200 } 12201 } 12202 12203 func TestContext2Apply_nilResponse(t *testing.T) { 12204 // empty config to remove our resource 12205 m := testModuleInline(t, map[string]string{ 12206 "main.tf": ` 12207 resource "test_object" "a" { 12208 } 12209 `, 12210 }) 12211 12212 p := simpleMockProvider() 12213 p.ApplyResourceChangeResponse = &providers.ApplyResourceChangeResponse{} 12214 12215 ctx := testContext2(t, &ContextOpts{ 12216 Providers: map[addrs.Provider]providers.Factory{ 12217 addrs.NewDefaultProvider("test"): testProviderFuncFixed(p), 12218 }, 12219 }) 12220 12221 plan, diags := ctx.Plan(m, states.NewState(), DefaultPlanOpts) 12222 if diags.HasErrors() { 12223 t.Fatal(diags.Err()) 12224 } 12225 12226 _, diags = ctx.Apply(plan, m) 12227 if !diags.HasErrors() { 12228 t.Fatal("expected and error") 12229 } 12230 12231 errString := diags.ErrWithWarnings().Error() 12232 if !strings.Contains(errString, "invalid nil value") { 12233 t.Fatalf("error missing expected info: %q", errString) 12234 } 12235 } 12236 12237 //////////////////////////////////////////////////////////////////////////////// 12238 // NOTE: Due to the size of this file, new tests should be added to 12239 // context_apply2_test.go. 12240 ////////////////////////////////////////////////////////////////////////////////