github.com/boyvanduuren/terraform@v0.7.0-rc2.0.20160805175930-de822d909c40/command/push_test.go (about)

     1  package command
     2  
     3  import (
     4  	"archive/tar"
     5  	"bytes"
     6  	"compress/gzip"
     7  	"io"
     8  	"os"
     9  	"reflect"
    10  	"sort"
    11  	"testing"
    12  
    13  	atlas "github.com/hashicorp/atlas-go/v1"
    14  	"github.com/hashicorp/terraform/terraform"
    15  	"github.com/mitchellh/cli"
    16  )
    17  
    18  func TestPush_good(t *testing.T) {
    19  	tmp, cwd := testCwd(t)
    20  	defer testFixCwd(t, tmp, cwd)
    21  
    22  	// Create remote state file, this should be pulled
    23  	conf, srv := testRemoteState(t, testState(), 200)
    24  	defer srv.Close()
    25  
    26  	// Persist local remote state
    27  	s := terraform.NewState()
    28  	s.Serial = 5
    29  	s.Remote = conf
    30  	testStateFileRemote(t, s)
    31  
    32  	// Path where the archive will be "uploaded" to
    33  	archivePath := testTempFile(t)
    34  	defer os.Remove(archivePath)
    35  
    36  	client := &mockPushClient{File: archivePath}
    37  	ui := new(cli.MockUi)
    38  	c := &PushCommand{
    39  		Meta: Meta{
    40  			ContextOpts: testCtxConfig(testProvider()),
    41  			Ui:          ui,
    42  		},
    43  
    44  		client: client,
    45  	}
    46  
    47  	args := []string{
    48  		"-vcs=false",
    49  		testFixturePath("push"),
    50  	}
    51  	if code := c.Run(args); code != 0 {
    52  		t.Fatalf("bad: %d\n\n%s", code, ui.ErrorWriter.String())
    53  	}
    54  
    55  	actual := testArchiveStr(t, archivePath)
    56  	expected := []string{
    57  		".terraform/",
    58  		".terraform/terraform.tfstate",
    59  		"main.tf",
    60  	}
    61  	if !reflect.DeepEqual(actual, expected) {
    62  		t.Fatalf("bad: %#v", actual)
    63  	}
    64  
    65  	variables := make(map[string]interface{})
    66  	if !reflect.DeepEqual(client.UpsertOptions.Variables, variables) {
    67  		t.Fatalf("bad: %#v", client.UpsertOptions)
    68  	}
    69  
    70  	if client.UpsertOptions.Name != "foo" {
    71  		t.Fatalf("bad: %#v", client.UpsertOptions)
    72  	}
    73  }
    74  
    75  func TestPush_input(t *testing.T) {
    76  	tmp, cwd := testCwd(t)
    77  	defer testFixCwd(t, tmp, cwd)
    78  
    79  	// Create remote state file, this should be pulled
    80  	conf, srv := testRemoteState(t, testState(), 200)
    81  	defer srv.Close()
    82  
    83  	// Persist local remote state
    84  	s := terraform.NewState()
    85  	s.Serial = 5
    86  	s.Remote = conf
    87  	testStateFileRemote(t, s)
    88  
    89  	// Path where the archive will be "uploaded" to
    90  	archivePath := testTempFile(t)
    91  	defer os.Remove(archivePath)
    92  
    93  	client := &mockPushClient{File: archivePath}
    94  	ui := new(cli.MockUi)
    95  	c := &PushCommand{
    96  		Meta: Meta{
    97  			ContextOpts: testCtxConfig(testProvider()),
    98  			Ui:          ui,
    99  		},
   100  
   101  		client: client,
   102  	}
   103  
   104  	// Disable test mode so input would be asked and setup the
   105  	// input reader/writers.
   106  	test = false
   107  	defer func() { test = true }()
   108  	defaultInputReader = bytes.NewBufferString("foo\n")
   109  	defaultInputWriter = new(bytes.Buffer)
   110  
   111  	args := []string{
   112  		"-vcs=false",
   113  		testFixturePath("push-input"),
   114  	}
   115  	if code := c.Run(args); code != 0 {
   116  		t.Fatalf("bad: %d\n\n%s", code, ui.ErrorWriter.String())
   117  	}
   118  
   119  	variables := map[string]interface{}{
   120  		"foo": "foo",
   121  	}
   122  
   123  	if !reflect.DeepEqual(client.UpsertOptions.Variables, variables) {
   124  		t.Fatalf("bad: %#v", client.UpsertOptions.Variables)
   125  	}
   126  }
   127  
   128  // We want a variable from atlas to fill a missing variable locally
   129  func TestPush_inputPartial(t *testing.T) {
   130  	tmp, cwd := testCwd(t)
   131  	defer testFixCwd(t, tmp, cwd)
   132  
   133  	// Create remote state file, this should be pulled
   134  	conf, srv := testRemoteState(t, testState(), 200)
   135  	defer srv.Close()
   136  
   137  	// Persist local remote state
   138  	s := terraform.NewState()
   139  	s.Serial = 5
   140  	s.Remote = conf
   141  	testStateFileRemote(t, s)
   142  
   143  	// Path where the archive will be "uploaded" to
   144  	archivePath := testTempFile(t)
   145  	defer os.Remove(archivePath)
   146  
   147  	client := &mockPushClient{
   148  		File: archivePath,
   149  		GetResult: map[string]atlas.TFVar{
   150  			"foo": atlas.TFVar{Key: "foo", Value: "bar"},
   151  		},
   152  	}
   153  	ui := new(cli.MockUi)
   154  	c := &PushCommand{
   155  		Meta: Meta{
   156  			ContextOpts: testCtxConfig(testProvider()),
   157  			Ui:          ui,
   158  		},
   159  
   160  		client: client,
   161  	}
   162  
   163  	// Disable test mode so input would be asked and setup the
   164  	// input reader/writers.
   165  	test = false
   166  	defer func() { test = true }()
   167  	defaultInputReader = bytes.NewBufferString("foo\n")
   168  	defaultInputWriter = new(bytes.Buffer)
   169  
   170  	args := []string{
   171  		"-vcs=false",
   172  		testFixturePath("push-input-partial"),
   173  	}
   174  	if code := c.Run(args); code != 0 {
   175  		t.Fatalf("bad: %d\n\n%s", code, ui.ErrorWriter.String())
   176  	}
   177  
   178  	expectedTFVars := []atlas.TFVar{
   179  		{Key: "bar", Value: "foo"},
   180  		{Key: "foo", Value: "bar"},
   181  	}
   182  	if !reflect.DeepEqual(client.UpsertOptions.TFVars, expectedTFVars) {
   183  		t.Logf("expected: %#v", expectedTFVars)
   184  		t.Fatalf("got:      %#v", client.UpsertOptions.TFVars)
   185  	}
   186  }
   187  
   188  // This tests that the push command will override Atlas variables
   189  // if requested.
   190  func TestPush_localOverride(t *testing.T) {
   191  	// Disable test mode so input would be asked and setup the
   192  	// input reader/writers.
   193  	test = false
   194  	defer func() { test = true }()
   195  	defaultInputReader = bytes.NewBufferString("nope\n")
   196  	defaultInputWriter = new(bytes.Buffer)
   197  
   198  	tmp, cwd := testCwd(t)
   199  	defer testFixCwd(t, tmp, cwd)
   200  
   201  	// Create remote state file, this should be pulled
   202  	conf, srv := testRemoteState(t, testState(), 200)
   203  	defer srv.Close()
   204  
   205  	// Persist local remote state
   206  	s := terraform.NewState()
   207  	s.Serial = 5
   208  	s.Remote = conf
   209  	testStateFileRemote(t, s)
   210  
   211  	// Path where the archive will be "uploaded" to
   212  	archivePath := testTempFile(t)
   213  	defer os.Remove(archivePath)
   214  
   215  	client := &mockPushClient{File: archivePath}
   216  	// Provided vars should override existing ones
   217  	client.GetResult = map[string]atlas.TFVar{
   218  		"foo": atlas.TFVar{
   219  			Key:   "foo",
   220  			Value: "old",
   221  		},
   222  	}
   223  	ui := new(cli.MockUi)
   224  	c := &PushCommand{
   225  		Meta: Meta{
   226  			ContextOpts: testCtxConfig(testProvider()),
   227  			Ui:          ui,
   228  		},
   229  
   230  		client: client,
   231  	}
   232  
   233  	path := testFixturePath("push-tfvars")
   234  	args := []string{
   235  		"-var-file", path + "/terraform.tfvars",
   236  		"-vcs=false",
   237  		"-overwrite=foo",
   238  		path,
   239  	}
   240  	if code := c.Run(args); code != 0 {
   241  		t.Fatalf("bad: %d\n\n%s", code, ui.ErrorWriter.String())
   242  	}
   243  
   244  	actual := testArchiveStr(t, archivePath)
   245  	expected := []string{
   246  		".terraform/",
   247  		".terraform/terraform.tfstate",
   248  		"main.tf",
   249  		"terraform.tfvars",
   250  	}
   251  	if !reflect.DeepEqual(actual, expected) {
   252  		t.Fatalf("bad: %#v", actual)
   253  	}
   254  
   255  	if client.UpsertOptions.Name != "foo" {
   256  		t.Fatalf("bad: %#v", client.UpsertOptions)
   257  	}
   258  
   259  	expectedTFVars := pushTFVars()
   260  
   261  	if !reflect.DeepEqual(client.UpsertOptions.TFVars, expectedTFVars) {
   262  		t.Logf("expected: %#v", expectedTFVars)
   263  		t.Fatalf("got:    %#v", client.UpsertOptions.TFVars)
   264  	}
   265  }
   266  
   267  // This tests that the push command will override Atlas variables
   268  // even if we don't have it defined locally
   269  func TestPush_remoteOverride(t *testing.T) {
   270  	// Disable test mode so input would be asked and setup the
   271  	// input reader/writers.
   272  	test = false
   273  	defer func() { test = true }()
   274  	defaultInputReader = bytes.NewBufferString("nope\n")
   275  	defaultInputWriter = new(bytes.Buffer)
   276  
   277  	tmp, cwd := testCwd(t)
   278  	defer testFixCwd(t, tmp, cwd)
   279  
   280  	// Create remote state file, this should be pulled
   281  	conf, srv := testRemoteState(t, testState(), 200)
   282  	defer srv.Close()
   283  
   284  	// Persist local remote state
   285  	s := terraform.NewState()
   286  	s.Serial = 5
   287  	s.Remote = conf
   288  	testStateFileRemote(t, s)
   289  
   290  	// Path where the archive will be "uploaded" to
   291  	archivePath := testTempFile(t)
   292  	defer os.Remove(archivePath)
   293  
   294  	client := &mockPushClient{File: archivePath}
   295  	// Provided vars should override existing ones
   296  	client.GetResult = map[string]atlas.TFVar{
   297  		"remote": atlas.TFVar{
   298  			Key:   "remote",
   299  			Value: "old",
   300  		},
   301  	}
   302  	ui := new(cli.MockUi)
   303  	c := &PushCommand{
   304  		Meta: Meta{
   305  			ContextOpts: testCtxConfig(testProvider()),
   306  			Ui:          ui,
   307  		},
   308  
   309  		client: client,
   310  	}
   311  
   312  	path := testFixturePath("push-tfvars")
   313  	args := []string{
   314  		"-var-file", path + "/terraform.tfvars",
   315  		"-vcs=false",
   316  		"-overwrite=remote",
   317  		"-var",
   318  		"remote=new",
   319  		path,
   320  	}
   321  
   322  	if code := c.Run(args); code != 0 {
   323  		t.Fatalf("bad: %d\n\n%s", code, ui.ErrorWriter.String())
   324  	}
   325  
   326  	actual := testArchiveStr(t, archivePath)
   327  	expected := []string{
   328  		".terraform/",
   329  		".terraform/terraform.tfstate",
   330  		"main.tf",
   331  		"terraform.tfvars",
   332  	}
   333  	if !reflect.DeepEqual(actual, expected) {
   334  		t.Fatalf("bad: %#v", actual)
   335  	}
   336  
   337  	if client.UpsertOptions.Name != "foo" {
   338  		t.Fatalf("bad: %#v", client.UpsertOptions)
   339  	}
   340  
   341  	found := false
   342  	// find the "remote" var and make sure we're going to set it
   343  	for _, tfVar := range client.UpsertOptions.TFVars {
   344  		if tfVar.Key == "remote" {
   345  			found = true
   346  			if tfVar.Value != "new" {
   347  				t.Log("'remote' variable should be set to 'new'")
   348  				t.Fatalf("sending instead: %#v", tfVar)
   349  			}
   350  		}
   351  	}
   352  
   353  	if !found {
   354  		t.Fatal("'remote' variable not being sent to atlas")
   355  	}
   356  }
   357  
   358  // This tests that the push command prefers Atlas variables over
   359  // local ones.
   360  func TestPush_preferAtlas(t *testing.T) {
   361  	// Disable test mode so input would be asked and setup the
   362  	// input reader/writers.
   363  	test = false
   364  	defer func() { test = true }()
   365  	defaultInputReader = bytes.NewBufferString("nope\n")
   366  	defaultInputWriter = new(bytes.Buffer)
   367  
   368  	tmp, cwd := testCwd(t)
   369  	defer testFixCwd(t, tmp, cwd)
   370  
   371  	// Create remote state file, this should be pulled
   372  	conf, srv := testRemoteState(t, testState(), 200)
   373  	defer srv.Close()
   374  
   375  	// Persist local remote state
   376  	s := terraform.NewState()
   377  	s.Serial = 5
   378  	s.Remote = conf
   379  	testStateFileRemote(t, s)
   380  
   381  	// Path where the archive will be "uploaded" to
   382  	archivePath := testTempFile(t)
   383  	defer os.Remove(archivePath)
   384  
   385  	client := &mockPushClient{File: archivePath}
   386  	// Provided vars should override existing ones
   387  	client.GetResult = map[string]atlas.TFVar{
   388  		"foo": atlas.TFVar{
   389  			Key:   "foo",
   390  			Value: "old",
   391  		},
   392  	}
   393  	ui := new(cli.MockUi)
   394  	c := &PushCommand{
   395  		Meta: Meta{
   396  			ContextOpts: testCtxConfig(testProvider()),
   397  			Ui:          ui,
   398  		},
   399  
   400  		client: client,
   401  	}
   402  
   403  	path := testFixturePath("push-tfvars")
   404  	args := []string{
   405  		"-var-file", path + "/terraform.tfvars",
   406  		"-vcs=false",
   407  		path,
   408  	}
   409  	if code := c.Run(args); code != 0 {
   410  		t.Fatalf("bad: %d\n\n%s", code, ui.ErrorWriter.String())
   411  	}
   412  
   413  	actual := testArchiveStr(t, archivePath)
   414  	expected := []string{
   415  		".terraform/",
   416  		".terraform/terraform.tfstate",
   417  		"main.tf",
   418  		"terraform.tfvars",
   419  	}
   420  	if !reflect.DeepEqual(actual, expected) {
   421  		t.Fatalf("bad: %#v", actual)
   422  	}
   423  
   424  	if client.UpsertOptions.Name != "foo" {
   425  		t.Fatalf("bad: %#v", client.UpsertOptions)
   426  	}
   427  
   428  	// change the expected response to match our change
   429  	expectedTFVars := pushTFVars()
   430  	for i, v := range expectedTFVars {
   431  		if v.Key == "foo" {
   432  			expectedTFVars[i] = atlas.TFVar{Key: "foo", Value: "old"}
   433  		}
   434  	}
   435  
   436  	if !reflect.DeepEqual(expectedTFVars, client.UpsertOptions.TFVars) {
   437  		t.Logf("expected: %#v", expectedTFVars)
   438  		t.Fatalf("got:      %#v", client.UpsertOptions.TFVars)
   439  	}
   440  }
   441  
   442  // This tests that the push command will send the variables in tfvars
   443  func TestPush_tfvars(t *testing.T) {
   444  	// Disable test mode so input would be asked and setup the
   445  	// input reader/writers.
   446  	test = false
   447  	defer func() { test = true }()
   448  	defaultInputReader = bytes.NewBufferString("nope\n")
   449  	defaultInputWriter = new(bytes.Buffer)
   450  
   451  	tmp, cwd := testCwd(t)
   452  	defer testFixCwd(t, tmp, cwd)
   453  
   454  	// Create remote state file, this should be pulled
   455  	conf, srv := testRemoteState(t, testState(), 200)
   456  	defer srv.Close()
   457  
   458  	// Persist local remote state
   459  	s := terraform.NewState()
   460  	s.Serial = 5
   461  	s.Remote = conf
   462  	testStateFileRemote(t, s)
   463  
   464  	// Path where the archive will be "uploaded" to
   465  	archivePath := testTempFile(t)
   466  	defer os.Remove(archivePath)
   467  
   468  	client := &mockPushClient{File: archivePath}
   469  	ui := new(cli.MockUi)
   470  	c := &PushCommand{
   471  		Meta: Meta{
   472  			ContextOpts: testCtxConfig(testProvider()),
   473  			Ui:          ui,
   474  		},
   475  
   476  		client: client,
   477  	}
   478  
   479  	path := testFixturePath("push-tfvars")
   480  	args := []string{
   481  		"-var-file", path + "/terraform.tfvars",
   482  		"-vcs=false",
   483  		"-var",
   484  		"bar=1",
   485  		path,
   486  	}
   487  	if code := c.Run(args); code != 0 {
   488  		t.Fatalf("bad: %d\n\n%s", code, ui.ErrorWriter.String())
   489  	}
   490  
   491  	actual := testArchiveStr(t, archivePath)
   492  	expected := []string{
   493  		".terraform/",
   494  		".terraform/terraform.tfstate",
   495  		"main.tf",
   496  		"terraform.tfvars",
   497  	}
   498  	if !reflect.DeepEqual(actual, expected) {
   499  		t.Fatalf("bad: %#v", actual)
   500  	}
   501  
   502  	if client.UpsertOptions.Name != "foo" {
   503  		t.Fatalf("bad: %#v", client.UpsertOptions)
   504  	}
   505  
   506  	//now check TFVars
   507  	tfvars := pushTFVars()
   508  	// update bar to match cli value
   509  	for i, v := range tfvars {
   510  		if v.Key == "bar" {
   511  			tfvars[i].Value = "1"
   512  			tfvars[i].IsHCL = true
   513  		}
   514  	}
   515  
   516  	for i, expected := range tfvars {
   517  		got := client.UpsertOptions.TFVars[i]
   518  		if got != expected {
   519  			t.Logf("%2d expected: %#v", i, expected)
   520  			t.Fatalf("        got: %#v", got)
   521  		}
   522  	}
   523  }
   524  
   525  func TestPush_name(t *testing.T) {
   526  	tmp, cwd := testCwd(t)
   527  	defer testFixCwd(t, tmp, cwd)
   528  
   529  	// Create remote state file, this should be pulled
   530  	conf, srv := testRemoteState(t, testState(), 200)
   531  	defer srv.Close()
   532  
   533  	// Persist local remote state
   534  	s := terraform.NewState()
   535  	s.Serial = 5
   536  	s.Remote = conf
   537  	testStateFileRemote(t, s)
   538  
   539  	// Path where the archive will be "uploaded" to
   540  	archivePath := testTempFile(t)
   541  	defer os.Remove(archivePath)
   542  
   543  	client := &mockPushClient{File: archivePath}
   544  	ui := new(cli.MockUi)
   545  	c := &PushCommand{
   546  		Meta: Meta{
   547  			ContextOpts: testCtxConfig(testProvider()),
   548  			Ui:          ui,
   549  		},
   550  
   551  		client: client,
   552  	}
   553  
   554  	args := []string{
   555  		"-name", "bar",
   556  		"-vcs=false",
   557  		testFixturePath("push"),
   558  	}
   559  	if code := c.Run(args); code != 0 {
   560  		t.Fatalf("bad: %d\n\n%s", code, ui.ErrorWriter.String())
   561  	}
   562  
   563  	if client.UpsertOptions.Name != "bar" {
   564  		t.Fatalf("bad: %#v", client.UpsertOptions)
   565  	}
   566  }
   567  
   568  func TestPush_noState(t *testing.T) {
   569  	tmp, cwd := testCwd(t)
   570  	defer testFixCwd(t, tmp, cwd)
   571  
   572  	ui := new(cli.MockUi)
   573  	c := &PushCommand{
   574  		Meta: Meta{
   575  			ContextOpts: testCtxConfig(testProvider()),
   576  			Ui:          ui,
   577  		},
   578  	}
   579  
   580  	args := []string{}
   581  	if code := c.Run(args); code != 1 {
   582  		t.Fatalf("bad: \n%s", ui.ErrorWriter.String())
   583  	}
   584  }
   585  
   586  func TestPush_noRemoteState(t *testing.T) {
   587  	state := &terraform.State{
   588  		Modules: []*terraform.ModuleState{
   589  			&terraform.ModuleState{
   590  				Path: []string{"root"},
   591  				Resources: map[string]*terraform.ResourceState{
   592  					"test_instance.foo": &terraform.ResourceState{
   593  						Type: "test_instance",
   594  						Primary: &terraform.InstanceState{
   595  							ID: "bar",
   596  						},
   597  					},
   598  				},
   599  			},
   600  		},
   601  	}
   602  	statePath := testStateFile(t, state)
   603  
   604  	ui := new(cli.MockUi)
   605  	c := &PushCommand{
   606  		Meta: Meta{
   607  			Ui: ui,
   608  		},
   609  	}
   610  
   611  	args := []string{
   612  		"-state", statePath,
   613  	}
   614  	if code := c.Run(args); code != 1 {
   615  		t.Fatalf("bad: %d\n\n%s", code, ui.ErrorWriter.String())
   616  	}
   617  }
   618  
   619  func TestPush_plan(t *testing.T) {
   620  	tmp, cwd := testCwd(t)
   621  	defer testFixCwd(t, tmp, cwd)
   622  
   623  	// Create remote state file, this should be pulled
   624  	conf, srv := testRemoteState(t, testState(), 200)
   625  	defer srv.Close()
   626  
   627  	// Persist local remote state
   628  	s := terraform.NewState()
   629  	s.Serial = 5
   630  	s.Remote = conf
   631  	testStateFileRemote(t, s)
   632  
   633  	// Create a plan
   634  	planPath := testPlanFile(t, &terraform.Plan{
   635  		Module: testModule(t, "apply"),
   636  	})
   637  
   638  	ui := new(cli.MockUi)
   639  	c := &PushCommand{
   640  		Meta: Meta{
   641  			ContextOpts: testCtxConfig(testProvider()),
   642  			Ui:          ui,
   643  		},
   644  	}
   645  
   646  	args := []string{planPath}
   647  	if code := c.Run(args); code != 1 {
   648  		t.Fatalf("bad: %d\n\n%s", code, ui.ErrorWriter.String())
   649  	}
   650  }
   651  
   652  func testArchiveStr(t *testing.T, path string) []string {
   653  	f, err := os.Open(path)
   654  	if err != nil {
   655  		t.Fatalf("err: %s", err)
   656  	}
   657  	defer f.Close()
   658  
   659  	// Ungzip
   660  	gzipR, err := gzip.NewReader(f)
   661  	if err != nil {
   662  		t.Fatalf("err: %s", err)
   663  	}
   664  
   665  	// Accumulator
   666  	result := make([]string, 0, 10)
   667  
   668  	// Untar
   669  	tarR := tar.NewReader(gzipR)
   670  	for {
   671  		header, err := tarR.Next()
   672  		if err == io.EOF {
   673  			break
   674  		}
   675  		if err != nil {
   676  			t.Fatalf("err: %s", err)
   677  		}
   678  
   679  		result = append(result, header.Name)
   680  	}
   681  
   682  	sort.Strings(result)
   683  	return result
   684  }
   685  
   686  func pushTFVars() []atlas.TFVar {
   687  	return []atlas.TFVar{
   688  		{"bar", "foo", false},
   689  		{"baz", `{
   690    A      = "a"
   691    interp = "${file("t.txt")}"
   692  }`, true},
   693  		{"fob", `["a", "quotes \"in\" quotes"]`, true},
   694  		{"foo", "bar", false},
   695  	}
   696  }
   697  
   698  // the structure returned from the push-tfvars test fixture
   699  func pushTFVarsMap() map[string]atlas.TFVar {
   700  	vars := make(map[string]atlas.TFVar)
   701  	for _, v := range pushTFVars() {
   702  		vars[v.Key] = v
   703  	}
   704  	return vars
   705  }