Skip to content

Commit 680ac0c

Browse files
committed
Add Revision to version CLI output and add JSON support
Also add JSON format support
1 parent 5e5dbed commit 680ac0c

File tree

9 files changed

+239
-19
lines changed

9 files changed

+239
-19
lines changed

command/commands_oss.go

+1-1
Original file line numberDiff line numberDiff line change
@@ -220,6 +220,6 @@ func init() {
220220
Register("tls cert", func(ui cli.Ui) (cli.Command, error) { return tlscert.New(), nil })
221221
Register("tls cert create", func(ui cli.Ui) (cli.Command, error) { return tlscertcreate.New(ui), nil })
222222
Register("validate", func(ui cli.Ui) (cli.Command, error) { return validate.New(ui), nil })
223-
Register("version", func(ui cli.Ui) (cli.Command, error) { return version.New(ui, verHuman), nil })
223+
Register("version", func(ui cli.Ui) (cli.Command, error) { return version.New(ui), nil })
224224
Register("watch", func(ui cli.Ui) (cli.Command, error) { return watch.New(ui, MakeShutdownCh()), nil })
225225
}

command/version/formatter.go

+69
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
package version
2+
3+
import (
4+
"bytes"
5+
"encoding/json"
6+
"fmt"
7+
)
8+
9+
const (
10+
PrettyFormat string = "pretty"
11+
JSONFormat string = "json"
12+
)
13+
14+
type Formatter interface {
15+
Format(info *VersionInfo) (string, error)
16+
}
17+
18+
func GetSupportedFormats() []string {
19+
return []string{PrettyFormat, JSONFormat}
20+
}
21+
22+
func NewFormatter(format string) (Formatter, error) {
23+
switch format {
24+
case PrettyFormat:
25+
return newPrettyFormatter(), nil
26+
case JSONFormat:
27+
return newJSONFormatter(), nil
28+
default:
29+
return nil, fmt.Errorf("Unknown format: %s", format)
30+
}
31+
}
32+
33+
type prettyFormatter struct{}
34+
35+
func newPrettyFormatter() Formatter {
36+
return &prettyFormatter{}
37+
}
38+
39+
func (_ *prettyFormatter) Format(info *VersionInfo) (string, error) {
40+
var buffer bytes.Buffer
41+
buffer.WriteString(fmt.Sprintf("Consul %s\n", info.HumanVersion))
42+
if info.Revision != "" {
43+
buffer.WriteString(fmt.Sprintf("Revision %s\n", info.Revision))
44+
}
45+
46+
var supplement string
47+
if info.RPC.Default < info.RPC.Max {
48+
supplement = fmt.Sprintf(" (agent will automatically use protocol >%d when speaking to compatible agents)",
49+
info.RPC.Default)
50+
}
51+
buffer.WriteString(fmt.Sprintf("Protocol %d spoken by default, understands %d to %d%s\n",
52+
info.RPC.Default, info.RPC.Min, info.RPC.Max, supplement))
53+
54+
return buffer.String(), nil
55+
}
56+
57+
type jsonFormatter struct{}
58+
59+
func newJSONFormatter() Formatter {
60+
return &jsonFormatter{}
61+
}
62+
63+
func (_ *jsonFormatter) Format(info *VersionInfo) (string, error) {
64+
b, err := json.MarshalIndent(info, "", " ")
65+
if err != nil {
66+
return "", fmt.Errorf("Failed to marshal version info: %v", err)
67+
}
68+
return string(b), nil
69+
}

command/version/formatter_test.go

