1package command 2 3import ( 4 "bytes" 5 "crypto/md5" 6 "encoding/base64" 7 "encoding/json" 8 "fmt" 9 "io" 10 "io/ioutil" 11 "net/http" 12 "net/http/httptest" 13 "os" 14 "os/exec" 15 "path/filepath" 16 "strings" 17 "syscall" 18 "testing" 19 20 svchost "github.com/hashicorp/terraform-svchost" 21 22 "github.com/hashicorp/terraform-svchost/disco" 23 "github.com/hashicorp/terraform/internal/addrs" 24 backendInit "github.com/hashicorp/terraform/internal/backend/init" 25 backendLocal "github.com/hashicorp/terraform/internal/backend/local" 26 "github.com/hashicorp/terraform/internal/command/views" 27 "github.com/hashicorp/terraform/internal/configs" 28 "github.com/hashicorp/terraform/internal/configs/configload" 29 "github.com/hashicorp/terraform/internal/configs/configschema" 30 "github.com/hashicorp/terraform/internal/copy" 31 "github.com/hashicorp/terraform/internal/getproviders" 32 "github.com/hashicorp/terraform/internal/initwd" 33 legacy "github.com/hashicorp/terraform/internal/legacy/terraform" 34 _ "github.com/hashicorp/terraform/internal/logging" 35 "github.com/hashicorp/terraform/internal/plans" 36 "github.com/hashicorp/terraform/internal/plans/planfile" 37 "github.com/hashicorp/terraform/internal/providers" 38 "github.com/hashicorp/terraform/internal/registry" 39 "github.com/hashicorp/terraform/internal/states" 40 "github.com/hashicorp/terraform/internal/states/statefile" 41 "github.com/hashicorp/terraform/internal/states/statemgr" 42 "github.com/hashicorp/terraform/internal/terminal" 43 "github.com/hashicorp/terraform/internal/terraform" 44 "github.com/hashicorp/terraform/version" 45 "github.com/zclconf/go-cty/cty" 46) 47 48// These are the directories for our test data and fixtures. 49var ( 50 fixtureDir = "./testdata" 51 testDataDir = "./testdata" 52) 53 54// a top level temp directory which will be cleaned after all tests 55var testingDir string 56 57func init() { 58 test = true 59 60 // Initialize the backends 61 backendInit.Init(nil) 62 63 // Expand the data and fixture dirs on init because 64 // we change the working directory in some tests. 65 var err error 66 fixtureDir, err = filepath.Abs(fixtureDir) 67 if err != nil { 68 panic(err) 69 } 70 71 testDataDir, err = filepath.Abs(testDataDir) 72 if err != nil { 73 panic(err) 74 } 75 76 testingDir, err = ioutil.TempDir(testingDir, "tf") 77 if err != nil { 78 panic(err) 79 } 80} 81 82func TestMain(m *testing.M) { 83 defer os.RemoveAll(testingDir) 84 85 // Make sure backend init is initialized, since our tests tend to assume it. 86 backendInit.Init(nil) 87 88 os.Exit(m.Run()) 89} 90 91func tempDir(t *testing.T) string { 92 t.Helper() 93 94 dir, err := ioutil.TempDir(testingDir, "tf") 95 if err != nil { 96 t.Fatalf("err: %s", err) 97 } 98 99 dir, err = filepath.EvalSymlinks(dir) 100 if err != nil { 101 t.Fatal(err) 102 } 103 104 if err := os.RemoveAll(dir); err != nil { 105 t.Fatalf("err: %s", err) 106 } 107 108 return dir 109} 110 111func testFixturePath(name string) string { 112 return filepath.Join(fixtureDir, name) 113} 114 115func metaOverridesForProvider(p providers.Interface) *testingOverrides { 116 return &testingOverrides{ 117 Providers: map[addrs.Provider]providers.Factory{ 118 addrs.NewDefaultProvider("test"): providers.FactoryFixed(p), 119 }, 120 } 121} 122 123func testModuleWithSnapshot(t *testing.T, name string) (*configs.Config, *configload.Snapshot) { 124 t.Helper() 125 126 dir := filepath.Join(fixtureDir, name) 127 // FIXME: We're not dealing with the cleanup function here because 128 // this testModule function is used all over and so we don't want to 129 // change its interface at this late stage. 130 loader, _ := configload.NewLoaderForTests(t) 131 132 // Test modules usually do not refer to remote sources, and for local 133 // sources only this ultimately just records all of the module paths 134 // in a JSON file so that we can load them below. 135 inst := initwd.NewModuleInstaller(loader.ModulesDir(), registry.NewClient(nil, nil)) 136 _, instDiags := inst.InstallModules(dir, true, initwd.ModuleInstallHooksImpl{}) 137 if instDiags.HasErrors() { 138 t.Fatal(instDiags.Err()) 139 } 140 141 config, snap, diags := loader.LoadConfigWithSnapshot(dir) 142 if diags.HasErrors() { 143 t.Fatal(diags.Error()) 144 } 145 146 return config, snap 147} 148 149// testPlan returns a non-nil noop plan. 150func testPlan(t *testing.T) *plans.Plan { 151 t.Helper() 152 153 // This is what an empty configuration block would look like after being 154 // decoded with the schema of the "local" backend. 155 backendConfig := cty.ObjectVal(map[string]cty.Value{ 156 "path": cty.NullVal(cty.String), 157 "workspace_dir": cty.NullVal(cty.String), 158 }) 159 backendConfigRaw, err := plans.NewDynamicValue(backendConfig, backendConfig.Type()) 160 if err != nil { 161 t.Fatal(err) 162 } 163 164 return &plans.Plan{ 165 Backend: plans.Backend{ 166 // This is just a placeholder so that the plan file can be written 167 // out. Caller may wish to override it to something more "real" 168 // where the plan will actually be subsequently applied. 169 Type: "local", 170 Config: backendConfigRaw, 171 }, 172 Changes: plans.NewChanges(), 173 } 174} 175 176func testPlanFile(t *testing.T, configSnap *configload.Snapshot, state *states.State, plan *plans.Plan) string { 177 t.Helper() 178 179 stateFile := &statefile.File{ 180 Lineage: "", 181 State: state, 182 TerraformVersion: version.SemVer, 183 } 184 prevStateFile := &statefile.File{ 185 Lineage: "", 186 State: state, // we just assume no changes detected during refresh 187 TerraformVersion: version.SemVer, 188 } 189 190 path := testTempFile(t) 191 err := planfile.Create(path, configSnap, prevStateFile, stateFile, plan) 192 if err != nil { 193 t.Fatalf("failed to create temporary plan file: %s", err) 194 } 195 196 return path 197} 198 199// testPlanFileNoop is a shortcut function that creates a plan file that 200// represents no changes and returns its path. This is useful when a test 201// just needs any plan file, and it doesn't matter what is inside it. 202func testPlanFileNoop(t *testing.T) string { 203 snap := &configload.Snapshot{ 204 Modules: map[string]*configload.SnapshotModule{ 205 "": { 206 Dir: ".", 207 Files: map[string][]byte{ 208 "main.tf": nil, 209 }, 210 }, 211 }, 212 } 213 state := states.NewState() 214 plan := testPlan(t) 215 return testPlanFile(t, snap, state, plan) 216} 217 218func testReadPlan(t *testing.T, path string) *plans.Plan { 219 t.Helper() 220 221 f, err := planfile.Open(path) 222 if err != nil { 223 t.Fatalf("error opening plan file %q: %s", path, err) 224 } 225 defer f.Close() 226 227 p, err := f.ReadPlan() 228 if err != nil { 229 t.Fatalf("error reading plan from plan file %q: %s", path, err) 230 } 231 232 return p 233} 234 235// testState returns a test State structure that we use for a lot of tests. 236func testState() *states.State { 237 return states.BuildState(func(s *states.SyncState) { 238 s.SetResourceInstanceCurrent( 239 addrs.Resource{ 240 Mode: addrs.ManagedResourceMode, 241 Type: "test_instance", 242 Name: "foo", 243 }.Instance(addrs.NoKey).Absolute(addrs.RootModuleInstance), 244 &states.ResourceInstanceObjectSrc{ 245 // The weird whitespace here is reflective of how this would 246 // get written out in a real state file, due to the indentation 247 // of all of the containing wrapping objects and arrays. 248 AttrsJSON: []byte("{\n \"id\": \"bar\"\n }"), 249 Status: states.ObjectReady, 250 Dependencies: []addrs.ConfigResource{}, 251 }, 252 addrs.AbsProviderConfig{ 253 Provider: addrs.NewDefaultProvider("test"), 254 Module: addrs.RootModule, 255 }, 256 ) 257 // DeepCopy is used here to ensure our synthetic state matches exactly 258 // with a state that will have been copied during the command 259 // operation, and all fields have been copied correctly. 260 }).DeepCopy() 261} 262 263// writeStateForTesting is a helper that writes the given naked state to the 264// given writer, generating a stub *statefile.File wrapper which is then 265// immediately discarded. 266func writeStateForTesting(state *states.State, w io.Writer) error { 267 sf := &statefile.File{ 268 Serial: 0, 269 Lineage: "fake-for-testing", 270 State: state, 271 } 272 return statefile.Write(sf, w) 273} 274 275// testStateMgrCurrentLineage returns the current lineage for the given state 276// manager, or the empty string if it does not use lineage. This is primarily 277// for testing against the local backend, which always supports lineage. 278func testStateMgrCurrentLineage(mgr statemgr.Persistent) string { 279 if pm, ok := mgr.(statemgr.PersistentMeta); ok { 280 m := pm.StateSnapshotMeta() 281 return m.Lineage 282 } 283 return "" 284} 285 286// markStateForMatching is a helper that writes a specific marker value to 287// a state so that it can be recognized later with getStateMatchingMarker. 288// 289// Internally this just sets a root module output value called "testing_mark" 290// to the given string value. If the state is being checked in other ways, 291// the test code may need to compensate for the addition or overwriting of this 292// special output value name. 293// 294// The given mark string is returned verbatim, to allow the following pattern 295// in tests: 296// 297// mark := markStateForMatching(state, "foo") 298// // (do stuff to the state) 299// assertStateHasMarker(state, mark) 300func markStateForMatching(state *states.State, mark string) string { 301 state.RootModule().SetOutputValue("testing_mark", cty.StringVal(mark), false) 302 return mark 303} 304 305// getStateMatchingMarker is used with markStateForMatching to retrieve the 306// mark string previously added to the given state. If no such mark is present, 307// the result is an empty string. 308func getStateMatchingMarker(state *states.State) string { 309 os := state.RootModule().OutputValues["testing_mark"] 310 if os == nil { 311 return "" 312 } 313 v := os.Value 314 if v.Type() == cty.String && v.IsKnown() && !v.IsNull() { 315 return v.AsString() 316 } 317 return "" 318} 319 320// stateHasMarker is a helper around getStateMatchingMarker that also includes 321// the equality test, for more convenient use in test assertion branches. 322func stateHasMarker(state *states.State, want string) bool { 323 return getStateMatchingMarker(state) == want 324} 325 326// assertStateHasMarker wraps stateHasMarker to automatically generate a 327// fatal test result (i.e. t.Fatal) if the marker doesn't match. 328func assertStateHasMarker(t *testing.T, state *states.State, want string) { 329 if !stateHasMarker(state, want) { 330 t.Fatalf("wrong state marker\ngot: %q\nwant: %q", getStateMatchingMarker(state), want) 331 } 332} 333 334func testStateFile(t *testing.T, s *states.State) string { 335 t.Helper() 336 337 path := testTempFile(t) 338 339 f, err := os.Create(path) 340 if err != nil { 341 t.Fatalf("failed to create temporary state file %s: %s", path, err) 342 } 343 defer f.Close() 344 345 err = writeStateForTesting(s, f) 346 if err != nil { 347 t.Fatalf("failed to write state to temporary file %s: %s", path, err) 348 } 349 350 return path 351} 352 353// testStateFileDefault writes the state out to the default statefile 354// in the cwd. Use `testCwd` to change into a temp cwd. 355func testStateFileDefault(t *testing.T, s *states.State) { 356 t.Helper() 357 358 f, err := os.Create(DefaultStateFilename) 359 if err != nil { 360 t.Fatalf("err: %s", err) 361 } 362 defer f.Close() 363 364 if err := writeStateForTesting(s, f); err != nil { 365 t.Fatalf("err: %s", err) 366 } 367} 368 369// testStateFileWorkspaceDefault writes the state out to the default statefile 370// for the given workspace in the cwd. Use `testCwd` to change into a temp cwd. 371func testStateFileWorkspaceDefault(t *testing.T, workspace string, s *states.State) string { 372 t.Helper() 373 374 workspaceDir := filepath.Join(backendLocal.DefaultWorkspaceDir, workspace) 375 err := os.MkdirAll(workspaceDir, os.ModePerm) 376 if err != nil { 377 t.Fatalf("err: %s", err) 378 } 379 380 path := filepath.Join(workspaceDir, DefaultStateFilename) 381 f, err := os.Create(path) 382 if err != nil { 383 t.Fatalf("err: %s", err) 384 } 385 defer f.Close() 386 387 if err := writeStateForTesting(s, f); err != nil { 388 t.Fatalf("err: %s", err) 389 } 390 391 return path 392} 393 394// testStateFileRemote writes the state out to the remote statefile 395// in the cwd. Use `testCwd` to change into a temp cwd. 396func testStateFileRemote(t *testing.T, s *legacy.State) string { 397 t.Helper() 398 399 path := filepath.Join(DefaultDataDir, DefaultStateFilename) 400 if err := os.MkdirAll(filepath.Dir(path), 0755); err != nil { 401 t.Fatalf("err: %s", err) 402 } 403 404 f, err := os.Create(path) 405 if err != nil { 406 t.Fatalf("err: %s", err) 407 } 408 defer f.Close() 409 410 if err := legacy.WriteState(s, f); err != nil { 411 t.Fatalf("err: %s", err) 412 } 413 414 return path 415} 416 417// testStateRead reads the state from a file 418func testStateRead(t *testing.T, path string) *states.State { 419 t.Helper() 420 421 f, err := os.Open(path) 422 if err != nil { 423 t.Fatalf("err: %s", err) 424 } 425 defer f.Close() 426 427 sf, err := statefile.Read(f) 428 if err != nil { 429 t.Fatalf("err: %s", err) 430 } 431 432 return sf.State 433} 434 435// testDataStateRead reads a "data state", which is a file format resembling 436// our state format v3 that is used only to track current backend settings. 437// 438// This old format still uses *legacy.State, but should be replaced with 439// a more specialized type in a later release. 440func testDataStateRead(t *testing.T, path string) *legacy.State { 441 t.Helper() 442 443 f, err := os.Open(path) 444 if err != nil { 445 t.Fatalf("err: %s", err) 446 } 447 defer f.Close() 448 449 s, err := legacy.ReadState(f) 450 if err != nil { 451 t.Fatalf("err: %s", err) 452 } 453 454 return s 455} 456 457// testStateOutput tests that the state at the given path contains 458// the expected state string. 459func testStateOutput(t *testing.T, path string, expected string) { 460 t.Helper() 461 462 newState := testStateRead(t, path) 463 actual := strings.TrimSpace(newState.String()) 464 expected = strings.TrimSpace(expected) 465 if actual != expected { 466 t.Fatalf("expected:\n%s\nactual:\n%s", expected, actual) 467 } 468} 469 470func testProvider() *terraform.MockProvider { 471 p := new(terraform.MockProvider) 472 p.PlanResourceChangeFn = func(req providers.PlanResourceChangeRequest) (resp providers.PlanResourceChangeResponse) { 473 resp.PlannedState = req.ProposedNewState 474 return resp 475 } 476 477 p.ReadResourceFn = func(req providers.ReadResourceRequest) providers.ReadResourceResponse { 478 return providers.ReadResourceResponse{ 479 NewState: req.PriorState, 480 } 481 } 482 return p 483} 484 485func testTempFile(t *testing.T) string { 486 t.Helper() 487 488 return filepath.Join(testTempDir(t), "state.tfstate") 489} 490 491func testTempDir(t *testing.T) string { 492 t.Helper() 493 494 d, err := ioutil.TempDir(testingDir, "tf") 495 if err != nil { 496 t.Fatalf("err: %s", err) 497 } 498 499 d, err = filepath.EvalSymlinks(d) 500 if err != nil { 501 t.Fatal(err) 502 } 503 504 return d 505} 506 507// testChdir changes the directory and returns a function to defer to 508// revert the old cwd. 509func testChdir(t *testing.T, new string) func() { 510 t.Helper() 511 512 old, err := os.Getwd() 513 if err != nil { 514 t.Fatalf("err: %s", err) 515 } 516 517 if err := os.Chdir(new); err != nil { 518 t.Fatalf("err: %v", err) 519 } 520 521 return func() { 522 // Re-run the function ignoring the defer result 523 testChdir(t, old) 524 } 525} 526 527// testCwd is used to change the current working directory 528// into a test directory that should be removed after 529func testCwd(t *testing.T) (string, string) { 530 t.Helper() 531 532 tmp, err := ioutil.TempDir(testingDir, "tf") 533 if err != nil { 534 t.Fatalf("err: %v", err) 535 } 536 537 cwd, err := os.Getwd() 538 if err != nil { 539 t.Fatalf("err: %v", err) 540 } 541 542 if err := os.Chdir(tmp); err != nil { 543 t.Fatalf("err: %v", err) 544 } 545 546 return tmp, cwd 547} 548 549// testFixCwd is used to as a defer to testDir 550func testFixCwd(t *testing.T, tmp, cwd string) { 551 t.Helper() 552 553 if err := os.Chdir(cwd); err != nil { 554 t.Fatalf("err: %v", err) 555 } 556 557 if err := os.RemoveAll(tmp); err != nil { 558 t.Fatalf("err: %v", err) 559 } 560} 561 562// testStdinPipe changes os.Stdin to be a pipe that sends the data from 563// the reader before closing the pipe. 564// 565// The returned function should be deferred to properly clean up and restore 566// the original stdin. 567func testStdinPipe(t *testing.T, src io.Reader) func() { 568 t.Helper() 569 570 r, w, err := os.Pipe() 571 if err != nil { 572 t.Fatalf("err: %s", err) 573 } 574 575 // Modify stdin to point to our new pipe 576 old := os.Stdin 577 os.Stdin = r 578 579 // Copy the data from the reader to the pipe 580 go func() { 581 defer w.Close() 582 io.Copy(w, src) 583 }() 584 585 return func() { 586 // Close our read end 587 r.Close() 588 589 // Reset stdin 590 os.Stdin = old 591 } 592} 593 594// Modify os.Stdout to write to the given buffer. Note that this is generally 595// not useful since the commands are configured to write to a cli.Ui, not 596// Stdout directly. Commands like `console` though use the raw stdout. 597func testStdoutCapture(t *testing.T, dst io.Writer) func() { 598 t.Helper() 599 600 r, w, err := os.Pipe() 601 if err != nil { 602 t.Fatalf("err: %s", err) 603 } 604 605 // Modify stdout 606 old := os.Stdout 607 os.Stdout = w 608 609 // Copy 610 doneCh := make(chan struct{}) 611 go func() { 612 defer close(doneCh) 613 defer r.Close() 614 io.Copy(dst, r) 615 }() 616 617 return func() { 618 // Close the writer end of the pipe 619 w.Sync() 620 w.Close() 621 622 // Reset stdout 623 os.Stdout = old 624 625 // Wait for the data copy to complete to avoid a race reading data 626 <-doneCh 627 } 628} 629 630// testInteractiveInput configures tests so that the answers given are sent 631// in order to interactive prompts. The returned function must be called 632// in a defer to clean up. 633func testInteractiveInput(t *testing.T, answers []string) func() { 634 t.Helper() 635 636 // Disable test mode so input is called 637 test = false 638 639 // Set up reader/writers 640 testInputResponse = answers 641 defaultInputReader = bytes.NewBufferString("") 642 defaultInputWriter = new(bytes.Buffer) 643 644 // Return the cleanup 645 return func() { 646 test = true 647 testInputResponse = nil 648 } 649} 650 651// testInputMap configures tests so that the given answers are returned 652// for calls to Input when the right question is asked. The key is the 653// question "Id" that is used. 654func testInputMap(t *testing.T, answers map[string]string) func() { 655 t.Helper() 656 657 // Disable test mode so input is called 658 test = false 659 660 // Set up reader/writers 661 defaultInputReader = bytes.NewBufferString("") 662 defaultInputWriter = new(bytes.Buffer) 663 664 // Setup answers 665 testInputResponse = nil 666 testInputResponseMap = answers 667 668 // Return the cleanup 669 return func() { 670 test = true 671 testInputResponseMap = nil 672 } 673} 674 675// testBackendState is used to make a test HTTP server to test a configured 676// backend. This returns the complete state that can be saved. Use 677// `testStateFileRemote` to write the returned state. 678// 679// When using this function, the configuration fixture for the test must 680// include an empty configuration block for the HTTP backend, like this: 681// 682// terraform { 683// backend "http" { 684// } 685// } 686// 687// If such a block isn't present, or if it isn't empty, then an error will 688// be returned about the backend configuration having changed and that 689// "terraform init" must be run, since the test backend config cache created 690// by this function contains the hash for an empty configuration. 691func testBackendState(t *testing.T, s *states.State, c int) (*legacy.State, *httptest.Server) { 692 t.Helper() 693 694 var b64md5 string 695 buf := bytes.NewBuffer(nil) 696 697 cb := func(resp http.ResponseWriter, req *http.Request) { 698 if req.Method == "PUT" { 699 resp.WriteHeader(c) 700 return 701 } 702 if s == nil { 703 resp.WriteHeader(404) 704 return 705 } 706 707 resp.Header().Set("Content-MD5", b64md5) 708 resp.Write(buf.Bytes()) 709 } 710 711 // If a state was given, make sure we calculate the proper b64md5 712 if s != nil { 713 err := statefile.Write(&statefile.File{State: s}, buf) 714 if err != nil { 715 t.Fatalf("err: %v", err) 716 } 717 md5 := md5.Sum(buf.Bytes()) 718 b64md5 = base64.StdEncoding.EncodeToString(md5[:16]) 719 } 720 721 srv := httptest.NewServer(http.HandlerFunc(cb)) 722 723 backendConfig := &configs.Backend{ 724 Type: "http", 725 Config: configs.SynthBody("<testBackendState>", map[string]cty.Value{}), 726 } 727 b := backendInit.Backend("http")() 728 configSchema := b.ConfigSchema() 729 hash := backendConfig.Hash(configSchema) 730 731 state := legacy.NewState() 732 state.Backend = &legacy.BackendState{ 733 Type: "http", 734 ConfigRaw: json.RawMessage(fmt.Sprintf(`{"address":%q}`, srv.URL)), 735 Hash: uint64(hash), 736 } 737 738 return state, srv 739} 740 741// testRemoteState is used to make a test HTTP server to return a given 742// state file that can be used for testing legacy remote state. 743// 744// The return values are a *legacy.State instance that should be written 745// as the "data state" (really: backend state) and the server that the 746// returned data state refers to. 747func testRemoteState(t *testing.T, s *states.State, c int) (*legacy.State, *httptest.Server) { 748 t.Helper() 749 750 var b64md5 string 751 buf := bytes.NewBuffer(nil) 752 753 cb := func(resp http.ResponseWriter, req *http.Request) { 754 if req.Method == "PUT" { 755 resp.WriteHeader(c) 756 return 757 } 758 if s == nil { 759 resp.WriteHeader(404) 760 return 761 } 762 763 resp.Header().Set("Content-MD5", b64md5) 764 resp.Write(buf.Bytes()) 765 } 766 767 retState := legacy.NewState() 768 769 srv := httptest.NewServer(http.HandlerFunc(cb)) 770 b := &legacy.BackendState{ 771 Type: "http", 772 } 773 b.SetConfig(cty.ObjectVal(map[string]cty.Value{ 774 "address": cty.StringVal(srv.URL), 775 }), &configschema.Block{ 776 Attributes: map[string]*configschema.Attribute{ 777 "address": { 778 Type: cty.String, 779 Required: true, 780 }, 781 }, 782 }) 783 retState.Backend = b 784 785 if s != nil { 786 err := statefile.Write(&statefile.File{State: s}, buf) 787 if err != nil { 788 t.Fatalf("failed to write initial state: %v", err) 789 } 790 } 791 792 return retState, srv 793} 794 795// testlockState calls a separate process to the lock the state file at path. 796// deferFunc should be called in the caller to properly unlock the file. 797// Since many tests change the working directory, the sourcedir argument must be 798// supplied to locate the statelocker.go source. 799func testLockState(sourceDir, path string) (func(), error) { 800 // build and run the binary ourselves so we can quickly terminate it for cleanup 801 buildDir, err := ioutil.TempDir(testingDir, "locker") 802 if err != nil { 803 return nil, err 804 } 805 cleanFunc := func() { 806 os.RemoveAll(buildDir) 807 } 808 809 source := filepath.Join(sourceDir, "statelocker.go") 810 lockBin := filepath.Join(buildDir, "statelocker") 811 812 cmd := exec.Command("go", "build", "-o", lockBin, source) 813 cmd.Dir = filepath.Dir(sourceDir) 814 815 out, err := cmd.CombinedOutput() 816 if err != nil { 817 cleanFunc() 818 return nil, fmt.Errorf("%s %s", err, out) 819 } 820 821 locker := exec.Command(lockBin, path) 822 pr, pw, err := os.Pipe() 823 if err != nil { 824 cleanFunc() 825 return nil, err 826 } 827 defer pr.Close() 828 defer pw.Close() 829 locker.Stderr = pw 830 locker.Stdout = pw 831 832 if err := locker.Start(); err != nil { 833 return nil, err 834 } 835 deferFunc := func() { 836 cleanFunc() 837 locker.Process.Signal(syscall.SIGTERM) 838 locker.Wait() 839 } 840 841 // wait for the process to lock 842 buf := make([]byte, 1024) 843 n, err := pr.Read(buf) 844 if err != nil { 845 return deferFunc, fmt.Errorf("read from statelocker returned: %s", err) 846 } 847 848 output := string(buf[:n]) 849 if !strings.HasPrefix(output, "LOCKID") { 850 return deferFunc, fmt.Errorf("statelocker wrote: %s", string(buf[:n])) 851 } 852 return deferFunc, nil 853} 854 855// testCopyDir recursively copies a directory tree, attempting to preserve 856// permissions. Source directory must exist, destination directory must *not* 857// exist. Symlinks are ignored and skipped. 858func testCopyDir(t *testing.T, src, dst string) { 859 t.Helper() 860 861 src = filepath.Clean(src) 862 dst = filepath.Clean(dst) 863 864 si, err := os.Stat(src) 865 if err != nil { 866 t.Fatal(err) 867 } 868 if !si.IsDir() { 869 t.Fatal("source is not a directory") 870 } 871 872 _, err = os.Stat(dst) 873 if err != nil && !os.IsNotExist(err) { 874 t.Fatal(err) 875 } 876 if err == nil { 877 t.Fatal("destination already exists") 878 } 879 880 err = os.MkdirAll(dst, si.Mode()) 881 if err != nil { 882 t.Fatal(err) 883 } 884 885 entries, err := ioutil.ReadDir(src) 886 if err != nil { 887 return 888 } 889 890 for _, entry := range entries { 891 srcPath := filepath.Join(src, entry.Name()) 892 dstPath := filepath.Join(dst, entry.Name()) 893 894 // If the entry is a symlink, we copy the contents 895 for entry.Mode()&os.ModeSymlink != 0 { 896 target, err := os.Readlink(srcPath) 897 if err != nil { 898 t.Fatal(err) 899 } 900 901 entry, err = os.Stat(target) 902 if err != nil { 903 t.Fatal(err) 904 } 905 } 906 907 if entry.IsDir() { 908 testCopyDir(t, srcPath, dstPath) 909 } else { 910 err = copy.CopyFile(srcPath, dstPath) 911 if err != nil { 912 t.Fatal(err) 913 } 914 } 915 } 916} 917 918// normalizeJSON removes all insignificant whitespace from the given JSON buffer 919// and returns it as a string for easier comparison. 920func normalizeJSON(t *testing.T, src []byte) string { 921 t.Helper() 922 var buf bytes.Buffer 923 err := json.Compact(&buf, src) 924 if err != nil { 925 t.Fatalf("error normalizing JSON: %s", err) 926 } 927 return buf.String() 928} 929 930func mustResourceAddr(s string) addrs.ConfigResource { 931 addr, diags := addrs.ParseAbsResourceStr(s) 932 if diags.HasErrors() { 933 panic(diags.Err()) 934 } 935 return addr.Config() 936} 937 938// This map from provider type name to namespace is used by the fake registry 939// when called via LookupLegacyProvider. Providers not in this map will return 940// a 404 Not Found error. 941var legacyProviderNamespaces = map[string]string{ 942 "foo": "hashicorp", 943 "bar": "hashicorp", 944 "baz": "terraform-providers", 945 "qux": "hashicorp", 946} 947 948// This map is used to mock the provider redirect feature. 949var movedProviderNamespaces = map[string]string{ 950 "qux": "acme", 951} 952 953// testServices starts up a local HTTP server running a fake provider registry 954// service which responds only to discovery requests and legacy provider lookup 955// API calls. 956// 957// The final return value is a function to call at the end of a test function 958// to shut down the test server. After you call that function, the discovery 959// object becomes useless. 960func testServices(t *testing.T) (services *disco.Disco, cleanup func()) { 961 server := httptest.NewServer(http.HandlerFunc(fakeRegistryHandler)) 962 963 services = disco.New() 964 services.ForceHostServices(svchost.Hostname("registry.terraform.io"), map[string]interface{}{ 965 "providers.v1": server.URL + "/providers/v1/", 966 }) 967 968 return services, func() { 969 server.Close() 970 } 971} 972 973// testRegistrySource is a wrapper around testServices that uses the created 974// discovery object to produce a Source instance that is ready to use with the 975// fake registry services. 976// 977// As with testServices, the final return value is a function to call at the end 978// of your test in order to shut down the test server. 979func testRegistrySource(t *testing.T) (source *getproviders.RegistrySource, cleanup func()) { 980 services, close := testServices(t) 981 source = getproviders.NewRegistrySource(services) 982 return source, close 983} 984 985func fakeRegistryHandler(resp http.ResponseWriter, req *http.Request) { 986 path := req.URL.EscapedPath() 987 988 if !strings.HasPrefix(path, "/providers/v1/") { 989 resp.WriteHeader(404) 990 resp.Write([]byte(`not a provider registry endpoint`)) 991 return 992 } 993 994 pathParts := strings.Split(path, "/")[3:] 995 996 if len(pathParts) != 3 { 997 resp.WriteHeader(404) 998 resp.Write([]byte(`unrecognized path scheme`)) 999 return 1000 } 1001 1002 if pathParts[2] != "versions" { 1003 resp.WriteHeader(404) 1004 resp.Write([]byte(`this registry only supports legacy namespace lookup requests`)) 1005 return 1006 } 1007 1008 name := pathParts[1] 1009 1010 // Legacy lookup 1011 if pathParts[0] == "-" { 1012 if namespace, ok := legacyProviderNamespaces[name]; ok { 1013 resp.Header().Set("Content-Type", "application/json") 1014 resp.WriteHeader(200) 1015 if movedNamespace, ok := movedProviderNamespaces[name]; ok { 1016 resp.Write([]byte(fmt.Sprintf(`{"id":"%s/%s","moved_to":"%s/%s","versions":[{"version":"1.0.0","protocols":["4"]}]}`, namespace, name, movedNamespace, name))) 1017 } else { 1018 resp.Write([]byte(fmt.Sprintf(`{"id":"%s/%s","versions":[{"version":"1.0.0","protocols":["4"]}]}`, namespace, name))) 1019 } 1020 } else { 1021 resp.WriteHeader(404) 1022 resp.Write([]byte(`provider not found`)) 1023 } 1024 return 1025 } 1026 1027 // Also return versions for redirect target 1028 if namespace, ok := movedProviderNamespaces[name]; ok && pathParts[0] == namespace { 1029 resp.Header().Set("Content-Type", "application/json") 1030 resp.WriteHeader(200) 1031 resp.Write([]byte(fmt.Sprintf(`{"id":"%s/%s","versions":[{"version":"1.0.0","protocols":["4"]}]}`, namespace, name))) 1032 } else { 1033 resp.WriteHeader(404) 1034 resp.Write([]byte(`provider not found`)) 1035 } 1036} 1037 1038func testView(t *testing.T) (*views.View, func(*testing.T) *terminal.TestOutput) { 1039 streams, done := terminal.StreamsForTesting(t) 1040 return views.NewView(streams), done 1041} 1042