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