github.com/andrewrynhard/terraform@v0.9.5-0.20170502003928-8d286b83eae4/command/push_test.go (about)

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