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

Skip to content

feat(cli): add --output={text,json} to version cmd #7010

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

Merged
merged 10 commits into from
Apr 5, 2023
21 changes: 21 additions & 0 deletions cli/cliui/output.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package cliui
import (
"context"
"encoding/json"
"fmt"
"reflect"
"strings"

Expand Down Expand Up @@ -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
}
3 changes: 3 additions & 0 deletions cli/cliui/output_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand Down
32 changes: 1 addition & 31 deletions cli/root.go
Original file line number Diff line number Diff line change
Expand Up @@ -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(),
Expand Down Expand Up @@ -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
}
Expand Down
6 changes: 5 additions & 1 deletion cli/testdata/coder_version_--help.golden
Original file line number Diff line number Diff line change
@@ -1,6 +1,10 @@
Usage: coder version
Usage: coder version [flags]

Show coder version

Options
-o, --output string (default: text)
Output format. Available formats: text, json.

---
Run `coder --help` for a list of global options.
84 changes: 84 additions & 0 deletions cli/version.go
Original file line number Diff line number Diff line change
@@ -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
}
66 changes: 66 additions & 0 deletions cli/version_test.go
Original file line number Diff line number Diff line change
@@ -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 server 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)
})
}
}
13 changes: 12 additions & 1 deletion docs/cli/version.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.