github.com/ben-turner/terraform@v0.11.8-0.20180503104400-0cc9e050ecd4/command/command_test.go (about)

     1  package command
     2  
     3  import (
     4  	"bytes"
     5  	"crypto/md5"
     6  	"encoding/base64"
     7  	"encoding/json"
     8  	"flag"
     9  	"fmt"
    10  	"io"
    11  	"io/ioutil"
    12  	"log"
    13  	"net/http"
    14  	"net/http/httptest"
    15  	"os"
    16  	"os/exec"
    17  	"path/filepath"
    18  	"strings"
    19  	"syscall"
    20  	"testing"
    21  
    22  	"github.com/hashicorp/terraform/config/module"
    23  	"github.com/hashicorp/terraform/helper/logging"
    24  	"github.com/hashicorp/terraform/terraform"
    25  )
    26  
    27  // This is the directory where our test fixtures are.
    28  var fixtureDir = "./test-fixtures"
    29  
    30  // a top level temp directory which will be cleaned after all tests
    31  var testingDir string
    32  
    33  func init() {
    34  	test = true
    35  
    36  	// Expand the fixture dir on init because we change the working
    37  	// directory in some tests.
    38  	var err error
    39  	fixtureDir, err = filepath.Abs(fixtureDir)
    40  	if err != nil {
    41  		panic(err)
    42  	}
    43  
    44  	testingDir, err = ioutil.TempDir(testingDir, "tf")
    45  	if err != nil {
    46  		panic(err)
    47  	}
    48  }
    49  
    50  func TestMain(m *testing.M) {
    51  	defer os.RemoveAll(testingDir)
    52  
    53  	flag.Parse()
    54  	if testing.Verbose() {
    55  		// if we're verbose, use the logging requested by TF_LOG
    56  		logging.SetOutput()
    57  	} else {
    58  		// otherwise silence all logs
    59  		log.SetOutput(ioutil.Discard)
    60  	}
    61  
    62  	os.Exit(m.Run())
    63  }
    64  
    65  func tempDir(t *testing.T) string {
    66  	t.Helper()
    67  
    68  	dir, err := ioutil.TempDir(testingDir, "tf")
    69  	if err != nil {
    70  		t.Fatalf("err: %s", err)
    71  	}
    72  	if err := os.RemoveAll(dir); err != nil {
    73  		t.Fatalf("err: %s", err)
    74  	}
    75  
    76  	return dir
    77  }
    78  
    79  func testFixturePath(name string) string {
    80  	return filepath.Join(fixtureDir, name)
    81  }
    82  
    83  func metaOverridesForProvider(p terraform.ResourceProvider) *testingOverrides {
    84  	return &testingOverrides{
    85  		ProviderResolver: terraform.ResourceProviderResolverFixed(
    86  			map[string]terraform.ResourceProviderFactory{
    87  				"test": func() (terraform.ResourceProvider, error) {
    88  					return p, nil
    89  				},
    90  			},
    91  		),
    92  	}
    93  }
    94  
    95  func metaOverridesForProviderAndProvisioner(p terraform.ResourceProvider, pr terraform.ResourceProvisioner) *testingOverrides {
    96  	return &testingOverrides{
    97  		ProviderResolver: terraform.ResourceProviderResolverFixed(
    98  			map[string]terraform.ResourceProviderFactory{
    99  				"test": func() (terraform.ResourceProvider, error) {
   100  					return p, nil
   101  				},
   102  			},
   103  		),
   104  		Provisioners: map[string]terraform.ResourceProvisionerFactory{
   105  			"shell": func() (terraform.ResourceProvisioner, error) {
   106  				return pr, nil
   107  			},
   108  		},
   109  	}
   110  }
   111  
   112  func testModule(t *testing.T, name string) *module.Tree {
   113  	t.Helper()
   114  
   115  	mod, err := module.NewTreeModule("", filepath.Join(fixtureDir, name))
   116  	if err != nil {
   117  		t.Fatalf("err: %s", err)
   118  	}
   119  
   120  	s := module.NewStorage(tempDir(t), nil, nil)
   121  	s.Mode = module.GetModeGet
   122  	if err := mod.Load(s); err != nil {
   123  		t.Fatalf("err: %s", err)
   124  	}
   125  
   126  	return mod
   127  }
   128  
   129  // testPlan returns a non-nil noop plan.
   130  func testPlan(t *testing.T) *terraform.Plan {
   131  	t.Helper()
   132  
   133  	state := terraform.NewState()
   134  	state.RootModule().Outputs["foo"] = &terraform.OutputState{
   135  		Type:  "string",
   136  		Value: "foo",
   137  	}
   138  
   139  	return &terraform.Plan{
   140  		Module: testModule(t, "apply"),
   141  		State:  state,
   142  	}
   143  }
   144  
   145  func testPlanFile(t *testing.T, plan *terraform.Plan) string {
   146  	t.Helper()
   147  
   148  	path := testTempFile(t)
   149  
   150  	f, err := os.Create(path)
   151  	if err != nil {
   152  		t.Fatalf("err: %s", err)
   153  	}
   154  	defer f.Close()
   155  
   156  	if err := terraform.WritePlan(plan, f); err != nil {
   157  		t.Fatalf("err: %s", err)
   158  	}
   159  
   160  	return path
   161  }
   162  
   163  func testReadPlan(t *testing.T, path string) *terraform.Plan {
   164  	t.Helper()
   165  
   166  	f, err := os.Open(path)
   167  	if err != nil {
   168  		t.Fatalf("err: %s", err)
   169  	}
   170  	defer f.Close()
   171  
   172  	p, err := terraform.ReadPlan(f)
   173  	if err != nil {
   174  		t.Fatalf("err: %s", err)
   175  	}
   176  
   177  	return p
   178  }
   179  
   180  // testState returns a test State structure that we use for a lot of tests.
   181  func testState() *terraform.State {
   182  	state := &terraform.State{
   183  		Modules: []*terraform.ModuleState{
   184  			&terraform.ModuleState{
   185  				Path: []string{"root"},
   186  				Resources: map[string]*terraform.ResourceState{
   187  					"test_instance.foo": &terraform.ResourceState{
   188  						Type: "test_instance",
   189  						Primary: &terraform.InstanceState{
   190  							ID: "bar",
   191  						},
   192  					},
   193  				},
   194  				Outputs: map[string]*terraform.OutputState{},
   195  			},
   196  		},
   197  	}
   198  	state.Init()
   199  	return state
   200  }
   201  
   202  func testStateFile(t *testing.T, s *terraform.State) string {
   203  	t.Helper()
   204  
   205  	path := testTempFile(t)
   206  
   207  	f, err := os.Create(path)
   208  	if err != nil {
   209  		t.Fatalf("err: %s", err)
   210  	}
   211  	defer f.Close()
   212  
   213  	if err := terraform.WriteState(s, f); err != nil {
   214  		t.Fatalf("err: %s", err)
   215  	}
   216  
   217  	return path
   218  }
   219  
   220  // testStateFileDefault writes the state out to the default statefile
   221  // in the cwd. Use `testCwd` to change into a temp cwd.
   222  func testStateFileDefault(t *testing.T, s *terraform.State) string {
   223  	t.Helper()
   224  
   225  	f, err := os.Create(DefaultStateFilename)
   226  	if err != nil {
   227  		t.Fatalf("err: %s", err)
   228  	}
   229  	defer f.Close()
   230  
   231  	if err := terraform.WriteState(s, f); err != nil {
   232  		t.Fatalf("err: %s", err)
   233  	}
   234  
   235  	return DefaultStateFilename
   236  }
   237  
   238  // testStateFileRemote writes the state out to the remote statefile
   239  // in the cwd. Use `testCwd` to change into a temp cwd.
   240  func testStateFileRemote(t *testing.T, s *terraform.State) string {
   241  	t.Helper()
   242  
   243  	path := filepath.Join(DefaultDataDir, DefaultStateFilename)
   244  	if err := os.MkdirAll(filepath.Dir(path), 0755); err != nil {
   245  		t.Fatalf("err: %s", err)
   246  	}
   247  
   248  	f, err := os.Create(path)
   249  	if err != nil {
   250  		t.Fatalf("err: %s", err)
   251  	}
   252  	defer f.Close()
   253  
   254  	if err := terraform.WriteState(s, f); err != nil {
   255  		t.Fatalf("err: %s", err)
   256  	}
   257  
   258  	return path
   259  }
   260  
   261  // testStateRead reads the state from a file
   262  func testStateRead(t *testing.T, path string) *terraform.State {
   263  	t.Helper()
   264  
   265  	f, err := os.Open(path)
   266  	if err != nil {
   267  		t.Fatalf("err: %s", err)
   268  	}
   269  	defer f.Close()
   270  
   271  	newState, err := terraform.ReadState(f)
   272  	if err != nil {
   273  		t.Fatalf("err: %s", err)
   274  	}
   275  
   276  	return newState
   277  }
   278  
   279  // testStateOutput tests that the state at the given path contains
   280  // the expected state string.
   281  func testStateOutput(t *testing.T, path string, expected string) {
   282  	t.Helper()
   283  
   284  	newState := testStateRead(t, path)
   285  	actual := strings.TrimSpace(newState.String())
   286  	expected = strings.TrimSpace(expected)
   287  	if actual != expected {
   288  		t.Fatalf("expected:\n%s\nactual:\n%s", expected, actual)
   289  	}
   290  }
   291  
   292  func testProvider() *terraform.MockResourceProvider {
   293  	p := new(terraform.MockResourceProvider)
   294  	p.DiffReturn = &terraform.InstanceDiff{}
   295  	p.RefreshFn = func(
   296  		info *terraform.InstanceInfo,
   297  		s *terraform.InstanceState) (*terraform.InstanceState, error) {
   298  		return s, nil
   299  	}
   300  	p.ResourcesReturn = []terraform.ResourceType{
   301  		terraform.ResourceType{
   302  			Name: "test_instance",
   303  		},
   304  	}
   305  
   306  	return p
   307  }
   308  
   309  func testTempFile(t *testing.T) string {
   310  	t.Helper()
   311  
   312  	return filepath.Join(testTempDir(t), "state.tfstate")
   313  }
   314  
   315  func testTempDir(t *testing.T) string {
   316  	t.Helper()
   317  
   318  	d, err := ioutil.TempDir(testingDir, "tf")
   319  	if err != nil {
   320  		t.Fatalf("err: %s", err)
   321  	}
   322  
   323  	return d
   324  }
   325  
   326  // testRename renames the path to new and returns a function to defer to
   327  // revert the rename.
   328  func testRename(t *testing.T, base, path, new string) func() {
   329  	t.Helper()
   330  
   331  	if base != "" {
   332  		path = filepath.Join(base, path)
   333  		new = filepath.Join(base, new)
   334  	}
   335  
   336  	if err := os.Rename(path, new); err != nil {
   337  		t.Fatalf("err: %s", err)
   338  	}
   339  
   340  	return func() {
   341  		// Just re-rename and ignore the return value
   342  		testRename(t, "", new, path)
   343  	}
   344  }
   345  
   346  // testChdir changes the directory and returns a function to defer to
   347  // revert the old cwd.
   348  func testChdir(t *testing.T, new string) func() {
   349  	t.Helper()
   350  
   351  	old, err := os.Getwd()
   352  	if err != nil {
   353  		t.Fatalf("err: %s", err)
   354  	}
   355  
   356  	if err := os.Chdir(new); err != nil {
   357  		t.Fatalf("err: %v", err)
   358  	}
   359  
   360  	return func() {
   361  		// Re-run the function ignoring the defer result
   362  		testChdir(t, old)
   363  	}
   364  }
   365  
   366  // testCwd is used to change the current working directory
   367  // into a test directory that should be remoted after
   368  func testCwd(t *testing.T) (string, string) {
   369  	t.Helper()
   370  
   371  	tmp, err := ioutil.TempDir(testingDir, "tf")
   372  	if err != nil {
   373  		t.Fatalf("err: %v", err)
   374  	}
   375  
   376  	cwd, err := os.Getwd()
   377  	if err != nil {
   378  		t.Fatalf("err: %v", err)
   379  	}
   380  
   381  	if err := os.Chdir(tmp); err != nil {
   382  		t.Fatalf("err: %v", err)
   383  	}
   384  
   385  	return tmp, cwd
   386  }
   387  
   388  // testFixCwd is used to as a defer to testDir
   389  func testFixCwd(t *testing.T, tmp, cwd string) {
   390  	t.Helper()
   391  
   392  	if err := os.Chdir(cwd); err != nil {
   393  		t.Fatalf("err: %v", err)
   394  	}
   395  
   396  	if err := os.RemoveAll(tmp); err != nil {
   397  		t.Fatalf("err: %v", err)
   398  	}
   399  }
   400  
   401  // testStdinPipe changes os.Stdin to be a pipe that sends the data from
   402  // the reader before closing the pipe.
   403  //
   404  // The returned function should be deferred to properly clean up and restore
   405  // the original stdin.
   406  func testStdinPipe(t *testing.T, src io.Reader) func() {
   407  	t.Helper()
   408  
   409  	r, w, err := os.Pipe()
   410  	if err != nil {
   411  		t.Fatalf("err: %s", err)
   412  	}
   413  
   414  	// Modify stdin to point to our new pipe
   415  	old := os.Stdin
   416  	os.Stdin = r
   417  
   418  	// Copy the data from the reader to the pipe
   419  	go func() {
   420  		defer w.Close()
   421  		io.Copy(w, src)
   422  	}()
   423  
   424  	return func() {
   425  		// Close our read end
   426  		r.Close()
   427  
   428  		// Reset stdin
   429  		os.Stdin = old
   430  	}
   431  }
   432  
   433  // Modify os.Stdout to write to the given buffer. Note that this is generally
   434  // not useful since the commands are configured to write to a cli.Ui, not
   435  // Stdout directly. Commands like `console` though use the raw stdout.
   436  func testStdoutCapture(t *testing.T, dst io.Writer) func() {
   437  	t.Helper()
   438  
   439  	r, w, err := os.Pipe()
   440  	if err != nil {
   441  		t.Fatalf("err: %s", err)
   442  	}
   443  
   444  	// Modify stdout
   445  	old := os.Stdout
   446  	os.Stdout = w
   447  
   448  	// Copy
   449  	doneCh := make(chan struct{})
   450  	go func() {
   451  		defer close(doneCh)
   452  		defer r.Close()
   453  		io.Copy(dst, r)
   454  	}()
   455  
   456  	return func() {
   457  		// Close the writer end of the pipe
   458  		w.Sync()
   459  		w.Close()
   460  
   461  		// Reset stdout
   462  		os.Stdout = old
   463  
   464  		// Wait for the data copy to complete to avoid a race reading data
   465  		<-doneCh
   466  	}
   467  }
   468  
   469  // testInteractiveInput configures tests so that the answers given are sent
   470  // in order to interactive prompts. The returned function must be called
   471  // in a defer to clean up.
   472  func testInteractiveInput(t *testing.T, answers []string) func() {
   473  	t.Helper()
   474  
   475  	// Disable test mode so input is called
   476  	test = false
   477  
   478  	// Setup reader/writers
   479  	testInputResponse = answers
   480  	defaultInputReader = bytes.NewBufferString("")
   481  	defaultInputWriter = new(bytes.Buffer)
   482  
   483  	// Return the cleanup
   484  	return func() {
   485  		test = true
   486  		testInputResponse = nil
   487  	}
   488  }
   489  
   490  // testInputMap configures tests so that the given answers are returned
   491  // for calls to Input when the right question is asked. The key is the
   492  // question "Id" that is used.
   493  func testInputMap(t *testing.T, answers map[string]string) func() {
   494  	t.Helper()
   495  
   496  	// Disable test mode so input is called
   497  	test = false
   498  
   499  	// Setup reader/writers
   500  	defaultInputReader = bytes.NewBufferString("")
   501  	defaultInputWriter = new(bytes.Buffer)
   502  
   503  	// Setup answers
   504  	testInputResponse = nil
   505  	testInputResponseMap = answers
   506  
   507  	// Return the cleanup
   508  	return func() {
   509  		test = true
   510  		testInputResponseMap = nil
   511  	}
   512  }
   513  
   514  // testBackendState is used to make a test HTTP server to test a configured
   515  // backend. This returns the complete state that can be saved. Use
   516  // `testStateFileRemote` to write the returned state.
   517  func testBackendState(t *testing.T, s *terraform.State, c int) (*terraform.State, *httptest.Server) {
   518  	t.Helper()
   519  
   520  	var b64md5 string
   521  	buf := bytes.NewBuffer(nil)
   522  
   523  	cb := func(resp http.ResponseWriter, req *http.Request) {
   524  		if req.Method == "PUT" {
   525  			resp.WriteHeader(c)
   526  			return
   527  		}
   528  		if s == nil {
   529  			resp.WriteHeader(404)
   530  			return
   531  		}
   532  
   533  		resp.Header().Set("Content-MD5", b64md5)
   534  		resp.Write(buf.Bytes())
   535  	}
   536  
   537  	// If a state was given, make sure we calculate the proper b64md5
   538  	if s != nil {
   539  		enc := json.NewEncoder(buf)
   540  		if err := enc.Encode(s); err != nil {
   541  			t.Fatalf("err: %v", err)
   542  		}
   543  		md5 := md5.Sum(buf.Bytes())
   544  		b64md5 = base64.StdEncoding.EncodeToString(md5[:16])
   545  	}
   546  
   547  	srv := httptest.NewServer(http.HandlerFunc(cb))
   548  
   549  	state := terraform.NewState()
   550  	state.Backend = &terraform.BackendState{
   551  		Type:   "http",
   552  		Config: map[string]interface{}{"address": srv.URL},
   553  		Hash:   2529831861221416334,
   554  	}
   555  
   556  	return state, srv
   557  }
   558  
   559  // testRemoteState is used to make a test HTTP server to return a given
   560  // state file that can be used for testing legacy remote state.
   561  func testRemoteState(t *testing.T, s *terraform.State, c int) (*terraform.RemoteState, *httptest.Server) {
   562  	t.Helper()
   563  
   564  	var b64md5 string
   565  	buf := bytes.NewBuffer(nil)
   566  
   567  	cb := func(resp http.ResponseWriter, req *http.Request) {
   568  		if req.Method == "PUT" {
   569  			resp.WriteHeader(c)
   570  			return
   571  		}
   572  		if s == nil {
   573  			resp.WriteHeader(404)
   574  			return
   575  		}
   576  
   577  		resp.Header().Set("Content-MD5", b64md5)
   578  		resp.Write(buf.Bytes())
   579  	}
   580  
   581  	srv := httptest.NewServer(http.HandlerFunc(cb))
   582  	remote := &terraform.RemoteState{
   583  		Type:   "http",
   584  		Config: map[string]string{"address": srv.URL},
   585  	}
   586  
   587  	if s != nil {
   588  		// Set the remote data
   589  		s.Remote = remote
   590  
   591  		enc := json.NewEncoder(buf)
   592  		if err := enc.Encode(s); err != nil {
   593  			t.Fatalf("err: %v", err)
   594  		}
   595  		md5 := md5.Sum(buf.Bytes())
   596  		b64md5 = base64.StdEncoding.EncodeToString(md5[:16])
   597  	}
   598  
   599  	return remote, srv
   600  }
   601  
   602  // testlockState calls a separate process to the lock the state file at path.
   603  // deferFunc should be called in the caller to properly unlock the file.
   604  // Since many tests change the working durectory, the sourcedir argument must be
   605  // supplied to locate the statelocker.go source.
   606  func testLockState(sourceDir, path string) (func(), error) {
   607  	// build and run the binary ourselves so we can quickly terminate it for cleanup
   608  	buildDir, err := ioutil.TempDir(testingDir, "locker")
   609  	if err != nil {
   610  		return nil, err
   611  	}
   612  	cleanFunc := func() {
   613  		os.RemoveAll(buildDir)
   614  	}
   615  
   616  	source := filepath.Join(sourceDir, "statelocker.go")
   617  	lockBin := filepath.Join(buildDir, "statelocker")
   618  
   619  	out, err := exec.Command("go", "build", "-o", lockBin, source).CombinedOutput()
   620  	if err != nil {
   621  		cleanFunc()
   622  		return nil, fmt.Errorf("%s %s", err, out)
   623  	}
   624  
   625  	locker := exec.Command(lockBin, path)
   626  	pr, pw, err := os.Pipe()
   627  	if err != nil {
   628  		cleanFunc()
   629  		return nil, err
   630  	}
   631  	defer pr.Close()
   632  	defer pw.Close()
   633  	locker.Stderr = pw
   634  	locker.Stdout = pw
   635  
   636  	if err := locker.Start(); err != nil {
   637  		return nil, err
   638  	}
   639  	deferFunc := func() {
   640  		cleanFunc()
   641  		locker.Process.Signal(syscall.SIGTERM)
   642  		locker.Wait()
   643  	}
   644  
   645  	// wait for the process to lock
   646  	buf := make([]byte, 1024)
   647  	n, err := pr.Read(buf)
   648  	if err != nil {
   649  		return deferFunc, fmt.Errorf("read from statelocker returned: %s", err)
   650  	}
   651  
   652  	output := string(buf[:n])
   653  	if !strings.HasPrefix(output, "LOCKID") {
   654  		return deferFunc, fmt.Errorf("statelocker wrote: %s", string(buf[:n]))
   655  	}
   656  	return deferFunc, nil
   657  }