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