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