github.com/graywolf-at-work-2/terraform-vendor@v1.4.5/internal/command/plan_test.go (about) 1 package command 2 3 import ( 4 "bytes" 5 "context" 6 "fmt" 7 "io/ioutil" 8 "os" 9 "path" 10 "path/filepath" 11 "strings" 12 "sync" 13 "testing" 14 "time" 15 16 "github.com/davecgh/go-spew/spew" 17 "github.com/zclconf/go-cty/cty" 18 19 "github.com/hashicorp/terraform/internal/addrs" 20 backendinit "github.com/hashicorp/terraform/internal/backend/init" 21 "github.com/hashicorp/terraform/internal/checks" 22 "github.com/hashicorp/terraform/internal/configs/configschema" 23 "github.com/hashicorp/terraform/internal/plans" 24 "github.com/hashicorp/terraform/internal/providers" 25 "github.com/hashicorp/terraform/internal/states" 26 "github.com/hashicorp/terraform/internal/terraform" 27 "github.com/hashicorp/terraform/internal/tfdiags" 28 ) 29 30 func TestPlan(t *testing.T) { 31 td := t.TempDir() 32 testCopyDir(t, testFixturePath("plan"), td) 33 defer testChdir(t, td)() 34 35 p := planFixtureProvider() 36 view, done := testView(t) 37 c := &PlanCommand{ 38 Meta: Meta{ 39 testingOverrides: metaOverridesForProvider(p), 40 View: view, 41 }, 42 } 43 44 args := []string{} 45 code := c.Run(args) 46 output := done(t) 47 if code != 0 { 48 t.Fatalf("bad: %d\n\n%s", code, output.Stderr()) 49 } 50 } 51 52 func TestPlan_lockedState(t *testing.T) { 53 td := t.TempDir() 54 testCopyDir(t, testFixturePath("plan"), td) 55 defer testChdir(t, td)() 56 57 unlock, err := testLockState(t, testDataDir, filepath.Join(td, DefaultStateFilename)) 58 if err != nil { 59 t.Fatal(err) 60 } 61 defer unlock() 62 63 p := planFixtureProvider() 64 view, done := testView(t) 65 c := &PlanCommand{ 66 Meta: Meta{ 67 testingOverrides: metaOverridesForProvider(p), 68 View: view, 69 }, 70 } 71 72 args := []string{} 73 code := c.Run(args) 74 if code == 0 { 75 t.Fatal("expected error", done(t).Stdout()) 76 } 77 78 output := done(t).Stderr() 79 if !strings.Contains(output, "lock") { 80 t.Fatal("command output does not look like a lock error:", output) 81 } 82 } 83 84 func TestPlan_plan(t *testing.T) { 85 testCwd(t) 86 87 planPath := testPlanFileNoop(t) 88 89 p := testProvider() 90 view, done := testView(t) 91 c := &PlanCommand{ 92 Meta: Meta{ 93 testingOverrides: metaOverridesForProvider(p), 94 View: view, 95 }, 96 } 97 98 args := []string{planPath} 99 code := c.Run(args) 100 output := done(t) 101 if code != 1 { 102 t.Fatalf("wrong exit status %d; want 1\nstderr: %s", code, output.Stderr()) 103 } 104 } 105 106 func TestPlan_destroy(t *testing.T) { 107 td := t.TempDir() 108 testCopyDir(t, testFixturePath("plan"), td) 109 defer testChdir(t, td)() 110 111 originalState := states.BuildState(func(s *states.SyncState) { 112 s.SetResourceInstanceCurrent( 113 addrs.Resource{ 114 Mode: addrs.ManagedResourceMode, 115 Type: "test_instance", 116 Name: "foo", 117 }.Instance(addrs.NoKey).Absolute(addrs.RootModuleInstance), 118 &states.ResourceInstanceObjectSrc{ 119 AttrsJSON: []byte(`{"id":"bar"}`), 120 Status: states.ObjectReady, 121 }, 122 addrs.AbsProviderConfig{ 123 Provider: addrs.NewDefaultProvider("test"), 124 Module: addrs.RootModule, 125 }, 126 ) 127 }) 128 outPath := testTempFile(t) 129 statePath := testStateFile(t, originalState) 130 131 p := planFixtureProvider() 132 view, done := testView(t) 133 c := &PlanCommand{ 134 Meta: Meta{ 135 testingOverrides: metaOverridesForProvider(p), 136 View: view, 137 }, 138 } 139 140 args := []string{ 141 "-destroy", 142 "-out", outPath, 143 "-state", statePath, 144 } 145 code := c.Run(args) 146 output := done(t) 147 if code != 0 { 148 t.Fatalf("bad: %d\n\n%s", code, output.Stderr()) 149 } 150 151 plan := testReadPlan(t, outPath) 152 for _, rc := range plan.Changes.Resources { 153 if got, want := rc.Action, plans.Delete; got != want { 154 t.Fatalf("wrong action %s for %s; want %s\nplanned change: %s", got, rc.Addr, want, spew.Sdump(rc)) 155 } 156 } 157 } 158 159 func TestPlan_noState(t *testing.T) { 160 td := t.TempDir() 161 testCopyDir(t, testFixturePath("plan"), td) 162 defer testChdir(t, td)() 163 164 p := planFixtureProvider() 165 view, done := testView(t) 166 c := &PlanCommand{ 167 Meta: Meta{ 168 testingOverrides: metaOverridesForProvider(p), 169 View: view, 170 }, 171 } 172 173 args := []string{} 174 code := c.Run(args) 175 output := done(t) 176 if code != 0 { 177 t.Fatalf("bad: %d\n\n%s", code, output.Stderr()) 178 } 179 180 // Verify that refresh was called 181 if p.ReadResourceCalled { 182 t.Fatal("ReadResource should not be called") 183 } 184 185 // Verify that the provider was called with the existing state 186 actual := p.PlanResourceChangeRequest.PriorState 187 expected := cty.NullVal(p.GetProviderSchemaResponse.ResourceTypes["test_instance"].Block.ImpliedType()) 188 if !expected.RawEquals(actual) { 189 t.Fatalf("wrong prior state\ngot: %#v\nwant: %#v", actual, expected) 190 } 191 } 192 193 func TestPlan_outPath(t *testing.T) { 194 td := t.TempDir() 195 testCopyDir(t, testFixturePath("plan"), td) 196 defer testChdir(t, td)() 197 198 outPath := filepath.Join(td, "test.plan") 199 200 p := planFixtureProvider() 201 view, done := testView(t) 202 c := &PlanCommand{ 203 Meta: Meta{ 204 testingOverrides: metaOverridesForProvider(p), 205 View: view, 206 }, 207 } 208 209 p.PlanResourceChangeResponse = &providers.PlanResourceChangeResponse{ 210 PlannedState: cty.NullVal(cty.EmptyObject), 211 } 212 213 args := []string{ 214 "-out", outPath, 215 } 216 code := c.Run(args) 217 output := done(t) 218 if code != 0 { 219 t.Fatalf("bad: %d\n\n%s", code, output.Stderr()) 220 } 221 222 testReadPlan(t, outPath) // will call t.Fatal itself if the file cannot be read 223 } 224 225 func TestPlan_outPathNoChange(t *testing.T) { 226 td := t.TempDir() 227 testCopyDir(t, testFixturePath("plan"), td) 228 defer testChdir(t, td)() 229 230 originalState := states.BuildState(func(s *states.SyncState) { 231 s.SetResourceInstanceCurrent( 232 addrs.Resource{ 233 Mode: addrs.ManagedResourceMode, 234 Type: "test_instance", 235 Name: "foo", 236 }.Instance(addrs.NoKey).Absolute(addrs.RootModuleInstance), 237 &states.ResourceInstanceObjectSrc{ 238 // Aside from "id" (which is computed) the values here must 239 // exactly match the values in the "plan" test fixture in order 240 // to produce the empty plan we need for this test. 241 AttrsJSON: []byte(`{"id":"bar","ami":"bar","network_interface":[{"description":"Main network interface","device_index":"0"}]}`), 242 Status: states.ObjectReady, 243 }, 244 addrs.AbsProviderConfig{ 245 Provider: addrs.NewDefaultProvider("test"), 246 Module: addrs.RootModule, 247 }, 248 ) 249 }) 250 statePath := testStateFile(t, originalState) 251 252 outPath := filepath.Join(td, "test.plan") 253 254 p := planFixtureProvider() 255 view, done := testView(t) 256 c := &PlanCommand{ 257 Meta: Meta{ 258 testingOverrides: metaOverridesForProvider(p), 259 View: view, 260 }, 261 } 262 263 args := []string{ 264 "-out", outPath, 265 "-state", statePath, 266 } 267 code := c.Run(args) 268 output := done(t) 269 if code != 0 { 270 t.Fatalf("bad: %d\n\n%s", code, output.Stderr()) 271 } 272 273 plan := testReadPlan(t, outPath) 274 if !plan.Changes.Empty() { 275 t.Fatalf("Expected empty plan to be written to plan file, got: %s", spew.Sdump(plan)) 276 } 277 } 278 279 func TestPlan_outPathWithError(t *testing.T) { 280 td := t.TempDir() 281 testCopyDir(t, testFixturePath("plan-fail-condition"), td) 282 defer testChdir(t, td)() 283 284 outPath := filepath.Join(td, "test.plan") 285 286 p := planFixtureProvider() 287 view, done := testView(t) 288 c := &PlanCommand{ 289 Meta: Meta{ 290 testingOverrides: metaOverridesForProvider(p), 291 View: view, 292 }, 293 } 294 295 p.PlanResourceChangeResponse = &providers.PlanResourceChangeResponse{ 296 PlannedState: cty.NullVal(cty.EmptyObject), 297 } 298 299 args := []string{ 300 "-out", outPath, 301 } 302 code := c.Run(args) 303 output := done(t) 304 if code == 0 { 305 t.Fatal("expected non-zero exit status", output) 306 } 307 308 plan := testReadPlan(t, outPath) // will call t.Fatal itself if the file cannot be read 309 if !plan.Errored { 310 t.Fatal("plan should be marked with Errored") 311 } 312 313 if plan.Checks == nil { 314 t.Fatal("plan contains no checks") 315 } 316 317 // the checks should only contain one failure 318 results := plan.Checks.ConfigResults.Elements() 319 if len(results) != 1 { 320 t.Fatal("incorrect number of check results", len(results)) 321 } 322 if results[0].Value.Status != checks.StatusFail { 323 t.Errorf("incorrect status, got %s", results[0].Value.Status) 324 } 325 } 326 327 // When using "-out" with a backend, the plan should encode the backend config 328 func TestPlan_outBackend(t *testing.T) { 329 // Create a temporary working directory that is empty 330 td := t.TempDir() 331 testCopyDir(t, testFixturePath("plan-out-backend"), td) 332 defer testChdir(t, td)() 333 334 originalState := states.BuildState(func(s *states.SyncState) { 335 s.SetResourceInstanceCurrent( 336 addrs.Resource{ 337 Mode: addrs.ManagedResourceMode, 338 Type: "test_instance", 339 Name: "foo", 340 }.Instance(addrs.NoKey).Absolute(addrs.RootModuleInstance), 341 &states.ResourceInstanceObjectSrc{ 342 AttrsJSON: []byte(`{"id":"bar","ami":"bar"}`), 343 Status: states.ObjectReady, 344 }, 345 addrs.AbsProviderConfig{ 346 Provider: addrs.NewDefaultProvider("test"), 347 Module: addrs.RootModule, 348 }, 349 ) 350 }) 351 352 // Set up our backend state 353 dataState, srv := testBackendState(t, originalState, 200) 354 defer srv.Close() 355 testStateFileRemote(t, dataState) 356 357 outPath := "foo" 358 p := testProvider() 359 p.GetProviderSchemaResponse = &providers.GetProviderSchemaResponse{ 360 ResourceTypes: map[string]providers.Schema{ 361 "test_instance": { 362 Block: &configschema.Block{ 363 Attributes: map[string]*configschema.Attribute{ 364 "id": { 365 Type: cty.String, 366 Computed: true, 367 }, 368 "ami": { 369 Type: cty.String, 370 Optional: true, 371 }, 372 }, 373 }, 374 }, 375 }, 376 } 377 p.PlanResourceChangeFn = func(req providers.PlanResourceChangeRequest) providers.PlanResourceChangeResponse { 378 return providers.PlanResourceChangeResponse{ 379 PlannedState: req.ProposedNewState, 380 } 381 } 382 view, done := testView(t) 383 c := &PlanCommand{ 384 Meta: Meta{ 385 testingOverrides: metaOverridesForProvider(p), 386 View: view, 387 }, 388 } 389 390 args := []string{ 391 "-out", outPath, 392 } 393 code := c.Run(args) 394 output := done(t) 395 if code != 0 { 396 t.Logf("stdout: %s", output.Stdout()) 397 t.Fatalf("plan command failed with exit code %d\n\n%s", code, output.Stderr()) 398 } 399 400 plan := testReadPlan(t, outPath) 401 if !plan.Changes.Empty() { 402 t.Fatalf("Expected empty plan to be written to plan file, got: %s", spew.Sdump(plan)) 403 } 404 405 if got, want := plan.Backend.Type, "http"; got != want { 406 t.Errorf("wrong backend type %q; want %q", got, want) 407 } 408 if got, want := plan.Backend.Workspace, "default"; got != want { 409 t.Errorf("wrong backend workspace %q; want %q", got, want) 410 } 411 { 412 httpBackend := backendinit.Backend("http")() 413 schema := httpBackend.ConfigSchema() 414 got, err := plan.Backend.Config.Decode(schema.ImpliedType()) 415 if err != nil { 416 t.Fatalf("failed to decode backend config in plan: %s", err) 417 } 418 want, err := dataState.Backend.Config(schema) 419 if err != nil { 420 t.Fatalf("failed to decode cached config: %s", err) 421 } 422 if !want.RawEquals(got) { 423 t.Errorf("wrong backend config\ngot: %#v\nwant: %#v", got, want) 424 } 425 } 426 } 427 428 func TestPlan_refreshFalse(t *testing.T) { 429 // Create a temporary working directory that is empty 430 td := t.TempDir() 431 testCopyDir(t, testFixturePath("plan"), td) 432 defer testChdir(t, td)() 433 434 p := planFixtureProvider() 435 view, done := testView(t) 436 c := &PlanCommand{ 437 Meta: Meta{ 438 testingOverrides: metaOverridesForProvider(p), 439 View: view, 440 }, 441 } 442 443 args := []string{ 444 "-refresh=false", 445 } 446 code := c.Run(args) 447 output := done(t) 448 if code != 0 { 449 t.Fatalf("bad: %d\n\n%s", code, output.Stderr()) 450 } 451 452 if p.ReadResourceCalled { 453 t.Fatal("ReadResource should not have been called") 454 } 455 } 456 457 func TestPlan_state(t *testing.T) { 458 // Create a temporary working directory that is empty 459 td := t.TempDir() 460 testCopyDir(t, testFixturePath("plan"), td) 461 defer testChdir(t, td)() 462 463 originalState := testState() 464 statePath := testStateFile(t, originalState) 465 466 p := planFixtureProvider() 467 view, done := testView(t) 468 c := &PlanCommand{ 469 Meta: Meta{ 470 testingOverrides: metaOverridesForProvider(p), 471 View: view, 472 }, 473 } 474 475 args := []string{ 476 "-state", statePath, 477 } 478 code := c.Run(args) 479 output := done(t) 480 if code != 0 { 481 t.Fatalf("bad: %d\n\n%s", code, output.Stderr()) 482 } 483 484 // Verify that the provider was called with the existing state 485 actual := p.PlanResourceChangeRequest.PriorState 486 expected := cty.ObjectVal(map[string]cty.Value{ 487 "id": cty.StringVal("bar"), 488 "ami": cty.NullVal(cty.String), 489 "network_interface": cty.ListValEmpty(cty.Object(map[string]cty.Type{ 490 "device_index": cty.String, 491 "description": cty.String, 492 })), 493 }) 494 if !expected.RawEquals(actual) { 495 t.Fatalf("wrong prior state\ngot: %#v\nwant: %#v", actual, expected) 496 } 497 } 498 499 func TestPlan_stateDefault(t *testing.T) { 500 // Create a temporary working directory that is empty 501 td := t.TempDir() 502 testCopyDir(t, testFixturePath("plan"), td) 503 defer testChdir(t, td)() 504 505 // Generate state and move it to the default path 506 originalState := testState() 507 statePath := testStateFile(t, originalState) 508 os.Rename(statePath, path.Join(td, "terraform.tfstate")) 509 510 p := planFixtureProvider() 511 view, done := testView(t) 512 c := &PlanCommand{ 513 Meta: Meta{ 514 testingOverrides: metaOverridesForProvider(p), 515 View: view, 516 }, 517 } 518 519 args := []string{} 520 code := c.Run(args) 521 output := done(t) 522 if code != 0 { 523 t.Fatalf("bad: %d\n\n%s", code, output.Stderr()) 524 } 525 526 // Verify that the provider was called with the existing state 527 actual := p.PlanResourceChangeRequest.PriorState 528 expected := cty.ObjectVal(map[string]cty.Value{ 529 "id": cty.StringVal("bar"), 530 "ami": cty.NullVal(cty.String), 531 "network_interface": cty.ListValEmpty(cty.Object(map[string]cty.Type{ 532 "device_index": cty.String, 533 "description": cty.String, 534 })), 535 }) 536 if !expected.RawEquals(actual) { 537 t.Fatalf("wrong prior state\ngot: %#v\nwant: %#v", actual, expected) 538 } 539 } 540 541 func TestPlan_validate(t *testing.T) { 542 // This is triggered by not asking for input so we have to set this to false 543 test = false 544 defer func() { test = true }() 545 546 td := t.TempDir() 547 testCopyDir(t, testFixturePath("plan-invalid"), td) 548 defer testChdir(t, td)() 549 550 p := testProvider() 551 p.GetProviderSchemaResponse = &providers.GetProviderSchemaResponse{ 552 ResourceTypes: map[string]providers.Schema{ 553 "test_instance": { 554 Block: &configschema.Block{ 555 Attributes: map[string]*configschema.Attribute{ 556 "id": {Type: cty.String, Optional: true, Computed: true}, 557 }, 558 }, 559 }, 560 }, 561 } 562 p.PlanResourceChangeFn = func(req providers.PlanResourceChangeRequest) providers.PlanResourceChangeResponse { 563 return providers.PlanResourceChangeResponse{ 564 PlannedState: req.ProposedNewState, 565 } 566 } 567 view, done := testView(t) 568 c := &PlanCommand{ 569 Meta: Meta{ 570 testingOverrides: metaOverridesForProvider(p), 571 View: view, 572 }, 573 } 574 575 args := []string{"-no-color"} 576 code := c.Run(args) 577 output := done(t) 578 if code != 1 { 579 t.Fatalf("bad: %d\n\n%s", code, output.Stderr()) 580 } 581 582 actual := output.Stderr() 583 if want := "Error: Invalid count argument"; !strings.Contains(actual, want) { 584 t.Fatalf("unexpected error output\ngot:\n%s\n\nshould contain: %s", actual, want) 585 } 586 if want := "9: count = timestamp()"; !strings.Contains(actual, want) { 587 t.Fatalf("unexpected error output\ngot:\n%s\n\nshould contain: %s", actual, want) 588 } 589 } 590 591 func TestPlan_vars(t *testing.T) { 592 // Create a temporary working directory that is empty 593 td := t.TempDir() 594 testCopyDir(t, testFixturePath("plan-vars"), td) 595 defer testChdir(t, td)() 596 597 p := planVarsFixtureProvider() 598 view, done := testView(t) 599 c := &PlanCommand{ 600 Meta: Meta{ 601 testingOverrides: metaOverridesForProvider(p), 602 View: view, 603 }, 604 } 605 606 actual := "" 607 p.PlanResourceChangeFn = func(req providers.PlanResourceChangeRequest) (resp providers.PlanResourceChangeResponse) { 608 actual = req.ProposedNewState.GetAttr("value").AsString() 609 resp.PlannedState = req.ProposedNewState 610 return 611 } 612 613 args := []string{ 614 "-var", "foo=bar", 615 } 616 code := c.Run(args) 617 output := done(t) 618 if code != 0 { 619 t.Fatalf("bad: %d\n\n%s", code, output.Stderr()) 620 } 621 622 if actual != "bar" { 623 t.Fatal("didn't work") 624 } 625 } 626 627 func TestPlan_varsInvalid(t *testing.T) { 628 testCases := []struct { 629 args []string 630 wantErr string 631 }{ 632 { 633 []string{"-var", "foo"}, 634 `The given -var option "foo" is not correctly specified.`, 635 }, 636 { 637 []string{"-var", "foo = bar"}, 638 `Variable name "foo " is invalid due to trailing space.`, 639 }, 640 } 641 642 // Create a temporary working directory that is empty 643 td := t.TempDir() 644 testCopyDir(t, testFixturePath("plan-vars"), td) 645 defer testChdir(t, td)() 646 647 for _, tc := range testCases { 648 t.Run(strings.Join(tc.args, " "), func(t *testing.T) { 649 p := planVarsFixtureProvider() 650 view, done := testView(t) 651 c := &PlanCommand{ 652 Meta: Meta{ 653 testingOverrides: metaOverridesForProvider(p), 654 View: view, 655 }, 656 } 657 658 code := c.Run(tc.args) 659 output := done(t) 660 if code != 1 { 661 t.Fatalf("bad: %d\n\n%s", code, output.Stdout()) 662 } 663 664 got := output.Stderr() 665 if !strings.Contains(got, tc.wantErr) { 666 t.Fatalf("bad error output, want %q, got:\n%s", tc.wantErr, got) 667 } 668 }) 669 } 670 } 671 672 func TestPlan_varsUnset(t *testing.T) { 673 // Create a temporary working directory that is empty 674 td := t.TempDir() 675 testCopyDir(t, testFixturePath("plan-vars"), td) 676 defer testChdir(t, td)() 677 678 // The plan command will prompt for interactive input of var.foo. 679 // We'll answer "bar" to that prompt, which should then allow this 680 // configuration to apply even though var.foo doesn't have a 681 // default value and there are no -var arguments on our command line. 682 683 // This will (helpfully) panic if more than one variable is requested during plan: 684 // https://github.com/hashicorp/terraform/issues/26027 685 close := testInteractiveInput(t, []string{"bar"}) 686 defer close() 687 688 p := planVarsFixtureProvider() 689 view, done := testView(t) 690 c := &PlanCommand{ 691 Meta: Meta{ 692 testingOverrides: metaOverridesForProvider(p), 693 View: view, 694 }, 695 } 696 697 args := []string{} 698 code := c.Run(args) 699 output := done(t) 700 if code != 0 { 701 t.Fatalf("bad: %d\n\n%s", code, output.Stderr()) 702 } 703 } 704 705 // This test adds a required argument to the test provider to validate 706 // processing of user input: 707 // https://github.com/hashicorp/terraform/issues/26035 708 func TestPlan_providerArgumentUnset(t *testing.T) { 709 // Create a temporary working directory that is empty 710 td := t.TempDir() 711 testCopyDir(t, testFixturePath("plan"), td) 712 defer testChdir(t, td)() 713 714 // Disable test mode so input would be asked 715 test = false 716 defer func() { test = true }() 717 718 // The plan command will prompt for interactive input of provider.test.region 719 defaultInputReader = bytes.NewBufferString("us-east-1\n") 720 721 p := planFixtureProvider() 722 // override the planFixtureProvider schema to include a required provider argument 723 p.GetProviderSchemaResponse = &providers.GetProviderSchemaResponse{ 724 Provider: providers.Schema{ 725 Block: &configschema.Block{ 726 Attributes: map[string]*configschema.Attribute{ 727 "region": {Type: cty.String, Required: true}, 728 }, 729 }, 730 }, 731 ResourceTypes: map[string]providers.Schema{ 732 "test_instance": { 733 Block: &configschema.Block{ 734 Attributes: map[string]*configschema.Attribute{ 735 "id": {Type: cty.String, Optional: true, Computed: true}, 736 "ami": {Type: cty.String, Optional: true, Computed: true}, 737 }, 738 BlockTypes: map[string]*configschema.NestedBlock{ 739 "network_interface": { 740 Nesting: configschema.NestingList, 741 Block: configschema.Block{ 742 Attributes: map[string]*configschema.Attribute{ 743 "device_index": {Type: cty.String, Optional: true}, 744 "description": {Type: cty.String, Optional: true}, 745 }, 746 }, 747 }, 748 }, 749 }, 750 }, 751 }, 752 DataSources: map[string]providers.Schema{ 753 "test_data_source": { 754 Block: &configschema.Block{ 755 Attributes: map[string]*configschema.Attribute{ 756 "id": { 757 Type: cty.String, 758 Required: true, 759 }, 760 "valid": { 761 Type: cty.Bool, 762 Computed: true, 763 }, 764 }, 765 }, 766 }, 767 }, 768 } 769 view, done := testView(t) 770 c := &PlanCommand{ 771 Meta: Meta{ 772 testingOverrides: metaOverridesForProvider(p), 773 View: view, 774 }, 775 } 776 777 args := []string{} 778 code := c.Run(args) 779 output := done(t) 780 if code != 0 { 781 t.Fatalf("bad: %d\n\n%s", code, output.Stderr()) 782 } 783 } 784 785 // Test that terraform properly merges provider configuration that's split 786 // between config files and interactive input variables. 787 // https://github.com/hashicorp/terraform/issues/28956 788 func TestPlan_providerConfigMerge(t *testing.T) { 789 td := t.TempDir() 790 testCopyDir(t, testFixturePath("plan-provider-input"), td) 791 defer testChdir(t, td)() 792 793 // Disable test mode so input would be asked 794 test = false 795 defer func() { test = true }() 796 797 // The plan command will prompt for interactive input of provider.test.region 798 defaultInputReader = bytes.NewBufferString("us-east-1\n") 799 800 p := planFixtureProvider() 801 // override the planFixtureProvider schema to include a required provider argument and a nested block 802 p.GetProviderSchemaResponse = &providers.GetProviderSchemaResponse{ 803 Provider: providers.Schema{ 804 Block: &configschema.Block{ 805 Attributes: map[string]*configschema.Attribute{ 806 "region": {Type: cty.String, Required: true}, 807 "url": {Type: cty.String, Required: true}, 808 }, 809 BlockTypes: map[string]*configschema.NestedBlock{ 810 "auth": { 811 Nesting: configschema.NestingList, 812 Block: configschema.Block{ 813 Attributes: map[string]*configschema.Attribute{ 814 "user": {Type: cty.String, Required: true}, 815 "password": {Type: cty.String, Required: true}, 816 }, 817 }, 818 }, 819 }, 820 }, 821 }, 822 ResourceTypes: map[string]providers.Schema{ 823 "test_instance": { 824 Block: &configschema.Block{ 825 Attributes: map[string]*configschema.Attribute{ 826 "id": {Type: cty.String, Optional: true, Computed: true}, 827 }, 828 }, 829 }, 830 }, 831 } 832 833 view, done := testView(t) 834 c := &PlanCommand{ 835 Meta: Meta{ 836 testingOverrides: metaOverridesForProvider(p), 837 View: view, 838 }, 839 } 840 841 args := []string{} 842 code := c.Run(args) 843 output := done(t) 844 if code != 0 { 845 t.Fatalf("bad: %d\n\n%s", code, output.Stderr()) 846 } 847 848 if !p.ConfigureProviderCalled { 849 t.Fatal("configure provider not called") 850 } 851 852 // For this test, we want to confirm that we've sent the expected config 853 // value *to* the provider. 854 got := p.ConfigureProviderRequest.Config 855 want := cty.ObjectVal(map[string]cty.Value{ 856 "auth": cty.ListVal([]cty.Value{ 857 cty.ObjectVal(map[string]cty.Value{ 858 "user": cty.StringVal("one"), 859 "password": cty.StringVal("onepw"), 860 }), 861 cty.ObjectVal(map[string]cty.Value{ 862 "user": cty.StringVal("two"), 863 "password": cty.StringVal("twopw"), 864 }), 865 }), 866 "region": cty.StringVal("us-east-1"), 867 "url": cty.StringVal("example.com"), 868 }) 869 870 if !got.RawEquals(want) { 871 t.Fatal("wrong provider config") 872 } 873 874 } 875 876 func TestPlan_varFile(t *testing.T) { 877 // Create a temporary working directory that is empty 878 td := t.TempDir() 879 testCopyDir(t, testFixturePath("plan-vars"), td) 880 defer testChdir(t, td)() 881 882 varFilePath := testTempFile(t) 883 if err := ioutil.WriteFile(varFilePath, []byte(planVarFile), 0644); err != nil { 884 t.Fatalf("err: %s", err) 885 } 886 887 p := planVarsFixtureProvider() 888 view, done := testView(t) 889 c := &PlanCommand{ 890 Meta: Meta{ 891 testingOverrides: metaOverridesForProvider(p), 892 View: view, 893 }, 894 } 895 896 actual := "" 897 p.PlanResourceChangeFn = func(req providers.PlanResourceChangeRequest) (resp providers.PlanResourceChangeResponse) { 898 actual = req.ProposedNewState.GetAttr("value").AsString() 899 resp.PlannedState = req.ProposedNewState 900 return 901 } 902 903 args := []string{ 904 "-var-file", varFilePath, 905 } 906 code := c.Run(args) 907 output := done(t) 908 if code != 0 { 909 t.Fatalf("bad: %d\n\n%s", code, output.Stderr()) 910 } 911 912 if actual != "bar" { 913 t.Fatal("didn't work") 914 } 915 } 916 917 func TestPlan_varFileDefault(t *testing.T) { 918 // Create a temporary working directory that is empty 919 td := t.TempDir() 920 testCopyDir(t, testFixturePath("plan-vars"), td) 921 defer testChdir(t, td)() 922 923 varFilePath := filepath.Join(td, "terraform.tfvars") 924 if err := ioutil.WriteFile(varFilePath, []byte(planVarFile), 0644); err != nil { 925 t.Fatalf("err: %s", err) 926 } 927 928 p := planVarsFixtureProvider() 929 view, done := testView(t) 930 c := &PlanCommand{ 931 Meta: Meta{ 932 testingOverrides: metaOverridesForProvider(p), 933 View: view, 934 }, 935 } 936 937 actual := "" 938 p.PlanResourceChangeFn = func(req providers.PlanResourceChangeRequest) (resp providers.PlanResourceChangeResponse) { 939 actual = req.ProposedNewState.GetAttr("value").AsString() 940 resp.PlannedState = req.ProposedNewState 941 return 942 } 943 944 args := []string{} 945 code := c.Run(args) 946 output := done(t) 947 if code != 0 { 948 t.Fatalf("bad: %d\n\n%s", code, output.Stderr()) 949 } 950 951 if actual != "bar" { 952 t.Fatal("didn't work") 953 } 954 } 955 956 func TestPlan_varFileWithDecls(t *testing.T) { 957 // Create a temporary working directory that is empty 958 td := t.TempDir() 959 testCopyDir(t, testFixturePath("plan-vars"), td) 960 defer testChdir(t, td)() 961 962 varFilePath := testTempFile(t) 963 if err := ioutil.WriteFile(varFilePath, []byte(planVarFileWithDecl), 0644); err != nil { 964 t.Fatalf("err: %s", err) 965 } 966 967 p := planVarsFixtureProvider() 968 view, done := testView(t) 969 c := &PlanCommand{ 970 Meta: Meta{ 971 testingOverrides: metaOverridesForProvider(p), 972 View: view, 973 }, 974 } 975 976 args := []string{ 977 "-var-file", varFilePath, 978 } 979 code := c.Run(args) 980 output := done(t) 981 if code == 0 { 982 t.Fatalf("succeeded; want failure\n\n%s", output.Stdout()) 983 } 984 985 msg := output.Stderr() 986 if got, want := msg, "Variable declaration in .tfvars file"; !strings.Contains(got, want) { 987 t.Fatalf("missing expected error message\nwant message containing %q\ngot:\n%s", want, got) 988 } 989 } 990 991 func TestPlan_detailedExitcode(t *testing.T) { 992 td := t.TempDir() 993 testCopyDir(t, testFixturePath("plan"), td) 994 defer testChdir(t, td)() 995 996 t.Run("return 1", func(t *testing.T) { 997 view, done := testView(t) 998 c := &PlanCommand{ 999 Meta: Meta{ 1000 // Running plan without setting testingOverrides is similar to plan without init 1001 View: view, 1002 }, 1003 } 1004 code := c.Run([]string{"-detailed-exitcode"}) 1005 output := done(t) 1006 if code != 1 { 1007 t.Fatalf("bad: %d\n\n%s", code, output.Stderr()) 1008 } 1009 }) 1010 1011 t.Run("return 2", func(t *testing.T) { 1012 p := planFixtureProvider() 1013 view, done := testView(t) 1014 c := &PlanCommand{ 1015 Meta: Meta{ 1016 testingOverrides: metaOverridesForProvider(p), 1017 View: view, 1018 }, 1019 } 1020 1021 code := c.Run([]string{"-detailed-exitcode"}) 1022 output := done(t) 1023 if code != 2 { 1024 t.Fatalf("bad: %d\n\n%s", code, output.Stderr()) 1025 } 1026 }) 1027 } 1028 1029 func TestPlan_detailedExitcode_emptyDiff(t *testing.T) { 1030 td := t.TempDir() 1031 testCopyDir(t, testFixturePath("plan-emptydiff"), td) 1032 defer testChdir(t, td)() 1033 1034 p := testProvider() 1035 view, done := testView(t) 1036 c := &PlanCommand{ 1037 Meta: Meta{ 1038 testingOverrides: metaOverridesForProvider(p), 1039 View: view, 1040 }, 1041 } 1042 1043 args := []string{"-detailed-exitcode"} 1044 code := c.Run(args) 1045 output := done(t) 1046 if code != 0 { 1047 t.Fatalf("bad: %d\n\n%s", code, output.Stderr()) 1048 } 1049 } 1050 1051 func TestPlan_shutdown(t *testing.T) { 1052 // Create a temporary working directory that is empty 1053 td := t.TempDir() 1054 testCopyDir(t, testFixturePath("apply-shutdown"), td) 1055 defer testChdir(t, td)() 1056 1057 cancelled := make(chan struct{}) 1058 shutdownCh := make(chan struct{}) 1059 1060 p := testProvider() 1061 view, done := testView(t) 1062 c := &PlanCommand{ 1063 Meta: Meta{ 1064 testingOverrides: metaOverridesForProvider(p), 1065 View: view, 1066 ShutdownCh: shutdownCh, 1067 }, 1068 } 1069 1070 p.StopFn = func() error { 1071 close(cancelled) 1072 return nil 1073 } 1074 1075 var once sync.Once 1076 1077 p.PlanResourceChangeFn = func(req providers.PlanResourceChangeRequest) (resp providers.PlanResourceChangeResponse) { 1078 once.Do(func() { 1079 shutdownCh <- struct{}{} 1080 }) 1081 1082 // Because of the internal lock in the MockProvider, we can't 1083 // coordinate directly with the calling of Stop, and making the 1084 // MockProvider concurrent is disruptive to a lot of existing tests. 1085 // Wait here a moment to help make sure the main goroutine gets to the 1086 // Stop call before we exit, or the plan may finish before it can be 1087 // canceled. 1088 time.Sleep(200 * time.Millisecond) 1089 1090 s := req.ProposedNewState.AsValueMap() 1091 s["ami"] = cty.StringVal("bar") 1092 resp.PlannedState = cty.ObjectVal(s) 1093 return 1094 } 1095 1096 p.GetProviderSchemaResponse = &providers.GetProviderSchemaResponse{ 1097 ResourceTypes: map[string]providers.Schema{ 1098 "test_instance": { 1099 Block: &configschema.Block{ 1100 Attributes: map[string]*configschema.Attribute{ 1101 "ami": {Type: cty.String, Optional: true}, 1102 }, 1103 }, 1104 }, 1105 }, 1106 } 1107 1108 code := c.Run([]string{}) 1109 output := done(t) 1110 if code != 1 { 1111 t.Errorf("wrong exit code %d; want 1\noutput:\n%s", code, output.Stdout()) 1112 } 1113 1114 select { 1115 case <-cancelled: 1116 default: 1117 t.Error("command not cancelled") 1118 } 1119 } 1120 1121 func TestPlan_init_required(t *testing.T) { 1122 td := t.TempDir() 1123 testCopyDir(t, testFixturePath("plan"), td) 1124 defer testChdir(t, td)() 1125 1126 view, done := testView(t) 1127 c := &PlanCommand{ 1128 Meta: Meta{ 1129 // Running plan without setting testingOverrides is similar to plan without init 1130 View: view, 1131 }, 1132 } 1133 1134 args := []string{"-no-color"} 1135 code := c.Run(args) 1136 output := done(t) 1137 if code != 1 { 1138 t.Fatalf("expected error, got success") 1139 } 1140 got := output.Stderr() 1141 if !(strings.Contains(got, "terraform init") && strings.Contains(got, "provider registry.terraform.io/hashicorp/test: required by this configuration but no version is selected")) { 1142 t.Fatal("wrong error message in output:", got) 1143 } 1144 } 1145 1146 // Config with multiple resources, targeting plan of a subset 1147 func TestPlan_targeted(t *testing.T) { 1148 td := t.TempDir() 1149 testCopyDir(t, testFixturePath("apply-targeted"), td) 1150 defer testChdir(t, td)() 1151 1152 p := testProvider() 1153 p.GetProviderSchemaResponse = &providers.GetProviderSchemaResponse{ 1154 ResourceTypes: map[string]providers.Schema{ 1155 "test_instance": { 1156 Block: &configschema.Block{ 1157 Attributes: map[string]*configschema.Attribute{ 1158 "id": {Type: cty.String, Computed: true}, 1159 }, 1160 }, 1161 }, 1162 }, 1163 } 1164 p.PlanResourceChangeFn = func(req providers.PlanResourceChangeRequest) providers.PlanResourceChangeResponse { 1165 return providers.PlanResourceChangeResponse{ 1166 PlannedState: req.ProposedNewState, 1167 } 1168 } 1169 1170 view, done := testView(t) 1171 c := &PlanCommand{ 1172 Meta: Meta{ 1173 testingOverrides: metaOverridesForProvider(p), 1174 View: view, 1175 }, 1176 } 1177 1178 args := []string{ 1179 "-target", "test_instance.foo", 1180 "-target", "test_instance.baz", 1181 } 1182 code := c.Run(args) 1183 output := done(t) 1184 if code != 0 { 1185 t.Fatalf("bad: %d\n\n%s", code, output.Stderr()) 1186 } 1187 1188 if got, want := output.Stdout(), "3 to add, 0 to change, 0 to destroy"; !strings.Contains(got, want) { 1189 t.Fatalf("bad change summary, want %q, got:\n%s", want, got) 1190 } 1191 } 1192 1193 // Diagnostics for invalid -target flags 1194 func TestPlan_targetFlagsDiags(t *testing.T) { 1195 testCases := map[string]string{ 1196 "test_instance.": "Dot must be followed by attribute name.", 1197 "test_instance": "Resource specification must include a resource type and name.", 1198 } 1199 1200 for target, wantDiag := range testCases { 1201 t.Run(target, func(t *testing.T) { 1202 td := testTempDir(t) 1203 defer os.RemoveAll(td) 1204 defer testChdir(t, td)() 1205 1206 view, done := testView(t) 1207 c := &PlanCommand{ 1208 Meta: Meta{ 1209 View: view, 1210 }, 1211 } 1212 1213 args := []string{ 1214 "-target", target, 1215 } 1216 code := c.Run(args) 1217 output := done(t) 1218 if code != 1 { 1219 t.Fatalf("bad: %d\n\n%s", code, output.Stdout()) 1220 } 1221 1222 got := output.Stderr() 1223 if !strings.Contains(got, target) { 1224 t.Fatalf("bad error output, want %q, got:\n%s", target, got) 1225 } 1226 if !strings.Contains(got, wantDiag) { 1227 t.Fatalf("bad error output, want %q, got:\n%s", wantDiag, got) 1228 } 1229 }) 1230 } 1231 } 1232 1233 func TestPlan_replace(t *testing.T) { 1234 td := t.TempDir() 1235 testCopyDir(t, testFixturePath("plan-replace"), td) 1236 defer testChdir(t, td)() 1237 1238 originalState := states.BuildState(func(s *states.SyncState) { 1239 s.SetResourceInstanceCurrent( 1240 addrs.Resource{ 1241 Mode: addrs.ManagedResourceMode, 1242 Type: "test_instance", 1243 Name: "a", 1244 }.Instance(addrs.NoKey).Absolute(addrs.RootModuleInstance), 1245 &states.ResourceInstanceObjectSrc{ 1246 AttrsJSON: []byte(`{"id":"hello"}`), 1247 Status: states.ObjectReady, 1248 }, 1249 addrs.AbsProviderConfig{ 1250 Provider: addrs.NewDefaultProvider("test"), 1251 Module: addrs.RootModule, 1252 }, 1253 ) 1254 }) 1255 statePath := testStateFile(t, originalState) 1256 1257 p := testProvider() 1258 p.GetProviderSchemaResponse = &providers.GetProviderSchemaResponse{ 1259 ResourceTypes: map[string]providers.Schema{ 1260 "test_instance": { 1261 Block: &configschema.Block{ 1262 Attributes: map[string]*configschema.Attribute{ 1263 "id": {Type: cty.String, Computed: true}, 1264 }, 1265 }, 1266 }, 1267 }, 1268 } 1269 p.PlanResourceChangeFn = func(req providers.PlanResourceChangeRequest) providers.PlanResourceChangeResponse { 1270 return providers.PlanResourceChangeResponse{ 1271 PlannedState: req.ProposedNewState, 1272 } 1273 } 1274 1275 view, done := testView(t) 1276 c := &PlanCommand{ 1277 Meta: Meta{ 1278 testingOverrides: metaOverridesForProvider(p), 1279 View: view, 1280 }, 1281 } 1282 1283 args := []string{ 1284 "-state", statePath, 1285 "-no-color", 1286 "-replace", "test_instance.a", 1287 } 1288 code := c.Run(args) 1289 output := done(t) 1290 if code != 0 { 1291 t.Fatalf("wrong exit code %d\n\n%s", code, output.Stderr()) 1292 } 1293 1294 stdout := output.Stdout() 1295 if got, want := stdout, "1 to add, 0 to change, 1 to destroy"; !strings.Contains(got, want) { 1296 t.Errorf("wrong plan summary\ngot output:\n%s\n\nwant substring: %s", got, want) 1297 } 1298 if got, want := stdout, "test_instance.a will be replaced, as requested"; !strings.Contains(got, want) { 1299 t.Errorf("missing replace explanation\ngot output:\n%s\n\nwant substring: %s", got, want) 1300 } 1301 } 1302 1303 // Verify that the parallelism flag allows no more than the desired number of 1304 // concurrent calls to PlanResourceChange. 1305 func TestPlan_parallelism(t *testing.T) { 1306 // Create a temporary working directory that is empty 1307 td := t.TempDir() 1308 testCopyDir(t, testFixturePath("parallelism"), td) 1309 defer testChdir(t, td)() 1310 1311 par := 4 1312 1313 // started is a semaphore that we use to ensure that we never have more 1314 // than "par" plan operations happening concurrently 1315 started := make(chan struct{}, par) 1316 1317 // beginCtx is used as a starting gate to hold back PlanResourceChange 1318 // calls until we reach the desired concurrency. The cancel func "begin" is 1319 // called once we reach the desired concurrency, allowing all apply calls 1320 // to proceed in unison. 1321 beginCtx, begin := context.WithCancel(context.Background()) 1322 1323 // Since our mock provider has its own mutex preventing concurrent calls 1324 // to ApplyResourceChange, we need to use a number of separate providers 1325 // here. They will all have the same mock implementation function assigned 1326 // but crucially they will each have their own mutex. 1327 providerFactories := map[addrs.Provider]providers.Factory{} 1328 for i := 0; i < 10; i++ { 1329 name := fmt.Sprintf("test%d", i) 1330 provider := &terraform.MockProvider{} 1331 provider.GetProviderSchemaResponse = &providers.GetProviderSchemaResponse{ 1332 ResourceTypes: map[string]providers.Schema{ 1333 name + "_instance": {Block: &configschema.Block{}}, 1334 }, 1335 } 1336 provider.PlanResourceChangeFn = func(req providers.PlanResourceChangeRequest) providers.PlanResourceChangeResponse { 1337 // If we ever have more than our intended parallelism number of 1338 // plan operations running concurrently, the semaphore will fail. 1339 select { 1340 case started <- struct{}{}: 1341 defer func() { 1342 <-started 1343 }() 1344 default: 1345 t.Fatal("too many concurrent apply operations") 1346 } 1347 1348 // If we never reach our intended parallelism, the context will 1349 // never be canceled and the test will time out. 1350 if len(started) >= par { 1351 begin() 1352 } 1353 <-beginCtx.Done() 1354 1355 // do some "work" 1356 // Not required for correctness, but makes it easier to spot a 1357 // failure when there is more overlap. 1358 time.Sleep(10 * time.Millisecond) 1359 return providers.PlanResourceChangeResponse{ 1360 PlannedState: req.ProposedNewState, 1361 } 1362 } 1363 providerFactories[addrs.NewDefaultProvider(name)] = providers.FactoryFixed(provider) 1364 } 1365 testingOverrides := &testingOverrides{ 1366 Providers: providerFactories, 1367 } 1368 1369 view, done := testView(t) 1370 c := &PlanCommand{ 1371 Meta: Meta{ 1372 testingOverrides: testingOverrides, 1373 View: view, 1374 }, 1375 } 1376 1377 args := []string{ 1378 fmt.Sprintf("-parallelism=%d", par), 1379 } 1380 1381 res := c.Run(args) 1382 output := done(t) 1383 if res != 0 { 1384 t.Fatal(output.Stdout()) 1385 } 1386 } 1387 1388 func TestPlan_warnings(t *testing.T) { 1389 td := t.TempDir() 1390 testCopyDir(t, testFixturePath("plan"), td) 1391 defer testChdir(t, td)() 1392 1393 t.Run("full warnings", func(t *testing.T) { 1394 p := planWarningsFixtureProvider() 1395 view, done := testView(t) 1396 c := &PlanCommand{ 1397 Meta: Meta{ 1398 testingOverrides: metaOverridesForProvider(p), 1399 View: view, 1400 }, 1401 } 1402 code := c.Run([]string{}) 1403 output := done(t) 1404 if code != 0 { 1405 t.Fatalf("bad: %d\n\n%s", code, output.Stderr()) 1406 } 1407 // the output should contain 3 warnings (returned by planWarningsFixtureProvider()) 1408 wantWarnings := []string{ 1409 "warning 1", 1410 "warning 2", 1411 "warning 3", 1412 } 1413 for _, want := range wantWarnings { 1414 if !strings.Contains(output.Stdout(), want) { 1415 t.Errorf("missing warning %s", want) 1416 } 1417 } 1418 }) 1419 1420 t.Run("compact warnings", func(t *testing.T) { 1421 p := planWarningsFixtureProvider() 1422 view, done := testView(t) 1423 c := &PlanCommand{ 1424 Meta: Meta{ 1425 testingOverrides: metaOverridesForProvider(p), 1426 View: view, 1427 }, 1428 } 1429 code := c.Run([]string{"-compact-warnings"}) 1430 output := done(t) 1431 if code != 0 { 1432 t.Fatalf("bad: %d\n\n%s", code, output.Stderr()) 1433 } 1434 // the output should contain 3 warnings (returned by planWarningsFixtureProvider()) 1435 // and the message that plan was run with -compact-warnings 1436 wantWarnings := []string{ 1437 "warning 1", 1438 "warning 2", 1439 "warning 3", 1440 "To see the full warning notes, run Terraform without -compact-warnings.", 1441 } 1442 for _, want := range wantWarnings { 1443 if !strings.Contains(output.Stdout(), want) { 1444 t.Errorf("missing warning %s", want) 1445 } 1446 } 1447 }) 1448 } 1449 1450 func TestPlan_jsonGoldenReference(t *testing.T) { 1451 // Create a temporary working directory that is empty 1452 td := t.TempDir() 1453 testCopyDir(t, testFixturePath("plan"), td) 1454 defer testChdir(t, td)() 1455 1456 p := planFixtureProvider() 1457 view, done := testView(t) 1458 c := &PlanCommand{ 1459 Meta: Meta{ 1460 testingOverrides: metaOverridesForProvider(p), 1461 View: view, 1462 }, 1463 } 1464 1465 args := []string{ 1466 "-json", 1467 } 1468 code := c.Run(args) 1469 output := done(t) 1470 if code != 0 { 1471 t.Fatalf("bad: %d\n\n%s", code, output.Stderr()) 1472 } 1473 1474 checkGoldenReference(t, output, "plan") 1475 } 1476 1477 // planFixtureSchema returns a schema suitable for processing the 1478 // configuration in testdata/plan . This schema should be 1479 // assigned to a mock provider named "test". 1480 func planFixtureSchema() *providers.GetProviderSchemaResponse { 1481 return &providers.GetProviderSchemaResponse{ 1482 ResourceTypes: map[string]providers.Schema{ 1483 "test_instance": { 1484 Block: &configschema.Block{ 1485 Attributes: map[string]*configschema.Attribute{ 1486 "id": {Type: cty.String, Optional: true, Computed: true}, 1487 "ami": {Type: cty.String, Optional: true}, 1488 }, 1489 BlockTypes: map[string]*configschema.NestedBlock{ 1490 "network_interface": { 1491 Nesting: configschema.NestingList, 1492 Block: configschema.Block{ 1493 Attributes: map[string]*configschema.Attribute{ 1494 "device_index": {Type: cty.String, Optional: true}, 1495 "description": {Type: cty.String, Optional: true}, 1496 }, 1497 }, 1498 }, 1499 }, 1500 }, 1501 }, 1502 }, 1503 DataSources: map[string]providers.Schema{ 1504 "test_data_source": { 1505 Block: &configschema.Block{ 1506 Attributes: map[string]*configschema.Attribute{ 1507 "id": { 1508 Type: cty.String, 1509 Required: true, 1510 }, 1511 "valid": { 1512 Type: cty.Bool, 1513 Computed: true, 1514 }, 1515 }, 1516 }, 1517 }, 1518 }, 1519 } 1520 } 1521 1522 // planFixtureProvider returns a mock provider that is configured for basic 1523 // operation with the configuration in testdata/plan. This mock has 1524 // GetSchemaResponse and PlanResourceChangeFn populated, with the plan 1525 // step just passing through the new object proposed by Terraform Core. 1526 func planFixtureProvider() *terraform.MockProvider { 1527 p := testProvider() 1528 p.GetProviderSchemaResponse = planFixtureSchema() 1529 p.PlanResourceChangeFn = func(req providers.PlanResourceChangeRequest) providers.PlanResourceChangeResponse { 1530 return providers.PlanResourceChangeResponse{ 1531 PlannedState: req.ProposedNewState, 1532 } 1533 } 1534 p.ReadDataSourceFn = func(req providers.ReadDataSourceRequest) providers.ReadDataSourceResponse { 1535 return providers.ReadDataSourceResponse{ 1536 State: cty.ObjectVal(map[string]cty.Value{ 1537 "id": cty.StringVal("zzzzz"), 1538 "valid": cty.BoolVal(true), 1539 }), 1540 } 1541 } 1542 return p 1543 } 1544 1545 // planVarsFixtureSchema returns a schema suitable for processing the 1546 // configuration in testdata/plan-vars . This schema should be 1547 // assigned to a mock provider named "test". 1548 func planVarsFixtureSchema() *providers.GetProviderSchemaResponse { 1549 return &providers.GetProviderSchemaResponse{ 1550 ResourceTypes: map[string]providers.Schema{ 1551 "test_instance": { 1552 Block: &configschema.Block{ 1553 Attributes: map[string]*configschema.Attribute{ 1554 "id": {Type: cty.String, Optional: true, Computed: true}, 1555 "value": {Type: cty.String, Optional: true}, 1556 }, 1557 }, 1558 }, 1559 }, 1560 } 1561 } 1562 1563 // planVarsFixtureProvider returns a mock provider that is configured for basic 1564 // operation with the configuration in testdata/plan-vars. This mock has 1565 // GetSchemaResponse and PlanResourceChangeFn populated, with the plan 1566 // step just passing through the new object proposed by Terraform Core. 1567 func planVarsFixtureProvider() *terraform.MockProvider { 1568 p := testProvider() 1569 p.GetProviderSchemaResponse = planVarsFixtureSchema() 1570 p.PlanResourceChangeFn = func(req providers.PlanResourceChangeRequest) providers.PlanResourceChangeResponse { 1571 return providers.PlanResourceChangeResponse{ 1572 PlannedState: req.ProposedNewState, 1573 } 1574 } 1575 p.ReadDataSourceFn = func(req providers.ReadDataSourceRequest) providers.ReadDataSourceResponse { 1576 return providers.ReadDataSourceResponse{ 1577 State: cty.ObjectVal(map[string]cty.Value{ 1578 "id": cty.StringVal("zzzzz"), 1579 "valid": cty.BoolVal(true), 1580 }), 1581 } 1582 } 1583 return p 1584 } 1585 1586 // planFixtureProvider returns a mock provider that is configured for basic 1587 // operation with the configuration in testdata/plan. This mock has 1588 // GetSchemaResponse and PlanResourceChangeFn populated, returning 3 warnings. 1589 func planWarningsFixtureProvider() *terraform.MockProvider { 1590 p := testProvider() 1591 p.GetProviderSchemaResponse = planFixtureSchema() 1592 p.PlanResourceChangeFn = func(req providers.PlanResourceChangeRequest) providers.PlanResourceChangeResponse { 1593 return providers.PlanResourceChangeResponse{ 1594 Diagnostics: tfdiags.Diagnostics{ 1595 tfdiags.SimpleWarning("warning 1"), 1596 tfdiags.SimpleWarning("warning 2"), 1597 tfdiags.SimpleWarning("warning 3"), 1598 }, 1599 PlannedState: req.ProposedNewState, 1600 } 1601 } 1602 p.ReadDataSourceFn = func(req providers.ReadDataSourceRequest) providers.ReadDataSourceResponse { 1603 return providers.ReadDataSourceResponse{ 1604 State: cty.ObjectVal(map[string]cty.Value{ 1605 "id": cty.StringVal("zzzzz"), 1606 "valid": cty.BoolVal(true), 1607 }), 1608 } 1609 } 1610 return p 1611 } 1612 1613 const planVarFile = ` 1614 foo = "bar" 1615 ` 1616 1617 const planVarFileWithDecl = ` 1618 foo = "bar" 1619 1620 variable "nope" { 1621 } 1622 `