Skip to content

Commit 5adbd34

Browse files
committed
refactor: Change workspace list -json to output static logs, instead of structured logs.
1 parent f3c391b commit 5adbd34

File tree

4 files changed

+206
-172
lines changed

4 files changed

+206
-172
lines changed
Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
// Copyright (c) HashiCorp, Inc.
2+
// SPDX-License-Identifier: BUSL-1.1
3+
4+
package arguments
5+
6+
import (
7+
"github.com/hashicorp/terraform/internal/tfdiags"
8+
)
9+
10+
// Workspace represents the command-line arguments common between all workspace subcommands.
11+
//
12+
// Subcommands that accept additional arguments should have a specific struct that embeds this struct.
13+
type Workspace struct {
14+
// ViewType specifies which output format to use
15+
ViewType ViewType
16+
}
17+
18+
// ParseWorkspaceList processes CLI arguments, returning a Workspace value and errors.
19+
// If errors are encountered, an Workspace value is still returned representing
20+
// the best effort interpretation of the arguments.
21+
func ParseWorkspaceList(args []string) (*Workspace, tfdiags.Diagnostics) {
22+
var diags tfdiags.Diagnostics
23+
24+
var jsonOutput bool
25+
cmdFlags := defaultFlagSet("workspace list")
26+
cmdFlags.BoolVar(&jsonOutput, "json", false, "produce JSON output")
27+
if err := cmdFlags.Parse(args); err != nil {
28+
diags = diags.Append(tfdiags.Sourceless(
29+
tfdiags.Error,
30+
"Failed to parse command-line flags",
31+
err.Error(),
32+
))
33+
}
34+
35+
workspace := &Workspace{}
36+
37+
switch {
38+
case jsonOutput:
39+
workspace.ViewType = ViewJSON
40+
default:
41+
workspace.ViewType = ViewHuman
42+
}
43+
44+
return workspace, diags
45+
}

internal/command/views/workspace.go

Lines changed: 62 additions & 92 deletions
Original file line numberDiff line numberDiff line change
@@ -5,38 +5,26 @@ package views
55

66
import (
77
"bytes"
8-
"errors"
8+
"encoding/json"
99
"fmt"
1010
"strings"
1111

1212
"github.com/hashicorp/terraform/internal/command/arguments"
13+
viewsjson "github.com/hashicorp/terraform/internal/command/views/json"
1314
"github.com/hashicorp/terraform/internal/tfdiags"
1415
)
1516

16-
// The Workspace view is used for workspace subcommands.
17-
type Workspace interface {
18-
Diagnostics(diags tfdiags.Diagnostics)
19-
Output(message string)
20-
21-
// These methods are present in the interface to allow
22-
// backwards compatibility while human-readable output is
23-
// fulfilled using the cli.Ui interface.
24-
Error(message string)
25-
Warn(message string)
26-
}
27-
17+
// The WorkspaceList view is used for the `workspace list` subcommand.
2818
type WorkspaceList interface {
29-
Workspace
30-
31-
List(selected string, list []string)
19+
List(selected string, list []string, diags tfdiags.Diagnostics)
3220
}
3321

