Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Commit 00d468b

Browse files
authored
feat(cli): add --output={text,json} to version cmd (coder#7010)
* feat(cliui): add TextFormat * feat(cli): add --format={text,json} to version cmd
1 parent 9c4ccd7 commit 00d468b

File tree

7 files changed

+192
-33
lines changed

7 files changed

+192
-33
lines changed

cli/cliui/output.go

+21
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ package cliui
33
import (
44
"context"
55
"encoding/json"
6+
"fmt"
67
"reflect"
78
"strings"
89

@@ -171,3 +172,23 @@ func (jsonFormat) Format(_ context.Context, data any) (string, error) {
171172

172173
return string(outBytes), nil
173174
}
175+
176+
type textFormat struct{}
177+
178+
var _ OutputFormat = textFormat{}
179+
180+
// TextFormat is a formatter that just outputs unstructured text.
181+
// It uses fmt.Sprintf under the hood.
182+
func TextFormat() OutputFormat {
183+
return textFormat{}
184+
}
185+
186+
func (textFormat) ID() string {
187+
return "text"
188+
}
189+
190+
func (textFormat) AttachOptions(_ *clibase.OptionSet) {}
191+
192+
func (textFormat) Format(_ context.Context, data any) (string, error) {
193+
return fmt.Sprintf("%s", data), nil
194+
}

cli/cliui/output_test.go

+3
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,9 @@ func Test_OutputFormatter(t *testing.T) {
5050
require.Panics(t, func() {
5151
cliui.NewOutputFormatter(cliui.JSONFormat())
5252
})
53+
require.NotPanics(t, func() {
54+
cliui.NewOutputFormatter(cliui.JSONFormat(), cliui.TextFormat())
55+
})
5356
})
5457

5558
t.Run("NoMissingFormatID", func(t *testing.T) {

cli/root.go

+1-31
Original file line numberDiff line numberDiff line change
@@ -82,7 +82,7 @@ func (r *RootCmd) Core() []*clibase.Cmd {
8282
r.templates(),
8383
r.users(),
8484
r.tokens(),
85-
r.version(),
85+
r.version(defaultVersionInfo),
8686

8787
// Workspace Commands
8888
r.configSSH(),
@@ -370,36 +370,6 @@ func LoggerFromContext(ctx context.Context) (slog.Logger, bool) {
370370
return l, ok
371371
}
372372

373-
// version prints the coder version
374-
func (*RootCmd) version() *clibase.Cmd {
375-
return &clibase.Cmd{
376-
Use: "version",
377-
Short: "Show coder version",
378-
Handler: func(inv *clibase.Invocation) error {
379-
var str strings.Builder
380-
_, _ = str.WriteString("Coder ")
381-
if buildinfo.IsAGPL() {
382-
_, _ = str.WriteString("(AGPL) ")
383-
}
384-
_, _ = str.WriteString(buildinfo.Version())
385-
buildTime, valid := buildinfo.Time()
386-
if valid {
387-
_, _ = str.WriteString(" " + buildTime.Format(time.UnixDate))
388-
}
389-
_, _ = str.WriteString("\r\n" + buildinfo.ExternalURL() + "\r\n\r\n")
390-
391-
if buildinfo.IsSlim() {
392-
_, _ = str.WriteString(fmt.Sprintf("Slim build of Coder, does not support the %s subcommand.\n", cliui.Styles.Code.Render("server")))
393-
} else {
394-
_, _ = str.WriteString(fmt.Sprintf("Full build of Coder, supports the %s subcommand.\n", cliui.Styles.Code.Render("server")))
395-
}
396-
397-
_, _ = fmt.Fprint(inv.Stdout, str.String())
398-
return nil
399-
},
400-
}
401-
}
402-
403373
func isTest() bool {
404374
return flag.Lookup("test.v") != nil
405375
}
+5-1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,10 @@
1-
Usage: coder version
1+
Usage: coder version [flags]
22

33
Show coder version
44

5+
Options
6+
-o, --output string (default: text)
7+
Output format. Available formats: text, json.
8+
59
---
610
Run `coder --help` for a list of global options.

cli/version.go

+84
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,84 @@
1+
package cli
2+
3+
import (
4+
"fmt"
5+
"strings"
6+
"time"
7+
8+
"github.com/coder/coder/buildinfo"
9+
"github.com/coder/coder/cli/clibase"
10+
"github.com/coder/coder/cli/cliui"
11+
)
12+
13+
// versionInfo wraps the stuff we get from buildinfo so that it's
14+
// easier to emit in different formats.
15+
type versionInfo struct {
16+
Version string `json:"version"`
17+
BuildTime time.Time `json:"build_time"`
18+
ExternalURL string `json:"external_url"`
19+
Slim bool `json:"slim"`
20+
AGPL bool `json:"agpl"`
21+
}
22+
23+
// String() implements Stringer
24+
func (vi versionInfo) String() string {
25+
var str strings.Builder
26+
_, _ = str.WriteString("Coder ")
27+
if vi.AGPL {
28+
_, _ = str.WriteString("(AGPL) ")
29+
}
30+
_, _ = str.WriteString(vi.Version)
31+
32+
if !vi.BuildTime.IsZero() {
33+
_, _ = str.WriteString(" " + vi.BuildTime.Format(time.UnixDate))
34+
}
35+
_, _ = str.WriteString("\r\n" + vi.ExternalURL + "\r\n\r\n")
36+
37+
if vi.Slim {
38+
_, _ = str.WriteString(fmt.Sprintf("Slim build of Coder, does not support the %s subcommand.", cliui.Styles.Code.Render("server")))
39+
} else {
40+
_, _ = str.WriteString(fmt.Sprintf("Full build of Coder, supports the %s subcommand.", cliui.Styles.Code.Render("server")))
41+
}
42+
return str.String()
43+
}
44+
45+
func defaultVersionInfo() *versionInfo {
46+
buildTime, _ := buildinfo.Time()
47+
return &versionInfo{
48+
Version: buildinfo.Version(),
49+
BuildTime: buildTime,
50+
ExternalURL: buildinfo.ExternalURL(),
51+
Slim: buildinfo.IsSlim(),
52+
AGPL: buildinfo.IsAGPL(),
53+
}
54+
}
55+
56+
// version prints the coder version
57+
func (*RootCmd) version(versionInfo func() *versionInfo) *clibase.Cmd {
58+
var (
59+
formatter = cliui.NewOutputFormatter(
60+
cliui.TextFormat(),
61+
cliui.JSONFormat(),
62+
)
63+
vi = versionInfo()
64+
)
65+
66+
cmd := &clibase.Cmd{
67+
Use: "version",
68+
Short: "Show coder version",
69+
Options: clibase.OptionSet{},
70+
Handler: func(inv *clibase.Invocation) error {
71+
out, err := formatter.Format(inv.Context(), vi)
72+
if err != nil {
73+
return err
74+
}
75+
76+
_, err = fmt.Fprintln(inv.Stdout, out)
77+
return err
78+
},
79+
}
80+
81+
formatter.AttachOptions(&cmd.Options)
82+
83+
return cmd
84+
}

cli/version_test.go

+66
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
package cli_test
2+
3+
import (
4+
"bytes"
5+
"context"
6+
"strings"
7+
"testing"
8+
9+
"github.com/stretchr/testify/require"
10+
11+
"github.com/coder/coder/cli/clitest"
12+
"github.com/coder/coder/testutil"
13+
)
14+
15+
func TestVersion(t *testing.T) {
16+
t.Parallel()
17+
expectedText := `Coder v0.0.0-devel
18+
https://github.com/coder/coder
19+
20+
Full build of Coder, supports the server subcommand.
21+
`
22+
expectedJSON := `{
23+
"version": "v0.0.0-devel",
24+
"build_time": "0001-01-01T00:00:00Z",
25+
"external_url": "https://github.com/coder/coder",
26+
"slim": false,
27+
"agpl": false
28+
}
29+
`
30+
for _, tt := range []struct {
31+
Name string
32+
Args []string
33+
Expected string
34+
}{
35+
{
36+
Name: "Defaults to human-readable output",
37+
Args: []string{"version"},
38+
Expected: expectedText,
39+
},
40+
{
41+
Name: "JSON output",
42+
Args: []string{"version", "--output=json"},
43+
Expected: expectedJSON,
44+
},
45+
{
46+
Name: "Text output",
47+
Args: []string{"version", "--output=text"},
48+
Expected: expectedText,
49+
},
50+
} {
51+
tt := tt
52+
t.Run(tt.Name, func(t *testing.T) {
53+
t.Parallel()
54+
ctx, cancel := context.WithTimeout(context.Background(), testutil.WaitShort)
55+
t.Cleanup(cancel)
56+
inv, _ := clitest.New(t, tt.Args...)
57+
buf := new(bytes.Buffer)
58+
inv.Stdout = buf
59+
err := inv.WithContext(ctx).Run()
60+
require.NoError(t, err)
61+
actual := buf.String()
62+
actual = strings.ReplaceAll(actual, "\r\n", "\n")
63+
require.Equal(t, tt.Expected, actual)
64+
})
65+
}
66+
}

docs/cli/version.md

+12-1
Original file line numberDiff line numberDiff line change
@@ -7,5 +7,16 @@ Show coder version
77
## Usage
88

99
```console
10-
coder version
10+
coder version [flags]
1111
```
12+
13+
## Options
14+
15+
### -o, --output
16+
17+
| | |
18+
| ------- | ------------------- |
19+
| Type | <code>string</code> |
20+
| Default | <code>text</code> |
21+
22+
Output format. Available formats: text, json.

0 commit comments

Comments
 (0)