github.com/joshgarnett/terraform@v0.5.4-0.20160219181435-92dc20bb3594/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_vars(t *testing.T) {
   890  	statePath := testTempFile(t)
   891  
   892  	p := testProvider()
   893  	ui := new(cli.MockUi)
   894  	c := &ApplyCommand{
   895  		Meta: Meta{
   896  			ContextOpts: testCtxConfig(p),
   897  			Ui:          ui,
   898  		},
   899  	}
   900  
   901  	actual := ""
   902  	p.DiffFn = func(
   903  		info *terraform.InstanceInfo,
   904  		s *terraform.InstanceState,
   905  		c *terraform.ResourceConfig) (*terraform.InstanceDiff, error) {
   906  		if v, ok := c.Config["value"]; ok {
   907  			actual = v.(string)
   908  		}
   909  
   910  		return &terraform.InstanceDiff{}, nil
   911  	}
   912  
   913  	args := []string{
   914  		"-var", "foo=bar",
   915  		"-state", statePath,
   916  		testFixturePath("apply-vars"),
   917  	}
   918  	if code := c.Run(args); code != 0 {
   919  		t.Fatalf("bad: %d\n\n%s", code, ui.ErrorWriter.String())
   920  	}
   921  
   922  	if actual != "bar" {
   923  		t.Fatal("didn't work")
   924  	}
   925  }
   926  
   927  func TestApply_varFile(t *testing.T) {
   928  	varFilePath := testTempFile(t)
   929  	if err := ioutil.WriteFile(varFilePath, []byte(applyVarFile), 0644); err != nil {
   930  		t.Fatalf("err: %s", err)
   931  	}
   932  
   933  	statePath := testTempFile(t)
   934  
   935  	p := testProvider()
   936  	ui := new(cli.MockUi)
   937  	c := &ApplyCommand{
   938  		Meta: Meta{
   939  			ContextOpts: testCtxConfig(p),
   940  			Ui:          ui,
   941  		},
   942  	}
   943  
   944  	actual := ""
   945  	p.DiffFn = func(
   946  		info *terraform.InstanceInfo,
   947  		s *terraform.InstanceState,
   948  		c *terraform.ResourceConfig) (*terraform.InstanceDiff, error) {
   949  		if v, ok := c.Config["value"]; ok {
   950  			actual = v.(string)
   951  		}
   952  
   953  		return &terraform.InstanceDiff{}, nil
   954  	}
   955  
   956  	args := []string{
   957  		"-var-file", varFilePath,
   958  		"-state", statePath,
   959  		testFixturePath("apply-vars"),
   960  	}
   961  	if code := c.Run(args); code != 0 {
   962  		t.Fatalf("bad: %d\n\n%s", code, ui.ErrorWriter.String())
   963  	}
   964  
   965  	if actual != "bar" {
   966  		t.Fatal("didn't work")
   967  	}
   968  }
   969  
   970  func TestApply_varFileDefault(t *testing.T) {
   971  	varFileDir := testTempDir(t)
   972  	varFilePath := filepath.Join(varFileDir, "terraform.tfvars")
   973  	if err := ioutil.WriteFile(varFilePath, []byte(applyVarFile), 0644); err != nil {
   974  		t.Fatalf("err: %s", err)
   975  	}
   976  
   977  	statePath := testTempFile(t)
   978  
   979  	cwd, err := os.Getwd()
   980  	if err != nil {
   981  		t.Fatalf("err: %s", err)
   982  	}
   983  	if err := os.Chdir(varFileDir); err != nil {
   984  		t.Fatalf("err: %s", err)
   985  	}
   986  	defer os.Chdir(cwd)
   987  
   988  	p := testProvider()
   989  	ui := new(cli.MockUi)
   990  	c := &ApplyCommand{
   991  		Meta: Meta{
   992  			ContextOpts: testCtxConfig(p),
   993  			Ui:          ui,
   994  		},
   995  	}
   996  
   997  	actual := ""
   998  	p.DiffFn = func(
   999  		info *terraform.InstanceInfo,
  1000  		s *terraform.InstanceState,
  1001  		c *terraform.ResourceConfig) (*terraform.InstanceDiff, error) {
  1002  		if v, ok := c.Config["value"]; ok {
  1003  			actual = v.(string)
  1004  		}
  1005  
  1006  		return &terraform.InstanceDiff{}, nil
  1007  	}
  1008  
  1009  	args := []string{
  1010  		"-state", statePath,
  1011  		testFixturePath("apply-vars"),
  1012  	}
  1013  	if code := c.Run(args); code != 0 {
  1014  		t.Fatalf("bad: %d\n\n%s", code, ui.ErrorWriter.String())
  1015  	}
  1016  
  1017  	if actual != "bar" {
  1018  		t.Fatal("didn't work")
  1019  	}
  1020  }
  1021  
  1022  func TestApply_varFileDefaultJSON(t *testing.T) {
  1023  	varFileDir := testTempDir(t)
  1024  	varFilePath := filepath.Join(varFileDir, "terraform.tfvars.json")
  1025  	if err := ioutil.WriteFile(varFilePath, []byte(applyVarFileJSON), 0644); err != nil {
  1026  		t.Fatalf("err: %s", err)
  1027  	}
  1028  
  1029  	statePath := testTempFile(t)
  1030  
  1031  	cwd, err := os.Getwd()
  1032  	if err != nil {
  1033  		t.Fatalf("err: %s", err)
  1034  	}
  1035  	if err := os.Chdir(varFileDir); err != nil {
  1036  		t.Fatalf("err: %s", err)
  1037  	}
  1038  	defer os.Chdir(cwd)
  1039  
  1040  	p := testProvider()
  1041  	ui := new(cli.MockUi)
  1042  	c := &ApplyCommand{
  1043  		Meta: Meta{
  1044  			ContextOpts: testCtxConfig(p),
  1045  			Ui:          ui,
  1046  		},
  1047  	}
  1048  
  1049  	actual := ""
  1050  	p.DiffFn = func(
  1051  		info *terraform.InstanceInfo,
  1052  		s *terraform.InstanceState,
  1053  		c *terraform.ResourceConfig) (*terraform.InstanceDiff, error) {
  1054  		if v, ok := c.Config["value"]; ok {
  1055  			actual = v.(string)
  1056  		}
  1057  
  1058  		return &terraform.InstanceDiff{}, nil
  1059  	}
  1060  
  1061  	args := []string{
  1062  		"-state", statePath,
  1063  		testFixturePath("apply-vars"),
  1064  	}
  1065  	if code := c.Run(args); code != 0 {
  1066  		t.Fatalf("bad: %d\n\n%s", code, ui.ErrorWriter.String())
  1067  	}
  1068  
  1069  	if actual != "bar" {
  1070  		t.Fatal("didn't work")
  1071  	}
  1072  }
  1073  
  1074  func TestApply_backup(t *testing.T) {
  1075  	originalState := &terraform.State{
  1076  		Modules: []*terraform.ModuleState{
  1077  			&terraform.ModuleState{
  1078  				Path: []string{"root"},
  1079  				Resources: map[string]*terraform.ResourceState{
  1080  					"test_instance.foo": &terraform.ResourceState{
  1081  						Type: "test_instance",
  1082  						Primary: &terraform.InstanceState{
  1083  							ID: "bar",
  1084  						},
  1085  					},
  1086  				},
  1087  			},
  1088  		},
  1089  	}
  1090  
  1091  	statePath := testStateFile(t, originalState)
  1092  	backupPath := testTempFile(t)
  1093  
  1094  	p := testProvider()
  1095  	p.DiffReturn = &terraform.InstanceDiff{
  1096  		Attributes: map[string]*terraform.ResourceAttrDiff{
  1097  			"ami": &terraform.ResourceAttrDiff{
  1098  				New: "bar",
  1099  			},
  1100  		},
  1101  	}
  1102  
  1103  	ui := new(cli.MockUi)
  1104  	c := &ApplyCommand{
  1105  		Meta: Meta{
  1106  			ContextOpts: testCtxConfig(p),
  1107  			Ui:          ui,
  1108  		},
  1109  	}
  1110  
  1111  	// Run the apply command pointing to our existing state
  1112  	args := []string{
  1113  		"-state", statePath,
  1114  		"-backup", backupPath,
  1115  		testFixturePath("apply"),
  1116  	}
  1117  	if code := c.Run(args); code != 0 {
  1118  		t.Fatalf("bad: %d\n\n%s", code, ui.ErrorWriter.String())
  1119  	}
  1120  
  1121  	// Verify a new state exists
  1122  	if _, err := os.Stat(statePath); err != nil {
  1123  		t.Fatalf("err: %s", err)
  1124  	}
  1125  
  1126  	f, err := os.Open(statePath)
  1127  	if err != nil {
  1128  		t.Fatalf("err: %s", err)
  1129  	}
  1130  	defer f.Close()
  1131  
  1132  	state, err := terraform.ReadState(f)
  1133  	if err != nil {
  1134  		t.Fatalf("err: %s", err)
  1135  	}
  1136  	if state == nil {
  1137  		t.Fatal("state should not be nil")
  1138  	}
  1139  
  1140  	// Should have a backup file
  1141  	f, err = os.Open(backupPath)
  1142  	if err != nil {
  1143  		t.Fatalf("err: %s", err)
  1144  	}
  1145  
  1146  	backupState, err := terraform.ReadState(f)
  1147  	f.Close()
  1148  	if err != nil {
  1149  		t.Fatalf("err: %s", err)
  1150  	}
  1151  
  1152  	actual := backupState.RootModule().Resources["test_instance.foo"]
  1153  	expected := originalState.RootModule().Resources["test_instance.foo"]
  1154  	if !reflect.DeepEqual(actual, expected) {
  1155  		t.Fatalf("bad: %#v %#v", actual, expected)
  1156  	}
  1157  }
  1158  
  1159  func TestApply_disableBackup(t *testing.T) {
  1160  	originalState := testState()
  1161  	statePath := testStateFile(t, originalState)
  1162  
  1163  	p := testProvider()
  1164  	p.DiffReturn = &terraform.InstanceDiff{
  1165  		Attributes: map[string]*terraform.ResourceAttrDiff{
  1166  			"ami": &terraform.ResourceAttrDiff{
  1167  				New: "bar",
  1168  			},
  1169  		},
  1170  	}
  1171  
  1172  	ui := new(cli.MockUi)
  1173  	c := &ApplyCommand{
  1174  		Meta: Meta{
  1175  			ContextOpts: testCtxConfig(p),
  1176  			Ui:          ui,
  1177  		},
  1178  	}
  1179  
  1180  	// Run the apply command pointing to our existing state
  1181  	args := []string{
  1182  		"-state", statePath,
  1183  		"-backup", "-",
  1184  		testFixturePath("apply"),
  1185  	}
  1186  	if code := c.Run(args); code != 0 {
  1187  		t.Fatalf("bad: %d\n\n%s", code, ui.ErrorWriter.String())
  1188  	}
  1189  
  1190  	// Verify that the provider was called with the existing state
  1191  	actual := strings.TrimSpace(p.DiffState.String())
  1192  	expected := strings.TrimSpace(testApplyDisableBackupStr)
  1193  	if actual != expected {
  1194  		t.Fatalf("bad:\n\n%s", actual)
  1195  	}
  1196  
  1197  	actual = strings.TrimSpace(p.ApplyState.String())
  1198  	expected = strings.TrimSpace(testApplyDisableBackupStateStr)
  1199  	if actual != expected {
  1200  		t.Fatalf("bad:\n\n%s", actual)
  1201  	}
  1202  
  1203  	// Verify a new state exists
  1204  	if _, err := os.Stat(statePath); err != nil {
  1205  		t.Fatalf("err: %s", err)
  1206  	}
  1207  
  1208  	f, err := os.Open(statePath)
  1209  	if err != nil {
  1210  		t.Fatalf("err: %s", err)
  1211  	}
  1212  	defer f.Close()
  1213  
  1214  	state, err := terraform.ReadState(f)
  1215  	if err != nil {
  1216  		t.Fatalf("err: %s", err)
  1217  	}
  1218  	if state == nil {
  1219  		t.Fatal("state should not be nil")
  1220  	}
  1221  
  1222  	// Ensure there is no backup
  1223  	_, err = os.Stat(statePath + DefaultBackupExtension)
  1224  	if err == nil || !os.IsNotExist(err) {
  1225  		t.Fatalf("backup should not exist")
  1226  	}
  1227  
  1228  	// Ensure there is no literal "-"
  1229  	_, err = os.Stat("-")
  1230  	if err == nil || !os.IsNotExist(err) {
  1231  		t.Fatalf("backup should not exist")
  1232  	}
  1233  }
  1234  
  1235  func testHttpServer(t *testing.T) net.Listener {
  1236  	ln, err := net.Listen("tcp", "127.0.0.1:0")
  1237  	if err != nil {
  1238  		t.Fatalf("err: %s", err)
  1239  	}
  1240  
  1241  	mux := http.NewServeMux()
  1242  	mux.HandleFunc("/header", testHttpHandlerHeader)
  1243  
  1244  	var server http.Server
  1245  	server.Handler = mux
  1246  	go server.Serve(ln)
  1247  
  1248  	return ln
  1249  }
  1250  
  1251  func testHttpHandlerHeader(w http.ResponseWriter, r *http.Request) {
  1252  	var url url.URL
  1253  	url.Scheme = "file"
  1254  	url.Path = filepath.ToSlash(testFixturePath("init"))
  1255  
  1256  	w.Header().Add("X-Terraform-Get", url.String())
  1257  	w.WriteHeader(200)
  1258  }
  1259  
  1260  const applyVarFile = `
  1261  foo = "bar"
  1262  `
  1263  
  1264  const applyVarFileJSON = `
  1265  { "foo": "bar" }
  1266  `
  1267  
  1268  const testApplyDisableBackupStr = `
  1269  ID = bar
  1270  `
  1271  
  1272  const testApplyDisableBackupStateStr = `
  1273  ID = bar
  1274  `
  1275  
  1276  const testApplyStateStr = `
  1277  ID = bar
  1278  `
  1279  
  1280  const testApplyStateDiffStr = `
  1281  ID = bar
  1282  `