3422
// NewWorkspace returns the Workspace implementation for the given ViewType.
3523
func NewWorkspaceList(vt arguments.ViewType, view *View) WorkspaceList {
3624
switch vt {
3725
case arguments.ViewJSON:
3826
return &WorkspaceJSON{
39-
view: NewJSONView(view),
27+
view: view,
4028
}
4129
case arguments.ViewHuman:
4230
// TODO: Allow use of WorkspaceHuman here when we remove use of cli.Ui from workspace commands.
@@ -49,75 +37,62 @@ func NewWorkspaceList(vt arguments.ViewType, view *View) WorkspaceList {
4937
// The WorkspaceJSON implementation renders machine-readable logs, suitable for
5038
// integrating with other software.
5139
type WorkspaceJSON struct {
52-
view *JSONView
40+
view *View
5341
}
5442

55-
var _ Workspace = (*WorkspaceJSON)(nil)
43+
var _ WorkspaceList = (*WorkspaceJSON)(nil)
5644

5745
// Diagnostics renders a list of diagnostics, including the option for compact warnings.
5846
func (v *WorkspaceJSON) Diagnostics(diags tfdiags.Diagnostics) {
5947
v.view.Diagnostics(diags)
6048
}
6149

62-
// Output is used to render text in the terminal via stdout.
63-
func (v *WorkspaceJSON) Output(msg string) {
64-
v.view.Log(msg)
50+
type WorkspaceListOutput struct {
51+
Workspaces []WorkspaceOutput `json:"workspaces"`
52+
Diagnostics []*viewsjson.Diagnostic `json:"diagnostics"`
53+
}
54+
55+
type WorkspaceOutput struct {
56+
Name string `json:"name"`
57+
IsCurrent bool `json:"is_current"`
6558
}
6659

6760
// List is used to log the list of present workspaces and indicate which is currently selected
68-
func (v *WorkspaceJSON) List(current string, list []string) {
69-
var msg bytes.Buffer
70-
for _, s := range list {
71-
if s == current {
72-
msg.WriteString("* ")
73-
} else {
74-
msg.WriteString(" ")
61+
func (v *WorkspaceJSON) List(current string, list []string, diags tfdiags.Diagnostics) {
62+
output := WorkspaceListOutput{}
63+
64+
for _, item := range list {
65+
workspace := WorkspaceOutput{
66+
Name: item,
67+
IsCurrent: item == current,
7568
}
76-
msg.WriteString(s + "\n")
69+
output.Workspaces = append(output.Workspaces, workspace)
7770
}
7871

79-
v.view.log.Info(
80-
msg.String(),
81-
"current", current,
82-
"workspaces", list)
83-
}
72+
if output.Workspaces == nil {
73+
// Make sure this always appears as an array in our output, since
74+
// this is easier to consume for dynamically-typed languages.
75+
output.Workspaces = []WorkspaceOutput{}
76+
}
8477

85-
// Error
86-
//
87-
// This method is a temporary measure while the workspace subcommands contain both
88-
// use of cli.Ui for human output and view.View for machine-readable output.
89-
// In future calling code should use Diagnostics directly.
90-
//
91-
// If a message is being logged as an error we can create a native error (which can be made from a string),
92-
// use existing logic in (tfdiags.Diagnostics) Append to create an error diagnostic from a native error,
93-
// and then log that single error diagnostic.
94-
func (v *WorkspaceJSON) Error(msg string) {
95-
var diags tfdiags.Diagnostics
96-
err := errors.New(msg)
97-
diags = diags.Append(err)
98-
99-
v.Diagnostics(diags)
100-
}
78+
configSources := v.view.configSources()
79+
for _, diag := range diags {
80+
output.Diagnostics = append(output.Diagnostics, viewsjson.NewDiagnostic(diag, configSources))
81+
}
10182

102-
// Warn
103-
//
104-
// This method is a temporary measure while the workspace subcommands contain both
105-
// use of cli.Ui for human output and view.View for machine-readable output.
106-
// In future calling code should use Diagnostics directly.
107-
//
108-
// This method takes inspiration from how native errors are converted into error diagnostics;
109-
// the Details value is left empty and the provided string is used only in the Summary.
110-
// See : https://github.com/hashicorp/terraform/blob/v1.14.4/internal/tfdiags/error.go
111-
func (v *WorkspaceJSON) Warn(msg string) {
112-
var diags tfdiags.Diagnostics
113-
warn := tfdiags.Sourceless(
114-
tfdiags.Warning,
115-
msg,
116-
"",
117-
)
118-
diags = diags.Append(warn)
119-
120-
v.Diagnostics(diags)
83+
if output.Diagnostics == nil {
84+
// Make sure this always appears as an array in our output, since
85+
// this is easier to consume for dynamically-typed languages.
86+
output.Diagnostics = []*viewsjson.Diagnostic{}
87+
}
88+
89+
jsonOutput, err := json.MarshalIndent(output, "", " ")
90+
if err != nil {
91+
// Should never happen because we fully-control the input here
92+
panic(err)
93+
}
94+
95+
v.view.streams.Println(string(jsonOutput))
12196
}
12297

12398
// The WorkspaceHuman implementation renders human-readable text logs, suitable for
@@ -126,32 +101,27 @@ type WorkspaceHuman struct {
126101
view *View
127102
}
128103

129-
var _ Workspace = (*WorkspaceHuman)(nil)
104+
var _ WorkspaceList = (*WorkspaceHuman)(nil)
130105

131-
// Diagnostics renders a list of diagnostics, including the option for compact warnings.
132-
func (v *WorkspaceHuman) Diagnostics(diags tfdiags.Diagnostics) {
106+
func (v *WorkspaceHuman) List(selected string, list []string, diags tfdiags.Diagnostics) {
107+
// Print diags above output
133108
v.view.Diagnostics(diags)
134-
}
135109

136-
// Output is used to render text in the terminal, such as data returned from a command.
137-
func (v *WorkspaceHuman) Output(msg string) {
138-
v.view.streams.Println(v.prepareMessage(msg))
139-
}
140-
141-
// Error is implemented to fulfil the Workspace interface
142-
// Once we can make breaking changes that interface shouldn't have an
143-
// Error method, so this method should be deleted in future.
144-
func (v *WorkspaceHuman) Error(msg string) {
145-
panic("(WorkspaceHuman).Error should not be used")
146-
}
147-
148-
// Warn is implemented to fulfil the Workspace interface
149-
// Onc we can make breaking changes that interface shouldn't have an
150-
// Warn method, so this method should be deleted in future.
151-
func (v *WorkspaceHuman) Warn(msg string) {
152-
panic("(WorkspaceHuman).Warn should not be used")
110+
// Print list
111+
if len(list) > 0 {
112+
var out bytes.Buffer
113+
for _, s := range list {
114+
if s == selected {
115+
out.WriteString("* ")
116+
} else {
117+
out.WriteString(" ")
118+
}
119+
out.WriteString(s + "\n")
120+
}
121+
v.output(out.String())
122+
}
153123
}
154124

155-
func (v *WorkspaceHuman) prepareMessage(msg string) string {
125+
func (v *WorkspaceHuman) output(msg string) string {
156126
return v.view.colorize.Color(strings.TrimSpace(msg))
157127
}

0 commit comments

Comments
 (0)