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