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