#263: sq version host info (#271)

* sq version host info

* workflow: update bug_report.md with version instructions
This commit is contained in:
Neil O'Toole 2023-06-21 06:28:15 -06:00 committed by GitHub
parent 1f9183aa68
commit 97739da1e1
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
20 changed files with 561 additions and 201 deletions

View File

@ -11,16 +11,46 @@ assignees: ''
A clear and concise description of what the bug is.
**To Reproduce**
Steps to reproduce the behavior.
**Expected behavior**
A clear and concise description of what you expected to happen.
**`sq` version**
Include below the output of `sq version --yaml`:
```yaml
# REPLACE THIS WITH YOUR OUTPUT
buildinfo:
version: v0.39.0
commit: eedc11ec46d1f0e78628158cc6fd58850601d701
timestamp: 2023-06-21T11:41:34Z
latest_version: v0.39.0
host:
platform: darwin
arch: arm64
kernel: Darwin
kernel_version: 22.5.0
variant: macOS
variant_version: "13.4"
```
**Logs**
Attach [log file](https://sq.io/docs/overview/#logging).
Attach [log file](https://sq.io/docs/config/#logging).
Don't paste the log file contents into the GH comment: attach the file.
The exception is if you know for sure that only a particular snippet of
the log file is relevant: then you can paste that short snippet. Be sure
to enclose it in a code block.
**Screenshots**
If applicable, add screenshots to help explain your problem.
**Additional context**
Add any other context about the problem here.

View File

@ -7,6 +7,31 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
Breaking changes are annotated with ☢️.
## Upcoming
### Added
- [#263]: `sq version` now supports `--yaml` output.
- [#263]: `sq version` now outputs host OS details with `--verbose`, `--json`
and `--yaml` flags. The motivation behind this is bug submission: we want
to know which OS/arch the user is on. E.g. for `sq version -j`:
```json
{
"version": "v0.38.1",
"commit": "eedc11ec46d1f0e78628158cc6fd58850601d701",
"timestamp": "2023-06-21T11:41:34Z",
"latest_version": "v0.39.0",
"host": {
"platform": "darwin",
"arch": "arm64",
"kernel": "Darwin",
"kernel_version": "22.5.0",
"variant": "macOS",
"variant_version": "13.4"
}
}
```
## [v0.38.1] - 2023-06-19
### Fixed
@ -607,6 +632,7 @@ make working with lots of sources much easier.
[#256]: https://github.com/neilotoole/sq/issues/256
[#258]: https://github.com/neilotoole/sq/issues/258
[#261]: https://github.com/neilotoole/sq/issues/261
[#263]: https://github.com/neilotoole/sq/issues/263
[v0.15.2]: https://github.com/neilotoole/sq/releases/tag/v0.15.2
[v0.15.3]: https://github.com/neilotoole/sq/compare/v0.15.2...v0.15.3

View File

@ -63,8 +63,8 @@ func (bi BuildInfo) LogValue() slog.Value {
return gv
}
// Info returns BuildInfo.
func Info() BuildInfo {
// Get returns BuildInfo.
func Get() BuildInfo {
return BuildInfo{
Version: Version,
Commit: Commit,

View File

@ -27,6 +27,8 @@ import (
"strings"
"sync"
"github.com/neilotoole/sq/cli/hostinfo"
"github.com/neilotoole/sq/cli/run"
"github.com/neilotoole/sq/cli/flag"
@ -79,7 +81,8 @@ func Execute(ctx context.Context, stdin *os.File, stdout, stderr io.Writer, args
func ExecuteWith(ctx context.Context, ru *run.Run, args []string) error {
log := lg.FromContext(ctx)
log.Debug("EXECUTE", "args", strings.Join(args, " "))
log.Debug("Build info", "build", buildinfo.Info())
log.Debug("Build info", "build", buildinfo.Get())
log.Debug("Host info", "host", hostinfo.Get())
log.Debug("Config",
"config.version", ru.Config.Version,
lga.Path, ru.ConfigStore.Location(),

View File

@ -8,6 +8,25 @@ import (
"github.com/neilotoole/sq/cli/run"
)
var (
DoCompleteAddLocationFile = locCompListFiles
PreprocessFlagArgVars = preprocessFlagArgVars
LastHandlePart = lastHandlePart
GetVersionFromBrewFormula = getVersionFromBrewFormula
FetchBrewVersion = fetchBrewVersion
)
// ToTestParseLocStage is a helper to test the
// non-exported locCompletionHelper.locCompParseLoc method.
func DoTestParseLocStage(t testing.TB, ru *run.Run, loc string) (PlocStage, error) { //nolint:revive
ploc, err := locCompParseLoc(loc)
if err != nil {
return PlocInit, err
}
return ploc.stageDone, nil
}
type PlocStage = plocStage
const (
@ -19,16 +38,3 @@ const (
PlocHost = plocHost
PlocPath = plocPath
)
var DoCompleteAddLocationFile = locCompListFiles
// ToTestParseLocStage is a helper to test the
// non-exported locCompletionHelper.locCompParseLoc method.
func DoTestParseLocStage(t testing.TB, ru *run.Run, loc string) (PlocStage, error) { //nolint:revive
ploc, err := locCompParseLoc(loc)
if err != nil {
return PlocInit, err
}
return ploc.stageDone, nil
}

28
cli/cmd_mv_test.go Normal file
View File

@ -0,0 +1,28 @@
package cli_test
import (
"testing"
"github.com/neilotoole/sq/cli"
"github.com/neilotoole/sq/testh/tutil"
"github.com/stretchr/testify/require"
)
func TestLastHandlePart(t *testing.T) {
testCases := []struct {
in string
want string
}{
{"@handle", "handle"},
{"@prod/db", "db"},
{"@prod/sub/db", "db"},
}
for i, tc := range testCases {
tc := tc
t.Run(tutil.Name(i, tc.in), func(t *testing.T) {
got := cli.LastHandlePart(tc.in)
require.Equal(t, tc.want, got)
})
}
}

View File

@ -8,6 +8,8 @@ import (
"strings"
"testing"
"github.com/neilotoole/sq/cli"
"github.com/neilotoole/sq/cli/testrun"
"github.com/stretchr/testify/assert"
@ -19,7 +21,7 @@ import (
"github.com/neilotoole/sq/testh/sakila"
)
// TestCmdSLQ_Insert tests "sq QUERY --insert=@src.tbl".
// TestCmdSLQ_Insert_Create tests "sq QUERY --insert=@src.tbl".
func TestCmdSLQ_Insert_Create(t *testing.T) {
th := testh.New(t)
originSrc, destSrc := th.Source(sakila.SL3), th.Source(sakila.SL3)
@ -192,3 +194,83 @@ func TestCmdSLQ_ActiveSrcHandle(t *testing.T) {
recs = tr.MustReadCSV()
require.Equal(t, sakila.TblActorCount, len(recs))
}
func TestPreprocessFlagArgVars(t *testing.T) {
testCases := []struct {
name string
in []string
want []string
wantErr bool
}{
{
name: "empty",
in: []string{},
want: []string{},
},
{
name: "no flags",
in: []string{".actor"},
want: []string{".actor"},
},
{
name: "non-arg flag",
in: []string{"--json", ".actor"},
want: []string{"--json", ".actor"},
},
{
name: "non-arg flag with value",
in: []string{"--json", "true", ".actor"},
want: []string{"--json", "true", ".actor"},
},
{
name: "single arg flag",
in: []string{"--arg", "name", "TOM", ".actor"},
want: []string{"--arg", "name:TOM", ".actor"},
},
{
name: "invalid arg name",
in: []string{"--arg", "na me", "TOM", ".actor"},
wantErr: true,
},
{
name: "invalid arg name (with colon)",
in: []string{"--arg", "na:me", "TOM", ".actor"},
wantErr: true,
},
{
name: "colon in value",
in: []string{"--arg", "name", "T:OM", ".actor"},
want: []string{"--arg", "name:T:OM", ".actor"},
},
{
name: "single arg flag with whitespace",
in: []string{"--arg", "name", "TOM DOWD", ".actor"},
want: []string{"--arg", "name:TOM DOWD", ".actor"},
},
{
name: "two arg flags",
in: []string{"--arg", "name", "TOM", "--arg", "eyes", "blue", ".actor"},
want: []string{"--arg", "name:TOM", "--arg", "eyes:blue", ".actor"},
},
{
name: "two arg flags with interspersed flag",
in: []string{"--arg", "name", "TOM", "--json", "true", "--arg", "eyes", "blue", ".actor"},
want: []string{"--arg", "name:TOM", "--json", "true", "--arg", "eyes:blue", ".actor"},
},
}
for _, tc := range testCases {
tc := tc
t.Run(tc.name, func(t *testing.T) {
got, gotErr := cli.PreprocessFlagArgVars(tc.in)
if tc.wantErr {
t.Log(gotErr.Error())
require.Error(t, gotErr)
return
}
require.NoError(t, gotErr)
require.EqualValues(t, tc.want, got)
})
}
}

View File

@ -9,6 +9,8 @@ import (
"strings"
"time"
"github.com/neilotoole/sq/cli/hostinfo"
"github.com/neilotoole/sq/libsq/core/ioz"
"github.com/neilotoole/sq/cli/run"
@ -28,34 +30,53 @@ func newVersionCmd() *cobra.Command {
cmd := &cobra.Command{
Use: "version",
Short: "Show version info",
Long: `Show version info. Use --verbose for more detail.
The output will note if a new version of sq is available.
Long: `Show version info.
The output notes if a new version of sq is available.
Use --verbose, or --json or --yaml for more detail.
Before upgrading, check the changelog: https://sq.io/changelog`,
RunE: execVersion,
Example: ` # Show version (note that an update is available)
$ sq version
sq v0.32.0 Update available: v0.33.0
sq v0.38.0 Update available: v0.39.0
# Verbose output
$ sq version -v
sq v0.32.0 #4e176716 2023-04-15T15:46:00Z Update available: v0.33.0
sq v0.38.0
Version: v0.38.0
Commit: #4e176716
Timestamp: 2023-06-21T11:39:39Z
Latest version: v0.39.0
Host: darwin arm64 | Darwin 22.5.0 | macOS 13.4
[...]
# JSON output
$ sq version -j
{
"version": "v0.32.0",
"commit": "4e176716",
"timestamp": "2023-04-15T15:53:38Z",
"latest_version": "v0.33.0"
}
{
"version": "v0.38.0",
"commit": "4e176716",
"timestamp": "2023-06-19T22:08:54Z",
"latest_version": "v0.39.0",
"host": {
"platform": "darwin",
"arch": "arm64",
"kernel": "Darwin",
"kernel_version": "22.5.0",
"variant": "macOS",
"variant_version": "13.4"
}
}
# Extract just the semver string
$ sq version -j | jq -r .version
v0.32.0`,
v0.38.0`,
}
addTextFlags(cmd)
cmd.Flags().BoolP(flag.JSON, flag.JSONShort, false, flag.JSONUsage)
cmd.Flags().BoolP(flag.Compact, flag.CompactShort, false, flag.CompactUsage)
cmd.Flags().BoolP(flag.YAML, flag.YAMLShort, false, flag.YAMLUsage)
return cmd
}
@ -93,7 +114,7 @@ func execVersion(cmd *cobra.Command, _ []string) error {
}
}
return ru.Writers.Version.Version(buildinfo.Info(), latestVersion)
return ru.Writers.Version.Version(buildinfo.Get(), latestVersion, hostinfo.Get())
}
// fetchBrewVersion fetches the latest available sq version via

View File

@ -1,10 +1,21 @@
package cli
package cli_test
import (
"context"
"encoding/json"
"os"
"runtime"
"testing"
"github.com/neilotoole/sq/libsq/core/ioz"
"github.com/neilotoole/sq/cli"
"github.com/neilotoole/sq/cli/buildinfo"
"github.com/neilotoole/sq/cli/testrun"
"github.com/ecnepsnai/osquery"
"golang.org/x/mod/semver"
"github.com/stretchr/testify/require"
@ -14,13 +25,68 @@ func TestGetVersionFromBrewFormula(t *testing.T) {
f, err := os.ReadFile("testdata/sq-0.20.0.rb")
require.NoError(t, err)
vers, err := getVersionFromBrewFormula(f)
vers, err := cli.GetVersionFromBrewFormula(f)
require.NoError(t, err)
require.Equal(t, "0.20.0", vers)
}
func TestFetchBrewVersion(t *testing.T) {
latest, err := fetchBrewVersion(context.Background())
latest, err := cli.FetchBrewVersion(context.Background())
require.NoError(t, err)
require.True(t, semver.IsValid("v"+latest))
}
func TestOSQuery(t *testing.T) {
info, err := osquery.Get()
require.NoError(t, err)
t.Logf("%+v", info)
}
func TestCmdVersion(t *testing.T) {
bi := buildinfo.Get()
ctx := context.Background()
tr := testrun.New(ctx, t, nil)
// --text
err := tr.Exec("version", "--text")
require.NoError(t, err)
text := tr.Out.String()
require.Contains(t, text, bi.Version)
tr = testrun.New(ctx, t, nil)
err = tr.Exec("version", "--text", "--verbose")
require.NoError(t, err)
text = tr.Out.String()
checkStringsFn := func(text string) {
require.Contains(t, text, bi.Version)
require.Contains(t, text, runtime.GOOS)
require.Contains(t, text, runtime.GOARCH)
}
checkStringsFn(text)
// --json
tr = testrun.New(ctx, t, nil)
err = tr.Exec("version", "--json")
require.NoError(t, err)
text = tr.Out.String()
checkStringsFn(text)
m := map[string]any{}
err = json.Unmarshal(tr.Out.Bytes(), &m)
require.NoError(t, err)
require.Equal(t, runtime.GOOS, m["host"].(map[string]any)["platform"])
// --yaml
tr = testrun.New(ctx, t, nil)
err = tr.Exec("version", "--yaml")
require.NoError(t, err)
text = tr.Out.String()
checkStringsFn(text)
m = map[string]any{}
err = ioz.UnmarshallYAML(tr.Out.Bytes(), &m)
require.NoError(t, err)
require.Equal(t, runtime.GOOS, m["host"].(map[string]any)["platform"])
}

103
cli/hostinfo/hostinfo.go Normal file
View File

@ -0,0 +1,103 @@
// Package sysinfo provides high-level details about the runtime OS.
package hostinfo
import (
"runtime"
"strings"
"golang.org/x/exp/slog"
"github.com/ecnepsnai/osquery"
)
// Info encapsulates OS info.
type Info struct {
// The platform is a high-level description of the OS. This maps to GOOS.
Platform string `json:"platform" yaml:"platform"`
// Arch maps to runtime.
Arch string `json:"arch" yaml:"arch"`
// The name of the kernel used by the operating system.
Kernel string `json:"kernel,omitempty" yaml:"kernel,omitempty"`
// The specific version of the kernel.
KernelVersion string `json:"kernel_version,omitempty" yaml:"kernel_version,omitempty"`
// The variant or distribution of the kernel.
Variant string `json:"variant,omitempty" yaml:"variant,omitempty"`
// The version of the variant.
VariantVersion string `json:"variant_version,omitempty" yaml:"variant_version,omitempty"`
}
// LogValue implements slog.LogValuer.
func (si Info) LogValue() slog.Value {
return slog.GroupValue(
slog.String("platform", si.Platform),
slog.String("arch", si.Arch),
slog.String("kernel", si.Kernel),
slog.String("kernel_version", si.KernelVersion),
slog.String("variant", si.Variant),
slog.String("variant_version", si.VariantVersion),
)
}
// String returns a log-debug friendly representation.
func (si Info) String() string {
sb := strings.Builder{}
sb.WriteString(si.Platform)
sb.WriteRune('|')
sb.WriteString(si.Arch)
if si.Kernel != "" {
sb.WriteRune('|')
sb.WriteString(si.Kernel)
if si.KernelVersion != "" {
sb.WriteRune(' ')
sb.WriteString(si.KernelVersion)
}
}
if si.Variant != "" {
sb.WriteRune('|')
sb.WriteString(si.Variant)
if si.VariantVersion != "" {
sb.WriteRune(' ')
sb.WriteString(si.VariantVersion)
}
}
return sb.String()
}
// Get returns system information. At a minimum, Info.Platform and
// Info.Arch are guaranteed to be populated.
func Get() Info {
info := Info{
Platform: runtime.GOOS,
Arch: runtime.GOARCH,
}
osq, err := osquery.Get()
if err != nil || osq == nil {
return info
}
const unk = "unknown"
// osquery sets unknown fields to "unknown".
// We'd rather they be empty.
if osq.Kernel != unk {
info.Kernel = osq.Kernel
}
if osq.KernelVersion != unk {
info.KernelVersion = osq.KernelVersion
}
if osq.Variant != unk {
info.Variant = osq.Variant
}
if osq.VariantVersion != unk {
info.VariantVersion = osq.VariantVersion
}
return info
}

View File

@ -0,0 +1,18 @@
package hostinfo_test
import (
"testing"
"github.com/neilotoole/sq/cli/hostinfo"
"github.com/neilotoole/slogt"
)
func TestGet(t *testing.T) {
info := hostinfo.Get()
log := slogt.New(t)
log.Debug("Via slog", "sys", info)
t.Logf("Via string: %s", info.String())
}

View File

@ -1,138 +0,0 @@
package cli
import (
"testing"
"github.com/neilotoole/slogt"
"github.com/neilotoole/sq/libsq/core/options"
"github.com/neilotoole/sq/testh/tutil"
"github.com/stretchr/testify/require"
)
func Test_preprocessFlagArgVars(t *testing.T) {
testCases := []struct {
name string
in []string
want []string
wantErr bool
}{
{
name: "empty",
in: []string{},
want: []string{},
},
{
name: "no flags",
in: []string{".actor"},
want: []string{".actor"},
},
{
name: "non-arg flag",
in: []string{"--json", ".actor"},
want: []string{"--json", ".actor"},
},
{
name: "non-arg flag with value",
in: []string{"--json", "true", ".actor"},
want: []string{"--json", "true", ".actor"},
},
{
name: "single arg flag",
in: []string{"--arg", "name", "TOM", ".actor"},
want: []string{"--arg", "name:TOM", ".actor"},
},
{
name: "invalid arg name",
in: []string{"--arg", "na me", "TOM", ".actor"},
wantErr: true,
},
{
name: "invalid arg name (with colon)",
in: []string{"--arg", "na:me", "TOM", ".actor"},
wantErr: true,
},
{
name: "colon in value",
in: []string{"--arg", "name", "T:OM", ".actor"},
want: []string{"--arg", "name:T:OM", ".actor"},
},
{
name: "single arg flag with whitespace",
in: []string{"--arg", "name", "TOM DOWD", ".actor"},
want: []string{"--arg", "name:TOM DOWD", ".actor"},
},
{
name: "two arg flags",
in: []string{"--arg", "name", "TOM", "--arg", "eyes", "blue", ".actor"},
want: []string{"--arg", "name:TOM", "--arg", "eyes:blue", ".actor"},
},
{
name: "two arg flags with interspersed flag",
in: []string{"--arg", "name", "TOM", "--json", "true", "--arg", "eyes", "blue", ".actor"},
want: []string{"--arg", "name:TOM", "--json", "true", "--arg", "eyes:blue", ".actor"},
},
}
for _, tc := range testCases {
tc := tc
t.Run(tc.name, func(t *testing.T) {
got, gotErr := preprocessFlagArgVars(tc.in)
if tc.wantErr {
t.Log(gotErr.Error())
require.Error(t, gotErr)
return
}
require.NoError(t, gotErr)
require.EqualValues(t, tc.want, got)
})
}
}
func Test_lastHandlePart(t *testing.T) {
testCases := []struct {
in string
want string
}{
{"@handle", "handle"},
{"@prod/db", "db"},
{"@prod/sub/db", "db"},
}
for i, tc := range testCases {
tc := tc
t.Run(tutil.Name(i, tc.in), func(t *testing.T) {
got := lastHandlePart(tc.in)
require.Equal(t, tc.want, got)
})
}
}
func TestRegisterDefaultOpts(t *testing.T) {
log := slogt.New(t)
reg := &options.Registry{}
log.Debug("options.Registry (before)", "reg", reg)
RegisterDefaultOpts(reg)
log.Debug("options.Registry (after)", "reg", reg)
keys := reg.Keys()
require.Len(t, keys, 31)
for _, opt := range reg.Opts() {
opt := opt
t.Run(opt.Key(), func(t *testing.T) {
require.NotNil(t, opt)
require.NotEmpty(t, opt.Key())
require.NotNil(t, opt.GetAny(nil))
require.NotNil(t, opt.DefaultAny())
require.Equal(t, opt.GetAny(nil), opt.DefaultAny())
require.NotEmpty(t, opt.Usage())
require.True(t, opt.Short() >= 0)
require.Equal(t, opt.Key(), opt.String())
require.NotEmpty(t, opt.Help())
})
}
}

38
cli/options_test.go Normal file
View File

@ -0,0 +1,38 @@
package cli_test
import (
"testing"
"github.com/neilotoole/slogt"
"github.com/neilotoole/sq/cli"
"github.com/neilotoole/sq/libsq/core/options"
"github.com/stretchr/testify/require"
)
func TestRegisterDefaultOpts(t *testing.T) {
log := slogt.New(t)
reg := &options.Registry{}
log.Debug("options.Registry (before)", "reg", reg)
cli.RegisterDefaultOpts(reg)
log.Debug("options.Registry (after)", "reg", reg)
keys := reg.Keys()
require.Len(t, keys, 31)
for _, opt := range reg.Opts() {
opt := opt
t.Run(opt.Key(), func(t *testing.T) {
require.NotNil(t, opt)
require.NotEmpty(t, opt.Key())
require.NotNil(t, opt.GetAny(nil))
require.NotNil(t, opt.DefaultAny())
require.Equal(t, opt.GetAny(nil), opt.DefaultAny())
require.NotEmpty(t, opt.Usage())
require.True(t, opt.Short() >= 0)
require.Equal(t, opt.Key(), opt.String())
require.NotEmpty(t, opt.Help())
})
}
}

View File

@ -256,6 +256,7 @@ func newWriters(cmd *cobra.Command, o options.Options, out, errOut io.Writer,
w.Config = yamlw.NewConfigWriter(out2, pr)
w.Metadata = yamlw.NewMetadataWriter(out2, pr)
w.Source = yamlw.NewSourceWriter(out2, pr)
w.Version = yamlw.NewVersionWriter(out2, pr)
}
recwFn := getRecordWriterFunc(fm)

View File

@ -3,6 +3,8 @@ package jsonw
import (
"io"
"github.com/neilotoole/sq/cli/hostinfo"
"github.com/neilotoole/sq/cli/buildinfo"
"github.com/neilotoole/sq/cli/output"
)
@ -21,16 +23,19 @@ func NewVersionWriter(out io.Writer, pr *output.Printing) output.VersionWriter {
return &versionWriter{out: out, pr: pr}
}
func (w *versionWriter) Version(info buildinfo.BuildInfo, latestVersion string) error {
// Version implements output.VersionWriter.
func (w *versionWriter) Version(bi buildinfo.BuildInfo, latestVersion string, hi hostinfo.Info) error {
type cliBuildInfo struct {
buildinfo.BuildInfo
LatestVersion string `json:"latest_version"`
LatestVersion string `json:"latest_version"`
Host hostinfo.Info `json:"host"`
}
bi := cliBuildInfo{
BuildInfo: info,
cbi := cliBuildInfo{
BuildInfo: bi,
LatestVersion: latestVersion,
Host: hi,
}
return writeJSON(w.out, w.pr, bi)
return writeJSON(w.out, w.pr, cbi)
}

View File

@ -4,6 +4,8 @@ import (
"fmt"
"io"
"github.com/neilotoole/sq/cli/hostinfo"
"github.com/neilotoole/sq/cli/buildinfo"
"github.com/neilotoole/sq/cli/output"
"golang.org/x/mod/semver"
@ -23,38 +25,58 @@ func NewVersionWriter(out io.Writer, pr *output.Printing) output.VersionWriter {
return &versionWriter{out: out, pr: pr}
}
func (w *versionWriter) Version(bi buildinfo.BuildInfo, latestVersion string) error {
fmt.Fprintf(w.out, "sq %s", bi.Version)
// Version implements output.VersionWriter.
func (w *versionWriter) Version(bi buildinfo.BuildInfo, latestVersion string, hi hostinfo.Info) error {
var newerAvailable bool
if w.pr.Verbose {
if len(bi.Commit) > 0 {
if latestVersion != "" {
newerAvailable = semver.Compare(latestVersion, bi.Version) > 0
}
if !w.pr.Verbose {
fmt.Fprintf(w.out, "sq %s", bi.Version)
if newerAvailable {
fmt.Fprint(w.out, " ")
w.pr.Faint.Fprint(w.out, "#"+bi.Commit)
w.pr.Faint.Fprintln(w.out, "Update available: "+latestVersion)
}
return nil
}
if len(bi.Timestamp) > 0 {
fmt.Fprint(w.out, " ")
w.pr.Faint.Fprint(w.out, bi.Timestamp)
fmt.Fprintf(w.out, "sq %s\n", bi.Version)
w.pr.Faint.Fprintf(w.out, "Version: %s\n", bi.Version)
if bi.Commit != "" {
w.pr.Faint.Fprintf(w.out, "Commit: #%s\n", bi.Commit)
}
if bi.Timestamp != "" {
w.pr.Faint.Fprintf(w.out, "Timestamp: %s\n", bi.Timestamp)
}
// latestVersion = ""
w.pr.Faint.Fprint(w.out, "Latest version: ")
if latestVersion == "" {
w.pr.Error.Fprintf(w.out, "unknown\n")
} else {
if newerAvailable {
w.pr.Hilite.Fprintln(w.out, latestVersion)
} else {
w.pr.Faint.Fprintln(w.out, latestVersion)
}
}
showUpdate := semver.Compare(latestVersion, bi.Version) > 0
if showUpdate {
fmt.Fprint(w.out, " ")
w.pr.Faint.Fprint(w.out, "Update available: "+latestVersion)
}
w.pr.Faint.Fprintf(w.out, "Host: %s %s | %s %s | %s %s\n",
hi.Platform, hi.Arch, hi.Kernel, hi.KernelVersion, hi.Variant, hi.VariantVersion)
fmt.Fprintln(w.out)
// Follow GNU standards (mostly)
// https://www.gnu.org/prep/standards/html_node/_002d_002dversion.html#g_t_002d_002dversion
const notice = `MIT License: https://opensource.org/license/mit
Website: https://sq.io
Source code: https://github.com/neilotoole/sq
Notice: Copyright (c) 2023 Neil O'Toole`
w.pr.Faint.Fprintln(w.out, notice)
if w.pr.Verbose {
// Follow GNU standards (mostly)
// https://www.gnu.org/prep/standards/html_node/_002d_002dversion.html#g_t_002d_002dversion
const notice = `
Copyright (c) 2023 Neil O'Toole
MIT License: https://opensource.org/license/mit
Website: https://sq.io
Source code: https://github.com/neilotoole/sq`
w.pr.Faint.Fprintln(w.out, notice)
}
return nil
}

View File

@ -10,6 +10,8 @@ import (
"io"
"time"
"github.com/neilotoole/sq/cli/hostinfo"
"github.com/neilotoole/sq/libsq/core/record"
"github.com/neilotoole/sq/libsq/core/options"
@ -107,7 +109,7 @@ type VersionWriter interface {
// Version prints version info. Arg latestVersion is the latest
// version available from the homebrew repository. The value
// may be empty.
Version(info buildinfo.BuildInfo, latestVersion string) error
Version(bi buildinfo.BuildInfo, latestVersion string, si hostinfo.Info) error
}
// ConfigWriter prints config.

View File

@ -0,0 +1,44 @@
package yamlw
import (
"io"
"github.com/neilotoole/sq/cli/hostinfo"
"github.com/goccy/go-yaml/printer"
"github.com/neilotoole/sq/cli/buildinfo"
"github.com/neilotoole/sq/cli/output"
)
var _ output.VersionWriter = (*versionWriter)(nil)
// versionWriter implements output.VersionWriter for JSON.
type versionWriter struct {
p printer.Printer
out io.Writer
pr *output.Printing
}
// NewVersionWriter returns a new output.VersionWriter instance
// that outputs version info in JSON.
func NewVersionWriter(out io.Writer, pr *output.Printing) output.VersionWriter {
return &versionWriter{p: newPrinter(pr), out: out, pr: pr}
}
// Version implements output.VersionWriter.
func (w *versionWriter) Version(bi buildinfo.BuildInfo, latestVersion string, hi hostinfo.Info) error {
type cliBuildInfo struct {
buildinfo.BuildInfo
LatestVersion string `json:"latest_version"`
Host hostinfo.Info `json:"host"`
}
cbi := cliBuildInfo{
BuildInfo: bi,
LatestVersion: latestVersion,
Host: hi,
}
return writeYAML(w.out, w.p, cbi)
}

1
go.mod
View File

@ -32,6 +32,7 @@ require (
)
require (
github.com/ecnepsnai/osquery v1.0.0
github.com/goccy/go-yaml v1.11.0
github.com/jackc/pgx/v5 v5.3.1
github.com/mitchellh/go-wordwrap v1.0.1

2
go.sum
View File

@ -18,6 +18,8 @@ github.com/djherbis/fscache v0.10.1 h1:hDv+RGyvD+UDKyRYuLoVNbuRTnf2SrA2K3VyR1br9
github.com/djherbis/fscache v0.10.1/go.mod h1:yyPYtkNnnPXsW+81lAcQS6yab3G2CRfnPLotBvtbf0c=
github.com/dnaeon/go-vcr v1.1.0/go.mod h1:M7tiix8f0r6mKKJ3Yq/kqU1OYf3MnfmBWVbPx/yU9ko=
github.com/dnaeon/go-vcr v1.2.0/go.mod h1:R4UdLID7HZT3taECzJs4YgbbH6PIGXB6W/sc5OLb6RQ=
github.com/ecnepsnai/osquery v1.0.0 h1:FWwqlA7hCtO7Usg3N+DlZPNPHWf5nfdypnPEor2ayCs=
github.com/ecnepsnai/osquery v1.0.0/go.mod h1:vxsezNRznmkLa8UjVh88tlJiRbgW7iwinkjyg/Xc2RU=
github.com/emirpasic/gods v1.18.1 h1:FXtiHYKDGKCW2KzwZKx0iC0PQmdlorYgdFG9jPXJ1Bc=
github.com/emirpasic/gods v1.18.1/go.mod h1:8tpGGwCnJ5H4r6BWwaV6OrWmMoPhUl5jm/FMNAnJvWQ=
github.com/fatih/color v1.15.0 h1:kOqh6YHBtK8aywxGerMG2Eq3H6Qgoqeo13Bk2Mv/nBs=