Skip to content
Next Next commit
feat(cli): add --json output to version cmd
  • Loading branch information
johnstcn committed Apr 5, 2023
commit af0508e511032a4c173154676cb68a87b9037b2f
30 changes: 0 additions & 30 deletions cli/root.go
Original file line number Diff line number Diff line change
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
79 changes: 79 additions & 0 deletions cli/version.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
package cli

import (
"encoding/json"
"fmt"
"strings"
"time"

"github.com/coder/coder/buildinfo"
"github.com/coder/coder/cli/clibase"
"github.com/coder/coder/cli/cliui"
)

// version prints the coder version
func (*RootCmd) version() *clibase.Cmd {
handleHuman := func(inv *clibase.Invocation) error {
var str strings.Builder
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: do you need a string builder if there is no way to break execution in the middle of processing?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not sure, but this is just the previous code I copy-pasta'ed.

_, _ = 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
}

handleJSON := func(inv *clibase.Invocation) error {
buildTime, _ := buildinfo.Time()
versionInfo := struct {
Version string `json:"version"`
BuildTime string `json:"build_time"`
ExternalURL string `json:"external_url"`
Slim bool `json:"slim"`
AGPL bool `json:"agpl"`
}{
Version: buildinfo.Version(),
BuildTime: buildTime.Format(time.UnixDate),
ExternalURL: buildinfo.ExternalURL(),
Slim: buildinfo.IsSlim(),
AGPL: buildinfo.IsAGPL(),
}

enc := json.NewEncoder(inv.Stdout)
enc.SetIndent("", " ")
return enc.Encode(versionInfo)
}

var outputJSON bool

return &clibase.Cmd{
Use: "version",
Short: "Show coder version",
Options: clibase.OptionSet{
{
Flag: "json",
Description: "Emit version information in machine-readable JSON format.",
Value: clibase.BoolOf(&outputJSON),
},
},
Handler: func(inv *clibase.Invocation) error {
if outputJSON {
return handleJSON(inv)
}
return handleHuman(inv)
},
}
}
67 changes: 67 additions & 0 deletions cli/version_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
package cli_test

import (
"bytes"
"context"
"regexp"
"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()
ansiExpr := regexp.MustCompile("[\u001B\u009B][[\\]()#;?]*(?:(?:(?:[a-zA-Z\\d]*(?:;[a-zA-Z\\d]*)*)?\u0007)|(?:(?:\\d{1,4}(?:;\\d{0,4})*)?[\\dA-PRZcf-ntqry=><~]))")
clean := func(s string) string {
s = ansiExpr.ReplaceAllString(s, "")
s = strings.Replace(s, "\r\n", "\n", -1)
return s
}
expectedHuman := `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": "Mon Jan 1 00:00:00 UTC 0001",
"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: expectedHuman,
},
{
Name: "JSON output",
Args: []string{"version", "--json"},
Expected: expectedJSON,
},
} {
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 := clean(buf.String())
require.Equal(t, tt.Expected, actual)
})
}
}