mirror of https://github.com/coder/coder.git
feat(cli): add --output={text,json} to version cmd (#7010)
* feat(cliui): add TextFormat * feat(cli): add --format={text,json} to version cmd
This commit is contained in:
parent
9c4ccd76a0
commit
00d468b964
|
@ -3,6 +3,7 @@ package cliui
|
|||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"reflect"
|
||||
"strings"
|
||||
|
||||
|
@ -171,3 +172,23 @@ func (jsonFormat) Format(_ context.Context, data any) (string, error) {
|
|||
|
||||
return string(outBytes), nil
|
||||
}
|
||||
|
||||
type textFormat struct{}
|
||||
|
||||
var _ OutputFormat = textFormat{}
|
||||
|
||||
// TextFormat is a formatter that just outputs unstructured text.
|
||||
// It uses fmt.Sprintf under the hood.
|
||||
func TextFormat() OutputFormat {
|
||||
return textFormat{}
|
||||
}
|
||||
|
||||
func (textFormat) ID() string {
|
||||
return "text"
|
||||
}
|
||||
|
||||
func (textFormat) AttachOptions(_ *clibase.OptionSet) {}
|
||||
|
||||
func (textFormat) Format(_ context.Context, data any) (string, error) {
|
||||
return fmt.Sprintf("%s", data), nil
|
||||
}
|
||||
|
|
|
@ -50,6 +50,9 @@ func Test_OutputFormatter(t *testing.T) {
|
|||
require.Panics(t, func() {
|
||||
cliui.NewOutputFormatter(cliui.JSONFormat())
|
||||
})
|
||||
require.NotPanics(t, func() {
|
||||
cliui.NewOutputFormatter(cliui.JSONFormat(), cliui.TextFormat())
|
||||
})
|
||||
})
|
||||
|
||||
t.Run("NoMissingFormatID", func(t *testing.T) {
|
||||
|
|
32
cli/root.go
32
cli/root.go
|
@ -82,7 +82,7 @@ func (r *RootCmd) Core() []*clibase.Cmd {
|
|||
r.templates(),
|
||||
r.users(),
|
||||
r.tokens(),
|
||||
r.version(),
|
||||
r.version(defaultVersionInfo),
|
||||
|
||||
// Workspace Commands
|
||||
r.configSSH(),
|
||||
|
@ -370,36 +370,6 @@ func LoggerFromContext(ctx context.Context) (slog.Logger, bool) {
|
|||
return l, ok
|
||||
}
|
||||
|
||||
// version prints the coder version
|
||||
func (*RootCmd) version() *clibase.Cmd {
|
||||
return &clibase.Cmd{
|
||||
Use: "version",
|
||||
Short: "Show coder version",
|
||||
Handler: func(inv *clibase.Invocation) error {
|
||||
var str strings.Builder
|
||||
_, _ = str.WriteString("Coder ")
|
||||
if buildinfo.IsAGPL() {
|
||||
_, _ = str.WriteString("(AGPL) ")
|
||||
}
|
||||
_, _ = str.WriteString(buildinfo.Version())
|
||||
buildTime, valid := buildinfo.Time()
|
||||
if valid {
|
||||
_, _ = str.WriteString(" " + buildTime.Format(time.UnixDate))
|
||||
}
|
||||
_, _ = str.WriteString("\r\n" + buildinfo.ExternalURL() + "\r\n\r\n")
|
||||
|
||||
if buildinfo.IsSlim() {
|
||||
_, _ = str.WriteString(fmt.Sprintf("Slim build of Coder, does not support the %s subcommand.\n", cliui.Styles.Code.Render("server")))
|
||||
} else {
|
||||
_, _ = str.WriteString(fmt.Sprintf("Full build of Coder, supports the %s subcommand.\n", cliui.Styles.Code.Render("server")))
|
||||
}
|
||||
|
||||
_, _ = fmt.Fprint(inv.Stdout, str.String())
|
||||
return nil
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func isTest() bool {
|
||||
return flag.Lookup("test.v") != nil
|
||||
}
|
||||
|
|
|
@ -1,6 +1,10 @@
|
|||
Usage: coder version
|
||||
Usage: coder version [flags]
|
||||
|
||||
Show coder version
|
||||
|
||||
[1mOptions[0m
|
||||
-o, --output string (default: text)
|
||||
Output format. Available formats: text, json.
|
||||
|
||||
---
|
||||
Run `coder --help` for a list of global options.
|
||||
|
|
|
@ -0,0 +1,84 @@
|
|||
package cli
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/coder/coder/buildinfo"
|
||||
"github.com/coder/coder/cli/clibase"
|
||||
"github.com/coder/coder/cli/cliui"
|
||||
)
|
||||
|
||||
// versionInfo wraps the stuff we get from buildinfo so that it's
|
||||
// easier to emit in different formats.
|
||||
type versionInfo struct {
|
||||
Version string `json:"version"`
|
||||
BuildTime time.Time `json:"build_time"`
|
||||
ExternalURL string `json:"external_url"`
|
||||
Slim bool `json:"slim"`
|
||||
AGPL bool `json:"agpl"`
|
||||
}
|
||||
|
||||
// String() implements Stringer
|
||||
func (vi versionInfo) String() string {
|
||||
var str strings.Builder
|
||||
_, _ = str.WriteString("Coder ")
|
||||
if vi.AGPL {
|
||||
_, _ = str.WriteString("(AGPL) ")
|
||||
}
|
||||
_, _ = str.WriteString(vi.Version)
|
||||
|
||||
if !vi.BuildTime.IsZero() {
|
||||
_, _ = str.WriteString(" " + vi.BuildTime.Format(time.UnixDate))
|
||||
}
|
||||
_, _ = str.WriteString("\r\n" + vi.ExternalURL + "\r\n\r\n")
|
||||
|
||||
if vi.Slim {
|
||||
_, _ = str.WriteString(fmt.Sprintf("Slim build of Coder, does not support the %s subcommand.", cliui.Styles.Code.Render("server")))
|
||||
} else {
|
||||
_, _ = str.WriteString(fmt.Sprintf("Full build of Coder, supports the %s subcommand.", cliui.Styles.Code.Render("server")))
|
||||
}
|
||||
return str.String()
|
||||
}
|
||||
|
||||
func defaultVersionInfo() *versionInfo {
|
||||
buildTime, _ := buildinfo.Time()
|
||||
return &versionInfo{
|
||||
Version: buildinfo.Version(),
|
||||
BuildTime: buildTime,
|
||||
ExternalURL: buildinfo.ExternalURL(),
|
||||
Slim: buildinfo.IsSlim(),
|
||||
AGPL: buildinfo.IsAGPL(),
|
||||
}
|
||||
}
|
||||
|
||||
// version prints the coder version
|
||||
func (*RootCmd) version(versionInfo func() *versionInfo) *clibase.Cmd {
|
||||
var (
|
||||
formatter = cliui.NewOutputFormatter(
|
||||
cliui.TextFormat(),
|
||||
cliui.JSONFormat(),
|
||||
)
|
||||
vi = versionInfo()
|
||||
)
|
||||
|
||||
cmd := &clibase.Cmd{
|
||||
Use: "version",
|
||||
Short: "Show coder version",
|
||||
Options: clibase.OptionSet{},
|
||||
Handler: func(inv *clibase.Invocation) error {
|
||||
out, err := formatter.Format(inv.Context(), vi)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
_, err = fmt.Fprintln(inv.Stdout, out)
|
||||
return err
|
||||
},
|
||||
}
|
||||
|
||||
formatter.AttachOptions(&cmd.Options)
|
||||
|
||||
return cmd
|
||||
}
|
|
@ -0,0 +1,66 @@
|
|||
package cli_test
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
"github.com/coder/coder/cli/clitest"
|
||||
"github.com/coder/coder/testutil"
|
||||
)
|
||||
|
||||
func TestVersion(t *testing.T) {
|
||||
t.Parallel()
|
||||
expectedText := `Coder v0.0.0-devel
|
||||
https://github.com/coder/coder
|
||||
|
||||
Full build of Coder, supports the [;mserver[0m subcommand.
|
||||
`
|
||||
expectedJSON := `{
|
||||
"version": "v0.0.0-devel",
|
||||
"build_time": "0001-01-01T00:00:00Z",
|
||||
"external_url": "https://github.com/coder/coder",
|
||||
"slim": false,
|
||||
"agpl": false
|
||||
}
|
||||
`
|
||||
for _, tt := range []struct {
|
||||
Name string
|
||||
Args []string
|
||||
Expected string
|
||||
}{
|
||||
{
|
||||
Name: "Defaults to human-readable output",
|
||||
Args: []string{"version"},
|
||||
Expected: expectedText,
|
||||
},
|
||||
{
|
||||
Name: "JSON output",
|
||||
Args: []string{"version", "--output=json"},
|
||||
Expected: expectedJSON,
|
||||
},
|
||||
{
|
||||
Name: "Text output",
|
||||
Args: []string{"version", "--output=text"},
|
||||
Expected: expectedText,
|
||||
},
|
||||
} {
|
||||
tt := tt
|
||||
t.Run(tt.Name, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
ctx, cancel := context.WithTimeout(context.Background(), testutil.WaitShort)
|
||||
t.Cleanup(cancel)
|
||||
inv, _ := clitest.New(t, tt.Args...)
|
||||
buf := new(bytes.Buffer)
|
||||
inv.Stdout = buf
|
||||
err := inv.WithContext(ctx).Run()
|
||||
require.NoError(t, err)
|
||||
actual := buf.String()
|
||||
actual = strings.ReplaceAll(actual, "\r\n", "\n")
|
||||
require.Equal(t, tt.Expected, actual)
|
||||
})
|
||||
}
|
||||
}
|
|
@ -7,5 +7,16 @@ Show coder version
|
|||
## Usage
|
||||
|
||||
```console
|
||||
coder version
|
||||
coder version [flags]
|
||||
```
|
||||
|
||||
## Options
|
||||
|
||||
### -o, --output
|
||||
|
||||
| | |
|
||||
| ------- | ------------------- |
|
||||
| Type | <code>string</code> |
|
||||
| Default | <code>text</code> |
|
||||
|
||||
Output format. Available formats: text, json.
|
||||
|
|
Loading…
Reference in New Issue