1package command
2
3import (
4	"bytes"
5	"context"
6	"encoding/json"
7	"fmt"
8	"io/ioutil"
9	"log"
10	"os"
11	"path/filepath"
12	"strings"
13	"testing"
14
15	"github.com/davecgh/go-spew/spew"
16	"github.com/google/go-cmp/cmp"
17	"github.com/mitchellh/cli"
18	"github.com/zclconf/go-cty/cty"
19
20	"github.com/hashicorp/terraform/internal/addrs"
21	"github.com/hashicorp/terraform/internal/configs"
22	"github.com/hashicorp/terraform/internal/configs/configschema"
23	"github.com/hashicorp/terraform/internal/depsfile"
24	"github.com/hashicorp/terraform/internal/getproviders"
25	"github.com/hashicorp/terraform/internal/providercache"
26	"github.com/hashicorp/terraform/internal/states"
27	"github.com/hashicorp/terraform/internal/states/statemgr"
28)
29
30func TestInit_empty(t *testing.T) {
31	// Create a temporary working directory that is empty
32	td := tempDir(t)
33	os.MkdirAll(td, 0755)
34	defer os.RemoveAll(td)
35	defer testChdir(t, td)()
36
37	ui := new(cli.MockUi)
38	view, _ := testView(t)
39	c := &InitCommand{
40		Meta: Meta{
41			testingOverrides: metaOverridesForProvider(testProvider()),
42			Ui:               ui,
43			View:             view,
44		},
45	}
46
47	args := []string{}
48	if code := c.Run(args); code != 0 {
49		t.Fatalf("bad: \n%s", ui.ErrorWriter.String())
50	}
51}
52
53func TestInit_multipleArgs(t *testing.T) {
54	// Create a temporary working directory that is empty
55	td := tempDir(t)
56	os.MkdirAll(td, 0755)
57	defer os.RemoveAll(td)
58	defer testChdir(t, td)()
59
60	ui := new(cli.MockUi)
61	view, _ := testView(t)
62	c := &InitCommand{
63		Meta: Meta{
64			testingOverrides: metaOverridesForProvider(testProvider()),
65			Ui:               ui,
66			View:             view,
67		},
68	}
69
70	args := []string{
71		"bad",
72		"bad",
73	}
74	if code := c.Run(args); code != 1 {
75		t.Fatalf("bad: \n%s", ui.OutputWriter.String())
76	}
77}
78
79func TestInit_fromModule_cwdDest(t *testing.T) {
80	// Create a temporary working directory that is empty
81	td := tempDir(t)
82	os.MkdirAll(td, os.ModePerm)
83	defer os.RemoveAll(td)
84	defer testChdir(t, td)()
85
86	ui := new(cli.MockUi)
87	view, _ := testView(t)
88	c := &InitCommand{
89		Meta: Meta{
90			testingOverrides: metaOverridesForProvider(testProvider()),
91			Ui:               ui,
92			View:             view,
93		},
94	}
95
96	args := []string{
97		"-from-module=" + testFixturePath("init"),
98	}
99	if code := c.Run(args); code != 0 {
100		t.Fatalf("bad: \n%s", ui.ErrorWriter.String())
101	}
102
103	if _, err := os.Stat(filepath.Join(td, "hello.tf")); err != nil {
104		t.Fatalf("err: %s", err)
105	}
106}
107
108// https://github.com/hashicorp/terraform/issues/518
109func TestInit_fromModule_dstInSrc(t *testing.T) {
110	dir := tempDir(t)
111	if err := os.MkdirAll(dir, 0755); err != nil {
112		t.Fatalf("err: %s", err)
113	}
114	defer os.RemoveAll(dir)
115
116	// Change to the temporary directory
117	cwd, err := os.Getwd()
118	if err != nil {
119		t.Fatalf("err: %s", err)
120	}
121	if err := os.Chdir(dir); err != nil {
122		t.Fatalf("err: %s", err)
123	}
124	defer os.Chdir(cwd)
125
126	if err := os.Mkdir("foo", os.ModePerm); err != nil {
127		t.Fatal(err)
128	}
129
130	if _, err := os.Create("issue518.tf"); err != nil {
131		t.Fatalf("err: %s", err)
132	}
133
134	if err := os.Chdir("foo"); err != nil {
135		t.Fatalf("err: %s", err)
136	}
137
138	ui := new(cli.MockUi)
139	view, _ := testView(t)
140	c := &InitCommand{
141		Meta: Meta{
142			testingOverrides: metaOverridesForProvider(testProvider()),
143			Ui:               ui,
144			View:             view,
145		},
146	}
147
148	args := []string{
149		"-from-module=./..",
150	}
151	if code := c.Run(args); code != 0 {
152		t.Fatalf("bad: \n%s", ui.ErrorWriter.String())
153	}
154
155	if _, err := os.Stat(filepath.Join(dir, "foo", "issue518.tf")); err != nil {
156		t.Fatalf("err: %s", err)
157	}
158}
159
160func TestInit_get(t *testing.T) {
161	// Create a temporary working directory that is empty
162	td := tempDir(t)
163	testCopyDir(t, testFixturePath("init-get"), td)
164	defer os.RemoveAll(td)
165	defer testChdir(t, td)()
166
167	ui := new(cli.MockUi)
168	view, _ := testView(t)
169	c := &InitCommand{
170		Meta: Meta{
171			testingOverrides: metaOverridesForProvider(testProvider()),
172			Ui:               ui,
173			View:             view,
174		},
175	}
176
177	args := []string{}
178	if code := c.Run(args); code != 0 {
179		t.Fatalf("bad: \n%s", ui.ErrorWriter.String())
180	}
181
182	// Check output
183	output := ui.OutputWriter.String()
184	if !strings.Contains(output, "foo in foo") {
185		t.Fatalf("doesn't look like we installed module 'foo': %s", output)
186	}
187}
188
189func TestInit_getUpgradeModules(t *testing.T) {
190	// Create a temporary working directory that is empty
191	td := tempDir(t)
192	testCopyDir(t, testFixturePath("init-get"), td)
193	defer os.RemoveAll(td)
194	defer testChdir(t, td)()
195
196	ui := new(cli.MockUi)
197	view, _ := testView(t)
198	c := &InitCommand{
199		Meta: Meta{
200			testingOverrides: metaOverridesForProvider(testProvider()),
201			Ui:               ui,
202			View:             view,
203		},
204	}
205
206	args := []string{
207		"-get=true",
208		"-upgrade",
209	}
210	if code := c.Run(args); code != 0 {
211		t.Fatalf("command did not complete successfully:\n%s", ui.ErrorWriter.String())
212	}
213
214	// Check output
215	output := ui.OutputWriter.String()
216	if !strings.Contains(output, "Upgrading modules...") {
217		t.Fatalf("doesn't look like get upgrade: %s", output)
218	}
219}
220
221func TestInit_backend(t *testing.T) {
222	// Create a temporary working directory that is empty
223	td := tempDir(t)
224	testCopyDir(t, testFixturePath("init-backend"), td)
225	defer os.RemoveAll(td)
226	defer testChdir(t, td)()
227
228	ui := new(cli.MockUi)
229	view, _ := testView(t)
230	c := &InitCommand{
231		Meta: Meta{
232			testingOverrides: metaOverridesForProvider(testProvider()),
233			Ui:               ui,
234			View:             view,
235		},
236	}
237
238	args := []string{}
239	if code := c.Run(args); code != 0 {
240		t.Fatalf("bad: \n%s", ui.ErrorWriter.String())
241	}
242
243	if _, err := os.Stat(filepath.Join(DefaultDataDir, DefaultStateFilename)); err != nil {
244		t.Fatalf("err: %s", err)
245	}
246}
247
248func TestInit_backendUnset(t *testing.T) {
249	// Create a temporary working directory that is empty
250	td := tempDir(t)
251	testCopyDir(t, testFixturePath("init-backend"), td)
252	defer os.RemoveAll(td)
253	defer testChdir(t, td)()
254
255	{
256		log.Printf("[TRACE] TestInit_backendUnset: beginning first init")
257
258		ui := cli.NewMockUi()
259		view, _ := testView(t)
260		c := &InitCommand{
261			Meta: Meta{
262				testingOverrides: metaOverridesForProvider(testProvider()),
263				Ui:               ui,
264				View:             view,
265			},
266		}
267
268		// Init
269		args := []string{}
270		if code := c.Run(args); code != 0 {
271			t.Fatalf("bad: \n%s", ui.ErrorWriter.String())
272		}
273		log.Printf("[TRACE] TestInit_backendUnset: first init complete")
274		t.Logf("First run output:\n%s", ui.OutputWriter.String())
275		t.Logf("First run errors:\n%s", ui.ErrorWriter.String())
276
277		if _, err := os.Stat(filepath.Join(DefaultDataDir, DefaultStateFilename)); err != nil {
278			t.Fatalf("err: %s", err)
279		}
280	}
281
282	{
283		log.Printf("[TRACE] TestInit_backendUnset: beginning second init")
284
285		// Unset
286		if err := ioutil.WriteFile("main.tf", []byte(""), 0644); err != nil {
287			t.Fatalf("err: %s", err)
288		}
289
290		ui := cli.NewMockUi()
291		view, _ := testView(t)
292		c := &InitCommand{
293			Meta: Meta{
294				testingOverrides: metaOverridesForProvider(testProvider()),
295				Ui:               ui,
296				View:             view,
297			},
298		}
299
300		args := []string{"-force-copy"}
301		if code := c.Run(args); code != 0 {
302			t.Fatalf("bad: \n%s", ui.ErrorWriter.String())
303		}
304		log.Printf("[TRACE] TestInit_backendUnset: second init complete")
305		t.Logf("Second run output:\n%s", ui.OutputWriter.String())
306		t.Logf("Second run errors:\n%s", ui.ErrorWriter.String())
307
308		s := testDataStateRead(t, filepath.Join(DefaultDataDir, DefaultStateFilename))
309		if !s.Backend.Empty() {
310			t.Fatal("should not have backend config")
311		}
312	}
313}
314
315func TestInit_backendConfigFile(t *testing.T) {
316	// Create a temporary working directory that is empty
317	td := tempDir(t)
318	testCopyDir(t, testFixturePath("init-backend-config-file"), td)
319	defer os.RemoveAll(td)
320	defer testChdir(t, td)()
321
322	t.Run("good-config-file", func(t *testing.T) {
323		ui := new(cli.MockUi)
324		view, _ := testView(t)
325		c := &InitCommand{
326			Meta: Meta{
327				testingOverrides: metaOverridesForProvider(testProvider()),
328				Ui:               ui,
329				View:             view,
330			},
331		}
332		args := []string{"-backend-config", "input.config"}
333		if code := c.Run(args); code != 0 {
334			t.Fatalf("bad: \n%s", ui.ErrorWriter.String())
335		}
336
337		// Read our saved backend config and verify we have our settings
338		state := testDataStateRead(t, filepath.Join(DefaultDataDir, DefaultStateFilename))
339		if got, want := normalizeJSON(t, state.Backend.ConfigRaw), `{"path":"hello","workspace_dir":null}`; got != want {
340			t.Errorf("wrong config\ngot:  %s\nwant: %s", got, want)
341		}
342	})
343
344	// the backend config file must not be a full terraform block
345	t.Run("full-backend-config-file", func(t *testing.T) {
346		ui := new(cli.MockUi)
347		view, _ := testView(t)
348		c := &InitCommand{
349			Meta: Meta{
350				testingOverrides: metaOverridesForProvider(testProvider()),
351				Ui:               ui,
352				View:             view,
353			},
354		}
355		args := []string{"-backend-config", "backend.config"}
356		if code := c.Run(args); code != 1 {
357			t.Fatalf("expected error, got success\n")
358		}
359		if !strings.Contains(ui.ErrorWriter.String(), "Unsupported block type") {
360			t.Fatalf("wrong error: %s", ui.ErrorWriter)
361		}
362	})
363
364	// the backend config file must match the schema for the backend
365	t.Run("invalid-config-file", func(t *testing.T) {
366		ui := new(cli.MockUi)
367		view, _ := testView(t)
368		c := &InitCommand{
369			Meta: Meta{
370				testingOverrides: metaOverridesForProvider(testProvider()),
371				Ui:               ui,
372				View:             view,
373			},
374		}
375		args := []string{"-backend-config", "invalid.config"}
376		if code := c.Run(args); code != 1 {
377			t.Fatalf("expected error, got success\n")
378		}
379		if !strings.Contains(ui.ErrorWriter.String(), "Unsupported argument") {
380			t.Fatalf("wrong error: %s", ui.ErrorWriter)
381		}
382	})
383
384	// missing file is an error
385	t.Run("missing-config-file", func(t *testing.T) {
386		ui := new(cli.MockUi)
387		view, _ := testView(t)
388		c := &InitCommand{
389			Meta: Meta{
390				testingOverrides: metaOverridesForProvider(testProvider()),
391				Ui:               ui,
392				View:             view,
393			},
394		}
395		args := []string{"-backend-config", "missing.config"}
396		if code := c.Run(args); code != 1 {
397			t.Fatalf("expected error, got success\n")
398		}
399		if !strings.Contains(ui.ErrorWriter.String(), "Failed to read file") {
400			t.Fatalf("wrong error: %s", ui.ErrorWriter)
401		}
402	})
403
404	// blank filename clears the backend config
405	t.Run("blank-config-file", func(t *testing.T) {
406		ui := new(cli.MockUi)
407		view, _ := testView(t)
408		c := &InitCommand{
409			Meta: Meta{
410				testingOverrides: metaOverridesForProvider(testProvider()),
411				Ui:               ui,
412				View:             view,
413			},
414		}
415		args := []string{"-backend-config=", "-migrate-state"}
416		if code := c.Run(args); code != 0 {
417			t.Fatalf("bad: \n%s", ui.ErrorWriter.String())
418		}
419
420		// Read our saved backend config and verify the backend config is empty
421		state := testDataStateRead(t, filepath.Join(DefaultDataDir, DefaultStateFilename))
422		if got, want := normalizeJSON(t, state.Backend.ConfigRaw), `{"path":null,"workspace_dir":null}`; got != want {
423			t.Errorf("wrong config\ngot:  %s\nwant: %s", got, want)
424		}
425	})
426
427	// simulate the local backend having a required field which is not
428	// specified in the override file
429	t.Run("required-argument", func(t *testing.T) {
430		c := &InitCommand{}
431		schema := &configschema.Block{
432			Attributes: map[string]*configschema.Attribute{
433				"path": {
434					Type:     cty.String,
435					Optional: true,
436				},
437				"workspace_dir": {
438					Type:     cty.String,
439					Required: true,
440				},
441			},
442		}
443		flagConfigExtra := newRawFlags("-backend-config")
444		flagConfigExtra.Set("input.config")
445		_, diags := c.backendConfigOverrideBody(flagConfigExtra, schema)
446		if len(diags) != 0 {
447			t.Errorf("expected no diags, got: %s", diags.Err())
448		}
449	})
450}
451
452func TestInit_backendConfigFilePowershellConfusion(t *testing.T) {
453	// Create a temporary working directory that is empty
454	td := tempDir(t)
455	testCopyDir(t, testFixturePath("init-backend-config-file"), td)
456	defer os.RemoveAll(td)
457	defer testChdir(t, td)()
458
459	ui := new(cli.MockUi)
460	view, _ := testView(t)
461	c := &InitCommand{
462		Meta: Meta{
463			testingOverrides: metaOverridesForProvider(testProvider()),
464			Ui:               ui,
465			View:             view,
466		},
467	}
468
469	// SUBTLE: when using -flag=value with Powershell, unquoted values are
470	// broken into separate arguments. This results in the init command
471	// interpreting the flags as an empty backend-config setting (which is
472	// semantically valid!) followed by a custom configuration path.
473	//
474	// Adding the "=" here forces this codepath to be checked, and it should
475	// result in an early exit with a diagnostic that the provided
476	// configuration file is not a diretory.
477	args := []string{"-backend-config=", "./input.config"}
478	if code := c.Run(args); code != 1 {
479		t.Fatalf("got exit status %d; want 1\nstderr:\n%s\n\nstdout:\n%s", code, ui.ErrorWriter.String(), ui.OutputWriter.String())
480	}
481
482	output := ui.ErrorWriter.String()
483	if got, want := output, `Too many command line arguments`; !strings.Contains(got, want) {
484		t.Fatalf("wrong output\ngot:\n%s\n\nwant: message containing %q", got, want)
485	}
486}
487
488func TestInit_backendConfigFileChange(t *testing.T) {
489	// Create a temporary working directory that is empty
490	td := tempDir(t)
491	testCopyDir(t, testFixturePath("init-backend-config-file-change"), td)
492	defer os.RemoveAll(td)
493	defer testChdir(t, td)()
494
495	// Ask input
496	defer testInputMap(t, map[string]string{
497		"backend-migrate-to-new": "no",
498	})()
499
500	ui := new(cli.MockUi)
501	view, _ := testView(t)
502	c := &InitCommand{
503		Meta: Meta{
504			testingOverrides: metaOverridesForProvider(testProvider()),
505			Ui:               ui,
506			View:             view,
507		},
508	}
509
510	args := []string{"-backend-config", "input.config", "-migrate-state"}
511	if code := c.Run(args); code != 0 {
512		t.Fatalf("bad: \n%s", ui.ErrorWriter.String())
513	}
514
515	// Read our saved backend config and verify we have our settings
516	state := testDataStateRead(t, filepath.Join(DefaultDataDir, DefaultStateFilename))
517	if got, want := normalizeJSON(t, state.Backend.ConfigRaw), `{"path":"hello","workspace_dir":null}`; got != want {
518		t.Errorf("wrong config\ngot:  %s\nwant: %s", got, want)
519	}
520}
521
522func TestInit_backendMigrateWhileLocked(t *testing.T) {
523	// Create a temporary working directory that is empty
524	td := tempDir(t)
525	testCopyDir(t, testFixturePath("init-backend-migrate-while-locked"), td)
526	defer os.RemoveAll(td)
527	defer testChdir(t, td)()
528
529	providerSource, close := newMockProviderSource(t, map[string][]string{
530		"hashicorp/test": {"1.2.3"},
531	})
532	defer close()
533
534	ui := new(cli.MockUi)
535	view, _ := testView(t)
536	c := &InitCommand{
537		Meta: Meta{
538			testingOverrides: metaOverridesForProvider(testProvider()),
539			ProviderSource:   providerSource,
540			Ui:               ui,
541			View:             view,
542		},
543	}
544
545	// Create some state, so the backend has something to migrate from
546	f, err := os.Create("local-state.tfstate")
547	if err != nil {
548		t.Fatalf("err: %s", err)
549	}
550	err = writeStateForTesting(testState(), f)
551	f.Close()
552	if err != nil {
553		t.Fatalf("err: %s", err)
554	}
555
556	// Lock the source state
557	unlock, err := testLockState(testDataDir, "local-state.tfstate")
558	if err != nil {
559		t.Fatal(err)
560	}
561	defer unlock()
562
563	// Attempt to migrate
564	args := []string{"-backend-config", "input.config", "-migrate-state", "-force-copy"}
565	if code := c.Run(args); code == 0 {
566		t.Fatalf("expected nonzero exit code: %s", ui.OutputWriter.String())
567	}
568
569	// Disabling locking should work
570	args = []string{"-backend-config", "input.config", "-migrate-state", "-force-copy", "-lock=false"}
571	if code := c.Run(args); code != 0 {
572		t.Fatalf("expected zero exit code, got %d: %s", code, ui.ErrorWriter.String())
573	}
574}
575
576func TestInit_backendConfigKV(t *testing.T) {
577	// Create a temporary working directory that is empty
578	td := tempDir(t)
579	testCopyDir(t, testFixturePath("init-backend-config-kv"), td)
580	defer os.RemoveAll(td)
581	defer testChdir(t, td)()
582
583	ui := new(cli.MockUi)
584	view, _ := testView(t)
585	c := &InitCommand{
586		Meta: Meta{
587			testingOverrides: metaOverridesForProvider(testProvider()),
588			Ui:               ui,
589			View:             view,
590		},
591	}
592
593	args := []string{"-backend-config", "path=hello"}
594	if code := c.Run(args); code != 0 {
595		t.Fatalf("bad: \n%s", ui.ErrorWriter.String())
596	}
597
598	// Read our saved backend config and verify we have our settings
599	state := testDataStateRead(t, filepath.Join(DefaultDataDir, DefaultStateFilename))
600	if got, want := normalizeJSON(t, state.Backend.ConfigRaw), `{"path":"hello","workspace_dir":null}`; got != want {
601		t.Errorf("wrong config\ngot:  %s\nwant: %s", got, want)
602	}
603}
604
605func TestInit_backendConfigKVReInit(t *testing.T) {
606	// Create a temporary working directory that is empty
607	td := tempDir(t)
608	testCopyDir(t, testFixturePath("init-backend-config-kv"), td)
609	defer os.RemoveAll(td)
610	defer testChdir(t, td)()
611
612	ui := new(cli.MockUi)
613	view, _ := testView(t)
614	c := &InitCommand{
615		Meta: Meta{
616			testingOverrides: metaOverridesForProvider(testProvider()),
617			Ui:               ui,
618			View:             view,
619		},
620	}
621
622	args := []string{"-backend-config", "path=test"}
623	if code := c.Run(args); code != 0 {
624		t.Fatalf("bad: \n%s", ui.ErrorWriter.String())
625	}
626
627	ui = new(cli.MockUi)
628	c = &InitCommand{
629		Meta: Meta{
630			testingOverrides: metaOverridesForProvider(testProvider()),
631			Ui:               ui,
632			View:             view,
633		},
634	}
635
636	// a second init should require no changes, nor should it change the backend.
637	args = []string{"-input=false"}
638	if code := c.Run(args); code != 0 {
639		t.Fatalf("bad: \n%s", ui.ErrorWriter.String())
640	}
641
642	// make sure the backend is configured how we expect
643	configState := testDataStateRead(t, filepath.Join(DefaultDataDir, DefaultStateFilename))
644	cfg := map[string]interface{}{}
645	if err := json.Unmarshal(configState.Backend.ConfigRaw, &cfg); err != nil {
646		t.Fatal(err)
647	}
648	if cfg["path"] != "test" {
649		t.Fatalf(`expected backend path="test", got path="%v"`, cfg["path"])
650	}
651
652	// override the -backend-config options by settings
653	args = []string{"-input=false", "-backend-config", "", "-migrate-state"}
654	if code := c.Run(args); code != 0 {
655		t.Fatalf("bad: \n%s", ui.ErrorWriter.String())
656	}
657
658	// make sure the backend is configured how we expect
659	configState = testDataStateRead(t, filepath.Join(DefaultDataDir, DefaultStateFilename))
660	cfg = map[string]interface{}{}
661	if err := json.Unmarshal(configState.Backend.ConfigRaw, &cfg); err != nil {
662		t.Fatal(err)
663	}
664	if cfg["path"] != nil {
665		t.Fatalf(`expected backend path="<nil>", got path="%v"`, cfg["path"])
666	}
667}
668
669func TestInit_backendConfigKVReInitWithConfigDiff(t *testing.T) {
670	// Create a temporary working directory that is empty
671	td := tempDir(t)
672	testCopyDir(t, testFixturePath("init-backend"), td)
673	defer os.RemoveAll(td)
674	defer testChdir(t, td)()
675
676	ui := new(cli.MockUi)
677	view, _ := testView(t)
678	c := &InitCommand{
679		Meta: Meta{
680			testingOverrides: metaOverridesForProvider(testProvider()),
681			Ui:               ui,
682			View:             view,
683		},
684	}
685
686	args := []string{"-input=false"}
687	if code := c.Run(args); code != 0 {
688		t.Fatalf("bad: \n%s", ui.ErrorWriter.String())
689	}
690
691	ui = new(cli.MockUi)
692	c = &InitCommand{
693		Meta: Meta{
694			testingOverrides: metaOverridesForProvider(testProvider()),
695			Ui:               ui,
696			View:             view,
697		},
698	}
699
700	// a second init with identical config should require no changes, nor
701	// should it change the backend.
702	args = []string{"-input=false", "-backend-config", "path=foo"}
703	if code := c.Run(args); code != 0 {
704		t.Fatalf("bad: \n%s", ui.ErrorWriter.String())
705	}
706
707	// make sure the backend is configured how we expect
708	configState := testDataStateRead(t, filepath.Join(DefaultDataDir, DefaultStateFilename))
709	cfg := map[string]interface{}{}
710	if err := json.Unmarshal(configState.Backend.ConfigRaw, &cfg); err != nil {
711		t.Fatal(err)
712	}
713	if cfg["path"] != "foo" {
714		t.Fatalf(`expected backend path="foo", got path="%v"`, cfg["foo"])
715	}
716}
717
718func TestInit_backendCli_no_config_block(t *testing.T) {
719	// Create a temporary working directory that is empty
720	td := tempDir(t)
721	testCopyDir(t, testFixturePath("init"), td)
722	defer os.RemoveAll(td)
723	defer testChdir(t, td)()
724
725	ui := new(cli.MockUi)
726	view, _ := testView(t)
727	c := &InitCommand{
728		Meta: Meta{
729			testingOverrides: metaOverridesForProvider(testProvider()),
730			Ui:               ui,
731			View:             view,
732		},
733	}
734
735	args := []string{"-backend-config", "path=test"}
736	if code := c.Run(args); code != 0 {
737		t.Fatalf("got exit status %d; want 0\nstderr:\n%s\n\nstdout:\n%s", code, ui.ErrorWriter.String(), ui.OutputWriter.String())
738	}
739
740	errMsg := ui.ErrorWriter.String()
741	if !strings.Contains(errMsg, "Warning: Missing backend configuration") {
742		t.Fatal("expected missing backend block warning, got", errMsg)
743	}
744}
745
746func TestInit_backendReinitWithExtra(t *testing.T) {
747	td := tempDir(t)
748	testCopyDir(t, testFixturePath("init-backend-empty"), td)
749	defer os.RemoveAll(td)
750	defer testChdir(t, td)()
751
752	m := testMetaBackend(t, nil)
753	opts := &BackendOpts{
754		ConfigOverride: configs.SynthBody("synth", map[string]cty.Value{
755			"path": cty.StringVal("hello"),
756		}),
757		Init: true,
758	}
759
760	_, cHash, err := m.backendConfig(opts)
761	if err != nil {
762		t.Fatal(err)
763	}
764
765	ui := new(cli.MockUi)
766	view, _ := testView(t)
767	c := &InitCommand{
768		Meta: Meta{
769			testingOverrides: metaOverridesForProvider(testProvider()),
770			Ui:               ui,
771			View:             view,
772		},
773	}
774
775	args := []string{"-backend-config", "path=hello"}
776	if code := c.Run(args); code != 0 {
777		t.Fatalf("bad: \n%s", ui.ErrorWriter.String())
778	}
779
780	// Read our saved backend config and verify we have our settings
781	state := testDataStateRead(t, filepath.Join(DefaultDataDir, DefaultStateFilename))
782	if got, want := normalizeJSON(t, state.Backend.ConfigRaw), `{"path":"hello","workspace_dir":null}`; got != want {
783		t.Errorf("wrong config\ngot:  %s\nwant: %s", got, want)
784	}
785
786	if state.Backend.Hash != uint64(cHash) {
787		t.Fatal("mismatched state and config backend hashes")
788	}
789
790	// init again and make sure nothing changes
791	if code := c.Run(args); code != 0 {
792		t.Fatalf("bad: \n%s", ui.ErrorWriter.String())
793	}
794	state = testDataStateRead(t, filepath.Join(DefaultDataDir, DefaultStateFilename))
795	if got, want := normalizeJSON(t, state.Backend.ConfigRaw), `{"path":"hello","workspace_dir":null}`; got != want {
796		t.Errorf("wrong config\ngot:  %s\nwant: %s", got, want)
797	}
798	if state.Backend.Hash != uint64(cHash) {
799		t.Fatal("mismatched state and config backend hashes")
800	}
801}
802
803// move option from config to -backend-config args
804func TestInit_backendReinitConfigToExtra(t *testing.T) {
805	td := tempDir(t)
806	testCopyDir(t, testFixturePath("init-backend"), td)
807	defer os.RemoveAll(td)
808	defer testChdir(t, td)()
809
810	ui := new(cli.MockUi)
811	view, _ := testView(t)
812	c := &InitCommand{
813		Meta: Meta{
814			testingOverrides: metaOverridesForProvider(testProvider()),
815			Ui:               ui,
816			View:             view,
817		},
818	}
819
820	if code := c.Run([]string{"-input=false"}); code != 0 {
821		t.Fatalf("bad: \n%s", ui.ErrorWriter.String())
822	}
823
824	// Read our saved backend config and verify we have our settings
825	state := testDataStateRead(t, filepath.Join(DefaultDataDir, DefaultStateFilename))
826	if got, want := normalizeJSON(t, state.Backend.ConfigRaw), `{"path":"foo","workspace_dir":null}`; got != want {
827		t.Errorf("wrong config\ngot:  %s\nwant: %s", got, want)
828	}
829
830	backendHash := state.Backend.Hash
831
832	// init again but remove the path option from the config
833	cfg := "terraform {\n  backend \"local\" {}\n}\n"
834	if err := ioutil.WriteFile("main.tf", []byte(cfg), 0644); err != nil {
835		t.Fatal(err)
836	}
837
838	// We need a fresh InitCommand here because the old one now has our configuration
839	// file cached inside it, so it won't re-read the modification we just made.
840	c = &InitCommand{
841		Meta: Meta{
842			testingOverrides: metaOverridesForProvider(testProvider()),
843			Ui:               ui,
844			View:             view,
845		},
846	}
847
848	args := []string{"-input=false", "-backend-config=path=foo"}
849	if code := c.Run(args); code != 0 {
850		t.Fatalf("bad: \n%s", ui.ErrorWriter.String())
851	}
852	state = testDataStateRead(t, filepath.Join(DefaultDataDir, DefaultStateFilename))
853	if got, want := normalizeJSON(t, state.Backend.ConfigRaw), `{"path":"foo","workspace_dir":null}`; got != want {
854		t.Errorf("wrong config after moving to arg\ngot:  %s\nwant: %s", got, want)
855	}
856
857	if state.Backend.Hash == backendHash {
858		t.Fatal("state.Backend.Hash was not updated")
859	}
860}
861
862// make sure inputFalse stops execution on migrate
863func TestInit_inputFalse(t *testing.T) {
864	td := tempDir(t)
865	testCopyDir(t, testFixturePath("init-backend"), td)
866	defer os.RemoveAll(td)
867	defer testChdir(t, td)()
868
869	ui := new(cli.MockUi)
870	view, _ := testView(t)
871	c := &InitCommand{
872		Meta: Meta{
873			testingOverrides: metaOverridesForProvider(testProvider()),
874			Ui:               ui,
875			View:             view,
876		},
877	}
878
879	args := []string{"-input=false", "-backend-config=path=foo"}
880	if code := c.Run(args); code != 0 {
881		t.Fatalf("bad: \n%s", ui.ErrorWriter)
882	}
883
884	// write different states for foo and bar
885	fooState := states.BuildState(func(s *states.SyncState) {
886		s.SetOutputValue(
887			addrs.OutputValue{Name: "foo"}.Absolute(addrs.RootModuleInstance),
888			cty.StringVal("foo"),
889			false, // not sensitive
890		)
891	})
892	if err := statemgr.NewFilesystem("foo").WriteState(fooState); err != nil {
893		t.Fatal(err)
894	}
895	barState := states.BuildState(func(s *states.SyncState) {
896		s.SetOutputValue(
897			addrs.OutputValue{Name: "bar"}.Absolute(addrs.RootModuleInstance),
898			cty.StringVal("bar"),
899			false, // not sensitive
900		)
901	})
902	if err := statemgr.NewFilesystem("bar").WriteState(barState); err != nil {
903		t.Fatal(err)
904	}
905
906	ui = new(cli.MockUi)
907	c = &InitCommand{
908		Meta: Meta{
909			testingOverrides: metaOverridesForProvider(testProvider()),
910			Ui:               ui,
911			View:             view,
912		},
913	}
914
915	args = []string{"-input=false", "-backend-config=path=bar", "-migrate-state"}
916	if code := c.Run(args); code == 0 {
917		t.Fatal("init should have failed", ui.OutputWriter)
918	}
919
920	errMsg := ui.ErrorWriter.String()
921	if !strings.Contains(errMsg, "input disabled") {
922		t.Fatal("expected input disabled error, got", errMsg)
923	}
924
925	ui = new(cli.MockUi)
926	c = &InitCommand{
927		Meta: Meta{
928			testingOverrides: metaOverridesForProvider(testProvider()),
929			Ui:               ui,
930			View:             view,
931		},
932	}
933
934	// A missing input=false should abort rather than loop infinitely
935	args = []string{"-backend-config=path=baz"}
936	if code := c.Run(args); code == 0 {
937		t.Fatal("init should have failed", ui.OutputWriter)
938	}
939}
940
941func TestInit_getProvider(t *testing.T) {
942	// Create a temporary working directory that is empty
943	td := tempDir(t)
944	testCopyDir(t, testFixturePath("init-get-providers"), td)
945	defer os.RemoveAll(td)
946	defer testChdir(t, td)()
947
948	overrides := metaOverridesForProvider(testProvider())
949	ui := new(cli.MockUi)
950	view, _ := testView(t)
951	providerSource, close := newMockProviderSource(t, map[string][]string{
952		// looking for an exact version
953		"exact": {"1.2.3"},
954		// config requires >= 2.3.3
955		"greater-than": {"2.3.4", "2.3.3", "2.3.0"},
956		// config specifies
957		"between": {"3.4.5", "2.3.4", "1.2.3"},
958	})
959	defer close()
960	m := Meta{
961		testingOverrides: overrides,
962		Ui:               ui,
963		View:             view,
964		ProviderSource:   providerSource,
965	}
966
967	c := &InitCommand{
968		Meta: m,
969	}
970
971	args := []string{
972		"-backend=false", // should be possible to install plugins without backend init
973	}
974	if code := c.Run(args); code != 0 {
975		t.Fatalf("bad: \n%s", ui.ErrorWriter.String())
976	}
977
978	// check that we got the providers for our config
979	exactPath := fmt.Sprintf(".terraform/providers/registry.terraform.io/hashicorp/exact/1.2.3/%s", getproviders.CurrentPlatform)
980	if _, err := os.Stat(exactPath); os.IsNotExist(err) {
981		t.Fatal("provider 'exact' not downloaded")
982	}
983	greaterThanPath := fmt.Sprintf(".terraform/providers/registry.terraform.io/hashicorp/greater-than/2.3.4/%s", getproviders.CurrentPlatform)
984	if _, err := os.Stat(greaterThanPath); os.IsNotExist(err) {
985		t.Fatal("provider 'greater-than' not downloaded")
986	}
987	betweenPath := fmt.Sprintf(".terraform/providers/registry.terraform.io/hashicorp/between/2.3.4/%s", getproviders.CurrentPlatform)
988	if _, err := os.Stat(betweenPath); os.IsNotExist(err) {
989		t.Fatal("provider 'between' not downloaded")
990	}
991
992	t.Run("future-state", func(t *testing.T) {
993		// getting providers should fail if a state from a newer version of
994		// terraform exists, since InitCommand.getProviders needs to inspect that
995		// state.
996
997		f, err := os.Create(DefaultStateFilename)
998		if err != nil {
999			t.Fatalf("err: %s", err)
1000		}
1001		defer f.Close()
1002
1003		// Construct a mock state file from the far future
1004		type FutureState struct {
1005			Version          uint                     `json:"version"`
1006			Lineage          string                   `json:"lineage"`
1007			TerraformVersion string                   `json:"terraform_version"`
1008			Outputs          map[string]interface{}   `json:"outputs"`
1009			Resources        []map[string]interface{} `json:"resources"`
1010		}
1011		fs := &FutureState{
1012			Version:          999,
1013			Lineage:          "123-456-789",
1014			TerraformVersion: "999.0.0",
1015			Outputs:          make(map[string]interface{}),
1016			Resources:        make([]map[string]interface{}, 0),
1017		}
1018		src, err := json.MarshalIndent(fs, "", "  ")
1019		if err != nil {
1020			t.Fatalf("failed to marshal future state: %s", err)
1021		}
1022		src = append(src, '\n')
1023		_, err = f.Write(src)
1024		if err != nil {
1025			t.Fatal(err)
1026		}
1027
1028		ui := new(cli.MockUi)
1029		view, _ := testView(t)
1030		m.Ui = ui
1031		m.View = view
1032		c := &InitCommand{
1033			Meta: m,
1034		}
1035
1036		if code := c.Run(nil); code == 0 {
1037			t.Fatal("expected error, got:", ui.OutputWriter)
1038		}
1039
1040		errMsg := ui.ErrorWriter.String()
1041		if !strings.Contains(errMsg, "Unsupported state file format") {
1042			t.Fatal("unexpected error:", errMsg)
1043		}
1044	})
1045}
1046
1047func TestInit_getProviderSource(t *testing.T) {
1048	// Create a temporary working directory that is empty
1049	td := tempDir(t)
1050	testCopyDir(t, testFixturePath("init-get-provider-source"), td)
1051	defer os.RemoveAll(td)
1052	defer testChdir(t, td)()
1053
1054	overrides := metaOverridesForProvider(testProvider())
1055	ui := new(cli.MockUi)
1056	view, _ := testView(t)
1057	providerSource, close := newMockProviderSource(t, map[string][]string{
1058		// looking for an exact version
1059		"acme/alpha": {"1.2.3"},
1060		// config doesn't specify versions for other providers
1061		"registry.example.com/acme/beta": {"1.0.0"},
1062		"gamma":                          {"2.0.0"},
1063	})
1064	defer close()
1065	m := Meta{
1066		testingOverrides: overrides,
1067		Ui:               ui,
1068		View:             view,
1069		ProviderSource:   providerSource,
1070	}
1071
1072	c := &InitCommand{
1073		Meta: m,
1074	}
1075
1076	args := []string{
1077		"-backend=false", // should be possible to install plugins without backend init
1078	}
1079	if code := c.Run(args); code != 0 {
1080		t.Fatalf("bad: \n%s", ui.ErrorWriter.String())
1081	}
1082
1083	// check that we got the providers for our config
1084	exactPath := fmt.Sprintf(".terraform/providers/registry.terraform.io/acme/alpha/1.2.3/%s", getproviders.CurrentPlatform)
1085	if _, err := os.Stat(exactPath); os.IsNotExist(err) {
1086		t.Error("provider 'alpha' not downloaded")
1087	}
1088	greaterThanPath := fmt.Sprintf(".terraform/providers/registry.example.com/acme/beta/1.0.0/%s", getproviders.CurrentPlatform)
1089	if _, err := os.Stat(greaterThanPath); os.IsNotExist(err) {
1090		t.Error("provider 'beta' not downloaded")
1091	}
1092	betweenPath := fmt.Sprintf(".terraform/providers/registry.terraform.io/hashicorp/gamma/2.0.0/%s", getproviders.CurrentPlatform)
1093	if _, err := os.Stat(betweenPath); os.IsNotExist(err) {
1094		t.Error("provider 'gamma' not downloaded")
1095	}
1096}
1097
1098func TestInit_getProviderLegacyFromState(t *testing.T) {
1099	// Create a temporary working directory that is empty
1100	td := tempDir(t)
1101	testCopyDir(t, testFixturePath("init-get-provider-legacy-from-state"), td)
1102	defer os.RemoveAll(td)
1103	defer testChdir(t, td)()
1104
1105	overrides := metaOverridesForProvider(testProvider())
1106	ui := new(cli.MockUi)
1107	view, _ := testView(t)
1108	providerSource, close := newMockProviderSource(t, map[string][]string{
1109		"acme/alpha": {"1.2.3"},
1110	})
1111	defer close()
1112	m := Meta{
1113		testingOverrides: overrides,
1114		Ui:               ui,
1115		View:             view,
1116		ProviderSource:   providerSource,
1117	}
1118
1119	c := &InitCommand{
1120		Meta: m,
1121	}
1122
1123	if code := c.Run(nil); code != 1 {
1124		t.Fatalf("got exit status %d; want 1\nstderr:\n%s\n\nstdout:\n%s", code, ui.ErrorWriter.String(), ui.OutputWriter.String())
1125	}
1126
1127	// Expect this diagnostic output
1128	wants := []string{
1129		"Invalid legacy provider address",
1130		"You must complete the Terraform 0.13 upgrade process",
1131	}
1132	got := ui.ErrorWriter.String()
1133	for _, want := range wants {
1134		if !strings.Contains(got, want) {
1135			t.Fatalf("expected output to contain %q, got:\n\n%s", want, got)
1136		}
1137	}
1138}
1139
1140func TestInit_getProviderInvalidPackage(t *testing.T) {
1141	// Create a temporary working directory that is empty
1142	td := tempDir(t)
1143	testCopyDir(t, testFixturePath("init-get-provider-invalid-package"), td)
1144	defer os.RemoveAll(td)
1145	defer testChdir(t, td)()
1146
1147	overrides := metaOverridesForProvider(testProvider())
1148	ui := new(cli.MockUi)
1149	view, _ := testView(t)
1150
1151	// create a provider source which allows installing an invalid package
1152	addr := addrs.MustParseProviderSourceString("invalid/package")
1153	version := getproviders.MustParseVersion("1.0.0")
1154	meta, close, err := getproviders.FakeInstallablePackageMeta(
1155		addr,
1156		version,
1157		getproviders.VersionList{getproviders.MustParseVersion("5.0")},
1158		getproviders.CurrentPlatform,
1159		"terraform-package", // should be "terraform-provider-package"
1160	)
1161	defer close()
1162	if err != nil {
1163		t.Fatalf("failed to prepare fake package for %s %s: %s", addr.ForDisplay(), version, err)
1164	}
1165	providerSource := getproviders.NewMockSource([]getproviders.PackageMeta{meta}, nil)
1166
1167	m := Meta{
1168		testingOverrides: overrides,
1169		Ui:               ui,
1170		View:             view,
1171		ProviderSource:   providerSource,
1172	}
1173
1174	c := &InitCommand{
1175		Meta: m,
1176	}
1177
1178	args := []string{
1179		"-backend=false", // should be possible to install plugins without backend init
1180	}
1181	if code := c.Run(args); code != 1 {
1182		t.Fatalf("got exit status %d; want 1\nstderr:\n%s\n\nstdout:\n%s", code, ui.ErrorWriter.String(), ui.OutputWriter.String())
1183	}
1184
1185	// invalid provider should be installed
1186	packagePath := fmt.Sprintf(".terraform/providers/registry.terraform.io/invalid/package/1.0.0/%s/terraform-package", getproviders.CurrentPlatform)
1187	if _, err := os.Stat(packagePath); os.IsNotExist(err) {
1188		t.Fatal("provider 'invalid/package' not downloaded")
1189	}
1190
1191	wantErrors := []string{
1192		"Failed to install provider",
1193		"could not find executable file starting with terraform-provider-package",
1194	}
1195	got := ui.ErrorWriter.String()
1196	for _, wantError := range wantErrors {
1197		if !strings.Contains(got, wantError) {
1198			t.Fatalf("missing error:\nwant: %q\ngot:\n%s", wantError, got)
1199		}
1200	}
1201}
1202
1203func TestInit_getProviderDetectedLegacy(t *testing.T) {
1204	// Create a temporary working directory that is empty
1205	td := tempDir(t)
1206	testCopyDir(t, testFixturePath("init-get-provider-detected-legacy"), td)
1207	defer os.RemoveAll(td)
1208	defer testChdir(t, td)()
1209
1210	// We need to construct a multisource with a mock source and a registry
1211	// source: the mock source will return ErrRegistryProviderNotKnown for an
1212	// unknown provider, and the registry source will allow us to look up the
1213	// appropriate namespace if possible.
1214	providerSource, psClose := newMockProviderSource(t, map[string][]string{
1215		"hashicorp/foo":           {"1.2.3"},
1216		"terraform-providers/baz": {"2.3.4"}, // this will not be installed
1217	})
1218	defer psClose()
1219	registrySource, rsClose := testRegistrySource(t)
1220	defer rsClose()
1221	multiSource := getproviders.MultiSource{
1222		{Source: providerSource},
1223		{Source: registrySource},
1224	}
1225
1226	ui := new(cli.MockUi)
1227	view, _ := testView(t)
1228	m := Meta{
1229		Ui:             ui,
1230		View:           view,
1231		ProviderSource: multiSource,
1232	}
1233
1234	c := &InitCommand{
1235		Meta: m,
1236	}
1237
1238	args := []string{
1239		"-backend=false", // should be possible to install plugins without backend init
1240	}
1241	if code := c.Run(args); code == 0 {
1242		t.Fatalf("expected error, got output: \n%s", ui.OutputWriter.String())
1243	}
1244
1245	// foo should be installed
1246	fooPath := fmt.Sprintf(".terraform/providers/registry.terraform.io/hashicorp/foo/1.2.3/%s", getproviders.CurrentPlatform)
1247	if _, err := os.Stat(fooPath); os.IsNotExist(err) {
1248		t.Error("provider 'foo' not installed")
1249	}
1250	// baz should not be installed
1251	bazPath := fmt.Sprintf(".terraform/providers/registry.terraform.io/terraform-providers/baz/2.3.4/%s", getproviders.CurrentPlatform)
1252	if _, err := os.Stat(bazPath); !os.IsNotExist(err) {
1253		t.Error("provider 'baz' installed, but should not be")
1254	}
1255
1256	// error output is the main focus of this test
1257	errOutput := ui.ErrorWriter.String()
1258	errors := []string{
1259		"Failed to query available provider packages",
1260		"Could not retrieve the list of available versions",
1261		"registry.terraform.io/hashicorp/baz",
1262		"registry.terraform.io/hashicorp/frob",
1263	}
1264	for _, want := range errors {
1265		if !strings.Contains(errOutput, want) {
1266			t.Fatalf("expected error %q: %s", want, errOutput)
1267		}
1268	}
1269}
1270
1271func TestInit_providerSource(t *testing.T) {
1272	// Create a temporary working directory that is empty
1273	td := tempDir(t)
1274	testCopyDir(t, testFixturePath("init-required-providers"), td)
1275	defer os.RemoveAll(td)
1276	defer testChdir(t, td)()
1277
1278	providerSource, close := newMockProviderSource(t, map[string][]string{
1279		"test":      {"1.2.3", "1.2.4"},
1280		"test-beta": {"1.2.4"},
1281		"source":    {"1.2.2", "1.2.3", "1.2.1"},
1282	})
1283	defer close()
1284
1285	ui := new(cli.MockUi)
1286	view, _ := testView(t)
1287	m := Meta{
1288		testingOverrides: metaOverridesForProvider(testProvider()),
1289		Ui:               ui,
1290		View:             view,
1291		ProviderSource:   providerSource,
1292	}
1293
1294	c := &InitCommand{
1295		Meta: m,
1296	}
1297
1298	args := []string{}
1299
1300	if code := c.Run(args); code != 0 {
1301		t.Fatalf("bad: \n%s", ui.ErrorWriter.String())
1302	}
1303	if strings.Contains(ui.OutputWriter.String(), "Terraform has initialized, but configuration upgrades may be needed") {
1304		t.Fatalf("unexpected \"configuration upgrade\" warning in output")
1305	}
1306
1307	cacheDir := m.providerLocalCacheDir()
1308	gotPackages := cacheDir.AllAvailablePackages()
1309	wantPackages := map[addrs.Provider][]providercache.CachedProvider{
1310		addrs.NewDefaultProvider("test"): {
1311			{
1312				Provider:   addrs.NewDefaultProvider("test"),
1313				Version:    getproviders.MustParseVersion("1.2.3"),
1314				PackageDir: expectedPackageInstallPath("test", "1.2.3", false),
1315			},
1316		},
1317		addrs.NewDefaultProvider("test-beta"): {
1318			{
1319				Provider:   addrs.NewDefaultProvider("test-beta"),
1320				Version:    getproviders.MustParseVersion("1.2.4"),
1321				PackageDir: expectedPackageInstallPath("test-beta", "1.2.4", false),
1322			},
1323		},
1324		addrs.NewDefaultProvider("source"): {
1325			{
1326				Provider:   addrs.NewDefaultProvider("source"),
1327				Version:    getproviders.MustParseVersion("1.2.3"),
1328				PackageDir: expectedPackageInstallPath("source", "1.2.3", false),
1329			},
1330		},
1331	}
1332	if diff := cmp.Diff(wantPackages, gotPackages); diff != "" {
1333		t.Errorf("wrong cache directory contents after upgrade\n%s", diff)
1334	}
1335
1336	locks, err := m.lockedDependencies()
1337	if err != nil {
1338		t.Fatalf("failed to get locked dependencies: %s", err)
1339	}
1340	gotProviderLocks := locks.AllProviders()
1341	wantProviderLocks := map[addrs.Provider]*depsfile.ProviderLock{
1342		addrs.NewDefaultProvider("test-beta"): depsfile.NewProviderLock(
1343			addrs.NewDefaultProvider("test-beta"),
1344			getproviders.MustParseVersion("1.2.4"),
1345			getproviders.MustParseVersionConstraints("= 1.2.4"),
1346			[]getproviders.Hash{
1347				getproviders.HashScheme1.New("see6W06w09Ea+AobFJ+mbvPTie6ASqZAAdlFZbs8BSM="),
1348			},
1349		),
1350		addrs.NewDefaultProvider("test"): depsfile.NewProviderLock(
1351			addrs.NewDefaultProvider("test"),
1352			getproviders.MustParseVersion("1.2.3"),
1353			getproviders.MustParseVersionConstraints("= 1.2.3"),
1354			[]getproviders.Hash{
1355				getproviders.HashScheme1.New("wlbEC2mChQZ2hhgUhl6SeVLPP7fMqOFUZAQhQ9GIIno="),
1356			},
1357		),
1358		addrs.NewDefaultProvider("source"): depsfile.NewProviderLock(
1359			addrs.NewDefaultProvider("source"),
1360			getproviders.MustParseVersion("1.2.3"),
1361			getproviders.MustParseVersionConstraints("= 1.2.3"),
1362			[]getproviders.Hash{
1363				getproviders.HashScheme1.New("myS3qb3px3tRBq1ZWRYJeUH+kySWpBc0Yy8rw6W7/p4="),
1364			},
1365		),
1366	}
1367	if diff := cmp.Diff(gotProviderLocks, wantProviderLocks, depsfile.ProviderLockComparer); diff != "" {
1368		t.Errorf("wrong version selections after upgrade\n%s", diff)
1369	}
1370
1371	outputStr := ui.OutputWriter.String()
1372	if want := "Installed hashicorp/test v1.2.3 (verified checksum)"; !strings.Contains(outputStr, want) {
1373		t.Fatalf("unexpected output: %s\nexpected to include %q", outputStr, want)
1374	}
1375}
1376
1377func TestInit_cancel(t *testing.T) {
1378	// This test runs `terraform init` as if SIGINT (or similar on other
1379	// platforms) were sent to it, testing that it is interruptible.
1380
1381	td := tempDir(t)
1382	testCopyDir(t, testFixturePath("init-required-providers"), td)
1383	defer os.RemoveAll(td)
1384	defer testChdir(t, td)()
1385
1386	providerSource, closeSrc := newMockProviderSource(t, map[string][]string{
1387		"test":      {"1.2.3", "1.2.4"},
1388		"test-beta": {"1.2.4"},
1389		"source":    {"1.2.2", "1.2.3", "1.2.1"},
1390	})
1391	defer closeSrc()
1392
1393	// our shutdown channel is pre-closed so init will exit as soon as it
1394	// starts a cancelable portion of the process.
1395	shutdownCh := make(chan struct{})
1396	close(shutdownCh)
1397
1398	ui := cli.NewMockUi()
1399	view, _ := testView(t)
1400	m := Meta{
1401		testingOverrides: metaOverridesForProvider(testProvider()),
1402		Ui:               ui,
1403		View:             view,
1404		ProviderSource:   providerSource,
1405		ShutdownCh:       shutdownCh,
1406	}
1407
1408	c := &InitCommand{
1409		Meta: m,
1410	}
1411
1412	args := []string{}
1413
1414	if code := c.Run(args); code == 0 {
1415		t.Fatalf("succeeded; wanted error")
1416	}
1417	// Currently the first operation that is cancelable is provider
1418	// installation, so our error message comes from there. If we
1419	// make the earlier steps cancelable in future then it'd be
1420	// expected for this particular message to change.
1421	if got, want := ui.ErrorWriter.String(), `Provider installation was canceled by an interrupt signal`; !strings.Contains(got, want) {
1422		t.Fatalf("wrong error message\nshould contain: %s\ngot:\n%s", want, got)
1423	}
1424}
1425
1426func TestInit_getUpgradePlugins(t *testing.T) {
1427	// Create a temporary working directory that is empty
1428	td := tempDir(t)
1429	testCopyDir(t, testFixturePath("init-get-providers"), td)
1430	defer os.RemoveAll(td)
1431	defer testChdir(t, td)()
1432
1433	providerSource, close := newMockProviderSource(t, map[string][]string{
1434		// looking for an exact version
1435		"exact": {"1.2.3"},
1436		// config requires >= 2.3.3
1437		"greater-than": {"2.3.4", "2.3.3", "2.3.0"},
1438		// config specifies > 1.0.0 , < 3.0.0
1439		"between": {"3.4.5", "2.3.4", "1.2.3"},
1440	})
1441	defer close()
1442
1443	ui := new(cli.MockUi)
1444	view, _ := testView(t)
1445	m := Meta{
1446		testingOverrides: metaOverridesForProvider(testProvider()),
1447		Ui:               ui,
1448		View:             view,
1449		ProviderSource:   providerSource,
1450	}
1451
1452	installFakeProviderPackages(t, &m, map[string][]string{
1453		"exact":        {"0.0.1"},
1454		"greater-than": {"2.3.3"},
1455	})
1456
1457	c := &InitCommand{
1458		Meta: m,
1459	}
1460
1461	args := []string{
1462		"-upgrade=true",
1463	}
1464	if code := c.Run(args); code != 0 {
1465		t.Fatalf("command did not complete successfully:\n%s", ui.ErrorWriter.String())
1466	}
1467
1468	cacheDir := m.providerLocalCacheDir()
1469	gotPackages := cacheDir.AllAvailablePackages()
1470	wantPackages := map[addrs.Provider][]providercache.CachedProvider{
1471		// "between" wasn't previously installed at all, so we installed
1472		// the newest available version that matched the version constraints.
1473		addrs.NewDefaultProvider("between"): {
1474			{
1475				Provider:   addrs.NewDefaultProvider("between"),
1476				Version:    getproviders.MustParseVersion("2.3.4"),
1477				PackageDir: expectedPackageInstallPath("between", "2.3.4", false),
1478			},
1479		},
1480		// The existing version of "exact" did not match the version constraints,
1481		// so we installed what the configuration selected as well.
1482		addrs.NewDefaultProvider("exact"): {
1483			{
1484				Provider:   addrs.NewDefaultProvider("exact"),
1485				Version:    getproviders.MustParseVersion("1.2.3"),
1486				PackageDir: expectedPackageInstallPath("exact", "1.2.3", false),
1487			},
1488			// Previous version is still there, but not selected
1489			{
1490				Provider:   addrs.NewDefaultProvider("exact"),
1491				Version:    getproviders.MustParseVersion("0.0.1"),
1492				PackageDir: expectedPackageInstallPath("exact", "0.0.1", false),
1493			},
1494		},
1495		// The existing version of "greater-than" _did_ match the constraints,
1496		// but a newer version was available and the user specified
1497		// -upgrade and so we upgraded it anyway.
1498		addrs.NewDefaultProvider("greater-than"): {
1499			{
1500				Provider:   addrs.NewDefaultProvider("greater-than"),
1501				Version:    getproviders.MustParseVersion("2.3.4"),
1502				PackageDir: expectedPackageInstallPath("greater-than", "2.3.4", false),
1503			},
1504			// Previous version is still there, but not selected
1505			{
1506				Provider:   addrs.NewDefaultProvider("greater-than"),
1507				Version:    getproviders.MustParseVersion("2.3.3"),
1508				PackageDir: expectedPackageInstallPath("greater-than", "2.3.3", false),
1509			},
1510		},
1511	}
1512	if diff := cmp.Diff(wantPackages, gotPackages); diff != "" {
1513		t.Errorf("wrong cache directory contents after upgrade\n%s", diff)
1514	}
1515
1516	locks, err := m.lockedDependencies()
1517	if err != nil {
1518		t.Fatalf("failed to get locked dependencies: %s", err)
1519	}
1520	gotProviderLocks := locks.AllProviders()
1521	wantProviderLocks := map[addrs.Provider]*depsfile.ProviderLock{
1522		addrs.NewDefaultProvider("between"): depsfile.NewProviderLock(
1523			addrs.NewDefaultProvider("between"),
1524			getproviders.MustParseVersion("2.3.4"),
1525			getproviders.MustParseVersionConstraints("> 1.0.0, < 3.0.0"),
1526			[]getproviders.Hash{
1527				getproviders.HashScheme1.New("JVqAvZz88A+hS2wHVtTWQkHaxoA/LrUAz0H3jPBWPIA="),
1528			},
1529		),
1530		addrs.NewDefaultProvider("exact"): depsfile.NewProviderLock(
1531			addrs.NewDefaultProvider("exact"),
1532			getproviders.MustParseVersion("1.2.3"),
1533			getproviders.MustParseVersionConstraints("= 1.2.3"),
1534			[]getproviders.Hash{
1535				getproviders.HashScheme1.New("H1TxWF8LyhBb6B4iUdKhLc/S9sC/jdcrCykpkbGcfbg="),
1536			},
1537		),
1538		addrs.NewDefaultProvider("greater-than"): depsfile.NewProviderLock(
1539			addrs.NewDefaultProvider("greater-than"),
1540			getproviders.MustParseVersion("2.3.4"),
1541			getproviders.MustParseVersionConstraints(">= 2.3.3"),
1542			[]getproviders.Hash{
1543				getproviders.HashScheme1.New("SJPpXx/yoFE/W+7eCipjJ+G21xbdnTBD7lWodZ8hWkU="),
1544			},
1545		),
1546	}
1547	if diff := cmp.Diff(gotProviderLocks, wantProviderLocks, depsfile.ProviderLockComparer); diff != "" {
1548		t.Errorf("wrong version selections after upgrade\n%s", diff)
1549	}
1550}
1551
1552func TestInit_getProviderMissing(t *testing.T) {
1553	// Create a temporary working directory that is empty
1554	td := tempDir(t)
1555	testCopyDir(t, testFixturePath("init-get-providers"), td)
1556	defer os.RemoveAll(td)
1557	defer testChdir(t, td)()
1558
1559	providerSource, close := newMockProviderSource(t, map[string][]string{
1560		// looking for exact version 1.2.3
1561		"exact": {"1.2.4"},
1562		// config requires >= 2.3.3
1563		"greater-than": {"2.3.4", "2.3.3", "2.3.0"},
1564		// config specifies
1565		"between": {"3.4.5", "2.3.4", "1.2.3"},
1566	})
1567	defer close()
1568
1569	ui := new(cli.MockUi)
1570	view, _ := testView(t)
1571	m := Meta{
1572		testingOverrides: metaOverridesForProvider(testProvider()),
1573		Ui:               ui,
1574		View:             view,
1575		ProviderSource:   providerSource,
1576	}
1577
1578	c := &InitCommand{
1579		Meta: m,
1580	}
1581
1582	args := []string{}
1583	if code := c.Run(args); code == 0 {
1584		t.Fatalf("expected error, got output: \n%s", ui.OutputWriter.String())
1585	}
1586
1587	if !strings.Contains(ui.ErrorWriter.String(), "no available releases match") {
1588		t.Fatalf("unexpected error output: %s", ui.ErrorWriter)
1589	}
1590}
1591
1592func TestInit_checkRequiredVersion(t *testing.T) {
1593	// Create a temporary working directory that is empty
1594	td := tempDir(t)
1595	testCopyDir(t, testFixturePath("init-check-required-version"), td)
1596	defer os.RemoveAll(td)
1597	defer testChdir(t, td)()
1598
1599	ui := cli.NewMockUi()
1600	view, _ := testView(t)
1601	c := &InitCommand{
1602		Meta: Meta{
1603			testingOverrides: metaOverridesForProvider(testProvider()),
1604			Ui:               ui,
1605			View:             view,
1606		},
1607	}
1608
1609	args := []string{}
1610	if code := c.Run(args); code != 1 {
1611		t.Fatalf("got exit status %d; want 1\nstderr:\n%s\n\nstdout:\n%s", code, ui.ErrorWriter.String(), ui.OutputWriter.String())
1612	}
1613	errStr := ui.ErrorWriter.String()
1614	if !strings.Contains(errStr, `required_version = "~> 0.9.0"`) {
1615		t.Fatalf("output should point to unmet version constraint, but is:\n\n%s", errStr)
1616	}
1617	if strings.Contains(errStr, `required_version = ">= 0.13.0"`) {
1618		t.Fatalf("output should not point to met version constraint, but is:\n\n%s", errStr)
1619	}
1620}
1621
1622// Verify that init will error out with an invalid version constraint, even if
1623// there are other invalid configuration constructs.
1624func TestInit_checkRequiredVersionFirst(t *testing.T) {
1625	t.Run("root_module", func(t *testing.T) {
1626		td := tempDir(t)
1627		testCopyDir(t, testFixturePath("init-check-required-version-first"), td)
1628		defer os.RemoveAll(td)
1629		defer testChdir(t, td)()
1630
1631		ui := cli.NewMockUi()
1632		view, _ := testView(t)
1633		c := &InitCommand{
1634			Meta: Meta{
1635				testingOverrides: metaOverridesForProvider(testProvider()),
1636				Ui:               ui,
1637				View:             view,
1638			},
1639		}
1640
1641		args := []string{}
1642		if code := c.Run(args); code != 1 {
1643			t.Fatalf("got exit status %d; want 1\nstderr:\n%s\n\nstdout:\n%s", code, ui.ErrorWriter.String(), ui.OutputWriter.String())
1644		}
1645		errStr := ui.ErrorWriter.String()
1646		if !strings.Contains(errStr, `Unsupported Terraform Core version`) {
1647			t.Fatalf("output should point to unmet version constraint, but is:\n\n%s", errStr)
1648		}
1649	})
1650	t.Run("sub_module", func(t *testing.T) {
1651		td := tempDir(t)
1652		testCopyDir(t, testFixturePath("init-check-required-version-first-module"), td)
1653		defer os.RemoveAll(td)
1654		defer testChdir(t, td)()
1655
1656		ui := cli.NewMockUi()
1657		view, _ := testView(t)
1658		c := &InitCommand{
1659			Meta: Meta{
1660				testingOverrides: metaOverridesForProvider(testProvider()),
1661				Ui:               ui,
1662				View:             view,
1663			},
1664		}
1665
1666		args := []string{}
1667		if code := c.Run(args); code != 1 {
1668			t.Fatalf("got exit status %d; want 1\nstderr:\n%s\n\nstdout:\n%s", code, ui.ErrorWriter.String(), ui.OutputWriter.String())
1669		}
1670		errStr := ui.ErrorWriter.String()
1671		if !strings.Contains(errStr, `Unsupported Terraform Core version`) {
1672			t.Fatalf("output should point to unmet version constraint, but is:\n\n%s", errStr)
1673		}
1674	})
1675}
1676
1677func TestInit_providerLockFile(t *testing.T) {
1678	// Create a temporary working directory that is empty
1679	td := tempDir(t)
1680	testCopyDir(t, testFixturePath("init-provider-lock-file"), td)
1681	defer os.RemoveAll(td)
1682	defer testChdir(t, td)()
1683
1684	providerSource, close := newMockProviderSource(t, map[string][]string{
1685		"test": {"1.2.3"},
1686	})
1687	defer close()
1688
1689	ui := new(cli.MockUi)
1690	view, _ := testView(t)
1691	m := Meta{
1692		testingOverrides: metaOverridesForProvider(testProvider()),
1693		Ui:               ui,
1694		View:             view,
1695		ProviderSource:   providerSource,
1696	}
1697
1698	c := &InitCommand{
1699		Meta: m,
1700	}
1701
1702	args := []string{}
1703	if code := c.Run(args); code != 0 {
1704		t.Fatalf("bad: \n%s", ui.ErrorWriter.String())
1705	}
1706
1707	lockFile := ".terraform.lock.hcl"
1708	buf, err := ioutil.ReadFile(lockFile)
1709	if err != nil {
1710		t.Fatalf("failed to read dependency lock file %s: %s", lockFile, err)
1711	}
1712	buf = bytes.TrimSpace(buf)
1713	// The hash in here is for the fake package that newMockProviderSource produces
1714	// (so it'll change if newMockProviderSource starts producing different contents)
1715	wantLockFile := strings.TrimSpace(`
1716# This file is maintained automatically by "terraform init".
1717# Manual edits may be lost in future updates.
1718
1719provider "registry.terraform.io/hashicorp/test" {
1720  version     = "1.2.3"
1721  constraints = "1.2.3"
1722  hashes = [
1723    "h1:wlbEC2mChQZ2hhgUhl6SeVLPP7fMqOFUZAQhQ9GIIno=",
1724  ]
1725}
1726`)
1727	if diff := cmp.Diff(wantLockFile, string(buf)); diff != "" {
1728		t.Errorf("wrong dependency lock file contents\n%s", diff)
1729	}
1730
1731	// Make the local directory read-only, and verify that rerunning init
1732	// succeeds, to ensure that we don't try to rewrite an unchanged lock file
1733	os.Chmod(".", 0555)
1734	if code := c.Run(args); code != 0 {
1735		t.Fatalf("bad: \n%s", ui.ErrorWriter.String())
1736	}
1737}
1738
1739func TestInit_providerLockFileReadonly(t *testing.T) {
1740	// The hash in here is for the fake package that newMockProviderSource produces
1741	// (so it'll change if newMockProviderSource starts producing different contents)
1742	inputLockFile := strings.TrimSpace(`
1743# This file is maintained automatically by "terraform init".
1744# Manual edits may be lost in future updates.
1745
1746provider "registry.terraform.io/hashicorp/test" {
1747  version     = "1.2.3"
1748  constraints = "1.2.3"
1749  hashes = [
1750    "zh:e919b507a91e23a00da5c2c4d0b64bcc7900b68d43b3951ac0f6e5d80387fbdc",
1751  ]
1752}
1753`)
1754
1755	badLockFile := strings.TrimSpace(`
1756# This file is maintained automatically by "terraform init".
1757# Manual edits may be lost in future updates.
1758
1759provider "registry.terraform.io/hashicorp/test" {
1760  version     = "1.2.3"
1761  constraints = "1.2.3"
1762  hashes = [
1763    "zh:0000000000000000000000000000000000000000000000000000000000000000",
1764  ]
1765}
1766`)
1767
1768	updatedLockFile := strings.TrimSpace(`
1769# This file is maintained automatically by "terraform init".
1770# Manual edits may be lost in future updates.
1771
1772provider "registry.terraform.io/hashicorp/test" {
1773  version     = "1.2.3"
1774  constraints = "1.2.3"
1775  hashes = [
1776    "h1:wlbEC2mChQZ2hhgUhl6SeVLPP7fMqOFUZAQhQ9GIIno=",
1777    "zh:e919b507a91e23a00da5c2c4d0b64bcc7900b68d43b3951ac0f6e5d80387fbdc",
1778  ]
1779}
1780`)
1781
1782	cases := []struct {
1783		desc      string
1784		fixture   string
1785		providers map[string][]string
1786		input     string
1787		args      []string
1788		ok        bool
1789		want      string
1790	}{
1791		{
1792			desc:      "default",
1793			fixture:   "init-provider-lock-file",
1794			providers: map[string][]string{"test": {"1.2.3"}},
1795			input:     inputLockFile,
1796			args:      []string{},
1797			ok:        true,
1798			want:      updatedLockFile,
1799		},
1800		{
1801			desc:      "readonly",
1802			fixture:   "init-provider-lock-file",
1803			providers: map[string][]string{"test": {"1.2.3"}},
1804			input:     inputLockFile,
1805			args:      []string{"-lockfile=readonly"},
1806			ok:        true,
1807			want:      inputLockFile,
1808		},
1809		{
1810			desc:      "conflict",
1811			fixture:   "init-provider-lock-file",
1812			providers: map[string][]string{"test": {"1.2.3"}},
1813			input:     inputLockFile,
1814			args:      []string{"-lockfile=readonly", "-upgrade"},
1815			ok:        false,
1816			want:      inputLockFile,
1817		},
1818		{
1819			desc:      "checksum mismatch",
1820			fixture:   "init-provider-lock-file",
1821			providers: map[string][]string{"test": {"1.2.3"}},
1822			input:     badLockFile,
1823			args:      []string{"-lockfile=readonly"},
1824			ok:        false,
1825			want:      badLockFile,
1826		},
1827		{
1828			desc:    "reject to change required provider dependences",
1829			fixture: "init-provider-lock-file-readonly-add",
1830			providers: map[string][]string{
1831				"test": {"1.2.3"},
1832				"foo":  {"1.0.0"},
1833			},
1834			input: inputLockFile,
1835			args:  []string{"-lockfile=readonly"},
1836			ok:    false,
1837			want:  inputLockFile,
1838		},
1839	}
1840
1841	for _, tc := range cases {
1842		t.Run(tc.desc, func(t *testing.T) {
1843			// Create a temporary working directory that is empty
1844			td := tempDir(t)
1845			testCopyDir(t, testFixturePath(tc.fixture), td)
1846			defer os.RemoveAll(td)
1847			defer testChdir(t, td)()
1848
1849			providerSource, close := newMockProviderSource(t, tc.providers)
1850			defer close()
1851
1852			ui := new(cli.MockUi)
1853			m := Meta{
1854				testingOverrides: metaOverridesForProvider(testProvider()),
1855				Ui:               ui,
1856				ProviderSource:   providerSource,
1857			}
1858
1859			c := &InitCommand{
1860				Meta: m,
1861			}
1862
1863			// write input lockfile
1864			lockFile := ".terraform.lock.hcl"
1865			if err := ioutil.WriteFile(lockFile, []byte(tc.input), 0644); err != nil {
1866				t.Fatalf("failed to write input lockfile: %s", err)
1867			}
1868
1869			code := c.Run(tc.args)
1870			if tc.ok && code != 0 {
1871				t.Fatalf("bad: \n%s", ui.ErrorWriter.String())
1872			}
1873			if !tc.ok && code == 0 {
1874				t.Fatalf("expected error, got output: \n%s", ui.OutputWriter.String())
1875			}
1876
1877			buf, err := ioutil.ReadFile(lockFile)
1878			if err != nil {
1879				t.Fatalf("failed to read dependency lock file %s: %s", lockFile, err)
1880			}
1881			buf = bytes.TrimSpace(buf)
1882			if diff := cmp.Diff(tc.want, string(buf)); diff != "" {
1883				t.Errorf("wrong dependency lock file contents\n%s", diff)
1884			}
1885		})
1886	}
1887}
1888
1889func TestInit_pluginDirReset(t *testing.T) {
1890	td := testTempDir(t)
1891	defer os.RemoveAll(td)
1892	defer testChdir(t, td)()
1893
1894	// An empty provider source
1895	providerSource, close := newMockProviderSource(t, nil)
1896	defer close()
1897
1898	ui := new(cli.MockUi)
1899	view, _ := testView(t)
1900	c := &InitCommand{
1901		Meta: Meta{
1902			testingOverrides: metaOverridesForProvider(testProvider()),
1903			Ui:               ui,
1904			View:             view,
1905			ProviderSource:   providerSource,
1906		},
1907	}
1908
1909	// make our vendor paths
1910	pluginPath := []string{"a", "b", "c"}
1911	for _, p := range pluginPath {
1912		if err := os.MkdirAll(p, 0755); err != nil {
1913			t.Fatal(err)
1914		}
1915	}
1916
1917	// run once and save the -plugin-dir
1918	args := []string{"-plugin-dir", "a"}
1919	if code := c.Run(args); code != 0 {
1920		t.Fatalf("bad: \n%s", ui.ErrorWriter)
1921	}
1922
1923	pluginDirs, err := c.loadPluginPath()
1924	if err != nil {
1925		t.Fatal(err)
1926	}
1927
1928	if len(pluginDirs) != 1 || pluginDirs[0] != "a" {
1929		t.Fatalf(`expected plugin dir ["a"], got %q`, pluginDirs)
1930	}
1931
1932	ui = new(cli.MockUi)
1933	c = &InitCommand{
1934		Meta: Meta{
1935			testingOverrides: metaOverridesForProvider(testProvider()),
1936			Ui:               ui,
1937			View:             view,
1938			ProviderSource:   providerSource, // still empty
1939		},
1940	}
1941
1942	// make sure we remove the plugin-dir record
1943	args = []string{"-plugin-dir="}
1944	if code := c.Run(args); code != 0 {
1945		t.Fatalf("bad: \n%s", ui.ErrorWriter)
1946	}
1947
1948	pluginDirs, err = c.loadPluginPath()
1949	if err != nil {
1950		t.Fatal(err)
1951	}
1952
1953	if len(pluginDirs) != 0 {
1954		t.Fatalf("expected no plugin dirs got %q", pluginDirs)
1955	}
1956}
1957
1958// Test user-supplied -plugin-dir
1959func TestInit_pluginDirProviders(t *testing.T) {
1960	td := tempDir(t)
1961	testCopyDir(t, testFixturePath("init-get-providers"), td)
1962	defer os.RemoveAll(td)
1963	defer testChdir(t, td)()
1964
1965	// An empty provider source
1966	providerSource, close := newMockProviderSource(t, nil)
1967	defer close()
1968
1969	ui := new(cli.MockUi)
1970	view, _ := testView(t)
1971	m := Meta{
1972		testingOverrides: metaOverridesForProvider(testProvider()),
1973		Ui:               ui,
1974		View:             view,
1975		ProviderSource:   providerSource,
1976	}
1977
1978	c := &InitCommand{
1979		Meta: m,
1980	}
1981
1982	// make our vendor paths
1983	pluginPath := []string{"a", "b", "c"}
1984	for _, p := range pluginPath {
1985		if err := os.MkdirAll(p, 0755); err != nil {
1986			t.Fatal(err)
1987		}
1988	}
1989
1990	// We'll put some providers in our plugin dirs. To do this, we'll pretend
1991	// for a moment that they are provider cache directories just because that
1992	// allows us to lean on our existing test helper functions to do this.
1993	for i, def := range [][]string{
1994		{"exact", "1.2.3"},
1995		{"greater-than", "2.3.4"},
1996		{"between", "2.3.4"},
1997	} {
1998		name, version := def[0], def[1]
1999		dir := providercache.NewDir(pluginPath[i])
2000		installFakeProviderPackagesElsewhere(t, dir, map[string][]string{
2001			name: {version},
2002		})
2003	}
2004
2005	args := []string{
2006		"-plugin-dir", "a",
2007		"-plugin-dir", "b",
2008		"-plugin-dir", "c",
2009	}
2010	if code := c.Run(args); code != 0 {
2011		t.Fatalf("bad: \n%s", ui.ErrorWriter)
2012	}
2013
2014	locks, err := m.lockedDependencies()
2015	if err != nil {
2016		t.Fatalf("failed to get locked dependencies: %s", err)
2017	}
2018	gotProviderLocks := locks.AllProviders()
2019	wantProviderLocks := map[addrs.Provider]*depsfile.ProviderLock{
2020		addrs.NewDefaultProvider("between"): depsfile.NewProviderLock(
2021			addrs.NewDefaultProvider("between"),
2022			getproviders.MustParseVersion("2.3.4"),
2023			getproviders.MustParseVersionConstraints("> 1.0.0, < 3.0.0"),
2024			[]getproviders.Hash{
2025				getproviders.HashScheme1.New("JVqAvZz88A+hS2wHVtTWQkHaxoA/LrUAz0H3jPBWPIA="),
2026			},
2027		),
2028		addrs.NewDefaultProvider("exact"): depsfile.NewProviderLock(
2029			addrs.NewDefaultProvider("exact"),
2030			getproviders.MustParseVersion("1.2.3"),
2031			getproviders.MustParseVersionConstraints("= 1.2.3"),
2032			[]getproviders.Hash{
2033				getproviders.HashScheme1.New("H1TxWF8LyhBb6B4iUdKhLc/S9sC/jdcrCykpkbGcfbg="),
2034			},
2035		),
2036		addrs.NewDefaultProvider("greater-than"): depsfile.NewProviderLock(
2037			addrs.NewDefaultProvider("greater-than"),
2038			getproviders.MustParseVersion("2.3.4"),
2039			getproviders.MustParseVersionConstraints(">= 2.3.3"),
2040			[]getproviders.Hash{
2041				getproviders.HashScheme1.New("SJPpXx/yoFE/W+7eCipjJ+G21xbdnTBD7lWodZ8hWkU="),
2042			},
2043		),
2044	}
2045	if diff := cmp.Diff(gotProviderLocks, wantProviderLocks, depsfile.ProviderLockComparer); diff != "" {
2046		t.Errorf("wrong version selections after upgrade\n%s", diff)
2047	}
2048
2049	// -plugin-dir overrides the normal provider source, so it should not have
2050	// seen any calls at all.
2051	if calls := providerSource.CallLog(); len(calls) > 0 {
2052		t.Errorf("unexpected provider source calls (want none)\n%s", spew.Sdump(calls))
2053	}
2054}
2055
2056// Test user-supplied -plugin-dir doesn't allow auto-install
2057func TestInit_pluginDirProvidersDoesNotGet(t *testing.T) {
2058	td := tempDir(t)
2059	testCopyDir(t, testFixturePath("init-get-providers"), td)
2060	defer os.RemoveAll(td)
2061	defer testChdir(t, td)()
2062
2063	// Our provider source has a suitable package for "between" available,
2064	// but we should ignore it because -plugin-dir is set and thus this
2065	// source is temporarily overridden during install.
2066	providerSource, close := newMockProviderSource(t, map[string][]string{
2067		"between": {"2.3.4"},
2068	})
2069	defer close()
2070
2071	ui := cli.NewMockUi()
2072	view, _ := testView(t)
2073	m := Meta{
2074		testingOverrides: metaOverridesForProvider(testProvider()),
2075		Ui:               ui,
2076		View:             view,
2077		ProviderSource:   providerSource,
2078	}
2079
2080	c := &InitCommand{
2081		Meta: m,
2082	}
2083
2084	// make our vendor paths
2085	pluginPath := []string{"a", "b"}
2086	for _, p := range pluginPath {
2087		if err := os.MkdirAll(p, 0755); err != nil {
2088			t.Fatal(err)
2089		}
2090	}
2091
2092	// We'll put some providers in our plugin dirs. To do this, we'll pretend
2093	// for a moment that they are provider cache directories just because that
2094	// allows us to lean on our existing test helper functions to do this.
2095	for i, def := range [][]string{
2096		{"exact", "1.2.3"},
2097		{"greater-than", "2.3.4"},
2098	} {
2099		name, version := def[0], def[1]
2100		dir := providercache.NewDir(pluginPath[i])
2101		installFakeProviderPackagesElsewhere(t, dir, map[string][]string{
2102			name: {version},
2103		})
2104	}
2105
2106	args := []string{
2107		"-plugin-dir", "a",
2108		"-plugin-dir", "b",
2109	}
2110	if code := c.Run(args); code == 0 {
2111		// should have been an error
2112		t.Fatalf("succeeded; want error\nstdout:\n%s\nstderr\n%s", ui.OutputWriter, ui.ErrorWriter)
2113	}
2114
2115	// The error output should mention the "between" provider but should not
2116	// mention either the "exact" or "greater-than" provider, because the
2117	// latter two are available via the -plugin-dir directories.
2118	errStr := ui.ErrorWriter.String()
2119	if subStr := "hashicorp/between"; !strings.Contains(errStr, subStr) {
2120		t.Errorf("error output should mention the 'between' provider\nwant substr: %s\ngot:\n%s", subStr, errStr)
2121	}
2122	if subStr := "hashicorp/exact"; strings.Contains(errStr, subStr) {
2123		t.Errorf("error output should not mention the 'exact' provider\ndo not want substr: %s\ngot:\n%s", subStr, errStr)
2124	}
2125	if subStr := "hashicorp/greater-than"; strings.Contains(errStr, subStr) {
2126		t.Errorf("error output should not mention the 'greater-than' provider\ndo not want substr: %s\ngot:\n%s", subStr, errStr)
2127	}
2128
2129	if calls := providerSource.CallLog(); len(calls) > 0 {
2130		t.Errorf("unexpected provider source calls (want none)\n%s", spew.Sdump(calls))
2131	}
2132}
2133
2134// Verify that plugin-dir doesn't prevent discovery of internal providers
2135func TestInit_pluginDirWithBuiltIn(t *testing.T) {
2136	td := tempDir(t)
2137	testCopyDir(t, testFixturePath("init-internal"), td)
2138	defer os.RemoveAll(td)
2139	defer testChdir(t, td)()
2140
2141	// An empty provider source
2142	providerSource, close := newMockProviderSource(t, nil)
2143	defer close()
2144
2145	ui := cli.NewMockUi()
2146	view, _ := testView(t)
2147	m := Meta{
2148		testingOverrides: metaOverridesForProvider(testProvider()),
2149		Ui:               ui,
2150		View:             view,
2151		ProviderSource:   providerSource,
2152	}
2153
2154	c := &InitCommand{
2155		Meta: m,
2156	}
2157
2158	args := []string{"-plugin-dir", "./"}
2159	if code := c.Run(args); code != 0 {
2160		t.Fatalf("error: %s", ui.ErrorWriter)
2161	}
2162
2163	outputStr := ui.OutputWriter.String()
2164	if subStr := "terraform.io/builtin/terraform is built in to Terraform"; !strings.Contains(outputStr, subStr) {
2165		t.Errorf("output should mention the terraform provider\nwant substr: %s\ngot:\n%s", subStr, outputStr)
2166	}
2167}
2168
2169func TestInit_invalidBuiltInProviders(t *testing.T) {
2170	// This test fixture includes two invalid provider dependencies:
2171	// - an implied dependency on terraform.io/builtin/terraform with an
2172	//   explicit version number, which is not allowed because it's builtin.
2173	// - an explicit dependency on terraform.io/builtin/nonexist, which does
2174	//   not exist at all.
2175	td := tempDir(t)
2176	testCopyDir(t, testFixturePath("init-internal-invalid"), td)
2177	defer os.RemoveAll(td)
2178	defer testChdir(t, td)()
2179
2180	// An empty provider source
2181	providerSource, close := newMockProviderSource(t, nil)
2182	defer close()
2183
2184	ui := cli.NewMockUi()
2185	view, _ := testView(t)
2186	m := Meta{
2187		testingOverrides: metaOverridesForProvider(testProvider()),
2188		Ui:               ui,
2189		View:             view,
2190		ProviderSource:   providerSource,
2191	}
2192
2193	c := &InitCommand{
2194		Meta: m,
2195	}
2196
2197	if code := c.Run(nil); code == 0 {
2198		t.Fatalf("succeeded, but was expecting error\nstdout:\n%s\nstderr:\n%s", ui.OutputWriter, ui.ErrorWriter)
2199	}
2200
2201	errStr := ui.ErrorWriter.String()
2202	if subStr := "Cannot use terraform.io/builtin/terraform: built-in"; !strings.Contains(errStr, subStr) {
2203		t.Errorf("error output should mention the terraform provider\nwant substr: %s\ngot:\n%s", subStr, errStr)
2204	}
2205	if subStr := "Cannot use terraform.io/builtin/nonexist: this Terraform release"; !strings.Contains(errStr, subStr) {
2206		t.Errorf("error output should mention the 'nonexist' provider\nwant substr: %s\ngot:\n%s", subStr, errStr)
2207	}
2208}
2209
2210// newMockProviderSource is a helper to succinctly construct a mock provider
2211// source that contains a set of packages matching the given provider versions
2212// that are available for installation (from temporary local files).
2213//
2214// The caller must call the returned close callback once the source is no
2215// longer needed, at which point it will clean up all of the temporary files
2216// and the packages in the source will no longer be available for installation.
2217//
2218// Provider addresses must be valid source strings, and passing only the
2219// provider name will be interpreted as a "default" provider under
2220// registry.terraform.io/hashicorp. If you need more control over the
2221// provider addresses, pass a full provider source string.
2222//
2223// This function also registers providers as belonging to the current platform,
2224// to ensure that they will be available to a provider installer operating in
2225// its default configuration.
2226//
2227// In case of any errors while constructing the source, this function will
2228// abort the current test using the given testing.T. Therefore a caller can
2229// assume that if this function returns then the result is valid and ready
2230// to use.
2231func newMockProviderSource(t *testing.T, availableProviderVersions map[string][]string) (source *getproviders.MockSource, close func()) {
2232	t.Helper()
2233	var packages []getproviders.PackageMeta
2234	var closes []func()
2235	close = func() {
2236		for _, f := range closes {
2237			f()
2238		}
2239	}
2240	for source, versions := range availableProviderVersions {
2241		addr := addrs.MustParseProviderSourceString(source)
2242		for _, versionStr := range versions {
2243			version, err := getproviders.ParseVersion(versionStr)
2244			if err != nil {
2245				close()
2246				t.Fatalf("failed to parse %q as a version number for %q: %s", versionStr, addr.ForDisplay(), err)
2247			}
2248			meta, close, err := getproviders.FakeInstallablePackageMeta(addr, version, getproviders.VersionList{getproviders.MustParseVersion("5.0")}, getproviders.CurrentPlatform, "")
2249			if err != nil {
2250				close()
2251				t.Fatalf("failed to prepare fake package for %s %s: %s", addr.ForDisplay(), versionStr, err)
2252			}
2253			closes = append(closes, close)
2254			packages = append(packages, meta)
2255		}
2256	}
2257
2258	return getproviders.NewMockSource(packages, nil), close
2259}
2260
2261// installFakeProviderPackages installs a fake package for the given provider
2262// names (interpreted as a "default" provider address) and versions into the
2263// local plugin cache for the given "meta".
2264//
2265// Any test using this must be using testChdir or some similar mechanism to
2266// make sure that it isn't writing directly into a test fixture or source
2267// directory within the codebase.
2268//
2269// If a requested package cannot be installed for some reason, this function
2270// will abort the test using the given testing.T. Therefore if this function
2271// returns the caller can assume that the requested providers have been
2272// installed.
2273func installFakeProviderPackages(t *testing.T, meta *Meta, providerVersions map[string][]string) {
2274	t.Helper()
2275
2276	cacheDir := meta.providerLocalCacheDir()
2277	installFakeProviderPackagesElsewhere(t, cacheDir, providerVersions)
2278}
2279
2280// installFakeProviderPackagesElsewhere is a variant of installFakeProviderPackages
2281// that will install packages into the given provider cache directory, rather
2282// than forcing the use of the local cache of the current "Meta".
2283func installFakeProviderPackagesElsewhere(t *testing.T, cacheDir *providercache.Dir, providerVersions map[string][]string) {
2284	t.Helper()
2285
2286	// It can be hard to spot the mistake of forgetting to run testChdir before
2287	// modifying the working directory, so we'll use a simple heuristic here
2288	// to try to detect that mistake and make a noisy error about it instead.
2289	wd, err := os.Getwd()
2290	if err == nil {
2291		wd = filepath.Clean(wd)
2292		// If the directory we're in is named "command" or if we're under a
2293		// directory named "testdata" then we'll assume a mistake and generate
2294		// an error. This will cause the test to fail but won't block it from
2295		// running.
2296		if filepath.Base(wd) == "command" || filepath.Base(wd) == "testdata" || strings.Contains(filepath.ToSlash(wd), "/testdata/") {
2297			t.Errorf("installFakeProviderPackage may be used only by tests that switch to a temporary working directory, e.g. using testChdir")
2298		}
2299	}
2300
2301	for name, versions := range providerVersions {
2302		addr := addrs.NewDefaultProvider(name)
2303		for _, versionStr := range versions {
2304			version, err := getproviders.ParseVersion(versionStr)
2305			if err != nil {
2306				t.Fatalf("failed to parse %q as a version number for %q: %s", versionStr, name, err)
2307			}
2308			meta, close, err := getproviders.FakeInstallablePackageMeta(addr, version, getproviders.VersionList{getproviders.MustParseVersion("5.0")}, getproviders.CurrentPlatform, "")
2309			// We're going to install all these fake packages before we return,
2310			// so we don't need to preserve them afterwards.
2311			defer close()
2312			if err != nil {
2313				t.Fatalf("failed to prepare fake package for %s %s: %s", name, versionStr, err)
2314			}
2315			_, err = cacheDir.InstallPackage(context.Background(), meta, nil)
2316			if err != nil {
2317				t.Fatalf("failed to install fake package for %s %s: %s", name, versionStr, err)
2318			}
2319		}
2320	}
2321}
2322
2323// expectedPackageInstallPath is a companion to installFakeProviderPackages
2324// that returns the path where the provider with the given name and version
2325// would be installed and, relatedly, where the installer will expect to
2326// find an already-installed version.
2327//
2328// Just as with installFakeProviderPackages, this function is a shortcut helper
2329// for "default-namespaced" providers as we commonly use in tests. If you need
2330// more control over the provider addresses, use functions of the underlying
2331// getproviders and providercache packages instead.
2332//
2333// The result always uses forward slashes, even on Windows, for consistency
2334// with how the getproviders and providercache packages build paths.
2335func expectedPackageInstallPath(name, version string, exe bool) string {
2336	platform := getproviders.CurrentPlatform
2337	baseDir := ".terraform/providers"
2338	if exe {
2339		p := fmt.Sprintf("registry.terraform.io/hashicorp/%s/%s/%s/terraform-provider-%s_%s", name, version, platform, name, version)
2340		if platform.OS == "windows" {
2341			p += ".exe"
2342		}
2343		return filepath.ToSlash(filepath.Join(baseDir, p))
2344	}
2345	return filepath.ToSlash(filepath.Join(
2346		baseDir, fmt.Sprintf("registry.terraform.io/hashicorp/%s/%s/%s", name, version, platform),
2347	))
2348}
2349