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