github.com/erriapo/terraform@v0.6.12-0.20160203182612-0340ea72354f/command/plan_test.go (about)

     1  package command
     2  
     3  import (
     4  	"bytes"
     5  	"io/ioutil"
     6  	"os"
     7  	"path/filepath"
     8  	"strings"
     9  	"testing"
    10  
    11  	"github.com/hashicorp/terraform/terraform"
    12  	"github.com/mitchellh/cli"
    13  )
    14  
    15  func TestPlan(t *testing.T) {
    16  	cwd, err := os.Getwd()
    17  	if err != nil {
    18  		t.Fatalf("err: %s", err)
    19  	}
    20  	if err := os.Chdir(testFixturePath("plan")); err != nil {
    21  		t.Fatalf("err: %s", err)
    22  	}
    23  	defer os.Chdir(cwd)
    24  
    25  	p := testProvider()
    26  	ui := new(cli.MockUi)
    27  	c := &PlanCommand{
    28  		Meta: Meta{
    29  			ContextOpts: testCtxConfig(p),
    30  			Ui:          ui,
    31  		},
    32  	}
    33  
    34  	args := []string{}
    35  	if code := c.Run(args); code != 0 {
    36  		t.Fatalf("bad: %d\n\n%s", code, ui.ErrorWriter.String())
    37  	}
    38  }
    39  
    40  func TestPlan_destroy(t *testing.T) {
    41  	originalState := &terraform.State{
    42  		Modules: []*terraform.ModuleState{
    43  			&terraform.ModuleState{
    44  				Path: []string{"root"},
    45  				Resources: map[string]*terraform.ResourceState{
    46  					"test_instance.foo": &terraform.ResourceState{
    47  						Type: "test_instance",
    48  						Primary: &terraform.InstanceState{
    49  							ID: "bar",
    50  						},
    51  					},
    52  				},
    53  			},
    54  		},
    55  	}
    56  
    57  	outPath := testTempFile(t)
    58  	statePath := testStateFile(t, originalState)
    59  
    60  	p := testProvider()
    61  	ui := new(cli.MockUi)
    62  	c := &PlanCommand{
    63  		Meta: Meta{
    64  			ContextOpts: testCtxConfig(p),
    65  			Ui:          ui,
    66  		},
    67  	}
    68  
    69  	args := []string{
    70  		"-destroy",
    71  		"-out", outPath,
    72  		"-state", statePath,
    73  		testFixturePath("plan"),
    74  	}
    75  	if code := c.Run(args); code != 0 {
    76  		t.Fatalf("bad: %d\n\n%s", code, ui.ErrorWriter.String())
    77  	}
    78  
    79  	if !p.RefreshCalled {
    80  		t.Fatal("refresh should be called")
    81  	}
    82  
    83  	plan := testReadPlan(t, outPath)
    84  	for _, m := range plan.Diff.Modules {
    85  		for _, r := range m.Resources {
    86  			if !r.Destroy {
    87  				t.Fatalf("bad: %#v", r)
    88  			}
    89  		}
    90  	}
    91  
    92  	f, err := os.Open(statePath + DefaultBackupExtension)
    93  	if err != nil {
    94  		t.Fatalf("err: %s", err)
    95  	}
    96  
    97  	backupState, err := terraform.ReadState(f)
    98  	f.Close()
    99  	if err != nil {
   100  		t.Fatalf("err: %s", err)
   101  	}
   102  
   103  	actualStr := strings.TrimSpace(backupState.String())
   104  	expectedStr := strings.TrimSpace(originalState.String())
   105  	if actualStr != expectedStr {
   106  		t.Fatalf("bad:\n\n%s\n\n%s", actualStr, expectedStr)
   107  	}
   108  }
   109  
   110  func TestPlan_noState(t *testing.T) {
   111  	p := testProvider()
   112  	ui := new(cli.MockUi)
   113  	c := &PlanCommand{
   114  		Meta: Meta{
   115  			ContextOpts: testCtxConfig(p),
   116  			Ui:          ui,
   117  		},
   118  	}
   119  
   120  	args := []string{
   121  		testFixturePath("plan"),
   122  	}
   123  	if code := c.Run(args); code != 0 {
   124  		t.Fatalf("bad: %d\n\n%s", code, ui.ErrorWriter.String())
   125  	}
   126  
   127  	// Verify that refresh was called
   128  	if p.RefreshCalled {
   129  		t.Fatal("refresh should not be called")
   130  	}
   131  
   132  	// Verify that the provider was called with the existing state
   133  	actual := strings.TrimSpace(p.DiffState.String())
   134  	expected := strings.TrimSpace(testPlanNoStateStr)
   135  	if actual != expected {
   136  		t.Fatalf("bad:\n\n%s", actual)
   137  	}
   138  }
   139  
   140  func TestPlan_outPath(t *testing.T) {
   141  	tf, err := ioutil.TempFile("", "tf")
   142  	if err != nil {
   143  		t.Fatalf("err: %s", err)
   144  	}
   145  	outPath := tf.Name()
   146  	os.Remove(tf.Name())
   147  
   148  	p := testProvider()
   149  	ui := new(cli.MockUi)
   150  	c := &PlanCommand{
   151  		Meta: Meta{
   152  			ContextOpts: testCtxConfig(p),
   153  			Ui:          ui,
   154  		},
   155  	}
   156  
   157  	p.DiffReturn = &terraform.InstanceDiff{
   158  		Destroy: true,
   159  	}
   160  
   161  	args := []string{
   162  		"-out", outPath,
   163  		testFixturePath("plan"),
   164  	}
   165  	if code := c.Run(args); code != 0 {
   166  		t.Fatalf("bad: %d\n\n%s", code, ui.ErrorWriter.String())
   167  	}
   168  
   169  	f, err := os.Open(outPath)
   170  	if err != nil {
   171  		t.Fatalf("err: %s", err)
   172  	}
   173  	defer f.Close()
   174  
   175  	if _, err := terraform.ReadPlan(f); err != nil {
   176  		t.Fatalf("err: %s", err)
   177  	}
   178  }
   179  
   180  func TestPlan_outPathNoChange(t *testing.T) {
   181  	originalState := &terraform.State{
   182  		Modules: []*terraform.ModuleState{
   183  			&terraform.ModuleState{
   184  				Path: []string{"root"},
   185  				Resources: map[string]*terraform.ResourceState{
   186  					"test_instance.foo": &terraform.ResourceState{
   187  						Type: "test_instance",
   188  						Primary: &terraform.InstanceState{
   189  							ID: "bar",
   190  						},
   191  					},
   192  				},
   193  			},
   194  		},
   195  	}
   196  	statePath := testStateFile(t, originalState)
   197  
   198  	tf, err := ioutil.TempFile("", "tf")
   199  	if err != nil {
   200  		t.Fatalf("err: %s", err)
   201  	}
   202  	outPath := tf.Name()
   203  	os.Remove(tf.Name())
   204  
   205  	p := testProvider()
   206  	ui := new(cli.MockUi)
   207  	c := &PlanCommand{
   208  		Meta: Meta{
   209  			ContextOpts: testCtxConfig(p),
   210  			Ui:          ui,
   211  		},
   212  	}
   213  
   214  	args := []string{
   215  		"-out", outPath,
   216  		"-state", statePath,
   217  		testFixturePath("plan"),
   218  	}
   219  	if code := c.Run(args); code != 0 {
   220  		t.Fatalf("bad: %d\n\n%s", code, ui.ErrorWriter.String())
   221  	}
   222  
   223  	plan := testReadPlan(t, outPath)
   224  	if !plan.Diff.Empty() {
   225  		t.Fatalf("Expected empty plan to be written to plan file, got: %s", plan)
   226  	}
   227  }
   228  
   229  func TestPlan_refresh(t *testing.T) {
   230  	p := testProvider()
   231  	ui := new(cli.MockUi)
   232  	c := &PlanCommand{
   233  		Meta: Meta{
   234  			ContextOpts: testCtxConfig(p),
   235  			Ui:          ui,
   236  		},
   237  	}
   238  
   239  	args := []string{
   240  		"-refresh=false",
   241  		testFixturePath("plan"),
   242  	}
   243  	if code := c.Run(args); code != 0 {
   244  		t.Fatalf("bad: %d\n\n%s", code, ui.ErrorWriter.String())
   245  	}
   246  
   247  	if p.RefreshCalled {
   248  		t.Fatal("refresh should not be called")
   249  	}
   250  }
   251  
   252  func TestPlan_state(t *testing.T) {
   253  	// Write out some prior state
   254  	tf, err := ioutil.TempFile("", "tf")
   255  	if err != nil {
   256  		t.Fatalf("err: %s", err)
   257  	}
   258  	statePath := tf.Name()
   259  	defer os.Remove(tf.Name())
   260  
   261  	originalState := testState()
   262  	err = terraform.WriteState(originalState, tf)
   263  	tf.Close()
   264  	if err != nil {
   265  		t.Fatalf("err: %s", err)
   266  	}
   267  
   268  	p := testProvider()
   269  	ui := new(cli.MockUi)
   270  	c := &PlanCommand{
   271  		Meta: Meta{
   272  			ContextOpts: testCtxConfig(p),
   273  			Ui:          ui,
   274  		},
   275  	}
   276  
   277  	args := []string{
   278  		"-state", statePath,
   279  		testFixturePath("plan"),
   280  	}
   281  	if code := c.Run(args); code != 0 {
   282  		t.Fatalf("bad: %d\n\n%s", code, ui.ErrorWriter.String())
   283  	}
   284  
   285  	// Verify that the provider was called with the existing state
   286  	actual := strings.TrimSpace(p.DiffState.String())
   287  	expected := strings.TrimSpace(testPlanStateStr)
   288  	if actual != expected {
   289  		t.Fatalf("bad:\n\n%s", actual)
   290  	}
   291  }
   292  
   293  func TestPlan_stateDefault(t *testing.T) {
   294  	originalState := testState()
   295  
   296  	// Write the state file in a temporary directory with the
   297  	// default filename.
   298  	td, err := ioutil.TempDir("", "tf")
   299  	if err != nil {
   300  		t.Fatalf("err: %s", err)
   301  	}
   302  	statePath := filepath.Join(td, DefaultStateFilename)
   303  
   304  	f, err := os.Create(statePath)
   305  	if err != nil {
   306  		t.Fatalf("err: %s", err)
   307  	}
   308  	err = terraform.WriteState(originalState, f)
   309  	f.Close()
   310  	if err != nil {
   311  		t.Fatalf("err: %s", err)
   312  	}
   313  
   314  	// Change to that directory
   315  	cwd, err := os.Getwd()
   316  	if err != nil {
   317  		t.Fatalf("err: %s", err)
   318  	}
   319  	if err := os.Chdir(filepath.Dir(statePath)); err != nil {
   320  		t.Fatalf("err: %s", err)
   321  	}
   322  	defer os.Chdir(cwd)
   323  
   324  	p := testProvider()
   325  	ui := new(cli.MockUi)
   326  	c := &PlanCommand{
   327  		Meta: Meta{
   328  			ContextOpts: testCtxConfig(p),
   329  			Ui:          ui,
   330  		},
   331  	}
   332  
   333  	args := []string{
   334  		testFixturePath("plan"),
   335  	}
   336  	if code := c.Run(args); code != 0 {
   337  		t.Fatalf("bad: %d\n\n%s", code, ui.ErrorWriter.String())
   338  	}
   339  
   340  	// Verify that the provider was called with the existing state
   341  	actual := strings.TrimSpace(p.DiffState.String())
   342  	expected := strings.TrimSpace(testPlanStateDefaultStr)
   343  	if actual != expected {
   344  		t.Fatalf("bad:\n\n%s", actual)
   345  	}
   346  }
   347  
   348  func TestPlan_vars(t *testing.T) {
   349  	p := testProvider()
   350  	ui := new(cli.MockUi)
   351  	c := &PlanCommand{
   352  		Meta: Meta{
   353  			ContextOpts: testCtxConfig(p),
   354  			Ui:          ui,
   355  		},
   356  	}
   357  
   358  	actual := ""
   359  	p.DiffFn = func(
   360  		info *terraform.InstanceInfo,
   361  		s *terraform.InstanceState,
   362  		c *terraform.ResourceConfig) (*terraform.InstanceDiff, error) {
   363  		if v, ok := c.Config["value"]; ok {
   364  			actual = v.(string)
   365  		}
   366  
   367  		return nil, nil
   368  	}
   369  
   370  	args := []string{
   371  		"-var", "foo=bar",
   372  		testFixturePath("plan-vars"),
   373  	}
   374  	if code := c.Run(args); code != 0 {
   375  		t.Fatalf("bad: %d\n\n%s", code, ui.ErrorWriter.String())
   376  	}
   377  
   378  	if actual != "bar" {
   379  		t.Fatal("didn't work")
   380  	}
   381  }
   382  
   383  func TestPlan_varsUnset(t *testing.T) {
   384  	// Disable test mode so input would be asked
   385  	test = false
   386  	defer func() { test = true }()
   387  
   388  	defaultInputReader = bytes.NewBufferString("bar\n")
   389  
   390  	p := testProvider()
   391  	ui := new(cli.MockUi)
   392  	c := &PlanCommand{
   393  		Meta: Meta{
   394  			ContextOpts: testCtxConfig(p),
   395  			Ui:          ui,
   396  		},
   397  	}
   398  
   399  	args := []string{
   400  		testFixturePath("plan-vars"),
   401  	}
   402  	if code := c.Run(args); code != 0 {
   403  		t.Fatalf("bad: %d\n\n%s", code, ui.ErrorWriter.String())
   404  	}
   405  }
   406  
   407  func TestPlan_varFile(t *testing.T) {
   408  	varFilePath := testTempFile(t)
   409  	if err := ioutil.WriteFile(varFilePath, []byte(planVarFile), 0644); err != nil {
   410  		t.Fatalf("err: %s", err)
   411  	}
   412  
   413  	p := testProvider()
   414  	ui := new(cli.MockUi)
   415  	c := &PlanCommand{
   416  		Meta: Meta{
   417  			ContextOpts: testCtxConfig(p),
   418  			Ui:          ui,
   419  		},
   420  	}
   421  
   422  	actual := ""
   423  	p.DiffFn = func(
   424  		info *terraform.InstanceInfo,
   425  		s *terraform.InstanceState,
   426  		c *terraform.ResourceConfig) (*terraform.InstanceDiff, error) {
   427  		if v, ok := c.Config["value"]; ok {
   428  			actual = v.(string)
   429  		}
   430  
   431  		return nil, nil
   432  	}
   433  
   434  	args := []string{
   435  		"-var-file", varFilePath,
   436  		testFixturePath("plan-vars"),
   437  	}
   438  	if code := c.Run(args); code != 0 {
   439  		t.Fatalf("bad: %d\n\n%s", code, ui.ErrorWriter.String())
   440  	}
   441  
   442  	if actual != "bar" {
   443  		t.Fatal("didn't work")
   444  	}
   445  }
   446  
   447  func TestPlan_varFileDefault(t *testing.T) {
   448  	varFileDir := testTempDir(t)
   449  	varFilePath := filepath.Join(varFileDir, "terraform.tfvars")
   450  	if err := ioutil.WriteFile(varFilePath, []byte(planVarFile), 0644); err != nil {
   451  		t.Fatalf("err: %s", err)
   452  	}
   453  
   454  	cwd, err := os.Getwd()
   455  	if err != nil {
   456  		t.Fatalf("err: %s", err)
   457  	}
   458  	if err := os.Chdir(varFileDir); err != nil {
   459  		t.Fatalf("err: %s", err)
   460  	}
   461  	defer os.Chdir(cwd)
   462  
   463  	p := testProvider()
   464  	ui := new(cli.MockUi)
   465  	c := &PlanCommand{
   466  		Meta: Meta{
   467  			ContextOpts: testCtxConfig(p),
   468  			Ui:          ui,
   469  		},
   470  	}
   471  
   472  	actual := ""
   473  	p.DiffFn = func(
   474  		info *terraform.InstanceInfo,
   475  		s *terraform.InstanceState,
   476  		c *terraform.ResourceConfig) (*terraform.InstanceDiff, error) {
   477  		if v, ok := c.Config["value"]; ok {
   478  			actual = v.(string)
   479  		}
   480  
   481  		return nil, nil
   482  	}
   483  
   484  	args := []string{
   485  		testFixturePath("plan-vars"),
   486  	}
   487  	if code := c.Run(args); code != 0 {
   488  		t.Fatalf("bad: %d\n\n%s", code, ui.ErrorWriter.String())
   489  	}
   490  
   491  	if actual != "bar" {
   492  		t.Fatal("didn't work")
   493  	}
   494  }
   495  
   496  func TestPlan_backup(t *testing.T) {
   497  	// Write out some prior state
   498  	tf, err := ioutil.TempFile("", "tf")
   499  	if err != nil {
   500  		t.Fatalf("err: %s", err)
   501  	}
   502  	statePath := tf.Name()
   503  	defer os.Remove(tf.Name())
   504  
   505  	// Write out some prior state
   506  	backupf, err := ioutil.TempFile("", "tf")
   507  	if err != nil {
   508  		t.Fatalf("err: %s", err)
   509  	}
   510  	backupPath := backupf.Name()
   511  	backupf.Close()
   512  	os.Remove(backupPath)
   513  	defer os.Remove(backupPath)
   514  
   515  	originalState := &terraform.State{
   516  		Modules: []*terraform.ModuleState{
   517  			&terraform.ModuleState{
   518  				Path: []string{"root"},
   519  				Resources: map[string]*terraform.ResourceState{
   520  					"test_instance.foo": &terraform.ResourceState{
   521  						Type: "test_instance",
   522  						Primary: &terraform.InstanceState{
   523  							ID: "bar",
   524  						},
   525  					},
   526  				},
   527  			},
   528  		},
   529  	}
   530  
   531  	err = terraform.WriteState(originalState, tf)
   532  	tf.Close()
   533  	if err != nil {
   534  		t.Fatalf("err: %s", err)
   535  	}
   536  
   537  	p := testProvider()
   538  	ui := new(cli.MockUi)
   539  	c := &PlanCommand{
   540  		Meta: Meta{
   541  			ContextOpts: testCtxConfig(p),
   542  			Ui:          ui,
   543  		},
   544  	}
   545  
   546  	args := []string{
   547  		"-state", statePath,
   548  		"-backup", backupPath,
   549  		testFixturePath("plan"),
   550  	}
   551  	if code := c.Run(args); code != 0 {
   552  		t.Fatalf("bad: %d\n\n%s", code, ui.ErrorWriter.String())
   553  	}
   554  
   555  	// Verify that the provider was called with the existing state
   556  	actual := strings.TrimSpace(p.DiffState.String())
   557  	expected := strings.TrimSpace(testPlanBackupStr)
   558  	if actual != expected {
   559  		t.Fatalf("bad:\n\n%s", actual)
   560  	}
   561  
   562  	// Verify the backup exist
   563  	f, err := os.Open(backupPath)
   564  	if err != nil {
   565  		t.Fatalf("err: %s", err)
   566  	}
   567  
   568  	backupState, err := terraform.ReadState(f)
   569  	f.Close()
   570  	if err != nil {
   571  		t.Fatalf("err: %s", err)
   572  	}
   573  
   574  	actualStr := strings.TrimSpace(backupState.String())
   575  	expectedStr := strings.TrimSpace(originalState.String())
   576  	if actualStr != expectedStr {
   577  		t.Fatalf("bad:\n\n%s\n\n%s", actualStr, expectedStr)
   578  	}
   579  }
   580  
   581  func TestPlan_disableBackup(t *testing.T) {
   582  	// Write out some prior state
   583  	tf, err := ioutil.TempFile("", "tf")
   584  	if err != nil {
   585  		t.Fatalf("err: %s", err)
   586  	}
   587  	statePath := tf.Name()
   588  	defer os.Remove(tf.Name())
   589  
   590  	originalState := &terraform.State{
   591  		Modules: []*terraform.ModuleState{
   592  			&terraform.ModuleState{
   593  				Path: []string{"root"},
   594  				Resources: map[string]*terraform.ResourceState{
   595  					"test_instance.foo": &terraform.ResourceState{
   596  						Type: "test_instance",
   597  						Primary: &terraform.InstanceState{
   598  							ID: "bar",
   599  						},
   600  					},
   601  				},
   602  			},
   603  		},
   604  	}
   605  
   606  	err = terraform.WriteState(originalState, tf)
   607  	tf.Close()
   608  	if err != nil {
   609  		t.Fatalf("err: %s", err)
   610  	}
   611  
   612  	p := testProvider()
   613  	ui := new(cli.MockUi)
   614  	c := &PlanCommand{
   615  		Meta: Meta{
   616  			ContextOpts: testCtxConfig(p),
   617  			Ui:          ui,
   618  		},
   619  	}
   620  
   621  	args := []string{
   622  		"-state", statePath,
   623  		"-backup", "-",
   624  		testFixturePath("plan"),
   625  	}
   626  	if code := c.Run(args); code != 0 {
   627  		t.Fatalf("bad: %d\n\n%s", code, ui.ErrorWriter.String())
   628  	}
   629  
   630  	// Verify that the provider was called with the existing state
   631  	actual := strings.TrimSpace(p.DiffState.String())
   632  	expected := strings.TrimSpace(testPlanDisableBackupStr)
   633  	if actual != expected {
   634  		t.Fatalf("bad:\n\n%s", actual)
   635  	}
   636  
   637  	// Ensure there is no backup
   638  	_, err = os.Stat(statePath + DefaultBackupExtension)
   639  	if err == nil || !os.IsNotExist(err) {
   640  		t.Fatalf("backup should not exist")
   641  	}
   642  }
   643  
   644  func TestPlan_detailedExitcode(t *testing.T) {
   645  	cwd, err := os.Getwd()
   646  	if err != nil {
   647  		t.Fatalf("err: %s", err)
   648  	}
   649  	if err := os.Chdir(testFixturePath("plan")); err != nil {
   650  		t.Fatalf("err: %s", err)
   651  	}
   652  	defer os.Chdir(cwd)
   653  
   654  	p := testProvider()
   655  	ui := new(cli.MockUi)
   656  	c := &PlanCommand{
   657  		Meta: Meta{
   658  			ContextOpts: testCtxConfig(p),
   659  			Ui:          ui,
   660  		},
   661  	}
   662  
   663  	args := []string{"-detailed-exitcode"}
   664  	if code := c.Run(args); code != 2 {
   665  		t.Fatalf("bad: %d\n\n%s", code, ui.ErrorWriter.String())
   666  	}
   667  }
   668  
   669  func TestPlan_detailedExitcode_emptyDiff(t *testing.T) {
   670  	cwd, err := os.Getwd()
   671  	if err != nil {
   672  		t.Fatalf("err: %s", err)
   673  	}
   674  	if err := os.Chdir(testFixturePath("plan-emptydiff")); err != nil {
   675  		t.Fatalf("err: %s", err)
   676  	}
   677  	defer os.Chdir(cwd)
   678  
   679  	p := testProvider()
   680  	ui := new(cli.MockUi)
   681  	c := &PlanCommand{
   682  		Meta: Meta{
   683  			ContextOpts: testCtxConfig(p),
   684  			Ui:          ui,
   685  		},
   686  	}
   687  
   688  	args := []string{"-detailed-exitcode"}
   689  	if code := c.Run(args); code != 0 {
   690  		t.Fatalf("bad: %d\n\n%s", code, ui.ErrorWriter.String())
   691  	}
   692  }
   693  
   694  const planVarFile = `
   695  foo = "bar"
   696  `
   697  
   698  const testPlanBackupStr = `
   699  ID = bar
   700  `
   701  
   702  const testPlanDisableBackupStr = `
   703  ID = bar
   704  `
   705  
   706  const testPlanNoStateStr = `
   707  <not created>
   708  `
   709  
   710  const testPlanStateStr = `
   711  ID = bar
   712  `
   713  
   714  const testPlanStateDefaultStr = `
   715  ID = bar
   716  `