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