-
Notifications
You must be signed in to change notification settings - Fork 10.1k
PSS: Update the workspace new subcommand to work with PSS, add E2E and integration tests for using workspace commands with PSS.
#37855
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from 11 commits
2d76ec1
38010e5
a6f6932
7f9bfe2
e3877b9
0df7988
a3bd371
f116924
d7b78a6
7bc3ea8
b8f9088
ae65335
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,122 @@ | ||
| // Copyright (c) HashiCorp, Inc. | ||
| // SPDX-License-Identifier: BUSL-1.1 | ||
|
|
||
| package e2etest | ||
|
|
||
| import ( | ||
| "fmt" | ||
| "os" | ||
| "path" | ||
| "path/filepath" | ||
| "strings" | ||
| "testing" | ||
|
|
||
| "github.com/hashicorp/terraform/internal/e2e" | ||
| "github.com/hashicorp/terraform/internal/getproviders" | ||
| ) | ||
|
|
||
| func TestPrimary_stateStore_workspaceCmd(t *testing.T) { | ||
| if v := os.Getenv("TF_TEST_EXPERIMENTS"); v == "" { | ||
| t.Skip("can't run without enabling experiments in the executable terraform binary, enable with TF_TEST_EXPERIMENTS=1") | ||
| } | ||
|
|
||
| if !canRunGoBuild { | ||
| // We're running in a separate-build-then-run context, so we can't | ||
| // currently execute this test which depends on being able to build | ||
| // new executable at runtime. | ||
| // | ||
| // (See the comment on canRunGoBuild's declaration for more information.) | ||
| t.Skip("can't run without building a new provider executable") | ||
| } | ||
| t.Parallel() | ||
|
|
||
| tf := e2e.NewBinary(t, terraformBin, "testdata/full-workflow-with-state-store-fs") | ||
| workspaceDirName := "states" // see test fixture value for workspace_dir | ||
|
|
||
| // In order to test integration with PSS we need a provider plugin implementing a state store. | ||
| // Here will build the simple6 (built with protocol v6) provider, which implements PSS. | ||
| simple6Provider := filepath.Join(tf.WorkDir(), "terraform-provider-simple6") | ||
| simple6ProviderExe := e2e.GoBuild("github.com/hashicorp/terraform/internal/provider-simple-v6/main", simple6Provider) | ||
|
|
||
| // Move the provider binaries into a directory that we will point terraform | ||
| // to using the -plugin-dir cli flag. | ||
| platform := getproviders.CurrentPlatform.String() | ||
| hashiDir := "cache/registry.terraform.io/hashicorp/" | ||
| if err := os.MkdirAll(tf.Path(hashiDir, "simple6/0.0.1/", platform), os.ModePerm); err != nil { | ||
| t.Fatal(err) | ||
| } | ||
| if err := os.Rename(simple6ProviderExe, tf.Path(hashiDir, "simple6/0.0.1/", platform, "terraform-provider-simple6")); err != nil { | ||
| t.Fatal(err) | ||
| } | ||
|
|
||
| //// Init | ||
| _, stderr, err := tf.Run("init", "-enable-pluggable-state-storage-experiment=true", "-plugin-dir=cache", "-no-color") | ||
| if err != nil { | ||
| t.Fatalf("unexpected error: %s\nstderr:\n%s", err, stderr) | ||
| } | ||
| fi, err := os.Stat(path.Join(tf.WorkDir(), workspaceDirName, "default", "terraform.tfstate")) | ||
| if err != nil { | ||
| t.Fatalf("failed to open default workspace's state file: %s", err) | ||
| } | ||
| if fi.Size() == 0 { | ||
| t.Fatal("default workspace's state file should not have size 0 bytes") | ||
| } | ||
|
|
||
| //// Create Workspace: terraform workspace new | ||
| newWorkspace := "foobar" | ||
| stdout, stderr, err := tf.Run("workspace", "new", newWorkspace, "-no-color") | ||
| if err != nil { | ||
| t.Fatalf("unexpected error: %s\nstderr:\n%s", err, stderr) | ||
| } | ||
| expectedMsg := fmt.Sprintf("Created and switched to workspace %q!", newWorkspace) | ||
| if !strings.Contains(stdout, expectedMsg) { | ||
| t.Errorf("unexpected output, expected %q, but got:\n%s", expectedMsg, stdout) | ||
| } | ||
| fi, err = os.Stat(path.Join(tf.WorkDir(), workspaceDirName, newWorkspace, "terraform.tfstate")) | ||
| if err != nil { | ||
| t.Fatalf("failed to open %s workspace's state file: %s", newWorkspace, err) | ||
| } | ||
| if fi.Size() == 0 { | ||
| t.Fatalf("%s workspace's state file should not have size 0 bytes", newWorkspace) | ||
| } | ||
|
|
||
| //// List Workspaces: : terraform workspace list | ||
| stdout, stderr, err = tf.Run("workspace", "list", "-no-color") | ||
| if err != nil { | ||
| t.Fatalf("unexpected error: %s\nstderr:\n%s", err, stderr) | ||
| } | ||
| if !strings.Contains(stdout, newWorkspace) { | ||
| t.Errorf("unexpected output, expected the new %q workspace to be listed present, but it's missing. Got:\n%s", newWorkspace, stdout) | ||
| } | ||
|
|
||
| //// Select Workspace: terraform workspace select | ||
| selectedWorkspace := "default" | ||
| stdout, stderr, err = tf.Run("workspace", "select", selectedWorkspace, "-no-color") | ||
| if err != nil { | ||
| t.Fatalf("unexpected error: %s\nstderr:\n%s", err, stderr) | ||
| } | ||
| expectedMsg = fmt.Sprintf("Switched to workspace %q.", selectedWorkspace) | ||
| if !strings.Contains(stdout, expectedMsg) { | ||
| t.Errorf("unexpected output, expected %q, but got:\n%s", expectedMsg, stdout) | ||
| } | ||
|
|
||
| //// Show Workspace: terraform workspace show | ||
| stdout, stderr, err = tf.Run("workspace", "show", "-no-color") | ||
| if err != nil { | ||
| t.Fatalf("unexpected error: %s\nstderr:\n%s", err, stderr) | ||
| } | ||
| expectedMsg = fmt.Sprintf("%s\n", selectedWorkspace) | ||
| if stdout != expectedMsg { | ||
| t.Errorf("unexpected output, expected %q, but got:\n%s", expectedMsg, stdout) | ||
| } | ||
|
|
||
| //// Delete Workspace: terraform workspace delete | ||
| stdout, stderr, err = tf.Run("workspace", "delete", newWorkspace, "-no-color") | ||
| if err != nil { | ||
| t.Fatalf("unexpected error: %s\nstderr:\n%s", err, stderr) | ||
| } | ||
| expectedMsg = fmt.Sprintf("Deleted workspace %q!\n", newWorkspace) | ||
| if stdout != expectedMsg { | ||
| t.Errorf("unexpected output, expected %q, but got:\n%s", expectedMsg, stdout) | ||
| } | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -7,6 +7,8 @@ terraform { | |
|
|
||
| state_store "simple6_fs" { | ||
| provider "simple6" {} | ||
|
|
||
| workspace_dir = "states" | ||
| } | ||
| } | ||
|
|
||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -4,7 +4,7 @@ | |
| package command | ||
|
|
||
| import ( | ||
| "io/ioutil" | ||
| "fmt" | ||
| "os" | ||
| "path/filepath" | ||
| "strings" | ||
|
|
@@ -16,11 +16,159 @@ import ( | |
| "github.com/hashicorp/terraform/internal/backend" | ||
| "github.com/hashicorp/terraform/internal/backend/local" | ||
| "github.com/hashicorp/terraform/internal/backend/remote-state/inmem" | ||
| "github.com/hashicorp/terraform/internal/providers" | ||
| "github.com/hashicorp/terraform/internal/states" | ||
| "github.com/hashicorp/terraform/internal/states/statefile" | ||
| "github.com/hashicorp/terraform/internal/states/statemgr" | ||
| ) | ||
|
|
||
| func TestWorkspace_allCommands_pluggableStateStore(t *testing.T) { | ||
|
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I wrote the E2E test for all the At this point I'm unsure about whether to keep both tests or to only keep the E2E test. |
||
| // Create a temporary working directory with pluggable state storage in the config | ||
| td := t.TempDir() | ||
| testCopyDir(t, testFixturePath("state-store-new"), td) | ||
| t.Chdir(td) | ||
|
|
||
| mock := testStateStoreMockWithChunkNegotiation(t, 1000) | ||
| newMeta := func(provider providers.Interface) (meta Meta, ui *cli.MockUi, close func()) { | ||
| // Assumes the mocked provider is hashicorp/test | ||
| providerSource, close := newMockProviderSource(t, map[string][]string{ | ||
| "hashicorp/test": {"1.2.3"}, | ||
| }) | ||
|
|
||
| ui = new(cli.MockUi) | ||
| view, _ := testView(t) | ||
| meta = Meta{ | ||
| AllowExperimentalFeatures: true, | ||
| Ui: ui, | ||
| View: view, | ||
| testingOverrides: &testingOverrides{ | ||
| Providers: map[addrs.Provider]providers.Factory{ | ||
| addrs.NewDefaultProvider("test"): providers.FactoryFixed(mock), | ||
| }, | ||
| }, | ||
| ProviderSource: providerSource, | ||
| } | ||
| return meta, ui, close | ||
| } | ||
|
|
||
| //// Init | ||
| meta, ui, close := newMeta(mock) | ||
| defer close() | ||
| intCmd := &InitCommand{ | ||
| Meta: meta, | ||
| } | ||
| args := []string{"-enable-pluggable-state-storage-experiment"} // Needed to test init changes for PSS project | ||
| code := intCmd.Run(args) | ||
| if code != 0 { | ||
| t.Fatalf("bad: %d\n\n%s\n%s", code, ui.ErrorWriter, ui.OutputWriter) | ||
| } | ||
| // We expect a state to have been created for the default workspace | ||
| if _, ok := mock.MockStates["default"]; !ok { | ||
| t.Fatal("expected the default workspace to exist, but it didn't") | ||
| } | ||
|
|
||
| //// Create Workspace | ||
| newWorkspace := "foobar" | ||
|
|
||
| meta, ui, close = newMeta(mock) | ||
| defer close() | ||
| newCmd := &WorkspaceNewCommand{ | ||
| Meta: meta, | ||
| } | ||
|
|
||
| current, _ := newCmd.Workspace() | ||
| if current != backend.DefaultStateName { | ||
| t.Fatal("before creating any custom workspaces, the current workspace should be 'default'") | ||
| } | ||
|
|
||
| args = []string{newWorkspace} | ||
| code = newCmd.Run(args) | ||
| if code != 0 { | ||
| t.Fatalf("bad: %d\n\n%s\n%s", code, ui.ErrorWriter, ui.OutputWriter) | ||
| } | ||
| expectedMsg := fmt.Sprintf("Created and switched to workspace %q!", newWorkspace) | ||
| if !strings.Contains(ui.OutputWriter.String(), expectedMsg) { | ||
| t.Errorf("unexpected output, expected %q, but got:\n%s", expectedMsg, ui.OutputWriter) | ||
| } | ||
| // We expect a state to have been created for the new custom workspace | ||
| if _, ok := mock.MockStates[newWorkspace]; !ok { | ||
| t.Fatalf("expected the %s workspace to exist, but it didn't", newWorkspace) | ||
| } | ||
| current, _ = newCmd.Workspace() | ||
| if current != newWorkspace { | ||
| t.Fatalf("current workspace should be %q, got %q", newWorkspace, current) | ||
| } | ||
|
|
||
| //// List Workspaces | ||
| meta, ui, close = newMeta(mock) | ||
| defer close() | ||
| listCmd := &WorkspaceListCommand{ | ||
| Meta: meta, | ||
| } | ||
| args = []string{} | ||
| code = listCmd.Run(args) | ||
| if code != 0 { | ||
| t.Fatalf("bad: %d\n\n%s\n%s", code, ui.ErrorWriter, ui.OutputWriter) | ||
| } | ||
| if !strings.Contains(ui.OutputWriter.String(), newWorkspace) { | ||
| t.Errorf("unexpected output, expected the new %q workspace to be listed present, but it's missing. Got:\n%s", newWorkspace, ui.OutputWriter) | ||
| } | ||
|
|
||
| //// Select Workspace | ||
| meta, ui, close = newMeta(mock) | ||
| defer close() | ||
| selCmd := &WorkspaceSelectCommand{ | ||
| Meta: meta, | ||
| } | ||
| selectedWorkspace := backend.DefaultStateName | ||
| args = []string{selectedWorkspace} | ||
| code = selCmd.Run(args) | ||
| if code != 0 { | ||
| t.Fatalf("bad: %d\n\n%s\n%s", code, ui.ErrorWriter, ui.OutputWriter) | ||
| } | ||
| expectedMsg = fmt.Sprintf("Switched to workspace %q.", selectedWorkspace) | ||
| if !strings.Contains(ui.OutputWriter.String(), expectedMsg) { | ||
| t.Errorf("unexpected output, expected %q, but got:\n%s", expectedMsg, ui.OutputWriter) | ||
| } | ||
|
|
||
| //// Show Workspace | ||
| meta, ui, close = newMeta(mock) | ||
| defer close() | ||
| showCmd := &WorkspaceShowCommand{ | ||
| Meta: meta, | ||
| } | ||
| args = []string{} | ||
| code = showCmd.Run(args) | ||
| if code != 0 { | ||
| t.Fatalf("bad: %d\n\n%s\n%s", code, ui.ErrorWriter, ui.OutputWriter) | ||
| } | ||
| expectedMsg = fmt.Sprintf("%s\n", selectedWorkspace) | ||
| if !strings.Contains(ui.OutputWriter.String(), expectedMsg) { | ||
| t.Errorf("unexpected output, expected %q, but got:\n%s", expectedMsg, ui.OutputWriter) | ||
| } | ||
|
|
||
| current, _ = newCmd.Workspace() | ||
| if current != backend.DefaultStateName { | ||
| t.Fatal("current workspace should be 'default'") | ||
| } | ||
|
|
||
| //// Delete Workspace | ||
| meta, ui, close = newMeta(mock) | ||
| defer close() | ||
| deleteCmd := &WorkspaceDeleteCommand{ | ||
| Meta: meta, | ||
| } | ||
| args = []string{newWorkspace} | ||
| code = deleteCmd.Run(args) | ||
| if code != 0 { | ||
| t.Fatalf("bad: %d\n\n%s\n%s", code, ui.ErrorWriter, ui.OutputWriter) | ||
| } | ||
| expectedMsg = fmt.Sprintf("Deleted workspace %q!\n", newWorkspace) | ||
| if !strings.Contains(ui.OutputWriter.String(), expectedMsg) { | ||
| t.Errorf("unexpected output, expected %q, but got:\n%s", expectedMsg, ui.OutputWriter) | ||
| } | ||
| } | ||
|
|
||
| func TestWorkspace_createAndChange(t *testing.T) { | ||
| // Create a temporary working directory that is empty | ||
| td := t.TempDir() | ||
|
|
@@ -114,7 +262,7 @@ func TestWorkspace_createAndList(t *testing.T) { | |
| t.Chdir(td) | ||
|
|
||
| // make sure a vars file doesn't interfere | ||
| err := ioutil.WriteFile( | ||
| err := os.WriteFile( | ||
| DefaultVarsFilename, | ||
| []byte(`foo = "bar"`), | ||
| 0644, | ||
|
|
@@ -162,7 +310,7 @@ func TestWorkspace_createAndShow(t *testing.T) { | |
| t.Chdir(td) | ||
|
|
||
| // make sure a vars file doesn't interfere | ||
| err := ioutil.WriteFile( | ||
| err := os.WriteFile( | ||
| DefaultVarsFilename, | ||
| []byte(`foo = "bar"`), | ||
| 0644, | ||
|
|
@@ -345,7 +493,7 @@ func TestWorkspace_delete(t *testing.T) { | |
| if err := os.MkdirAll(DefaultDataDir, 0755); err != nil { | ||
| t.Fatal(err) | ||
| } | ||
| if err := ioutil.WriteFile(filepath.Join(DefaultDataDir, local.DefaultWorkspaceFile), []byte("test"), 0644); err != nil { | ||
| if err := os.WriteFile(filepath.Join(DefaultDataDir, local.DefaultWorkspaceFile), []byte("test"), 0644); err != nil { | ||
| t.Fatal(err) | ||
| } | ||
|
|
||
|
|
||
Uh oh!
There was an error while loading. Please reload this page.