+63
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
package version
2+
3+
import (
4+
"flag"
5+
"fmt"
6+
"io/ioutil"
7+
"path/filepath"
8+
"testing"
9+
10+
"github.com/stretchr/testify/require"
11+
)
12+
13+
// update allows golden files to be updated based on the current output.
14+
var update = flag.Bool("update", false, "update golden files")
15+
16+
// golden reads and optionally writes the expected data to the golden file,
17+
// returning the contents as a string.
18+
func golden(t *testing.T, name, got string) string {
19+
t.Helper()
20+
21+
golden := filepath.Join("testdata", name+".golden")
22+
if *update && got != "" {
23+
err := ioutil.WriteFile(golden, []byte(got), 0644)
24+
require.NoError(t, err)
25+
}
26+
27+
expected, err := ioutil.ReadFile(golden)
28+
require.NoError(t, err)
29+
30+
return string(expected)
31+
}
32+
33+
func TestFormat(t *testing.T) {
34+
info := VersionInfo{
35+
HumanVersion: "1.99.3-beta1",
36+
Version: "1.99.3",
37+
Prerelease: "beta1",
38+
Revision: "5e5dbedd47a5f875b60e241c5555a9caab595246",
39+
RPC: RPCVersionInfo{
40+
Default: 2,
41+
Min: 1,
42+
Max: 3,
43+
},
44+
}
45+
46+
formatters := map[string]Formatter{
47+
"pretty": newPrettyFormatter(),
48+
// the JSON formatter ignores the showMeta
49+
"json": newJSONFormatter(),
50+
}
51+
52+
for fmtName, formatter := range formatters {
53+
t.Run(fmtName, func(t *testing.T) {
54+
actual, err := formatter.Format(&info)
55+
require.NoError(t, err)
56+
57+
gName := fmt.Sprintf("%s", fmtName)
58+
59+
expected := golden(t, gName, actual)
60+
require.Equal(t, expected, actual)
61+
})
62+
}
63+
}

command/version/testdata/json.golden

+10
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
{
2+
"Version": "1.99.3",
3+
"Revision": "5e5dbedd47a5f875b60e241c5555a9caab595246",
4+
"Prerelease": "beta1",
5+
"RPC": {
6+
"Default": 2,
7+
"Min": 1,
8+
"Max": 3
9+
}
10+
}
+3
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
Consul 1.99.3-beta1
2+
Revision 5e5dbedd47a5f875b60e241c5555a9caab595246
3+
Protocol 2 spoken by default, understands 1 to 3 (agent will automatically use protocol >2 when speaking to compatible agents)

command/version/version.go

+65-14
Original file line numberDiff line numberDiff line change
@@ -1,34 +1,80 @@
11
package version
22

33
import (
4+
"flag"
45
"fmt"
6+
"strings"
57

68
"github.com/hashicorp/consul/agent/consul"
9+
"github.com/hashicorp/consul/command/flags"
10+
"github.com/hashicorp/consul/version"
711
"github.com/mitchellh/cli"
812
)
913

