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