github.com/jrasell/terraform@v0.6.17-0.20160523115548-2652f5232949/command/apply_test.go (about)

     1  package command
     2  
     3  import (
     4  	"bytes"
     5  	"fmt"
     6  	"io/ioutil"
     7  	"net"
     8  	"net/http"
     9  	"net/url"
    10  	"os"
    11  	"path/filepath"
    12  	"reflect"
    13  	"strings"
    14  	"sync"
    15  	"sync/atomic"
    16  	"testing"
    17  	"time"
    18  
    19  	"github.com/hashicorp/terraform/terraform"
    20  	"github.com/mitchellh/cli"
    21  )
    22  
    23  func TestApply(t *testing.T) {
    24  	statePath := testTempFile(t)
    25  
    26  	p := testProvider()
    27  	ui := new(cli.MockUi)
    28  	c := &ApplyCommand{
    29  		Meta: Meta{
    30  			ContextOpts: testCtxConfig(p),
    31  			Ui:          ui,
    32  		},
    33  	}
    34  
    35  	args := []string{
    36  		"-state", statePath,
    37  		testFixturePath("apply"),
    38  	}
    39  	if code := c.Run(args); code != 0 {
    40  		t.Fatalf("bad: %d\n\n%s", code, ui.ErrorWriter.String())
    41  	}
    42  
    43  	if _, err := os.Stat(statePath); err != nil {
    44  		t.Fatalf("err: %s", err)
    45  	}
    46  
    47  	f, err := os.Open(statePath)
    48  	if err != nil {
    49  		t.Fatalf("err: %s", err)
    50  	}
    51  	defer f.Close()
    52  
    53  	state, err := terraform.ReadState(f)
    54  	if err != nil {
    55  		t.Fatalf("err: %s", err)
    56  	}
    57  	if state == nil {
    58  		t.Fatal("state should not be nil")
    59  	}
    60  }
    61  
    62  func TestApply_parallelism(t *testing.T) {
    63  	provider := testProvider()
    64  	statePath := testTempFile(t)
    65  
    66  	// This blocks all the appy functions. We close it when we exit so
    67  	// they end quickly after this test finishes.
    68  	block := make(chan struct{})
    69  	defer close(block)
    70  
    71  	var runCount uint64
    72  	provider.ApplyFn = func(
    73  		i *terraform.InstanceInfo,
    74  		s *terraform.InstanceState,
    75  		d *terraform.InstanceDiff) (*terraform.InstanceState, error) {
    76  		// Increment so we're counting parallelism
    77  		atomic.AddUint64(&runCount, 1)
    78  
    79  		// Block until we're done
    80  		<-block
    81  
    82  		return nil, nil
    83  	}
    84  
    85  	ui := new(cli.MockUi)
    86  	c := &ApplyCommand{
    87  		Meta: Meta{
    88  			ContextOpts: testCtxConfig(provider),
    89  			Ui:          ui,
    90  		},
    91  	}
    92  
    93  	par := uint64(5)
    94  	args := []string{
    95  		"-state", statePath,
    96  		fmt.Sprintf("-parallelism=%d", par),
    97  		testFixturePath("parallelism"),
    98  	}
    99  
   100  	// Run in a goroutine. We still try to catch any errors and
   101  	// get them on the error channel.
   102  	errCh := make(chan string, 1)
   103  	go func() {
   104  		if code := c.Run(args); code != 0 {
   105  			errCh <- ui.OutputWriter.String()
   106  		}
   107  	}()
   108  	select {
   109  	case <-time.After(1000 * time.Millisecond):
   110  	case err := <-errCh:
   111  		t.Fatalf("err: %s", err)
   112  	}
   113  
   114  	// The total in flight should equal the parallelism
   115  	if rc := atomic.LoadUint64(&runCount); rc != par {
   116  		t.Fatalf("Expected parallelism: %d, got: %d", par, rc)
   117  	}
   118  }
   119  
   120  func TestApply_configInvalid(t *testing.T) {
   121  	p := testProvider()
   122  	ui := new(cli.MockUi)
   123  	c := &ApplyCommand{
   124  		Meta: Meta{
   125  			ContextOpts: testCtxConfig(p),
   126  			Ui:          ui,
   127  		},
   128  	}
   129  
   130  	args := []string{
   131  		"-state", testTempFile(t),
   132  		testFixturePath("apply-config-invalid"),
   133  	}
   134  	if code := c.Run(args); code != 1 {
   135  		t.Fatalf("bad: \n%s", ui.OutputWriter.String())
   136  	}
   137  }
   138  
   139  func TestApply_defaultState(t *testing.T) {
   140  	td, err := ioutil.TempDir("", "tf")
   141  	if err != nil {
   142  		t.Fatalf("err: %s", err)
   143  	}
   144  	statePath := filepath.Join(td, DefaultStateFilename)
   145  
   146  	// Change to the temporary directory
   147  	cwd, err := os.Getwd()
   148  	if err != nil {
   149  		t.Fatalf("err: %s", err)
   150  	}
   151  	if err := os.Chdir(filepath.Dir(statePath)); err != nil {
   152  		t.Fatalf("err: %s", err)
   153  	}
   154  	defer os.Chdir(cwd)
   155  
   156  	p := testProvider()
   157  	ui := new(cli.MockUi)
   158  	c := &ApplyCommand{
   159  		Meta: Meta{
   160  			ContextOpts: testCtxConfig(p),
   161  			Ui:          ui,
   162  		},
   163  	}
   164  
   165  	args := []string{
   166  		testFixturePath("apply"),
   167  	}
   168  	if code := c.Run(args); code != 0 {
   169  		t.Fatalf("bad: %d\n\n%s", code, ui.ErrorWriter.String())
   170  	}
   171  
   172  	if _, err := os.Stat(statePath); err != nil {
   173  		t.Fatalf("err: %s", err)
   174  	}
   175  
   176  	f, err := os.Open(statePath)
   177  	if err != nil {
   178  		t.Fatalf("err: %s", err)
   179  	}
   180  	defer f.Close()
   181  
   182  	state, err := terraform.ReadState(f)
   183  	if err != nil {
   184  		t.Fatalf("err: %s", err)
   185  	}
   186  	if state == nil {
   187  		t.Fatal("state should not be nil")
   188  	}
   189  }
   190  
   191  func TestApply_error(t *testing.T) {
   192  	statePath := testTempFile(t)
   193  
   194  	p := testProvider()
   195  	ui := new(cli.MockUi)
   196  	c := &ApplyCommand{
   197  		Meta: Meta{
   198  			ContextOpts: testCtxConfig(p),
   199  			Ui:          ui,
   200  		},
   201  	}
   202  
   203  	var lock sync.Mutex
   204  	errored := false
   205  	p.ApplyFn = func(
   206  		info *terraform.InstanceInfo,
   207  		s *terraform.InstanceState,
   208  		d *terraform.InstanceDiff) (*terraform.InstanceState, error) {
   209  		lock.Lock()
   210  		defer lock.Unlock()
   211  
   212  		if !errored {
   213  			errored = true
   214  			return nil, fmt.Errorf("error")
   215  		}
   216  
   217  		return &terraform.InstanceState{ID: "foo"}, nil
   218  	}
   219  	p.DiffFn = func(
   220  		*terraform.InstanceInfo,
   221  		*terraform.InstanceState,
   222  		*terraform.ResourceConfig) (*terraform.InstanceDiff, error) {
   223  		return &terraform.InstanceDiff{
   224  			Attributes: map[string]*terraform.ResourceAttrDiff{
   225  				"ami": &terraform.ResourceAttrDiff{
   226  					New: "bar",
   227  				},
   228  			},
   229  		}, nil
   230  	}
   231  
   232  	args := []string{
   233  		"-state", statePath,
   234  		testFixturePath("apply-error"),
   235  	}
   236  	if code := c.Run(args); code != 1 {
   237  		t.Fatalf("bad: %d\n\n%s", code, ui.ErrorWriter.String())
   238  	}
   239  
   240  	if _, err := os.Stat(statePath); err != nil {
   241  		t.Fatalf("err: %s", err)
   242  	}
   243  
   244  	f, err := os.Open(statePath)
   245  	if err != nil {
   246  		t.Fatalf("err: %s", err)
   247  	}
   248  	defer f.Close()
   249  
   250  	state, err := terraform.ReadState(f)
   251  	if err != nil {
   252  		t.Fatalf("err: %s", err)
   253  	}
   254  	if state == nil {
   255  		t.Fatal("state should not be nil")
   256  	}
   257  	if len(state.RootModule().Resources) == 0 {
   258  		t.Fatal("no resources in state")
   259  	}
   260  }
   261  
   262  func TestApply_init(t *testing.T) {
   263  	// Change to the temporary directory
   264  	cwd, err := os.Getwd()
   265  	if err != nil {
   266  		t.Fatalf("err: %s", err)
   267  	}
   268  	dir := tempDir(t)
   269  	if err := os.MkdirAll(dir, 0755); err != nil {
   270  		t.Fatalf("err: %s", err)
   271  	}
   272  	if err := os.Chdir(dir); err != nil {
   273  		t.Fatalf("err: %s", err)
   274  	}
   275  	defer os.Chdir(cwd)
   276  
   277  	// Create the test fixtures
   278  	statePath := testTempFile(t)
   279  	ln := testHttpServer(t)
   280  	defer ln.Close()
   281  
   282  	// Initialize the command
   283  	p := testProvider()
   284  	ui := new(cli.MockUi)
   285  	c := &ApplyCommand{
   286  		Meta: Meta{
   287  			ContextOpts: testCtxConfig(p),
   288  			Ui:          ui,
   289  		},
   290  	}
   291  
   292  	// Build the URL to the init
   293  	var u url.URL
   294  	u.Scheme = "http"
   295  	u.Host = ln.Addr().String()
   296  	u.Path = "/header"
   297  
   298  	args := []string{
   299  		"-state", statePath,
   300  		u.String(),
   301  	}
   302  	if code := c.Run(args); code != 0 {
   303  		t.Fatalf("bad: %d\n\n%s", code, ui.ErrorWriter.String())
   304  	}
   305  
   306  	if _, err := os.Stat("hello.tf"); err != nil {
   307  		t.Fatalf("err: %s", err)
   308  	}
   309  
   310  	if _, err := os.Stat(statePath); err != nil {
   311  		t.Fatalf("err: %s", err)
   312  	}
   313  
   314  	f, err := os.Open(statePath)
   315  	if err != nil {
   316  		t.Fatalf("err: %s", err)
   317  	}
   318  	defer f.Close()
   319  
   320  	state, err := terraform.ReadState(f)
   321  	if err != nil {
   322  		t.Fatalf("err: %s", err)
   323  	}
   324  	if state == nil {
   325  		t.Fatal("state should not be nil")
   326  	}
   327  }
   328  
   329  func TestApply_input(t *testing.T) {
   330  	// Disable test mode so input would be asked
   331  	test = false
   332  	defer func() { test = true }()
   333  
   334  	// Set some default reader/writers for the inputs
   335  	defaultInputReader = bytes.NewBufferString("foo\n")
   336  	defaultInputWriter = new(bytes.Buffer)
   337  
   338  	statePath := testTempFile(t)
   339  
   340  	p := testProvider()
   341  	ui := new(cli.MockUi)
   342  	c := &ApplyCommand{
   343  		Meta: Meta{
   344  			ContextOpts: testCtxConfig(p),
   345  			Ui:          ui,
   346  		},
   347  	}
   348  
   349  	args := []string{
   350  		"-state", statePath,
   351  		testFixturePath("apply-input"),
   352  	}
   353  	if code := c.Run(args); code != 0 {
   354  		t.Fatalf("bad: %d\n\n%s", code, ui.ErrorWriter.String())
   355  	}
   356  
   357  	if !p.InputCalled {
   358  		t.Fatal("input should be called")
   359  	}
   360  }
   361  
   362  func TestApply_noArgs(t *testing.T) {
   363  	cwd, err := os.Getwd()
   364  	if err != nil {
   365  		t.Fatalf("err: %s", err)
   366  	}
   367  	if err := os.Chdir(testFixturePath("plan")); err != nil {
   368  		t.Fatalf("err: %s", err)
   369  	}
   370  	defer os.Chdir(cwd)
   371  
   372  	statePath := testTempFile(t)
   373  
   374  	p := testProvider()
   375  	ui := new(cli.MockUi)
   376  	c := &ApplyCommand{
   377  		Meta: Meta{
   378  			ContextOpts: testCtxConfig(p),
   379  			Ui:          ui,
   380  		},
   381  	}
   382  
   383  	args := []string{
   384  		"-state", statePath,
   385  	}
   386  	if code := c.Run(args); code != 0 {
   387  		t.Fatalf("bad: %d\n\n%s", code, ui.ErrorWriter.String())
   388  	}
   389  
   390  	if _, err := os.Stat(statePath); err != nil {
   391  		t.Fatalf("err: %s", err)
   392  	}
   393  
   394  	f, err := os.Open(statePath)
   395  	if err != nil {
   396  		t.Fatalf("err: %s", err)
   397  	}
   398  	defer f.Close()
   399  
   400  	state, err := terraform.ReadState(f)
   401  	if err != nil {
   402  		t.Fatalf("err: %s", err)
   403  	}
   404  	if state == nil {
   405  		t.Fatal("state should not be nil")
   406  	}
   407  }
   408  
   409  func TestApply_plan(t *testing.T) {
   410  	// Disable test mode so input would be asked
   411  	test = false
   412  	defer func() { test = true }()
   413  
   414  	// Set some default reader/writers for the inputs
   415  	defaultInputReader = new(bytes.Buffer)
   416  	defaultInputWriter = new(bytes.Buffer)
   417  
   418  	planPath := testPlanFile(t, &terraform.Plan{
   419  		Module: testModule(t, "apply"),
   420  	})
   421  	statePath := testTempFile(t)
   422  
   423  	p := testProvider()
   424  	ui := new(cli.MockUi)
   425  	c := &ApplyCommand{
   426  		Meta: Meta{
   427  			ContextOpts: testCtxConfig(p),
   428  			Ui:          ui,
   429  		},
   430  	}
   431  
   432  	args := []string{
   433  		"-state", statePath,
   434  		planPath,
   435  	}
   436  	if code := c.Run(args); code != 0 {
   437  		t.Fatalf("bad: %d\n\n%s", code, ui.ErrorWriter.String())
   438  	}
   439  
   440  	if p.InputCalled {
   441  		t.Fatalf("input should not be called for plans")
   442  	}
   443  
   444  	if _, err := os.Stat(statePath); err != nil {
   445  		t.Fatalf("err: %s", err)
   446  	}
   447  
   448  	f, err := os.Open(statePath)
   449  	if err != nil {
   450  		t.Fatalf("err: %s", err)
   451  	}
   452  	defer f.Close()
   453  
   454  	state, err := terraform.ReadState(f)
   455  	if err != nil {
   456  		t.Fatalf("err: %s", err)
   457  	}
   458  	if state == nil {
   459  		t.Fatal("state should not be nil")
   460  	}
   461  }
   462  
   463  func TestApply_plan_remoteState(t *testing.T) {
   464  	// Disable test mode so input would be asked
   465  	test = false
   466  	defer func() { test = true }()
   467  	tmp, cwd := testCwd(t)
   468  	defer testFixCwd(t, tmp, cwd)
   469  	remoteStatePath := filepath.Join(tmp, DefaultDataDir, DefaultStateFilename)
   470  	if err := os.MkdirAll(filepath.Dir(remoteStatePath), 0755); err != nil {
   471  		t.Fatalf("err: %s", err)
   472  	}
   473  
   474  	// Set some default reader/writers for the inputs
   475  	defaultInputReader = new(bytes.Buffer)
   476  	defaultInputWriter = new(bytes.Buffer)
   477  
   478  	// Create a remote state
   479  	state := testState()
   480  	conf, srv := testRemoteState(t, state, 200)
   481  	defer srv.Close()
   482  	state.Remote = conf
   483  
   484  	planPath := testPlanFile(t, &terraform.Plan{
   485  		Module: testModule(t, "apply"),
   486  		State:  state,
   487  	})
   488  
   489  	p := testProvider()
   490  	ui := new(cli.MockUi)
   491  	c := &ApplyCommand{
   492  		Meta: Meta{
   493  			ContextOpts: testCtxConfig(p),
   494  			Ui:          ui,
   495  		},
   496  	}
   497  
   498  	args := []string{
   499  		planPath,
   500  	}
   501  	if code := c.Run(args); code != 0 {
   502  		t.Fatalf("bad: %d\n\n%s", code, ui.ErrorWriter.String())
   503  	}
   504  
   505  	if p.InputCalled {
   506  		t.Fatalf("input should not be called for plans")
   507  	}
   508  
   509  	// State file should be not be installed
   510  	if _, err := os.Stat(filepath.Join(tmp, DefaultStateFilename)); err == nil {
   511  		t.Fatalf("State path should not exist")
   512  	}
   513  
   514  	// Check for remote state
   515  	if _, err := os.Stat(remoteStatePath); err != nil {
   516  		t.Fatalf("missing remote state: %s", err)
   517  	}
   518  }
   519  
   520  func TestApply_planWithVarFile(t *testing.T) {
   521  	varFileDir := testTempDir(t)
   522  	varFilePath := filepath.Join(varFileDir, "terraform.tfvars")
   523  	if err := ioutil.WriteFile(varFilePath, []byte(applyVarFile), 0644); err != nil {
   524  		t.Fatalf("err: %s", err)
   525  	}
   526  
   527  	planPath := testPlanFile(t, &terraform.Plan{
   528  		Module: testModule(t, "apply"),
   529  	})
   530  	statePath := testTempFile(t)
   531  
   532  	cwd, err := os.Getwd()
   533  	if err != nil {
   534  		t.Fatalf("err: %s", err)
   535  	}
   536  	if err := os.Chdir(varFileDir); err != nil {
   537  		t.Fatalf("err: %s", err)
   538  	}
   539  	defer os.Chdir(cwd)
   540  
   541  	p := testProvider()
   542  	ui := new(cli.MockUi)
   543  	c := &ApplyCommand{
   544  		Meta: Meta{
   545  			ContextOpts: testCtxConfig(p),
   546  			Ui:          ui,
   547  		},
   548  	}
   549  
   550  	args := []string{
   551  		"-state", statePath,
   552  		planPath,
   553  	}
   554  	if code := c.Run(args); code != 0 {
   555  		t.Fatalf("bad: %d\n\n%s", code, ui.ErrorWriter.String())
   556  	}
   557  
   558  	if _, err := os.Stat(statePath); err != nil {
   559  		t.Fatalf("err: %s", err)
   560  	}
   561  
   562  	f, err := os.Open(statePath)
   563  	if err != nil {
   564  		t.Fatalf("err: %s", err)
   565  	}
   566  	defer f.Close()
   567  
   568  	state, err := terraform.ReadState(f)
   569  	if err != nil {
   570  		t.Fatalf("err: %s", err)
   571  	}
   572  	if state == nil {
   573  		t.Fatal("state should not be nil")
   574  	}
   575  }
   576  
   577  func TestApply_planVars(t *testing.T) {
   578  	planPath := testPlanFile(t, &terraform.Plan{
   579  		Module: testModule(t, "apply"),
   580  	})
   581  	statePath := testTempFile(t)
   582  
   583  	p := testProvider()
   584  	ui := new(cli.MockUi)
   585  	c := &ApplyCommand{
   586  		Meta: Meta{
   587  			ContextOpts: testCtxConfig(p),
   588  			Ui:          ui,
   589  		},
   590  	}
   591  
   592  	args := []string{
   593  		"-state", statePath,
   594  		"-var", "foo=bar",
   595  		planPath,
   596  	}
   597  	if code := c.Run(args); code == 0 {
   598  		t.Fatal("should've failed")
   599  	}
   600  }
   601  
   602  func TestApply_refresh(t *testing.T) {
   603  	originalState := &terraform.State{
   604  		Modules: []*terraform.ModuleState{
   605  			&terraform.ModuleState{
   606  				Path: []string{"root"},
   607  				Resources: map[string]*terraform.ResourceState{
   608  					"test_instance.foo": &terraform.ResourceState{
   609  						Type: "test_instance",
   610  						Primary: &terraform.InstanceState{
   611  							ID: "bar",
   612  						},
   613  					},
   614  				},
   615  			},
   616  		},
   617  	}
   618  
   619  	statePath := testStateFile(t, originalState)
   620  
   621  	p := testProvider()
   622  	ui := new(cli.MockUi)
   623  	c := &ApplyCommand{
   624  		Meta: Meta{
   625  			ContextOpts: testCtxConfig(p),
   626  			Ui:          ui,
   627  		},
   628  	}
   629  
   630  	args := []string{
   631  		"-state", statePath,
   632  		testFixturePath("apply"),
   633  	}
   634  	if code := c.Run(args); code != 0 {
   635  		t.Fatalf("bad: %d\n\n%s", code, ui.ErrorWriter.String())
   636  	}
   637  
   638  	if !p.RefreshCalled {
   639  		t.Fatal("should call refresh")
   640  	}
   641  
   642  	if _, err := os.Stat(statePath); err != nil {
   643  		t.Fatalf("err: %s", err)
   644  	}
   645  
   646  	f, err := os.Open(statePath)
   647  	if err != nil {
   648  		t.Fatalf("err: %s", err)
   649  	}
   650  	defer f.Close()
   651  
   652  	state, err := terraform.ReadState(f)
   653  	if err != nil {
   654  		t.Fatalf("err: %s", err)
   655  	}
   656  	if state == nil {
   657  		t.Fatal("state should not be nil")
   658  	}
   659  
   660  	// Should have a backup file
   661  	f, err = os.Open(statePath + DefaultBackupExtension)
   662  	if err != nil {
   663  		t.Fatalf("err: %s", err)
   664  	}
   665  
   666  	backupState, err := terraform.ReadState(f)
   667  	f.Close()
   668  	if err != nil {
   669  		t.Fatalf("err: %s", err)
   670  	}
   671  
   672  	actualStr := strings.TrimSpace(backupState.String())
   673  	expectedStr := strings.TrimSpace(originalState.String())
   674  	if actualStr != expectedStr {
   675  		t.Fatalf("bad:\n\n%s\n\n%s", actualStr, expectedStr)
   676  	}
   677  }
   678  
   679  func TestApply_shutdown(t *testing.T) {
   680  	stopped := false
   681  	stopCh := make(chan struct{})
   682  	stopReplyCh := make(chan struct{})
   683  
   684  	statePath := testTempFile(t)
   685  
   686  	p := testProvider()
   687  	shutdownCh := make(chan struct{})
   688  	ui := new(cli.MockUi)
   689  	c := &ApplyCommand{
   690  		Meta: Meta{
   691  			ContextOpts: testCtxConfig(p),
   692  			Ui:          ui,
   693  		},
   694  
   695  		ShutdownCh: shutdownCh,
   696  	}
   697  
   698  	p.DiffFn = func(
   699  		*terraform.InstanceInfo,
   700  		*terraform.InstanceState,
   701  		*terraform.ResourceConfig) (*terraform.InstanceDiff, error) {
   702  		return &terraform.InstanceDiff{
   703  			Attributes: map[string]*terraform.ResourceAttrDiff{
   704  				"ami": &terraform.ResourceAttrDiff{
   705  					New: "bar",
   706  				},
   707  			},
   708  		}, nil
   709  	}
   710  	p.ApplyFn = func(
   711  		*terraform.InstanceInfo,
   712  		*terraform.InstanceState,
   713  		*terraform.InstanceDiff) (*terraform.InstanceState, error) {
   714  		if !stopped {
   715  			stopped = true
   716  			close(stopCh)
   717  			<-stopReplyCh
   718  		}
   719  
   720  		return &terraform.InstanceState{
   721  			ID: "foo",
   722  			Attributes: map[string]string{
   723  				"ami": "2",
   724  			},
   725  		}, nil
   726  	}
   727  
   728  	go func() {
   729  		<-stopCh
   730  		shutdownCh <- struct{}{}
   731  
   732  		// This is really dirty, but we have no other way to assure that
   733  		// tf.Stop() has been called. This doesn't assure it either, but
   734  		// it makes it much more certain.
   735  		time.Sleep(50 * time.Millisecond)
   736  
   737  		close(stopReplyCh)
   738  	}()
   739  
   740  	args := []string{
   741  		"-state", statePath,
   742  		testFixturePath("apply-shutdown"),
   743  	}
   744  	if code := c.Run(args); code != 0 {
   745  		t.Fatalf("bad: %d\n\n%s", code, ui.ErrorWriter.String())
   746  	}
   747  
   748  	if _, err := os.Stat(statePath); err != nil {
   749  		t.Fatalf("err: %s", err)
   750  	}
   751  
   752  	f, err := os.Open(statePath)
   753  	if err != nil {
   754  		t.Fatalf("err: %s", err)
   755  	}
   756  	defer f.Close()
   757  
   758  	state, err := terraform.ReadState(f)
   759  	if err != nil {
   760  		t.Fatalf("err: %s", err)
   761  	}
   762  	if state == nil {
   763  		t.Fatal("state should not be nil")
   764  	}
   765  
   766  	if len(state.RootModule().Resources) != 1 {
   767  		t.Fatalf("bad: %d", len(state.RootModule().Resources))
   768  	}
   769  }
   770  
   771  func TestApply_state(t *testing.T) {
   772  	originalState := &terraform.State{
   773  		Modules: []*terraform.ModuleState{
   774  			&terraform.ModuleState{
   775  				Path: []string{"root"},
   776  				Resources: map[string]*terraform.ResourceState{
   777  					"test_instance.foo": &terraform.ResourceState{
   778  						Type: "test_instance",
   779  						Primary: &terraform.InstanceState{
   780  							ID: "bar",
   781  						},
   782  					},
   783  				},
   784  			},
   785  		},
   786  	}
   787  
   788  	statePath := testStateFile(t, originalState)
   789  
   790  	p := testProvider()
   791  	p.DiffReturn = &terraform.InstanceDiff{
   792  		Attributes: map[string]*terraform.ResourceAttrDiff{
   793  			"ami": &terraform.ResourceAttrDiff{
   794  				New: "bar",
   795  			},
   796  		},
   797  	}
   798  
   799  	ui := new(cli.MockUi)
   800  	c := &ApplyCommand{
   801  		Meta: Meta{
   802  			ContextOpts: testCtxConfig(p),
   803  			Ui:          ui,
   804  		},
   805  	}
   806  
   807  	// Run the apply command pointing to our existing state
   808  	args := []string{
   809  		"-state", statePath,
   810  		testFixturePath("apply"),
   811  	}
   812  	if code := c.Run(args); code != 0 {
   813  		t.Fatalf("bad: %d\n\n%s", code, ui.ErrorWriter.String())
   814  	}
   815  
   816  	// Verify that the provider was called with the existing state
   817  	actual := strings.TrimSpace(p.DiffState.String())
   818  	expected := strings.TrimSpace(testApplyStateDiffStr)
   819  	if actual != expected {
   820  		t.Fatalf("bad:\n\n%s", actual)
   821  	}
   822  
   823  	actual = strings.TrimSpace(p.ApplyState.String())
   824  	expected = strings.TrimSpace(testApplyStateStr)
   825  	if actual != expected {
   826  		t.Fatalf("bad:\n\n%s", actual)
   827  	}
   828  
   829  	// Verify a new state exists
   830  	if _, err := os.Stat(statePath); err != nil {
   831  		t.Fatalf("err: %s", err)
   832  	}
   833  
   834  	f, err := os.Open(statePath)
   835  	if err != nil {
   836  		t.Fatalf("err: %s", err)
   837  	}
   838  	defer f.Close()
   839  
   840  	state, err := terraform.ReadState(f)
   841  	if err != nil {
   842  		t.Fatalf("err: %s", err)
   843  	}
   844  	if state == nil {
   845  		t.Fatal("state should not be nil")
   846  	}
   847  
   848  	// Should have a backup file
   849  	f, err = os.Open(statePath + DefaultBackupExtension)
   850  	if err != nil {
   851  		t.Fatalf("err: %s", err)
   852  	}
   853  
   854  	backupState, err := terraform.ReadState(f)
   855  	f.Close()
   856  	if err != nil {
   857  		t.Fatalf("err: %s", err)
   858  	}
   859  
   860  	// nil out the ConnInfo since that should not be restored
   861  	originalState.RootModule().Resources["test_instance.foo"].Primary.Ephemeral.ConnInfo = nil
   862  
   863  	actualStr := strings.TrimSpace(backupState.String())
   864  	expectedStr := strings.TrimSpace(originalState.String())
   865  	if actualStr != expectedStr {
   866  		t.Fatalf("bad:\n\n%s\n\n%s", actualStr, expectedStr)
   867  	}
   868  }
   869  
   870  func TestApply_stateNoExist(t *testing.T) {
   871  	p := testProvider()
   872  	ui := new(cli.MockUi)
   873  	c := &ApplyCommand{
   874  		Meta: Meta{
   875  			ContextOpts: testCtxConfig(p),
   876  			Ui:          ui,
   877  		},
   878  	}
   879  
   880  	args := []string{
   881  		"idontexist.tfstate",
   882  		testFixturePath("apply"),
   883  	}
   884  	if code := c.Run(args); code != 1 {
   885  		t.Fatalf("bad: \n%s", ui.OutputWriter.String())
   886  	}
   887  }
   888  
   889  func TestApply_sensitiveOutput(t *testing.T) {
   890  	p := testProvider()
   891  	ui := new(cli.MockUi)
   892  	c := &ApplyCommand{
   893  		Meta: Meta{
   894  			ContextOpts: testCtxConfig(p),
   895  			Ui:          ui,
   896  		},
   897  	}
   898  
   899  	statePath := testTempFile(t)
   900  
   901  	args := []string{
   902  		"-state", statePath,
   903  		testFixturePath("apply-sensitive-output"),
   904  	}
   905  
   906  	if code := c.Run(args); code != 0 {
   907  		t.Fatalf("bad: \n%s", ui.OutputWriter.String())
   908  	}
   909  
   910  	output := ui.OutputWriter.String()
   911  	if !strings.Contains(output, "notsensitive = Hello world") {
   912  		t.Fatalf("bad: output should contain 'notsensitive' output\n%s", output)
   913  	}
   914  	if !strings.Contains(output, "sensitive = <sensitive>") {
   915  		t.Fatalf("bad: output should contain 'sensitive' output\n%s", output)
   916  	}
   917  }
   918  
   919  func TestApply_stateFuture(t *testing.T) {
   920  	originalState := testState()
   921  	originalState.TFVersion = "99.99.99"
   922  	statePath := testStateFile(t, originalState)
   923  
   924  	p := testProvider()
   925  	ui := new(cli.MockUi)
   926  	c := &ApplyCommand{
   927  		Meta: Meta{
   928  			ContextOpts: testCtxConfig(p),
   929  			Ui:          ui,
   930  		},
   931  	}
   932  
   933  	args := []string{
   934  		"-state", statePath,
   935  		testFixturePath("apply"),
   936  	}
   937  	if code := c.Run(args); code == 0 {
   938  		t.Fatal("should fail")
   939  	}
   940  
   941  	f, err := os.Open(statePath)
   942  	if err != nil {
   943  		t.Fatalf("err: %s", err)
   944  	}
   945  
   946  	newState, err := terraform.ReadState(f)
   947  	f.Close()
   948  	if err != nil {
   949  		t.Fatalf("err: %s", err)
   950  	}
   951  
   952  	if !newState.Equal(originalState) {
   953  		t.Fatalf("bad: %#v", newState)
   954  	}
   955  	if newState.TFVersion != originalState.TFVersion {
   956  		t.Fatalf("bad: %#v", newState)
   957  	}
   958  }
   959  
   960  func TestApply_statePast(t *testing.T) {
   961  	originalState := testState()
   962  	originalState.TFVersion = "0.1.0"
   963  	statePath := testStateFile(t, originalState)
   964  
   965  	p := testProvider()
   966  	ui := new(cli.MockUi)
   967  	c := &ApplyCommand{
   968  		Meta: Meta{
   969  			ContextOpts: testCtxConfig(p),
   970  			Ui:          ui,
   971  		},
   972  	}
   973  
   974  	args := []string{
   975  		"-state", statePath,
   976  		testFixturePath("apply"),
   977  	}
   978  	if code := c.Run(args); code != 0 {
   979  		t.Fatalf("bad: %d\n\n%s", code, ui.ErrorWriter.String())
   980  	}
   981  }
   982  
   983  func TestApply_vars(t *testing.T) {
   984  	statePath := testTempFile(t)
   985  
   986  	p := testProvider()
   987  	ui := new(cli.MockUi)
   988  	c := &ApplyCommand{
   989  		Meta: Meta{
   990  			ContextOpts: testCtxConfig(p),
   991  			Ui:          ui,
   992  		},
   993  	}
   994  
   995  	actual := ""
   996  	p.DiffFn = func(
   997  		info *terraform.InstanceInfo,
   998  		s *terraform.InstanceState,
   999  		c *terraform.ResourceConfig) (*terraform.InstanceDiff, error) {
  1000  		if v, ok := c.Config["value"]; ok {
  1001  			actual = v.(string)
  1002  		}
  1003  
  1004  		return &terraform.InstanceDiff{}, nil
  1005  	}
  1006  
  1007  	args := []string{
  1008  		"-var", "foo=bar",
  1009  		"-state", statePath,
  1010  		testFixturePath("apply-vars"),
  1011  	}
  1012  	if code := c.Run(args); code != 0 {
  1013  		t.Fatalf("bad: %d\n\n%s", code, ui.ErrorWriter.String())
  1014  	}
  1015  
  1016  	if actual != "bar" {
  1017  		t.Fatal("didn't work")
  1018  	}
  1019  }
  1020  
  1021  func TestApply_varFile(t *testing.T) {
  1022  	varFilePath := testTempFile(t)
  1023  	if err := ioutil.WriteFile(varFilePath, []byte(applyVarFile), 0644); err != nil {
  1024  		t.Fatalf("err: %s", err)
  1025  	}
  1026  
  1027  	statePath := testTempFile(t)
  1028  
  1029  	p := testProvider()
  1030  	ui := new(cli.MockUi)
  1031  	c := &ApplyCommand{
  1032  		Meta: Meta{
  1033  			ContextOpts: testCtxConfig(p),
  1034  			Ui:          ui,
  1035  		},
  1036  	}
  1037  
  1038  	actual := ""
  1039  	p.DiffFn = func(
  1040  		info *terraform.InstanceInfo,
  1041  		s *terraform.InstanceState,
  1042  		c *terraform.ResourceConfig) (*terraform.InstanceDiff, error) {
  1043  		if v, ok := c.Config["value"]; ok {
  1044  			actual = v.(string)
  1045  		}
  1046  
  1047  		return &terraform.InstanceDiff{}, nil
  1048  	}
  1049  
  1050  	args := []string{
  1051  		"-var-file", varFilePath,
  1052  		"-state", statePath,
  1053  		testFixturePath("apply-vars"),
  1054  	}
  1055  	if code := c.Run(args); code != 0 {
  1056  		t.Fatalf("bad: %d\n\n%s", code, ui.ErrorWriter.String())
  1057  	}
  1058  
  1059  	if actual != "bar" {
  1060  		t.Fatal("didn't work")
  1061  	}
  1062  }
  1063  
  1064  func TestApply_varFileDefault(t *testing.T) {
  1065  	varFileDir := testTempDir(t)
  1066  	varFilePath := filepath.Join(varFileDir, "terraform.tfvars")
  1067  	if err := ioutil.WriteFile(varFilePath, []byte(applyVarFile), 0644); err != nil {
  1068  		t.Fatalf("err: %s", err)
  1069  	}
  1070  
  1071  	statePath := testTempFile(t)
  1072  
  1073  	cwd, err := os.Getwd()
  1074  	if err != nil {
  1075  		t.Fatalf("err: %s", err)
  1076  	}
  1077  	if err := os.Chdir(varFileDir); err != nil {
  1078  		t.Fatalf("err: %s", err)
  1079  	}
  1080  	defer os.Chdir(cwd)
  1081  
  1082  	p := testProvider()
  1083  	ui := new(cli.MockUi)
  1084  	c := &ApplyCommand{
  1085  		Meta: Meta{
  1086  			ContextOpts: testCtxConfig(p),
  1087  			Ui:          ui,
  1088  		},
  1089  	}
  1090  
  1091  	actual := ""
  1092  	p.DiffFn = func(
  1093  		info *terraform.InstanceInfo,
  1094  		s *terraform.InstanceState,
  1095  		c *terraform.ResourceConfig) (*terraform.InstanceDiff, error) {
  1096  		if v, ok := c.Config["value"]; ok {
  1097  			actual = v.(string)
  1098  		}
  1099  
  1100  		return &terraform.InstanceDiff{}, nil
  1101  	}
  1102  
  1103  	args := []string{
  1104  		"-state", statePath,
  1105  		testFixturePath("apply-vars"),
  1106  	}
  1107  	if code := c.Run(args); code != 0 {
  1108  		t.Fatalf("bad: %d\n\n%s", code, ui.ErrorWriter.String())
  1109  	}
  1110  
  1111  	if actual != "bar" {
  1112  		t.Fatal("didn't work")
  1113  	}
  1114  }
  1115  
  1116  func TestApply_varFileDefaultJSON(t *testing.T) {
  1117  	varFileDir := testTempDir(t)
  1118  	varFilePath := filepath.Join(varFileDir, "terraform.tfvars.json")
  1119  	if err := ioutil.WriteFile(varFilePath, []byte(applyVarFileJSON), 0644); err != nil {
  1120  		t.Fatalf("err: %s", err)
  1121  	}
  1122  
  1123  	statePath := testTempFile(t)
  1124  
  1125  	cwd, err := os.Getwd()
  1126  	if err != nil {
  1127  		t.Fatalf("err: %s", err)
  1128  	}
  1129  	if err := os.Chdir(varFileDir); err != nil {
  1130  		t.Fatalf("err: %s", err)
  1131  	}
  1132  	defer os.Chdir(cwd)
  1133  
  1134  	p := testProvider()
  1135  	ui := new(cli.MockUi)
  1136  	c := &ApplyCommand{
  1137  		Meta: Meta{
  1138  			ContextOpts: testCtxConfig(p),
  1139  			Ui:          ui,
  1140  		},
  1141  	}
  1142  
  1143  	actual := ""
  1144  	p.DiffFn = func(
  1145  		info *terraform.InstanceInfo,
  1146  		s *terraform.InstanceState,
  1147  		c *terraform.ResourceConfig) (*terraform.InstanceDiff, error) {
  1148  		if v, ok := c.Config["value"]; ok {
  1149  			actual = v.(string)
  1150  		}
  1151  
  1152  		return &terraform.InstanceDiff{}, nil
  1153  	}
  1154  
  1155  	args := []string{
  1156  		"-state", statePath,
  1157  		testFixturePath("apply-vars"),
  1158  	}
  1159  	if code := c.Run(args); code != 0 {
  1160  		t.Fatalf("bad: %d\n\n%s", code, ui.ErrorWriter.String())
  1161  	}
  1162  
  1163  	if actual != "bar" {
  1164  		t.Fatal("didn't work")
  1165  	}
  1166  }
  1167  
  1168  func TestApply_backup(t *testing.T) {
  1169  	originalState := &terraform.State{
  1170  		Modules: []*terraform.ModuleState{
  1171  			&terraform.ModuleState{
  1172  				Path: []string{"root"},
  1173  				Resources: map[string]*terraform.ResourceState{
  1174  					"test_instance.foo": &terraform.ResourceState{
  1175  						Type: "test_instance",
  1176  						Primary: &terraform.InstanceState{
  1177  							ID: "bar",
  1178  						},
  1179  					},
  1180  				},
  1181  			},
  1182  		},
  1183  	}
  1184  
  1185  	statePath := testStateFile(t, originalState)
  1186  	backupPath := testTempFile(t)
  1187  
  1188  	p := testProvider()
  1189  	p.DiffReturn = &terraform.InstanceDiff{
  1190  		Attributes: map[string]*terraform.ResourceAttrDiff{
  1191  			"ami": &terraform.ResourceAttrDiff{
  1192  				New: "bar",
  1193  			},
  1194  		},
  1195  	}
  1196  
  1197  	ui := new(cli.MockUi)
  1198  	c := &ApplyCommand{
  1199  		Meta: Meta{
  1200  			ContextOpts: testCtxConfig(p),
  1201  			Ui:          ui,
  1202  		},
  1203  	}
  1204  
  1205  	// Run the apply command pointing to our existing state
  1206  	args := []string{
  1207  		"-state", statePath,
  1208  		"-backup", backupPath,
  1209  		testFixturePath("apply"),
  1210  	}
  1211  	if code := c.Run(args); code != 0 {
  1212  		t.Fatalf("bad: %d\n\n%s", code, ui.ErrorWriter.String())
  1213  	}
  1214  
  1215  	// Verify a new state exists
  1216  	if _, err := os.Stat(statePath); err != nil {
  1217  		t.Fatalf("err: %s", err)
  1218  	}
  1219  
  1220  	f, err := os.Open(statePath)
  1221  	if err != nil {
  1222  		t.Fatalf("err: %s", err)
  1223  	}
  1224  	defer f.Close()
  1225  
  1226  	state, err := terraform.ReadState(f)
  1227  	if err != nil {
  1228  		t.Fatalf("err: %s", err)
  1229  	}
  1230  	if state == nil {
  1231  		t.Fatal("state should not be nil")
  1232  	}
  1233  
  1234  	// Should have a backup file
  1235  	f, err = os.Open(backupPath)
  1236  	if err != nil {
  1237  		t.Fatalf("err: %s", err)
  1238  	}
  1239  
  1240  	backupState, err := terraform.ReadState(f)
  1241  	f.Close()
  1242  	if err != nil {
  1243  		t.Fatalf("err: %s", err)
  1244  	}
  1245  
  1246  	actual := backupState.RootModule().Resources["test_instance.foo"]
  1247  	expected := originalState.RootModule().Resources["test_instance.foo"]
  1248  	if !reflect.DeepEqual(actual, expected) {
  1249  		t.Fatalf("bad: %#v %#v", actual, expected)
  1250  	}
  1251  }
  1252  
  1253  func TestApply_disableBackup(t *testing.T) {
  1254  	originalState := testState()
  1255  	statePath := testStateFile(t, originalState)
  1256  
  1257  	p := testProvider()
  1258  	p.DiffReturn = &terraform.InstanceDiff{
  1259  		Attributes: map[string]*terraform.ResourceAttrDiff{
  1260  			"ami": &terraform.ResourceAttrDiff{
  1261  				New: "bar",
  1262  			},
  1263  		},
  1264  	}
  1265  
  1266  	ui := new(cli.MockUi)
  1267  	c := &ApplyCommand{
  1268  		Meta: Meta{
  1269  			ContextOpts: testCtxConfig(p),
  1270  			Ui:          ui,
  1271  		},
  1272  	}
  1273  
  1274  	// Run the apply command pointing to our existing state
  1275  	args := []string{
  1276  		"-state", statePath,
  1277  		"-backup", "-",
  1278  		testFixturePath("apply"),
  1279  	}
  1280  	if code := c.Run(args); code != 0 {
  1281  		t.Fatalf("bad: %d\n\n%s", code, ui.ErrorWriter.String())
  1282  	}
  1283  
  1284  	// Verify that the provider was called with the existing state
  1285  	actual := strings.TrimSpace(p.DiffState.String())
  1286  	expected := strings.TrimSpace(testApplyDisableBackupStr)
  1287  	if actual != expected {
  1288  		t.Fatalf("bad:\n\n%s", actual)
  1289  	}
  1290  
  1291  	actual = strings.TrimSpace(p.ApplyState.String())
  1292  	expected = strings.TrimSpace(testApplyDisableBackupStateStr)
  1293  	if actual != expected {
  1294  		t.Fatalf("bad:\n\n%s", actual)
  1295  	}
  1296  
  1297  	// Verify a new state exists
  1298  	if _, err := os.Stat(statePath); err != nil {
  1299  		t.Fatalf("err: %s", err)
  1300  	}
  1301  
  1302  	f, err := os.Open(statePath)
  1303  	if err != nil {
  1304  		t.Fatalf("err: %s", err)
  1305  	}
  1306  	defer f.Close()
  1307  
  1308  	state, err := terraform.ReadState(f)
  1309  	if err != nil {
  1310  		t.Fatalf("err: %s", err)
  1311  	}
  1312  	if state == nil {
  1313  		t.Fatal("state should not be nil")
  1314  	}
  1315  
  1316  	// Ensure there is no backup
  1317  	_, err = os.Stat(statePath + DefaultBackupExtension)
  1318  	if err == nil || !os.IsNotExist(err) {
  1319  		t.Fatalf("backup should not exist")
  1320  	}
  1321  
  1322  	// Ensure there is no literal "-"
  1323  	_, err = os.Stat("-")
  1324  	if err == nil || !os.IsNotExist(err) {
  1325  		t.Fatalf("backup should not exist")
  1326  	}
  1327  }
  1328  
  1329  func testHttpServer(t *testing.T) net.Listener {
  1330  	ln, err := net.Listen("tcp", "127.0.0.1:0")
  1331  	if err != nil {
  1332  		t.Fatalf("err: %s", err)
  1333  	}
  1334  
  1335  	mux := http.NewServeMux()
  1336  	mux.HandleFunc("/header", testHttpHandlerHeader)
  1337  
  1338  	var server http.Server
  1339  	server.Handler = mux
  1340  	go server.Serve(ln)
  1341  
  1342  	return ln
  1343  }
  1344  
  1345  func testHttpHandlerHeader(w http.ResponseWriter, r *http.Request) {
  1346  	var url url.URL
  1347  	url.Scheme = "file"
  1348  	url.Path = filepath.ToSlash(testFixturePath("init"))
  1349  
  1350  	w.Header().Add("X-Terraform-Get", url.String())
  1351  	w.WriteHeader(200)
  1352  }
  1353  
  1354  const applyVarFile = `
  1355  foo = "bar"
  1356  `
  1357  
  1358  const applyVarFileJSON = `
  1359  { "foo": "bar" }
  1360  `
  1361  
  1362  const testApplyDisableBackupStr = `
  1363  ID = bar
  1364  `
  1365  
  1366  const testApplyDisableBackupStateStr = `
  1367  ID = bar
  1368  `
  1369  
  1370  const testApplyStateStr = `
  1371  ID = bar
  1372  `
  1373  
  1374  const testApplyStateDiffStr = `
  1375  ID = bar
  1376  `