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