10-
func New(ui cli.Ui, version string) *cmd {
11-
return &cmd{UI: ui, version: version}
14+
func New(ui cli.Ui) *cmd {
15+
c := &cmd{UI: ui}
16+
c.init()
17+
return c
1218
}
1319

1420
type cmd struct {
15-
UI cli.Ui
16-
version string
21+
UI cli.Ui
22+
flags *flag.FlagSet
23+
format string
24+
help string
1725
}
1826

19-
func (c *cmd) Run(_ []string) int {
20-
c.UI.Output(fmt.Sprintf("Consul %s", c.version))
27+
func (c *cmd) init() {
28+
c.flags = flag.NewFlagSet("", flag.ContinueOnError)
29+
c.flags.StringVar(
30+
&c.format,
31+
"format",
32+
PrettyFormat,
33+
fmt.Sprintf("Output format {%s}", strings.Join(GetSupportedFormats(), "|")))
34+
c.help = flags.Usage(help, c.flags)
2135

22-
const rpcProtocol = consul.DefaultRPCProtocol
36+
}
37+
38+
type RPCVersionInfo struct {
39+
Default int
40+
Min int
41+
Max int
42+
}
43+
44+
type VersionInfo struct {
45+
HumanVersion string `json:"-"`
46+
Version string
47+
Revision string
48+
Prerelease string
49+
RPC RPCVersionInfo
50+
}
2351

24-
var supplement string
25-
if rpcProtocol < consul.ProtocolVersionMax {
26-
supplement = fmt.Sprintf(" (agent will automatically use protocol >%d when speaking to compatible agents)",
27-
rpcProtocol)
52+
func (c *cmd) Run(args []string) int {
53+
if err := c.flags.Parse(args); err != nil {
54+
return 1
2855
}
29-
c.UI.Output(fmt.Sprintf("Protocol %d spoken by default, understands %d to %d%s",
30-
rpcProtocol, consul.ProtocolVersionMin, consul.ProtocolVersionMax, supplement))
3156

57+
formatter, err := NewFormatter(c.format)
58+
if err != nil {
59+
c.UI.Error(err.Error())
60+
return 1
61+
}
62+
out, err := formatter.Format(&VersionInfo{
63+
HumanVersion: version.GetHumanVersion(),
64+
Version: version.Version,
65+
Revision: version.GitCommit,
66+
Prerelease: version.VersionPrerelease,
67+
RPC: RPCVersionInfo{
68+
Default: consul.DefaultRPCProtocol,
69+
Min: int(consul.ProtocolVersionMin),
70+
Max: consul.ProtocolVersionMax,
71+
},
72+
})
73+
if err != nil {
74+
c.UI.Error(err.Error())
75+
return 1
76+
}
77+
c.UI.Output(out)
3278
return 0
3379
}
3480

@@ -37,5 +83,10 @@ func (c *cmd) Synopsis() string {
3783
}
3884

3985
func (c *cmd) Help() string {
40-
return ""
86+
return flags.Usage(c.help, nil)
4187
}
88+
89+
const synopsis = "Output Consul version information"
90+
const help = `
91+
Usage: consul version [options]
92+
`

command/version/version_test.go

+1-1
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ import (
99

1010
func TestVersionCommand_noTabs(t *testing.T) {
1111
t.Parallel()
12-
if strings.ContainsRune(New(cli.NewMockUi(), "").Help(), '\t') {
12+
if strings.ContainsRune(New(cli.NewMockUi()).Help(), '\t') {
1313
t.Fatal("help has tabs")
1414
}
1515
}

main.go

+1-2
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,6 @@ import (
1010
"github.com/hashicorp/consul/command/version"
1111
"github.com/hashicorp/consul/lib"
1212
_ "github.com/hashicorp/consul/service_os"
13-
consulversion "github.com/hashicorp/consul/version"
1413
"github.com/mitchellh/cli"
1514
)
1615

@@ -43,7 +42,7 @@ func realMain() int {
4342
}
4443

4544
if cli.IsVersion() {
46-
cmd := version.New(ui, consulversion.GetHumanVersion())
45+
cmd := version.New(ui)
4746
return cmd.Run(nil)
4847
}
4948

website/pages/docs/commands/version.mdx

+26-1
Original file line numberDiff line numberDiff line change
@@ -13,8 +13,33 @@ Command: `consul version`
1313

1414
The `version` command prints the version of Consul and the protocol versions it understands for speaking to other agents.
1515

16+
## Usage
17+
18+
Usage: `consul version [options]`
19+
20+
### Command Options
21+
22+
- `-format={pretty|json}` - Command output format. The default value is `pretty`.
23+
24+
## Plain Output
1625
```shell-session
1726
$ consul version
18-
Consul v0.7.4
27+
Consul v1.7.0
28+
Revision d1fc59061
1929
Protocol 2 spoken by default, understands 2 to 3 (agent will automatically use protocol >2 when speaking to compatible agents)
2030
```
31+
32+
## JSON Output
33+
```shell-session
34+
$ consul version -format=json
35+
{
36+
"Version": "1.8.0",
37+
"Revision": "d1fc59061",
38+
"Prerelease": "dev",
39+
"RPC": {
40+
"Default": 2,
41+
"Min": 2,
42+
"Max": 3
43+
}
44+
}
45+
```

0 commit comments

Comments
 (0)