github.com/kevinklinger/open_terraform@v1.3.6/noninternal/command/workspace_command_test.go (about)

     1  package command
     2  
     3  import (
     4  	"io/ioutil"
     5  	"os"
     6  	"path/filepath"
     7  	"strings"
     8  	"testing"
     9  
    10  	"github.com/kevinklinger/open_terraform/noninternal/addrs"
    11  	"github.com/kevinklinger/open_terraform/noninternal/backend"
    12  	"github.com/kevinklinger/open_terraform/noninternal/backend/local"
    13  	"github.com/kevinklinger/open_terraform/noninternal/backend/remote-state/inmem"
    14  	"github.com/kevinklinger/open_terraform/noninternal/states"
    15  	"github.com/kevinklinger/open_terraform/noninternal/states/statemgr"
    16  	"github.com/mitchellh/cli"
    17  
    18  	legacy "github.com/kevinklinger/open_terraform/noninternal/legacy/terraform"
    19  )
    20  
    21  func TestWorkspace_createAndChange(t *testing.T) {
    22  	// Create a temporary working directory that is empty
    23  	td := t.TempDir()
    24  	os.MkdirAll(td, 0755)
    25  	defer testChdir(t, td)()
    26  
    27  	newCmd := &WorkspaceNewCommand{}
    28  
    29  	current, _ := newCmd.Workspace()
    30  	if current != backend.DefaultStateName {
    31  		t.Fatal("current workspace should be 'default'")
    32  	}
    33  
    34  	args := []string{"test"}
    35  	ui := new(cli.MockUi)
    36  	view, _ := testView(t)
    37  	newCmd.Meta = Meta{Ui: ui, View: view}
    38  	if code := newCmd.Run(args); code != 0 {
    39  		t.Fatalf("bad: %d\n\n%s", code, ui.ErrorWriter)
    40  	}
    41  
    42  	current, _ = newCmd.Workspace()
    43  	if current != "test" {
    44  		t.Fatalf("current workspace should be 'test', got %q", current)
    45  	}
    46  
    47  	selCmd := &WorkspaceSelectCommand{}
    48  	args = []string{backend.DefaultStateName}
    49  	ui = new(cli.MockUi)
    50  	selCmd.Meta = Meta{Ui: ui, View: view}
    51  	if code := selCmd.Run(args); code != 0 {
    52  		t.Fatalf("bad: %d\n\n%s", code, ui.ErrorWriter)
    53  	}
    54  
    55  	current, _ = newCmd.Workspace()
    56  	if current != backend.DefaultStateName {
    57  		t.Fatal("current workspace should be 'default'")
    58  	}
    59  
    60  }
    61  
    62  // Create some workspaces and test the list output.
    63  // This also ensures we switch to the correct env after each call
    64  func TestWorkspace_createAndList(t *testing.T) {
    65  	// Create a temporary working directory that is empty
    66  	td := t.TempDir()
    67  	os.MkdirAll(td, 0755)
    68  	defer testChdir(t, td)()
    69  
    70  	// make sure a vars file doesn't interfere
    71  	err := ioutil.WriteFile(
    72  		DefaultVarsFilename,
    73  		[]byte(`foo = "bar"`),
    74  		0644,
    75  	)
    76  	if err != nil {
    77  		t.Fatal(err)
    78  	}
    79  
    80  	envs := []string{"test_a", "test_b", "test_c"}
    81  
    82  	// create multiple workspaces
    83  	for _, env := range envs {
    84  		ui := new(cli.MockUi)
    85  		view, _ := testView(t)
    86  		newCmd := &WorkspaceNewCommand{
    87  			Meta: Meta{Ui: ui, View: view},
    88  		}
    89  		if code := newCmd.Run([]string{env}); code != 0 {
    90  			t.Fatalf("bad: %d\n\n%s", code, ui.ErrorWriter)
    91  		}
    92  	}
    93  
    94  	listCmd := &WorkspaceListCommand{}
    95  	ui := new(cli.MockUi)
    96  	view, _ := testView(t)
    97  	listCmd.Meta = Meta{Ui: ui, View: view}
    98  
    99  	if code := listCmd.Run(nil); code != 0 {
   100  		t.Fatalf("bad: %d\n\n%s", code, ui.ErrorWriter)
   101  	}
   102  
   103  	actual := strings.TrimSpace(ui.OutputWriter.String())
   104  	expected := "default\n  test_a\n  test_b\n* test_c"
   105  
   106  	if actual != expected {
   107  		t.Fatalf("\nexpected: %q\nactual:  %q", expected, actual)
   108  	}
   109  }
   110  
   111  // Create some workspaces and test the show output.
   112  func TestWorkspace_createAndShow(t *testing.T) {
   113  	// Create a temporary working directory that is empty
   114  	td := t.TempDir()
   115  	os.MkdirAll(td, 0755)
   116  	defer testChdir(t, td)()
   117  
   118  	// make sure a vars file doesn't interfere
   119  	err := ioutil.WriteFile(
   120  		DefaultVarsFilename,
   121  		[]byte(`foo = "bar"`),
   122  		0644,
   123  	)
   124  	if err != nil {
   125  		t.Fatal(err)
   126  	}
   127  
   128  	// make sure current workspace show outputs "default"
   129  	showCmd := &WorkspaceShowCommand{}
   130  	ui := new(cli.MockUi)
   131  	view, _ := testView(t)
   132  	showCmd.Meta = Meta{Ui: ui, View: view}
   133  
   134  	if code := showCmd.Run(nil); code != 0 {
   135  		t.Fatalf("bad: %d\n\n%s", code, ui.ErrorWriter)
   136  	}
   137  
   138  	actual := strings.TrimSpace(ui.OutputWriter.String())
   139  	expected := "default"
   140  
   141  	if actual != expected {
   142  		t.Fatalf("\nexpected: %q\nactual:  %q", expected, actual)
   143  	}
   144  
   145  	newCmd := &WorkspaceNewCommand{}
   146  
   147  	env := []string{"test_a"}
   148  
   149  	// create test_a workspace
   150  	ui = new(cli.MockUi)
   151  	newCmd.Meta = Meta{Ui: ui, View: view}
   152  	if code := newCmd.Run(env); code != 0 {
   153  		t.Fatalf("bad: %d\n\n%s", code, ui.ErrorWriter)
   154  	}
   155  
   156  	selCmd := &WorkspaceSelectCommand{}
   157  	ui = new(cli.MockUi)
   158  	selCmd.Meta = Meta{Ui: ui, View: view}
   159  	if code := selCmd.Run(env); code != 0 {
   160  		t.Fatalf("bad: %d\n\n%s", code, ui.ErrorWriter)
   161  	}
   162  
   163  	showCmd = &WorkspaceShowCommand{}
   164  	ui = new(cli.MockUi)
   165  	showCmd.Meta = Meta{Ui: ui, View: view}
   166  
   167  	if code := showCmd.Run(nil); code != 0 {
   168  		t.Fatalf("bad: %d\n\n%s", code, ui.ErrorWriter)
   169  	}
   170  
   171  	actual = strings.TrimSpace(ui.OutputWriter.String())
   172  	expected = "test_a"
   173  
   174  	if actual != expected {
   175  		t.Fatalf("\nexpected: %q\nactual:  %q", expected, actual)
   176  	}
   177  }
   178  
   179  // Don't allow names that aren't URL safe
   180  func TestWorkspace_createInvalid(t *testing.T) {
   181  	// Create a temporary working directory that is empty
   182  	td := t.TempDir()
   183  	os.MkdirAll(td, 0755)
   184  	defer testChdir(t, td)()
   185  
   186  	envs := []string{"test_a*", "test_b/foo", "../../../test_c", "好_d"}
   187  
   188  	// create multiple workspaces
   189  	for _, env := range envs {
   190  		ui := new(cli.MockUi)
   191  		view, _ := testView(t)
   192  		newCmd := &WorkspaceNewCommand{
   193  			Meta: Meta{Ui: ui, View: view},
   194  		}
   195  		if code := newCmd.Run([]string{env}); code == 0 {
   196  			t.Fatalf("expected failure: \n%s", ui.OutputWriter)
   197  		}
   198  	}
   199  
   200  	// list workspaces to make sure none were created
   201  	listCmd := &WorkspaceListCommand{}
   202  	ui := new(cli.MockUi)
   203  	view, _ := testView(t)
   204  	listCmd.Meta = Meta{Ui: ui, View: view}
   205  
   206  	if code := listCmd.Run(nil); code != 0 {
   207  		t.Fatalf("bad: %d\n\n%s", code, ui.ErrorWriter)
   208  	}
   209  
   210  	actual := strings.TrimSpace(ui.OutputWriter.String())
   211  	expected := "* default"
   212  
   213  	if actual != expected {
   214  		t.Fatalf("\nexpected: %q\nactual:  %q", expected, actual)
   215  	}
   216  }
   217  
   218  func TestWorkspace_createWithState(t *testing.T) {
   219  	td := t.TempDir()
   220  	testCopyDir(t, testFixturePath("inmem-backend"), td)
   221  	defer testChdir(t, td)()
   222  	defer inmem.Reset()
   223  
   224  	// init the backend
   225  	ui := new(cli.MockUi)
   226  	view, _ := testView(t)
   227  	initCmd := &InitCommand{
   228  		Meta: Meta{Ui: ui, View: view},
   229  	}
   230  	if code := initCmd.Run([]string{}); code != 0 {
   231  		t.Fatalf("bad: \n%s", ui.ErrorWriter.String())
   232  	}
   233  
   234  	originalState := states.BuildState(func(s *states.SyncState) {
   235  		s.SetResourceInstanceCurrent(
   236  			addrs.Resource{
   237  				Mode: addrs.ManagedResourceMode,
   238  				Type: "test_instance",
   239  				Name: "foo",
   240  			}.Instance(addrs.NoKey).Absolute(addrs.RootModuleInstance),
   241  			&states.ResourceInstanceObjectSrc{
   242  				AttrsJSON: []byte(`{"id":"bar"}`),
   243  				Status:    states.ObjectReady,
   244  			},
   245  			addrs.AbsProviderConfig{
   246  				Provider: addrs.NewDefaultProvider("test"),
   247  				Module:   addrs.RootModule,
   248  			},
   249  		)
   250  	})
   251  
   252  	err := statemgr.NewFilesystem("test.tfstate").WriteState(originalState)
   253  	if err != nil {
   254  		t.Fatal(err)
   255  	}
   256  
   257  	workspace := "test_workspace"
   258  
   259  	args := []string{"-state", "test.tfstate", workspace}
   260  	ui = new(cli.MockUi)
   261  	newCmd := &WorkspaceNewCommand{
   262  		Meta: Meta{Ui: ui, View: view},
   263  	}
   264  	if code := newCmd.Run(args); code != 0 {
   265  		t.Fatalf("bad: %d\n\n%s", code, ui.ErrorWriter)
   266  	}
   267  
   268  	newPath := filepath.Join(local.DefaultWorkspaceDir, "test", DefaultStateFilename)
   269  	envState := statemgr.NewFilesystem(newPath)
   270  	err = envState.RefreshState()
   271  	if err != nil {
   272  		t.Fatal(err)
   273  	}
   274  
   275  	b := backend.TestBackendConfig(t, inmem.New(), nil)
   276  	sMgr, err := b.StateMgr(workspace)
   277  	if err != nil {
   278  		t.Fatal(err)
   279  	}
   280  
   281  	newState := sMgr.State()
   282  
   283  	if got, want := newState.String(), originalState.String(); got != want {
   284  		t.Fatalf("states not equal\ngot: %s\nwant: %s", got, want)
   285  	}
   286  }
   287  
   288  func TestWorkspace_delete(t *testing.T) {
   289  	td := t.TempDir()
   290  	os.MkdirAll(td, 0755)
   291  	defer testChdir(t, td)()
   292  
   293  	// create the workspace directories
   294  	if err := os.MkdirAll(filepath.Join(local.DefaultWorkspaceDir, "test"), 0755); err != nil {
   295  		t.Fatal(err)
   296  	}
   297  
   298  	// create the workspace file
   299  	if err := os.MkdirAll(DefaultDataDir, 0755); err != nil {
   300  		t.Fatal(err)
   301  	}
   302  	if err := ioutil.WriteFile(filepath.Join(DefaultDataDir, local.DefaultWorkspaceFile), []byte("test"), 0644); err != nil {
   303  		t.Fatal(err)
   304  	}
   305  
   306  	ui := new(cli.MockUi)
   307  	view, _ := testView(t)
   308  	delCmd := &WorkspaceDeleteCommand{
   309  		Meta: Meta{Ui: ui, View: view},
   310  	}
   311  
   312  	current, _ := delCmd.Workspace()
   313  	if current != "test" {
   314  		t.Fatal("wrong workspace:", current)
   315  	}
   316  
   317  	// we can't delete our current workspace
   318  	args := []string{"test"}
   319  	if code := delCmd.Run(args); code == 0 {
   320  		t.Fatal("expected error deleting current workspace")
   321  	}
   322  
   323  	// change back to default
   324  	if err := delCmd.SetWorkspace(backend.DefaultStateName); err != nil {
   325  		t.Fatal(err)
   326  	}
   327  
   328  	// try the delete again
   329  	ui = new(cli.MockUi)
   330  	delCmd.Meta.Ui = ui
   331  	if code := delCmd.Run(args); code != 0 {
   332  		t.Fatalf("error deleting workspace: %s", ui.ErrorWriter)
   333  	}
   334  
   335  	current, _ = delCmd.Workspace()
   336  	if current != backend.DefaultStateName {
   337  		t.Fatalf("wrong workspace: %q", current)
   338  	}
   339  }
   340  
   341  func TestWorkspace_deleteInvalid(t *testing.T) {
   342  	td := t.TempDir()
   343  	os.MkdirAll(td, 0755)
   344  	defer testChdir(t, td)()
   345  
   346  	// choose an invalid workspace name
   347  	workspace := "test workspace"
   348  	path := filepath.Join(local.DefaultWorkspaceDir, workspace)
   349  
   350  	// create the workspace directories
   351  	if err := os.MkdirAll(path, 0755); err != nil {
   352  		t.Fatal(err)
   353  	}
   354  
   355  	ui := new(cli.MockUi)
   356  	view, _ := testView(t)
   357  	delCmd := &WorkspaceDeleteCommand{
   358  		Meta: Meta{Ui: ui, View: view},
   359  	}
   360  
   361  	// delete the workspace
   362  	if code := delCmd.Run([]string{workspace}); code != 0 {
   363  		t.Fatalf("error deleting workspace: %s", ui.ErrorWriter)
   364  	}
   365  
   366  	if _, err := os.Stat(path); err == nil {
   367  		t.Fatalf("should have deleted workspace, but %s still exists", path)
   368  	} else if !os.IsNotExist(err) {
   369  		t.Fatalf("unexpected error for workspace path: %s", err)
   370  	}
   371  }
   372  
   373  func TestWorkspace_deleteWithState(t *testing.T) {
   374  	td := t.TempDir()
   375  	os.MkdirAll(td, 0755)
   376  	defer testChdir(t, td)()
   377  
   378  	// create the workspace directories
   379  	if err := os.MkdirAll(filepath.Join(local.DefaultWorkspaceDir, "test"), 0755); err != nil {
   380  		t.Fatal(err)
   381  	}
   382  
   383  	// create a non-empty state
   384  	originalState := &legacy.State{
   385  		Modules: []*legacy.ModuleState{
   386  			{
   387  				Path: []string{"root"},
   388  				Resources: map[string]*legacy.ResourceState{
   389  					"test_instance.foo": {
   390  						Type: "test_instance",
   391  						Primary: &legacy.InstanceState{
   392  							ID: "bar",
   393  						},
   394  					},
   395  				},
   396  			},
   397  		},
   398  	}
   399  
   400  	f, err := os.Create(filepath.Join(local.DefaultWorkspaceDir, "test", "terraform.tfstate"))
   401  	if err != nil {
   402  		t.Fatal(err)
   403  	}
   404  	defer f.Close()
   405  	if err := legacy.WriteState(originalState, f); err != nil {
   406  		t.Fatal(err)
   407  	}
   408  
   409  	ui := cli.NewMockUi()
   410  	view, _ := testView(t)
   411  	delCmd := &WorkspaceDeleteCommand{
   412  		Meta: Meta{Ui: ui, View: view},
   413  	}
   414  	args := []string{"test"}
   415  	if code := delCmd.Run(args); code == 0 {
   416  		t.Fatalf("expected failure without -force.\noutput: %s", ui.OutputWriter)
   417  	}
   418  	gotStderr := ui.ErrorWriter.String()
   419  	if want, got := `Workspace "test" is currently tracking the following resource instances`, gotStderr; !strings.Contains(got, want) {
   420  		t.Errorf("missing expected error message\nwant substring: %s\ngot:\n%s", want, got)
   421  	}
   422  	if want, got := `- test_instance.foo`, gotStderr; !strings.Contains(got, want) {
   423  		t.Errorf("error message doesn't mention the remaining instance\nwant substring: %s\ngot:\n%s", want, got)
   424  	}
   425  
   426  	ui = new(cli.MockUi)
   427  	delCmd.Meta.Ui = ui
   428  
   429  	args = []string{"-force", "test"}
   430  	if code := delCmd.Run(args); code != 0 {
   431  		t.Fatalf("failure: %s", ui.ErrorWriter)
   432  	}
   433  
   434  	if _, err := os.Stat(filepath.Join(local.DefaultWorkspaceDir, "test")); !os.IsNotExist(err) {
   435  		t.Fatal("env 'test' still exists!")
   436  	}
   437  }