mirror of
https://github.com/hashicorp/terraform.git
synced 2026-05-28 04:03:27 -04:00
Refactor workspace list human output to use a views-like architecture, so adding JSON output is possible without breaking changes (#38392)
* test: Add a test that asserts human output from workspace commands with colour enabled or disabled
* refactor: Update `workspace list` to use cli.Ui in a Views-like way for human output. Update how output is returned by the command to use a single List method.
* refactor: Make `workspace list` command parse its arguments using the `arguments` package
* refactor: Replace use of `ModulePath` with ` c.WorkingDir.RootModuleDir()` to separate concerns
* test: Update tests that feature the `workspace list` command to include a WorkingDir value.
This is necessary after the changes in b32b60f6a7
* feat: Detect unexpected arguments and flags using the arguments package.
* refactor: Remove duplicate call to c.View.Configure, add code comments explaining code.
* fix: Make sure argument parsing errors are handled as soon as the view is usable.
* test: Update TestWorkspace_extraArgError to account for new validation via the arguments package
* fix: Update outdated copyright headers
* test: Update test name
* refactor: Reintroduce the old behaviour when unexpected positional arguments were present by using a specific ParseWorkspaceList method
This commit is contained in:
parent
a28750d8d1
commit
c975e0cd78
5 changed files with 457 additions and 47 deletions
47
internal/command/arguments/workspace.go
Normal file
47
internal/command/arguments/workspace.go
Normal file
|
|
@ -0,0 +1,47 @@
|
|||
// Copyright IBM Corp. 2014, 2026
|
||||
// SPDX-License-Identifier: BUSL-1.1
|
||||
|
||||
package arguments
|
||||
|
||||
import (
|
||||
"errors"
|
||||
|
||||
"github.com/hashicorp/terraform/internal/tfdiags"
|
||||
)
|
||||
|
||||
// Workspace represents the command-line arguments common between all workspace subcommands.
|
||||
//
|
||||
// Subcommands that accept additional arguments should have a specific struct that embeds this struct.
|
||||
type Workspace struct {
|
||||
// ViewType specifies which output format to use
|
||||
ViewType ViewType
|
||||
}
|
||||
|
||||
type WorkspaceList struct {
|
||||
Workspace
|
||||
}
|
||||
|
||||
// ParseWorkspaceList processes CLI arguments, returning a WorkspaceList value and errors.
|
||||
// If errors are encountered, an WorkspaceList value is still returned representing
|
||||
// the best effort interpretation of the arguments.
|
||||
func ParseWorkspaceList(args []string) (*WorkspaceList, tfdiags.Diagnostics) {
|
||||
var diags tfdiags.Diagnostics
|
||||
|
||||
cmdFlags := defaultFlagSet("workspace list")
|
||||
if err := cmdFlags.Parse(args); err != nil {
|
||||
diags = diags.Append(tfdiags.Sourceless(
|
||||
tfdiags.Error,
|
||||
"Failed to parse command-line flags",
|
||||
err.Error(),
|
||||
))
|
||||
}
|
||||
|
||||
// `workspace list` takes no positional arguments. Historically there was a DIR argument that was replaced with the -chdir flag.
|
||||
// Here we replicate the old behaviour of suggesting the user to use -chdir if they provide any positional arguments.
|
||||
args = cmdFlags.Args()
|
||||
if len(args) != 0 {
|
||||
diags = diags.Append(errors.New("Too many command line arguments. Did you mean to use -chdir?"))
|
||||
}
|
||||
|
||||
return &WorkspaceList{Workspace: Workspace{ViewType: ViewHuman}}, diags
|
||||
}
|
||||
87
internal/command/arguments/workspace_test.go
Normal file
87
internal/command/arguments/workspace_test.go
Normal file
|
|
@ -0,0 +1,87 @@
|
|||
// Copyright IBM Corp. 2014, 2026
|
||||
// SPDX-License-Identifier: BUSL-1.1
|
||||
|
||||
package arguments
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/hashicorp/terraform/internal/tfdiags"
|
||||
)
|
||||
|
||||
func TestParseWorkspaceList_valid(t *testing.T) {
|
||||
testCases := map[string]struct {
|
||||
args []string
|
||||
want *WorkspaceList
|
||||
}{
|
||||
"defaults": {
|
||||
nil,
|
||||
&WorkspaceList{
|
||||
Workspace: Workspace{
|
||||
ViewType: ViewHuman,
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for name, tc := range testCases {
|
||||
t.Run(name, func(t *testing.T) {
|
||||
got, diags := ParseWorkspaceList(tc.args)
|
||||
if len(diags) > 0 {
|
||||
t.Fatalf("unexpected diags: %v", diags)
|
||||
}
|
||||
if *got != *tc.want {
|
||||
t.Fatalf("unexpected result\n got: %#v\nwant: %#v", got, tc.want)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestParseWorkspaceList_invalid(t *testing.T) {
|
||||
testCases := map[string]struct {
|
||||
args []string
|
||||
want *WorkspaceList
|
||||
wantDiags tfdiags.Diagnostics
|
||||
}{
|
||||
"unknown flag": {
|
||||
[]string{"-boop"},
|
||||
&WorkspaceList{
|
||||
Workspace: Workspace{
|
||||
ViewType: ViewHuman,
|
||||
},
|
||||
},
|
||||
tfdiags.Diagnostics{
|
||||
tfdiags.Sourceless(
|
||||
tfdiags.Error,
|
||||
"Failed to parse command-line flags",
|
||||
"flag provided but not defined: -boop",
|
||||
),
|
||||
},
|
||||
},
|
||||
"too many arguments": {
|
||||
[]string{"bar", "baz"},
|
||||
&WorkspaceList{
|
||||
Workspace: Workspace{
|
||||
ViewType: ViewHuman,
|
||||
},
|
||||
},
|
||||
tfdiags.Diagnostics{
|
||||
tfdiags.Sourceless(
|
||||
tfdiags.Error,
|
||||
"Too many command line arguments. Did you mean to use -chdir?",
|
||||
"", // No detail
|
||||
),
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for name, tc := range testCases {
|
||||
t.Run(name, func(t *testing.T) {
|
||||
got, gotDiags := ParseWorkspaceList(tc.args)
|
||||
if *got != *tc.want {
|
||||
t.Fatalf("unexpected result\n got: %#v\nwant: %#v", got, tc.want)
|
||||
}
|
||||
tfdiags.AssertDiagnosticsMatch(t, gotDiags, tc.wantDiags)
|
||||
})
|
||||
}
|
||||
}
|
||||
13
internal/command/views/workspace.go
Normal file
13
internal/command/views/workspace.go
Normal file
|
|
@ -0,0 +1,13 @@
|
|||
// Copyright IBM Corp. 2014, 2026
|
||||
// SPDX-License-Identifier: BUSL-1.1
|
||||
|
||||
package views
|
||||
|
||||
import (
|
||||
"github.com/hashicorp/terraform/internal/tfdiags"
|
||||
)
|
||||
|
||||
// The WorkspaceList view is used for the `workspace list` subcommand.
|
||||
type WorkspaceList interface {
|
||||
List(selected string, list []string, diags tfdiags.Diagnostics)
|
||||
}
|
||||
|
|
@ -17,11 +17,13 @@ import (
|
|||
"github.com/hashicorp/terraform/internal/backend/local"
|
||||
"github.com/hashicorp/terraform/internal/backend/remote-state/inmem"
|
||||
"github.com/hashicorp/terraform/internal/command/arguments"
|
||||
"github.com/hashicorp/terraform/internal/command/views"
|
||||
"github.com/hashicorp/terraform/internal/command/workdir"
|
||||
"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"
|
||||
"github.com/hashicorp/terraform/internal/terminal"
|
||||
)
|
||||
|
||||
func TestWorkspace_allCommands_pluggableStateStore(t *testing.T) {
|
||||
|
|
@ -108,6 +110,7 @@ func TestWorkspace_allCommands_pluggableStateStore(t *testing.T) {
|
|||
//// List Workspaces
|
||||
ui = new(cli.MockUi)
|
||||
meta.Ui = ui
|
||||
meta.WorkingDir = workdir.NewDir(".")
|
||||
listCmd := &WorkspaceListCommand{
|
||||
Meta: meta,
|
||||
}
|
||||
|
|
@ -381,11 +384,9 @@ func TestWorkspace_createAndList(t *testing.T) {
|
|||
// create multiple workspaces
|
||||
for _, env := range envs {
|
||||
ui := new(cli.MockUi)
|
||||
view, _ := testView(t)
|
||||
newCmd := &WorkspaceNewCommand{
|
||||
Meta: Meta{
|
||||
Ui: ui,
|
||||
View: view,
|
||||
WorkingDir: workdir.NewDir("."),
|
||||
},
|
||||
}
|
||||
|
|
@ -396,12 +397,11 @@ func TestWorkspace_createAndList(t *testing.T) {
|
|||
|
||||
listCmd := &WorkspaceListCommand{}
|
||||
ui := new(cli.MockUi)
|
||||
view, _ := testView(t)
|
||||
listCmd.Meta = Meta{
|
||||
Ui: ui,
|
||||
View: view,
|
||||
WorkingDir: workdir.NewDir("."),
|
||||
}
|
||||
|
||||
if code := listCmd.Run(nil); code != 0 {
|
||||
t.Fatalf("bad: %d\n\n%s", code, ui.ErrorWriter)
|
||||
}
|
||||
|
|
@ -523,10 +523,8 @@ func TestWorkspace_createInvalid(t *testing.T) {
|
|||
// list workspaces to make sure none were created
|
||||
listCmd := &WorkspaceListCommand{}
|
||||
ui := new(cli.MockUi)
|
||||
view, _ := testView(t)
|
||||
listCmd.Meta = Meta{
|
||||
Ui: ui,
|
||||
View: view,
|
||||
WorkingDir: workdir.NewDir("."),
|
||||
}
|
||||
|
||||
|
|
@ -873,7 +871,6 @@ func TestWorkspace_cannotDeleteDefaultWorkspace(t *testing.T) {
|
|||
ui = cli.NewMockUi()
|
||||
listCmd.Meta = Meta{
|
||||
Ui: ui,
|
||||
View: view,
|
||||
WorkingDir: workdir.NewDir("."),
|
||||
}
|
||||
|
||||
|
|
@ -1035,11 +1032,9 @@ func TestWorkspace_envCommandDeprecationWarnings(t *testing.T) {
|
|||
|
||||
// Assert `terraform env list` returns expected deprecation warning
|
||||
ui = new(cli.MockUi)
|
||||
view, _ = testView(t)
|
||||
listCmd := &WorkspaceListCommand{
|
||||
Meta: Meta{
|
||||
Ui: ui,
|
||||
View: view,
|
||||
WorkingDir: workdir.NewDir("."),
|
||||
},
|
||||
LegacyName: true,
|
||||
|
|
@ -1148,8 +1143,8 @@ func TestWorkspace_extraArgError(t *testing.T) {
|
|||
if code := listCmd.Run(args); code != 1 {
|
||||
t.Fatalf("bad: %d\n\n%s", code, ui.ErrorWriter)
|
||||
}
|
||||
expectedError = "Too many command line arguments. Did you mean to use -chdir?\n"
|
||||
if ui.ErrorWriter.String() != expectedError {
|
||||
expectedError = "Error: Too many command line arguments. Did you mean to use -chdir?\n"
|
||||
if !strings.Contains(ui.ErrorWriter.String(), expectedError) {
|
||||
t.Fatalf("expected error to include \"%s\" but was missing, got: %s", expectedError, ui.ErrorWriter.String())
|
||||
}
|
||||
|
||||
|
|
@ -1191,3 +1186,216 @@ func TestWorkspace_extraArgError(t *testing.T) {
|
|||
t.Fatalf("expected error to include %s but was missing, got: %s", expectedError, ui.ErrorWriter.String())
|
||||
}
|
||||
}
|
||||
|
||||
// Test human output from commands, with color enabled or disabled
|
||||
func TestWorkspace_humanOutput(t *testing.T) {
|
||||
newMeta := func(colourEnabled bool) (Meta, *cli.MockUi, *views.View, func(t *testing.T) *terminal.TestOutput) {
|
||||
ui := new(cli.MockUi)
|
||||
view, done := testView(t)
|
||||
return Meta{
|
||||
Ui: ui,
|
||||
View: view,
|
||||
Color: colourEnabled,
|
||||
WorkingDir: workdir.NewDir("."),
|
||||
}, ui, view, done
|
||||
}
|
||||
|
||||
// Create a temporary working directory that is empty
|
||||
td := t.TempDir()
|
||||
t.Chdir(td)
|
||||
|
||||
envsSet1 := []string{"test_a", "test_b", "test_c"}
|
||||
envsSet2 := []string{"test_d", "test_e", "test_f"}
|
||||
|
||||
// Assert output from creating a workspace with color enabled
|
||||
for _, env := range envsSet1 {
|
||||
useColor := true
|
||||
meta, ui, _, _ := newMeta(useColor)
|
||||
newCmd := &WorkspaceNewCommand{
|
||||
Meta: meta,
|
||||
}
|
||||
if code := newCmd.Run([]string{env}); code != 0 {
|
||||
t.Fatalf("bad: %d\n\n%s", code, ui.ErrorWriter)
|
||||
}
|
||||
|
||||
expectedOutput := fmt.Sprintf("\x1b[0m\x1b[32m\x1b[1mCreated and switched to workspace \"%s\"!\x1b[0m\x1b[32m\n\nYou're now on a new, empty workspace. Workspaces isolate their state,\nso if you run \"terraform plan\" Terraform will not see any existing state\nfor this configuration.\x1b[0m\n", env)
|
||||
if ui.OutputWriter.String() != expectedOutput {
|
||||
t.Fatalf("want: %s\ngot: %s", expectedOutput, ui.OutputWriter.String())
|
||||
}
|
||||
}
|
||||
|
||||
// Assert output from creating a workspace with color disabled
|
||||
for _, env := range envsSet2 {
|
||||
useColor := false
|
||||
meta, ui, _, _ := newMeta(useColor)
|
||||
newCmd := &WorkspaceNewCommand{
|
||||
Meta: meta,
|
||||
}
|
||||
if code := newCmd.Run([]string{env}); code != 0 {
|
||||
t.Fatalf("bad: %d\n\n%s", code, ui.ErrorWriter)
|
||||
}
|
||||
|
||||
expectedOutput := fmt.Sprintf("Created and switched to workspace \"%s\"!\n\nYou're now on a new, empty workspace. Workspaces isolate their state,\nso if you run \"terraform plan\" Terraform will not see any existing state\nfor this configuration.\n", env)
|
||||
if ui.OutputWriter.String() != expectedOutput {
|
||||
t.Fatalf("want: %s\ngot: %s", expectedOutput, ui.OutputWriter.String())
|
||||
}
|
||||
}
|
||||
|
||||
// NOTE: the last-created workspace will be selected: test_f
|
||||
|
||||
// Assert output from listing workspaces with color enabled
|
||||
useColor := true
|
||||
meta, ui, _, _ := newMeta(useColor)
|
||||
listCmd := &WorkspaceListCommand{
|
||||
Meta: meta,
|
||||
}
|
||||
if code := listCmd.Run(nil); code != 0 {
|
||||
t.Fatalf("bad: %d\n\n%s", code, ui.ErrorWriter)
|
||||
}
|
||||
actual := ui.OutputWriter.String()
|
||||
expectedOutput := " default\n test_a\n test_b\n test_c\n test_d\n test_e\n* test_f\n\n"
|
||||
if actual != expectedOutput {
|
||||
t.Fatalf("\nexpected: %q\nactual: %q", expectedOutput, actual)
|
||||
}
|
||||
|
||||
// Assert output from listing workspaces with color disabled
|
||||
useColor = false
|
||||
meta, ui, _, _ = newMeta(useColor)
|
||||
listCmd = &WorkspaceListCommand{
|
||||
Meta: meta,
|
||||
}
|
||||
if code := listCmd.Run(nil); code != 0 {
|
||||
t.Fatalf("bad: %d\n\n%s", code, ui.ErrorWriter)
|
||||
}
|
||||
actual = ui.OutputWriter.String()
|
||||
expectedOutput = " default\n test_a\n test_b\n test_c\n test_d\n test_e\n* test_f\n\n"
|
||||
if actual != expectedOutput {
|
||||
t.Fatalf("\nexpected: %q\nactual: %q", expectedOutput, actual)
|
||||
}
|
||||
|
||||
// Assert output from showing the current workspace with color enabled
|
||||
useColor = true
|
||||
meta, ui, _, _ = newMeta(useColor)
|
||||
showCmd := &WorkspaceShowCommand{
|
||||
Meta: meta,
|
||||
}
|
||||
if code := showCmd.Run(nil); code != 0 {
|
||||
t.Fatalf("bad: %d\n\n%s", code, ui.ErrorWriter)
|
||||
}
|
||||
actual = ui.OutputWriter.String()
|
||||
expectedOutput = "test_f\n"
|
||||
if actual != expectedOutput {
|
||||
t.Fatalf("\nexpected: %q\nactual: %q", expectedOutput, actual)
|
||||
}
|
||||
|
||||
// Assert output from showing the current workspace with color disabled
|
||||
useColor = false
|
||||
meta, ui, _, _ = newMeta(useColor)
|
||||
showCmd = &WorkspaceShowCommand{
|
||||
Meta: meta,
|
||||
}
|
||||
if code := showCmd.Run(nil); code != 0 {
|
||||
t.Fatalf("bad: %d\n\n%s", code, ui.ErrorWriter)
|
||||
}
|
||||
actual = ui.OutputWriter.String()
|
||||
expectedOutput = "test_f\n"
|
||||
if actual != expectedOutput {
|
||||
t.Fatalf("\nexpected: %q\nactual: %q", expectedOutput, actual)
|
||||
}
|
||||
|
||||
// Assert output from selecting a workspace with color enabled
|
||||
useColor = true
|
||||
meta, ui, _, _ = newMeta(useColor)
|
||||
selectCmd := &WorkspaceSelectCommand{
|
||||
Meta: meta,
|
||||
}
|
||||
args := []string{"test_a"}
|
||||
if code := selectCmd.Run(args); code != 0 {
|
||||
t.Fatalf("bad: %d\n\n%s", code, ui.ErrorWriter)
|
||||
}
|
||||
actual = ui.OutputWriter.String()
|
||||
expectedOutput = "\x1b[0m\x1b[32mSwitched to workspace \"test_a\".\x1b[0m\n"
|
||||
if actual != expectedOutput {
|
||||
t.Fatalf("want: %s\ngot: %s", expectedOutput, actual)
|
||||
}
|
||||
|
||||
// Assert output from selecting a workspace with color disabled
|
||||
useColor = false
|
||||
meta, ui, _, _ = newMeta(useColor)
|
||||
selectCmd = &WorkspaceSelectCommand{
|
||||
Meta: meta,
|
||||
}
|
||||
args = []string{"test_b"}
|
||||
if code := selectCmd.Run(args); code != 0 {
|
||||
t.Fatalf("bad: %d\n\n%s", code, ui.ErrorWriter)
|
||||
}
|
||||
actual = ui.OutputWriter.String()
|
||||
expectedOutput = "Switched to workspace \"test_b\".\n"
|
||||
if actual != expectedOutput {
|
||||
t.Fatalf("want: %s\ngot: %s", expectedOutput, actual)
|
||||
}
|
||||
|
||||
// Assert output from deleting a workspace with color enabled
|
||||
useColor = true
|
||||
meta, ui, _, _ = newMeta(useColor)
|
||||
deleteCmd := &WorkspaceDeleteCommand{
|
||||
Meta: meta,
|
||||
}
|
||||
args = []string{"test_c"}
|
||||
if code := deleteCmd.Run(args); code != 0 {
|
||||
t.Fatalf("bad: %d\n\n%s", code, ui.ErrorWriter)
|
||||
}
|
||||
actual = ui.OutputWriter.String()
|
||||
expectedOutput = "\x1b[0m\x1b[32mDeleted workspace \"test_c\"!\x1b[0m\n"
|
||||
if actual != expectedOutput {
|
||||
t.Fatalf("want: %s\ngot: %s", expectedOutput, actual)
|
||||
}
|
||||
|
||||
// Assert output from deleting a workspace with color disabled
|
||||
useColor = false
|
||||
meta, ui, _, _ = newMeta(useColor)
|
||||
deleteCmd = &WorkspaceDeleteCommand{
|
||||
Meta: meta,
|
||||
}
|
||||
args = []string{"test_d"}
|
||||
if code := deleteCmd.Run(args); code != 0 {
|
||||
t.Fatalf("bad: %d\n\n%s", code, ui.ErrorWriter)
|
||||
}
|
||||
actual = ui.OutputWriter.String()
|
||||
expectedOutput = "Deleted workspace \"test_d\"!\n"
|
||||
if actual != expectedOutput {
|
||||
t.Fatalf("want: %s\ngot: %s", expectedOutput, actual)
|
||||
}
|
||||
|
||||
// Assert error output from deleting a non-existent workspace with color enabled
|
||||
useColor = true
|
||||
meta, ui, _, _ = newMeta(useColor)
|
||||
deleteCmd = &WorkspaceDeleteCommand{
|
||||
Meta: meta,
|
||||
}
|
||||
args = []string{"foobar"}
|
||||
if code := deleteCmd.Run(args); code != 1 {
|
||||
t.Fatalf("expected error but got code %d:\n\n%s\n\n%s", code, ui.OutputWriter, ui.ErrorWriter)
|
||||
}
|
||||
actual = ui.ErrorWriter.String()
|
||||
expectedOutput = "\x1b[31mWorkspace \"foobar\" doesn't exist.\n\nYou can create this workspace with the \"new\" subcommand \nor include the \"-or-create\" flag with the \"select\" subcommand.\x1b[0m\x1b[0m\n"
|
||||
if actual != expectedOutput {
|
||||
t.Fatalf("want: %s\ngot: %s", expectedOutput, actual)
|
||||
}
|
||||
|
||||
// Assert error output from deleting a non-existent workspace with color disabled
|
||||
useColor = false
|
||||
meta, ui, _, _ = newMeta(useColor)
|
||||
deleteCmd = &WorkspaceDeleteCommand{
|
||||
Meta: meta,
|
||||
}
|
||||
args = []string{"foobar"}
|
||||
if code := deleteCmd.Run(args); code != 1 {
|
||||
t.Fatalf("expected error but got code %d:\n\n%s\n\n%s", code, ui.OutputWriter, ui.ErrorWriter)
|
||||
}
|
||||
actual = ui.ErrorWriter.String()
|
||||
expectedOutput = "Workspace \"foobar\" doesn't exist.\n\nYou can create this workspace with the \"new\" subcommand \nor include the \"-or-create\" flag with the \"select\" subcommand.\n"
|
||||
if actual != expectedOutput {
|
||||
t.Fatalf("want: %s\ngot: %s", expectedOutput, actual)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -8,7 +8,10 @@ import (
|
|||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/hashicorp/cli"
|
||||
"github.com/hashicorp/terraform/internal/command/arguments"
|
||||
"github.com/hashicorp/terraform/internal/command/views"
|
||||
"github.com/hashicorp/terraform/internal/tfdiags"
|
||||
"github.com/posener/complete"
|
||||
)
|
||||
|
||||
|
|
@ -17,29 +20,43 @@ type WorkspaceListCommand struct {
|
|||
LegacyName bool
|
||||
}
|
||||
|
||||
func (c *WorkspaceListCommand) Run(args []string) int {
|
||||
args = c.Meta.process(args)
|
||||
func (c *WorkspaceListCommand) Run(rawArgs []string) int {
|
||||
var diags tfdiags.Diagnostics
|
||||
|
||||
// c.Meta.process removes global flags (-no-color, -compact-warnings) and uses them to configure the Ui and View.
|
||||
//
|
||||
// Other command implementations remove those arguments via arguments.ParseView, instead. That is only possible if views
|
||||
// are used for both human and machine output. This command still uses cli.Ui for human output, so c.Meta.process is necessary.
|
||||
rawArgs = c.Meta.process(rawArgs)
|
||||
|
||||
// Parse command-specific arguments.
|
||||
args, diags := arguments.ParseWorkspaceList(rawArgs)
|
||||
|
||||
// Prepare the view
|
||||
//
|
||||
// Note - here the view uses:
|
||||
// - cli.Ui for human output
|
||||
// - view.View for machine-readable output
|
||||
//
|
||||
// Note: We don't call c.View.Configure here after obtaining the view because it's already called in c.Meta.process.
|
||||
// TODO: When we migrate human output to use views fully instead of cli.Ui we would replace using c.Meta.process with arguments.ParseView.
|
||||
// arguments.ParseView returns a 'common' View that can be used as an argument in the c.View.Configure method.
|
||||
view := newWorkspaceList(args.ViewType, c.View, c.Ui, &c.Meta)
|
||||
|
||||
// Warn against using `terraform env` commands
|
||||
envCommandShowWarning(c.Ui, c.LegacyName)
|
||||
|
||||
cmdFlags := c.Meta.defaultFlagSet("workspace list")
|
||||
cmdFlags.Usage = func() { c.Ui.Error(c.Help()) }
|
||||
if err := cmdFlags.Parse(args); err != nil {
|
||||
c.Ui.Error(fmt.Sprintf("Error parsing command-line flags: %s\n", err.Error()))
|
||||
return 1
|
||||
}
|
||||
|
||||
args = cmdFlags.Args()
|
||||
configPath, err := ModulePath(args)
|
||||
if err != nil {
|
||||
c.Ui.Error(err.Error())
|
||||
// Now the view is ready, process any error diagnostics from parsing arguments.
|
||||
if diags.HasErrors() {
|
||||
view.List("", nil, diags)
|
||||
return 1
|
||||
}
|
||||
|
||||
// Load the backend
|
||||
view := arguments.ViewHuman
|
||||
b, diags := c.backend(configPath, view)
|
||||
configPath := c.WorkingDir.RootModuleDir()
|
||||
b, diags := c.backend(configPath, args.ViewType)
|
||||
if diags.HasErrors() {
|
||||
c.showDiagnostics(diags)
|
||||
view.List("", nil, diags)
|
||||
return 1
|
||||
}
|
||||
|
||||
|
|
@ -49,34 +66,26 @@ func (c *WorkspaceListCommand) Run(args []string) int {
|
|||
states, wDiags := b.Workspaces()
|
||||
diags = diags.Append(wDiags)
|
||||
if wDiags.HasErrors() {
|
||||
c.Ui.Error(wDiags.Err().Error())
|
||||
view.List("", nil, diags)
|
||||
return 1
|
||||
}
|
||||
c.showDiagnostics(diags) // output warnings, if any
|
||||
|
||||
env, isOverridden := c.WorkspaceOverridden()
|
||||
|
||||
if len(states) != 0 {
|
||||
var out bytes.Buffer
|
||||
for _, s := range states {
|
||||
if s == env {
|
||||
out.WriteString("* ")
|
||||
} else {
|
||||
out.WriteString(" ")
|
||||
}
|
||||
out.WriteString(s + "\n")
|
||||
}
|
||||
|
||||
c.Ui.Output(out.String())
|
||||
} else {
|
||||
// Warn that no states exist
|
||||
c.showDiagnostics(warnNoEnvsExistDiag(env))
|
||||
}
|
||||
|
||||
if isOverridden {
|
||||
c.Ui.Output(envIsOverriddenNote)
|
||||
warn := tfdiags.Sourceless(
|
||||
tfdiags.Warning,
|
||||
envIsOverriddenNote,
|
||||
"",
|
||||
)
|
||||
diags = diags.Append(warn)
|
||||
}
|
||||
|
||||
// Print:
|
||||
// 1. Diagnostics
|
||||
// 2. The list of workspaces, highlighting the current workspace
|
||||
view.List(env, states, diags)
|
||||
|
||||
return 0
|
||||
}
|
||||
|
||||
|
|
@ -101,3 +110,49 @@ Usage: terraform [global options] workspace list
|
|||
func (c *WorkspaceListCommand) Synopsis() string {
|
||||
return "List Workspaces"
|
||||
}
|
||||
|
||||
type workspaceListHuman struct {
|
||||
ui cli.Ui
|
||||
meta *Meta
|
||||
}
|
||||
|
||||
// List is used to assemble the list of Workspaces and log it via Output
|
||||
func (v *workspaceListHuman) List(selected string, list []string, diags tfdiags.Diagnostics) {
|
||||
// Print diags above output
|
||||
v.meta.showDiagnostics(diags)
|
||||
|
||||
// Print list
|
||||
if len(list) > 0 {
|
||||
var out bytes.Buffer
|
||||
for _, s := range list {
|
||||
if s == selected {
|
||||
out.WriteString("* ")
|
||||
} else {
|
||||
out.WriteString(" ")
|
||||
}
|
||||
out.WriteString(s + "\n")
|
||||
}
|
||||
v.ui.Output(out.String())
|
||||
} else {
|
||||
// Warn that no states exist
|
||||
v.meta.showDiagnostics(warnNoEnvsExistDiag(selected))
|
||||
}
|
||||
}
|
||||
|
||||
// newWorkspaceList returns a views.WorkspaceList interface.
|
||||
//
|
||||
// When human-readable output is migrated from cli.Ui to views.View this method should be deleted and
|
||||
// replaced with using a views.NewWorkspaceList method.
|
||||
func newWorkspaceList(vt arguments.ViewType, view *views.View, ui cli.Ui, meta *Meta) views.WorkspaceList {
|
||||
switch vt {
|
||||
case arguments.ViewJSON:
|
||||
panic("JSON output is not supported for workspace list command")
|
||||
case arguments.ViewHuman:
|
||||
return &workspaceListHuman{
|
||||
ui: ui,
|
||||
meta: meta,
|
||||
}
|
||||
default:
|
||||
panic(fmt.Sprintf("unknown view type %v", vt))
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue