github.com/hugorut/terraform@v1.1.3/src/command/command_test.go (about)

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