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