Skip to content
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

Introduces --json flag for k6 version sub-command #4093

Merged
merged 5 commits into from
Dec 16, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 1 addition & 2 deletions cmd/root.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,6 @@ import (
"go.k6.io/k6/cmd/state"
"go.k6.io/k6/errext"
"go.k6.io/k6/errext/exitcodes"
"go.k6.io/k6/lib/consts"
"go.k6.io/k6/log"
)

Expand Down Expand Up @@ -79,7 +78,7 @@ func (c *rootCommand) persistentPreRunE(_ *cobra.Command, _ []string) error {
if err != nil {
return err
}
c.globalState.Logger.Debugf("k6 version: v%s", consts.FullVersion())
c.globalState.Logger.Debugf("k6 version: v%s", fullVersion())
return nil
}

Expand Down
156 changes: 147 additions & 9 deletions cmd/version.go
Original file line number Diff line number Diff line change
@@ -1,7 +1,10 @@
package cmd

import (
"encoding/json"
"fmt"
"runtime"
"runtime/debug"
"strings"

"github.com/spf13/cobra"
Expand All @@ -10,8 +13,89 @@ import (
"go.k6.io/k6/lib/consts"
)

const (
commitKey = "commit"
commitDirtyKey = "commit_dirty"
)

// fullVersion returns the maximally full version and build information for
// the currently running k6 executable.
func fullVersion() string {
details := versionDetails()

goVersionArch := fmt.Sprintf("%s, %s/%s", details["go_version"], details["go_os"], details["go_arch"])

k6version := fmt.Sprintf("%s", details["version"])
// for the fallback case when the version is not in the expected format
// cobra adds a "v" prefix to the version
k6version = strings.TrimLeft(k6version, "v")

commit, ok := details[commitKey].(string)
if !ok || commit == "" {
return fmt.Sprintf("%s (%s)", k6version, goVersionArch)
}

isDirty, ok := details[commitDirtyKey].(bool)
if ok && isDirty {
commit += "-dirty"
}

return fmt.Sprintf("%s (commit/%s, %s)", k6version, commit, goVersionArch)
}

// versionDetails returns the structured details about version
func versionDetails() map[string]interface{} {
v := consts.Version
if !strings.HasPrefix(v, "v") {
v = "v" + v
}

details := map[string]interface{}{
"version": v,
"go_version": runtime.Version(),
"go_os": runtime.GOOS,
"go_arch": runtime.GOARCH,
}

buildInfo, ok := debug.ReadBuildInfo()
if !ok {
return details
}

var (
commit string
dirty bool
)
for _, s := range buildInfo.Settings {
switch s.Key {
case "vcs.revision":
commitLen := 10
if len(s.Value) < commitLen {
commitLen = len(s.Value)
}
commit = s.Value[:commitLen]
case "vcs.modified":
if s.Value == "true" {
dirty = true
}
default:
}
}

if commit == "" {
return details
}

details[commitKey] = commit
if dirty {
details[commitDirtyKey] = true
}

return details
}

func versionString() string {
v := consts.FullVersion()
v := fullVersion()

if exts := ext.GetAll(); len(exts) > 0 {
extsDesc := make([]string, 0, len(exts))
Expand All @@ -24,16 +108,70 @@ func versionString() string {
return v
}

func getCmdVersion(_ *state.GlobalState) *cobra.Command {
// versionCmd represents the version command.
return &cobra.Command{
type versionCmd struct {
gs *state.GlobalState
isJSON bool
}

func (c *versionCmd) run(cmd *cobra.Command, _ []string) error {
if !c.isJSON {
root := cmd.Root()
root.SetArgs([]string{"--version"})
_ = root.Execute()
return nil
}

details := versionDetails()
if exts := ext.GetAll(); len(exts) > 0 {
type extInfo struct {
Module string `json:"module"`
Version string `json:"version"`
Imports []string `json:"imports"`
}

ext := make(map[string]extInfo)
for _, e := range exts {
key := e.Path + "@" + e.Version

if v, ok := ext[key]; ok {
v.Imports = append(v.Imports, e.Name)
ext[key] = v
continue
}

ext[key] = extInfo{
Module: e.Path,
Version: e.Version,
Imports: []string{e.Name},
}
}

list := make([]extInfo, 0, len(ext))
for _, v := range ext {
list = append(list, v)
}

details["extensions"] = list
}

if err := json.NewEncoder(c.gs.Stdout).Encode(details); err != nil {
return fmt.Errorf("failed to encode/output version details: %w", err)
}

return nil
}

func getCmdVersion(gs *state.GlobalState) *cobra.Command {
versionCmd := &versionCmd{gs: gs}

cmd := &cobra.Command{
Use: "version",
Short: "Show application version",
Long: `Show the application version and exit.`,
Run: func(cmd *cobra.Command, _ []string) {
root := cmd.Root()
root.SetArgs([]string{"--version"})
_ = root.Execute()
},
RunE: versionCmd.run,
olegbespalov marked this conversation as resolved.
Show resolved Hide resolved
}

cmd.Flags().BoolVar(&versionCmd.isJSON, "json", false, "if set, output version information will be in JSON format")

return cmd
}
82 changes: 82 additions & 0 deletions cmd/version_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
package cmd

import (
"encoding/json"
"runtime"
"testing"

"github.com/stretchr/testify/assert"
"go.k6.io/k6/cmd/tests"
"go.k6.io/k6/lib/consts"
)

func TestVersionFlag(t *testing.T) {
t.Parallel()

ts := tests.NewGlobalTestState(t)
ts.ExpectedExitCode = 0
ts.CmdArgs = []string{"k6", "--version"}

ExecuteWithGlobalState(ts.GlobalState)

stdout := ts.Stdout.String()
t.Log(stdout)
assert.NotEmpty(t, stdout)

// Check that the version/format string is correct
assert.Contains(t, stdout, "k6 v")
assert.Contains(t, stdout, consts.Version)
assert.Contains(t, stdout, runtime.Version())
assert.Contains(t, stdout, runtime.GOOS)
assert.Contains(t, stdout, runtime.GOARCH)
}

func TestVersionSubCommand(t *testing.T) {
t.Parallel()

ts := tests.NewGlobalTestState(t)
ts.ExpectedExitCode = 0
ts.CmdArgs = []string{"k6", "version"}

ExecuteWithGlobalState(ts.GlobalState)

stdout := ts.Stdout.String()
t.Log(stdout)
assert.NotEmpty(t, stdout)

// Check that the version/format string is correct
assert.Contains(t, stdout, "k6 v")
assert.Contains(t, stdout, consts.Version)
assert.Contains(t, stdout, runtime.Version())
assert.Contains(t, stdout, runtime.GOOS)
assert.Contains(t, stdout, runtime.GOARCH)
}

func TestVersionJSONSubCommand(t *testing.T) {
t.Parallel()

ts := tests.NewGlobalTestState(t)
ts.ExpectedExitCode = 0
ts.CmdArgs = []string{"k6", "version", "--json"}

ExecuteWithGlobalState(ts.GlobalState)

stdout := ts.Stdout.String()
t.Log(stdout)
assert.NotEmpty(t, stdout)

// try to unmarshal the JSON output
var details map[string]interface{}
err := json.Unmarshal([]byte(stdout), &details)
assert.NoError(t, err)

// Check that details are correct
assert.Contains(t, details, "version")
assert.Contains(t, details, "go_version")
assert.Contains(t, details, "go_os")
assert.Contains(t, details, "go_arch")
assert.Equal(t, "v"+consts.Version, details["version"])
assert.Equal(t, runtime.Version(), details["go_version"])
assert.Equal(t, runtime.GOOS, details["go_os"])
assert.Equal(t, runtime.GOARCH, details["go_arch"])
}
44 changes: 0 additions & 44 deletions lib/consts/consts.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,56 +2,12 @@
package consts

import (
"fmt"
"runtime"
"runtime/debug"
"strings"
)

// Version contains the current semantic version of k6.
const Version = "0.55.0"

// FullVersion returns the maximally full version and build information for
// the currently running k6 executable.
func FullVersion() string {
goVersionArch := fmt.Sprintf("%s, %s/%s", runtime.Version(), runtime.GOOS, runtime.GOARCH)

buildInfo, ok := debug.ReadBuildInfo()
if !ok {
return fmt.Sprintf("%s (%s)", Version, goVersionArch)
}

var (
commit string
dirty bool
)
for _, s := range buildInfo.Settings {
switch s.Key {
case "vcs.revision":
commitLen := 10
if len(s.Value) < commitLen {
commitLen = len(s.Value)
}
commit = s.Value[:commitLen]
case "vcs.modified":
if s.Value == "true" {
dirty = true
}
default:
}
}

if commit == "" {
return fmt.Sprintf("%s (%s)", Version, goVersionArch)
}

if dirty {
commit += "-dirty"
}

return fmt.Sprintf("%s (commit/%s, %s)", Version, commit, goVersionArch)
}

// Banner returns the ASCII-art banner with the k6 logo
func Banner() string {
banner := strings.Join([]string{
Expand Down
Loading