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