mirror of
https://github.com/neilotoole/sq.git
synced 2024-11-28 03:53:07 +03:00
* sq version host info * workflow: update bug_report.md with version instructions
This commit is contained in:
parent
1f9183aa68
commit
97739da1e1
32
.github/ISSUE_TEMPLATE/bug_report.md
vendored
32
.github/ISSUE_TEMPLATE/bug_report.md
vendored
@ -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.
|
||||
|
26
CHANGELOG.md
26
CHANGELOG.md
@ -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
|
||||
|
@ -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,
|
||||
|
@ -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(),
|
||||
|
@ -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
28
cli/cmd_mv_test.go
Normal 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)
|
||||
})
|
||||
}
|
||||
}
|
@ -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)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
@ -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
|
||||
|
@ -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
103
cli/hostinfo/hostinfo.go
Normal 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
|
||||
}
|
18
cli/hostinfo/hostinfo_test.go
Normal file
18
cli/hostinfo/hostinfo_test.go
Normal 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())
|
||||
}
|
@ -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
38
cli/options_test.go
Normal 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())
|
||||
})
|
||||
}
|
||||
}
|
@ -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)
|
||||
|
@ -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)
|
||||
}
|
||||
|
@ -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
|
||||
}
|
||||
|
@ -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.
|
||||
|
44
cli/output/yamlw/versionwriter.go
Normal file
44
cli/output/yamlw/versionwriter.go
Normal 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
1
go.mod
@ -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
2
go.sum
@ -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=
|
||||
|
Loading…
Reference in New Issue
Block a user