Cobra upgrade: includes shell completion work (#81)

Addressed #80
This commit is contained in:
Neil O'Toole 2021-02-22 00:37:00 -07:00 committed by GitHub
parent b5fdc5c0d3
commit 6870327508
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
41 changed files with 1229 additions and 567 deletions

View File

@ -101,7 +101,7 @@ brews:
name: sq
homepage: "https://sq.io"
description: "sq is a swiss army knife for data"
caveats: "This is a preview release of sq. Use with caution."
caveats: "For shell completion installation instructions, execute: sq completion --help"
tap:
owner: neilotoole

View File

@ -16,6 +16,7 @@ or dropping tables.
For other installation options, see [here](https://github.com/neilotoole/sq/wiki/Home#Install).
It is strongly advised to install [shell completion](#shell-completion).
### macOS
@ -52,6 +53,11 @@ sudo rpm -i https://github.com/neilotoole/sq/releases/latest/download/sq-linux-a
yum localinstall -y https://github.com/neilotoole/sq/releases/latest/download/sq-linux-amd64.rpm
```
## Shell completion
Shell completion is available for `bash`, `zsh`, `fish`, and `powershell`.
Execute `sq completion --help` for installation instructions.
## Quickstart

View File

@ -32,6 +32,7 @@ import (
"path/filepath"
"strconv"
"strings"
"sync"
"github.com/fatih/color"
"github.com/mattn/go-colorable"
@ -68,6 +69,10 @@ import (
"github.com/neilotoole/sq/libsq/source"
)
func init() {
cobra.EnableCommandSorting = false
}
// errNoMsg is a sentinel error indicating that a command
// has failed, but that no error message should be printed.
// This is useful in the case where any error information may
@ -77,7 +82,7 @@ var errNoMsg = errors.New("")
// Execute builds a RunContext using ctx and default
// settings, and invokes ExecuteWith.
func Execute(ctx context.Context, stdin *os.File, stdout, stderr io.Writer, args []string) error {
rc, err := newDefaultRunContext(ctx, stdin, stdout, stderr)
rc, err := newDefaultRunContext(stdin, stdout, stderr)
if err != nil {
printError(rc, err)
return err
@ -85,65 +90,95 @@ func Execute(ctx context.Context, stdin *os.File, stdout, stderr io.Writer, args
defer rc.Close() // ok to call rc.Close on nil rc
return ExecuteWith(rc, args)
return ExecuteWith(ctx, rc, args)
}
// ExecuteWith invokes the cobra CLI framework, ultimately
// resulting in a command being executed. The caller must
// invoke rc.Close.
func ExecuteWith(rc *RunContext, args []string) error {
func ExecuteWith(ctx context.Context, rc *RunContext, args []string) error {
rc.Log.Debugf("EXECUTE: %s", strings.Join(args, " "))
rc.Log.Debugf("Build: %s %s %s", buildinfo.Version, buildinfo.Commit, buildinfo.Timestamp)
rc.Log.Debugf("Config (cfg version %q) from: %s", rc.Config.Version, rc.ConfigStore.Location())
// NOTE: (Feb 2021): The project has been finally been upgraded
// to spf13/cobra v1.1.3, which does provide support
// for context.Context. However, the sq codebase is still using
// the workaround to smuggle Context to the commands. Presumably
// we'll refactor the code at some point to make use of cobra's
// support for Context.
ctx = WithRunContext(ctx, rc)
rootCmd := newCommandTree(rc)
var err error
// The following is a workaround for the fact that cobra doesn't
// currently (as of 2017) support executing the root command with
// arbitrary args. That is to say, if you execute:
//
// sq arg1 arg2
// $ sq @sakila_sl3.actor
//
// then cobra will look for a command named "arg1", and when it
// doesn't find such a command, it returns an "unknown command"
// error.
cmd, _, err := rootCmd.Find(args[1:])
if err != nil {
// This err will be the "unknown command" error.
// cobra still returns cmd though. It should be
// the root cmd.
if cmd == nil || cmd.Name() != rootCmd.Name() {
// should never happen
panic(fmt.Sprintf("bad cobra cmd state: %v", cmd))
}
// then cobra will look for a command named "@sakila_sl3.actor",
// and when it doesn't find such a command, it returns
// an "unknown command" error.
//
// NOTE: This entire mechanism is ancient. Perhaps cobra
// now handles this situation?
// If we have args [sq, arg1, arg2] then we redirect
// to the "slq" command by modifying args to
// look like: [query, arg1, arg2] -- noting that SetArgs
// doesn't want the first args element.
queryCmdArgs := append([]string{"slq"}, args[1:]...)
rootCmd.SetArgs(queryCmdArgs)
} else {
if cmd.Name() == rootCmd.Name() {
// Not sure why we have two paths to this, but it appears
// that we've found the root cmd again, so again
// we redirect to "query" cmd.
a := append([]string{"slq"}, args[1:]...)
rootCmd.SetArgs(a)
// We need to perform handling for autocomplete
if len(args) > 0 && args[0] == "__complete" {
if hasMatchingChildCommand(rootCmd, args[1]) {
// If there is a matching child command, we let rootCmd
// handle it, as per normal.
rootCmd.SetArgs(args)
} else {
// It's just a normal command like "sq ls" or such.
// There's no command matching the first argument to __complete.
// Therefore, we assume that we want to perform completion
// for the "slq" command (which is the pseudo-root command).
effectiveArgs := append([]string{"__complete", "slq"}, args[1:]...)
rootCmd.SetArgs(effectiveArgs)
}
} else {
var cmd *cobra.Command
cmd, _, err = rootCmd.Find(args)
if err != nil {
// This err will be the "unknown command" error.
// cobra still returns cmd though. It should be
// the root cmd.
if cmd == nil || cmd.Name() != rootCmd.Name() {
// Not sure if this can happen anymore? Can prob delete?
panic(fmt.Sprintf("bad cobra cmd state: %v", cmd))
}
// Explicitly set the args on rootCmd as this makes
// cobra happy when this func is executed via tests.
// Haven't explored the reason why.
rootCmd.SetArgs(args[1:])
// If we have args [sq, arg1, arg2] then we redirect
// to the "slq" command by modifying args to
// look like: [query, arg1, arg2] -- noting that SetArgs
// doesn't want the first args element.
effectiveArgs := append([]string{"slq"}, args...)
rootCmd.SetArgs(effectiveArgs)
} else {
if cmd.Name() == rootCmd.Name() {
// Not sure why we have two paths to this, but it appears
// that we've found the root cmd again, so again
// we redirect to "slq" cmd.
a := append([]string{"slq"}, args...)
rootCmd.SetArgs(a)
} else {
// It's just a normal command like "sq ls" or such.
// Explicitly set the args on rootCmd as this makes
// cobra happy when this func is executed via tests.
// Haven't explored the reason why.
rootCmd.SetArgs(args)
}
}
}
// Execute the rootCmd; cobra will find the appropriate
// Execute rootCmd; cobra will find the appropriate
// sub-command, and ultimately execute that command.
err = rootCmd.Execute()
err = rootCmd.ExecuteContext(ctx)
if err != nil {
printError(rc, err)
}
@ -151,11 +186,17 @@ func ExecuteWith(rc *RunContext, args []string) error {
return err
}
// cobraMu exists because cobra relies upon package-level
// constructs. This does not sit well with parallel tests.
var cobraMu sync.Mutex
// newCommandTree builds sq's command tree, returning
// the root cobra command.
func newCommandTree(rc *RunContext) (rootCmd *cobra.Command) {
rootCmd = newRootCmd()
cobraMu.Lock()
defer cobraMu.Unlock()
rootCmd = newRootCmd()
rootCmd.SetOut(rc.Out)
rootCmd.SetErr(rc.ErrOut)
@ -164,42 +205,52 @@ func newCommandTree(rc *RunContext) (rootCmd *cobra.Command) {
// The behavior of cobra in this regard seems to have
// changed? This particular incantation currently does the trick.
rootCmd.Flags().Bool(flagHelp, false, "Show sq help")
helpCmd := addCmd(rc, rootCmd, newHelpCmd)
rootCmd.Flags().SortFlags = false
helpCmd := addCmd(rc, rootCmd, newHelpCmd())
rootCmd.SetHelpCommand(helpCmd)
addCmd(rc, rootCmd, newSLQCmd)
addCmd(rc, rootCmd, newSQLCmd)
addCmd(rc, rootCmd, newSLQCmd())
addCmd(rc, rootCmd, newSQLCmd())
addCmd(rc, rootCmd, newSrcCommand)
addCmd(rc, rootCmd, newSrcAddCmd)
addCmd(rc, rootCmd, newSrcListCmd)
addCmd(rc, rootCmd, newSrcRemoveCmd)
addCmd(rc, rootCmd, newScratchCmd)
addCmd(rc, rootCmd, newSrcCommand())
addCmd(rc, rootCmd, newSrcAddCmd())
addCmd(rc, rootCmd, newSrcListCmd())
addCmd(rc, rootCmd, newSrcRemoveCmd())
addCmd(rc, rootCmd, newScratchCmd())
addCmd(rc, rootCmd, newInspectCmd)
addCmd(rc, rootCmd, newPingCmd)
addCmd(rc, rootCmd, newInspectCmd())
addCmd(rc, rootCmd, newPingCmd())
addCmd(rc, rootCmd, newVersionCmd)
addCmd(rc, rootCmd, newDriversCmd)
addCmd(rc, rootCmd, newVersionCmd())
tblCmd := addCmd(rc, rootCmd, newTblCmd)
addCmd(rc, tblCmd, newTblCopyCmd)
addCmd(rc, tblCmd, newTblTruncateCmd)
addCmd(rc, tblCmd, newTblDropCmd)
driverCmd := addCmd(rc, rootCmd, newDriverCmd())
addCmd(rc, driverCmd, newDriverListCmd())
addCmd(rc, rootCmd, newInstallBashCompletionCmd)
addCmd(rc, rootCmd, newGenerateZshCompletionCmd)
tblCmd := addCmd(rc, rootCmd, newTblCmd())
addCmd(rc, tblCmd, newTblCopyCmd())
addCmd(rc, tblCmd, newTblTruncateCmd())
addCmd(rc, tblCmd, newTblDropCmd())
addCmd(rc, rootCmd, newCompletionCmd())
return rootCmd
}
// runFunc is an expansion of cobra's RunE func that
// adds a RunContext as the first param.
type runFunc func(rc *RunContext, cmd *cobra.Command, args []string) error
// hasMatchingChildCommand returns true if s is a full or prefix
// match for any of cmd's children. For example, if cmd has
// children [inspect, ls, rm], then "insp" or "ls" would return true.
func hasMatchingChildCommand(cmd *cobra.Command, s string) bool {
for _, child := range cmd.Commands() {
if strings.HasPrefix(child.Name(), s) {
return true
}
}
return false
}
// addCmd adds the command returned by cmdFn to parentCmd.
func addCmd(rc *RunContext, parentCmd *cobra.Command, cmdFn func() (*cobra.Command, runFunc)) *cobra.Command {
cmd, fn := cmdFn()
func addCmd(rc *RunContext, parentCmd, cmd *cobra.Command) *cobra.Command {
cmd.Flags().SortFlags = false
if cmd.Name() != "help" {
// Don't add the --help flag to the help command.
@ -209,18 +260,19 @@ func addCmd(rc *RunContext, parentCmd *cobra.Command, cmdFn func() (*cobra.Comma
cmd.PreRunE = func(cmd *cobra.Command, args []string) error {
rc.Cmd = cmd
rc.Args = args
err := rc.preRunE()
err := rc.init()
return err
}
runE := cmd.RunE
cmd.RunE = func(cmd *cobra.Command, args []string) error {
if cmd.Flags().Changed(flagVersion) {
// Bit of a hack: flag --version on any command
// results in execVersion being invoked
return execVersion(rc, cmd, args)
return execVersion(cmd, args)
}
return fn(rc, cmd, args)
return runE(cmd, args)
}
// We handle the errors ourselves (rather than let cobra do it)
@ -232,13 +284,26 @@ func addCmd(rc *RunContext, parentCmd *cobra.Command, cmdFn func() (*cobra.Comma
return cmd
}
type runContextKey struct{}
// WithRunContext returns ctx with rc added as a value.
func WithRunContext(ctx context.Context, rc *RunContext) context.Context {
if ctx == nil {
ctx = context.Background()
}
return context.WithValue(ctx, runContextKey{}, rc)
}
// RunContextFrom extracts the RunContext added to ctx via WithRunContext.
func RunContextFrom(ctx context.Context) *RunContext {
return ctx.Value(runContextKey{}).(*RunContext)
}
// RunContext is a container for injectable resources passed
// to all execX funcs. The Close method should be invoked when
// the RunContext is no longer needed.
type RunContext struct {
// Context is the run's context.Context.
Context context.Context
// Stdin typically is os.Stdin, but can be changed for testing.
Stdin *os.File
@ -269,6 +334,9 @@ type RunContext struct {
// Log is the run's logger.
Log lg.Log
initOnce sync.Once
initErr error
// writers holds the various writer types that
// the CLI uses to print output.
writers *writers
@ -288,12 +356,11 @@ type RunContext struct {
// example if there's a config error). We do this to provide
// enough framework so that such an error can be logged or
// printed per the normal mechanisms if at all possible.
func newDefaultRunContext(ctx context.Context, stdin *os.File, stdout, stderr io.Writer) (*RunContext, error) {
func newDefaultRunContext(stdin *os.File, stdout, stderr io.Writer) (*RunContext, error) {
rc := &RunContext{
Context: ctx,
Stdin: stdin,
Out: stdout,
ErrOut: stderr,
Stdin: stdin,
Out: stdout,
ErrOut: stderr,
}
log, clnup, loggingErr := defaultLogging()
@ -325,10 +392,21 @@ func newDefaultRunContext(ctx context.Context, stdin *os.File, stdout, stderr io
return rc, nil
}
// preRunE is invoked by cobra prior to the command RunE being
// init is invoked by cobra prior to the command RunE being
// invoked. It sets up the registry, databases, writers and related
// fundamental components.
func (rc *RunContext) preRunE() error {
// fundamental components. Subsequent invocations of this method
// are no-op.
func (rc *RunContext) init() error {
rc.initOnce.Do(func() {
rc.initErr = rc.doInit()
})
return rc.initErr
}
// doInit performs the actual work of initializing rc.
// It must only be invoked once.
func (rc *RunContext) doInit() error {
rc.clnup = cleanup.New()
log, cfg := rc.Log, rc.Config

View File

@ -2,6 +2,7 @@ package cli_test
import (
"bytes"
"context"
"fmt"
"image/gif"
"io/ioutil"
@ -28,27 +29,23 @@ func TestSmoke(t *testing.T) {
t.Parallel()
// Execute a bunch of smoke test cases.
sqargs := func(a ...string) []string {
return append([]string{"sq"}, a...)
}
testCases := []struct {
a []string
// errBecause, if non-empty, indicates an error is expected.
errBecause string
}{
{a: sqargs("ls")},
{a: sqargs("ls", "-v")},
{a: sqargs("ls", "--help")},
{a: sqargs("inspect"), errBecause: "no active data source"},
{a: sqargs("inspect", "--help")},
{a: sqargs("version")},
{a: sqargs("--version")},
{a: sqargs("help")},
{a: sqargs("--help")},
{a: sqargs("ping", "all")},
{a: sqargs("ping", "--help")},
{a: sqargs("ping"), errBecause: "no active data source"},
{a: []string{"ls"}},
{a: []string{"ls", "-v"}},
{a: []string{"ls", "--help"}},
{a: []string{"inspect"}, errBecause: "no active data source"},
{a: []string{"inspect", "--help"}},
{a: []string{"version"}},
{a: []string{"--version"}},
{a: []string{"help"}},
{a: []string{"--help"}},
{a: []string{"ping", "all"}},
{a: []string{"ping", "--help"}},
{a: []string{"ping"}, errBecause: "no active data source"},
}
for _, tc := range testCases {
@ -58,7 +55,7 @@ func TestSmoke(t *testing.T) {
t.Parallel()
rc, out, errOut := newTestRunCtx(testlg.New(t))
err := cli.ExecuteWith(rc, tc.a)
err := cli.ExecuteWith(context.Background(), rc, tc.a)
// We log sq's output before doing assert, because it reads
// better in testing's output that way.

View File

@ -12,34 +12,34 @@ import (
"github.com/neilotoole/sq/libsq/source"
)
func newSrcAddCmd() (*cobra.Command, runFunc) {
func newSrcAddCmd() *cobra.Command {
cmd := &cobra.Command{
Use: "add [--driver=TYPE] [--handle=@HANDLE] LOCATION",
Use: "add [--driver=TYPE] [--handle=@HANDLE] LOCATION",
RunE: execSrcAdd,
Example: ` # add a Postgres source; will have generated handle @sakila_pg
sq add 'postgres://user:pass@localhost/sakila?sslmode=disable'
$ sq add 'postgres://user:pass@localhost/sakila?sslmode=disable'
# same as above, but explicitly setting flags
sq add --handle=@sakila_pg --driver=postgres 'postgres://user:pass@localhost/sakila?sslmode=disable'
$ sq add --handle=@sakila_pg --driver=postgres 'postgres://user:pass@localhost/sakila?sslmode=disable'
# same as above, but with short flags
sq add -h @sakila_pg --d postgres 'postgres://user:pass@localhost/sakila?sslmode=disable'
$ sq add -h @sakila_pg --d postgres 'postgres://user:pass@localhost/sakila?sslmode=disable'
# add a SQL Server source; will have generated handle @sakila_mssql
sq add 'sqlserver://user:pass@localhost?database=sakila'
# add a SQL Server source; will have generated handle @sakila_mssql or similar
$ sq add 'sqlserver://user:pass@localhost?database=sakila'
# add a sqlite db
sq add ./testdata/sqlite1.db
$ sq add ./testdata/sqlite1.db
# add an Excel spreadsheet, with options
sq add ./testdata/test1.xlsx --opts=header=true
$ sq add ./testdata/test1.xlsx --opts=header=true
# add a CSV source, with options
sq add ./testdata/person.csv --opts='header=true'
$ sq add ./testdata/person.csv --opts=header=true
# add a CSV source from a server (will be downloaded)
sq add https://sq.io/testdata/actor.csv
$ sq add https://sq.io/testdata/actor.csv
`,
Short: "Add data source",
Long: `Add data source specified by LOCATION and optionally identified by @HANDLE.
The format of LOCATION varies, but is generally a DB connection string, a
file path, or a URL.
@ -53,35 +53,41 @@ on LOCATION and the source driver type.
If flag --driver is omitted, sq will attempt to determine the
type from LOCATION via file suffix, content type, etc.. If the result
is ambiguous, specify the driver tye type via flag --driver.
is ambiguous, specify the driver type via flag --driver.
Flag --opts sets source specific options. Generally opts are relevant
Flag --opts sets source-specific options. Generally opts are relevant
to document source types (such as a CSV file). The most common
use is to specify that the document has a header row:
sq add actor.csv --opts=header=true
$ sq add actor.csv --opts=header=true
Available source driver types can be listed via "sq drivers".
Available source driver types can be listed via "sq driver ls".
At a minimum, the following drivers are bundled:
sqlite3 SQLite3
postgres Postgres
sqlserver Microsoft SQL Server
xlsx Microsoft Excel XLSX
mysql MySQL
csv Comma-Separated Values
tsv Tab-Separated Values
sqlite3 SQLite
postgres PostgreSQL
sqlserver Microsoft SQL Server
mysql MySQL
csv Comma-Separated Values
tsv Tab-Separated Values
json JSON
jsona JSON Array: LF-delimited JSON arrays
jsonl JSON Lines: LF-delimited JSON objects
xlsx Microsoft Excel XLSX
`,
Short: "Add data source",
}
cmd.Flags().StringP(flagDriver, flagDriverShort, "", flagDriverUsage)
_ = cmd.RegisterFlagCompletionFunc(flagDriver, completeDriverType)
cmd.Flags().StringP(flagSrcOptions, "", "", flagSrcOptionsUsage)
cmd.Flags().StringP(flagHandle, flagHandleShort, "", flagHandleUsage)
return cmd, execSrcAdd
return cmd
}
func execSrcAdd(rc *RunContext, cmd *cobra.Command, args []string) error {
func execSrcAdd(cmd *cobra.Command, args []string) error {
rc := RunContextFrom(cmd.Context())
if len(args) != 1 {
return errz.Errorf(msgInvalidArgs)
}
@ -94,9 +100,8 @@ func execSrcAdd(rc *RunContext, cmd *cobra.Command, args []string) error {
if cmd.Flags().Changed(flagDriver) {
val, _ := cmd.Flags().GetString(flagDriver)
typ = source.Type(strings.TrimSpace(val))
} else {
typ, err = rc.files.Type(rc.Context, loc)
typ, err = rc.files.Type(cmd.Context(), loc)
if err != nil {
return err
}
@ -148,8 +153,8 @@ func execSrcAdd(rc *RunContext, cmd *cobra.Command, args []string) error {
// unlike the other SQL DBs sq supports so far.
// Both of these forms are allowed:
//
// sq add sqlite3:///path/to/sakila.db
// sq add /path/to/sakila.db
// $ sq add sqlite3:///path/to/sakila.db
// $ sq add /path/to/sakila.db
//
// The second form is particularly nice for bash completion etc.
if typ == sqlite3.Type {
@ -182,7 +187,7 @@ func execSrcAdd(rc *RunContext, cmd *cobra.Command, args []string) error {
}
// TODO: should we really be pinging this src right now?
err = drvr.Ping(rc.Context, src)
err = drvr.Ping(cmd.Context(), src)
if err != nil {
return errz.Wrapf(err, "failed to ping %s [%s]", src.Handle, src.RedactedLocation())
}

View File

@ -1,109 +1,75 @@
package cli
import (
"os"
"runtime"
"github.com/spf13/cobra"
"github.com/neilotoole/sq/libsq/core/errz"
)
const bashCompletionFunc = `
__sq_list_sources()
{
local sq_output out
if sq_output=$(sq ls 2>/dev/null); then
out=($(echo "${sq_output}" | awk 'NR > 1 {print $1}'))
COMPREPLY=( $( compgen -W "${out[*]}" -- "$cur" ) )
fi
}
__sq_get_resource()
{
if [[ ${#nouns[@]} -eq 0 ]]; then
return 1
fi
__sq_list_sources ${nouns[${#nouns[@]} -1]}
if [[ $? -eq 0 ]]; then
return 0
fi
}
__custom_func() {
case ${last_command} in
sq_ls | sq_src | sq_rm | sq_inspect )
__sq_list_sources
return
;;
*)
;;
esac
}
`
func newInstallBashCompletionCmd() (*cobra.Command, runFunc) {
func newCompletionCmd() *cobra.Command {
cmd := &cobra.Command{
Use: "install-bash-completion",
Short: "Install bash completion script on Unix-ish systems.",
Hidden: true,
Use: "completion [bash|zsh|fish|powershell]",
Short: "Generate completion script",
RunE: execCompletion,
Long: `To load completions:
Bash:
$ source <(sq completion bash)
# To load completions for each session, execute once:
Linux:
$ sq completion bash > /etc/bash_completion.d/sq
MacOS:
$ sq completion bash > /usr/local/etc/bash_completion.d/sq
Zsh:
# If shell completion is not already enabled in your environment you will need
# to enable it. You can execute the following once:
$ echo "autoload -U compinit; compinit" >> ~/.zshrc
# To load completions for each session, execute once:
$ sq completion zsh > "${fpath[1]}/_sq"
# You will need to start a new shell for this setup to take effect.
Fish:
$ sq completion fish | source
# To load completions for each session, execute once:
$ sq completion fish > ~/.config/fish/completions/sq.fish
Powershell:
PS> sq completion powershell | Out-String | Invoke-Expression
# To load completions for every new session, run:
PS> sq completion powershell > sq.ps1
# and source this file from your powershell profile.
`,
DisableFlagsInUseLine: true,
ValidArgs: []string{"bash", "zsh", "fish", "powershell"},
Args: cobra.ExactValidArgs(1),
}
return cmd, execInstallBashCompletion
return cmd
}
func execInstallBashCompletion(rc *RunContext, cmd *cobra.Command, args []string) error {
log := rc.Log
var path string
switch runtime.GOOS {
case "windows":
log.Warnf("skipping install bash completion on windows")
return nil
case "darwin":
path = "/usr/local/etc/bash_completion.d/sq"
func execCompletion(cmd *cobra.Command, args []string) error {
rc := RunContextFrom(cmd.Context())
switch args[0] {
case "bash":
return cmd.Root().GenBashCompletion(rc.Out)
case "zsh":
return cmd.Root().GenZshCompletion(rc.Out)
case "fish":
return cmd.Root().GenFishCompletion(rc.Out, true)
case "powershell":
return cmd.Root().GenPowerShellCompletion(rc.Out)
default:
// it's unixish
path = " /etc/bash_completion.d/sq"
return errz.Errorf("invalid arg: %s", args[0])
}
// TODO: only write if necessary (check for version/timestamp/checksum)
err := cmd.Root().GenBashCompletionFile(path)
if err != nil {
log.Warnf("failed to write bash completion to %q: %v", path, err)
return err
}
return nil
}
func newGenerateZshCompletionCmd() (*cobra.Command, runFunc) {
cmd := &cobra.Command{
Use: "gen-zsh-completion",
Short: "Generate zsh completion script on Unix-ish systems.",
Hidden: true,
}
return cmd, execGenerateZshCompletion
}
func execGenerateZshCompletion(rc *RunContext, cmd *cobra.Command, args []string) error {
log := rc.Log
var path string
switch runtime.GOOS {
case "windows":
log.Warnf("skipping install zsh completion on windows")
return nil
case "darwin":
path = "/usr/local/etc/bash_completion.d/sq"
default:
// it's unixish
path = " /etc/bash_completion.d/sq"
}
err := cmd.Root().GenZshCompletion(os.Stdout)
if err != nil {
log.Warnf("failed to write zsh completion to %q: %v", path, err)
return err
}
return nil
}

46
cli/cmd_driver.go Normal file
View File

@ -0,0 +1,46 @@
package cli
import (
"github.com/spf13/cobra"
)
func newDriverCmd() *cobra.Command {
cmd := &cobra.Command{
Use: "driver",
Short: "List or manage drivers",
RunE: func(cmd *cobra.Command, args []string) error {
return cmd.Help()
},
Example: ` # List drivers
$ sq driver ls
# Install User Driver [TBD]
$ sq driver install ./rss.sq.yml
`,
}
return cmd
}
func newDriverListCmd() *cobra.Command {
cmd := &cobra.Command{
Use: "ls",
Short: "List available drivers",
Args: cobra.ExactArgs(0),
RunE: execDriverList,
}
cmd.Flags().BoolP(flagJSON, flagJSONShort, false, flagJSONUsage)
cmd.Flags().BoolP(flagTable, flagTableShort, false, flagTableUsage)
cmd.Flags().BoolP(flagHeader, flagHeaderShort, false, flagHeaderUsage)
cmd.Flags().BoolP(flagMonochrome, flagMonochromeShort, false, flagMonochromeUsage)
return cmd
}
func execDriverList(cmd *cobra.Command, args []string) error {
rc := RunContextFrom(cmd.Context())
drvrs := rc.registry.DriversMetadata()
return rc.writers.metaw.DriverMetadata(drvrs)
}

View File

@ -1,30 +0,0 @@
package cli
import (
"github.com/spf13/cobra"
"github.com/neilotoole/sq/libsq/core/errz"
)
func newDriversCmd() (*cobra.Command, runFunc) {
cmd := &cobra.Command{
Use: "drivers",
Short: "List available drivers",
}
cmd.Flags().BoolP(flagJSON, flagJSONShort, false, flagJSONUsage)
cmd.Flags().BoolP(flagTable, flagTableShort, false, flagTableUsage)
cmd.Flags().BoolP(flagHeader, flagHeaderShort, false, flagHeaderUsage)
cmd.Flags().BoolP(flagMonochrome, flagMonochromeShort, false, flagMonochromeUsage)
return cmd, execDrivers
}
func execDrivers(rc *RunContext, cmd *cobra.Command, args []string) error {
if len(args) > 0 {
return errz.Errorf("invalid arguments: zero arguments expected")
}
drvrs := rc.registry.DriversMetadata()
return rc.writers.metaw.DriverMetadata(drvrs)
}

View File

@ -2,16 +2,17 @@ package cli
import "github.com/spf13/cobra"
func newHelpCmd() (*cobra.Command, runFunc) {
func newHelpCmd() *cobra.Command {
cmd := &cobra.Command{
Use: "help",
Short: "Show sq help",
Hidden: true,
RunE: execHelp,
}
return cmd, execHelp
return cmd
}
func execHelp(rc *RunContext, cmd *cobra.Command, args []string) error {
func execHelp(cmd *cobra.Command, args []string) error {
return cmd.Root().Help()
}

View File

@ -7,40 +7,44 @@ import (
"github.com/neilotoole/sq/libsq/source"
)
func newInspectCmd() (*cobra.Command, runFunc) {
func newInspectCmd() *cobra.Command {
cmd := &cobra.Command{
Use: "inspect [@HANDLE|@HANDLE.TABLE|.TABLE]",
Example: ` # inspect active data source
sq inspect
# inspect @pg1 data source
sq inspect @pg1
# inspect 'tbluser' in @pg1 data source
sq inspect @pg1.tbluser
# inspect 'tbluser' in active data source
sq inspect .tbluser
# inspect piped data
cat data.xlsx | sq inspect`,
Use: "inspect [@HANDLE|@HANDLE.TABLE|.TABLE]",
Args: cobra.MaximumNArgs(1),
ValidArgsFunction: (&handleTableCompleter{
max: 1,
}).complete,
RunE: execInspect,
Short: "Inspect data source schema and stats",
Long: `Inspect a data source, or a particular table in a source,
listing table details, column names and types, row counts, etc.
If @HANDLE is not provided, the active data source is assumed.`,
Example: ` # inspect active data source
$ sq inspect
# inspect @pg1 data source
$ sq inspect @pg1
# inspect 'actor' in @pg1 data source
$ sq inspect @pg1.actor
# inspect 'actor' in active data source
$ sq inspect .actor
# inspect piped data
$ cat data.xlsx | sq inspect`,
}
cmd.Flags().BoolP(flagJSON, flagJSONShort, false, flagJSONUsage)
cmd.Flags().BoolP(flagTable, flagTableShort, false, flagTableUsage)
cmd.Flags().Bool(flagInspectFull, false, flagInspectFullUsage)
return cmd, execInspect
return cmd
}
func execInspect(rc *RunContext, cmd *cobra.Command, args []string) error {
if len(args) > 1 {
return errz.Errorf("too many arguments")
}
func execInspect(cmd *cobra.Command, args []string) error {
ctx := cmd.Context()
rc := RunContextFrom(ctx)
srcs := rc.Config.Sources
@ -56,7 +60,7 @@ func execInspect(rc *RunContext, cmd *cobra.Command, args []string) error {
// - We're inspecting the active src
// check if there's input on stdin
src, err = checkStdinSource(rc)
src, err = checkStdinSource(ctx, rc)
if err != nil {
return err
}
@ -107,15 +111,14 @@ func execInspect(rc *RunContext, cmd *cobra.Command, args []string) error {
}
}
dbase, err := rc.databases.Open(rc.Context, src)
dbase, err := rc.databases.Open(ctx, src)
if err != nil {
return errz.Wrapf(err, "failed to inspect %s", src.Handle)
}
//defer rc.Log.WarnIfCloseError(dbase)
if table != "" {
var tblMeta *source.TableMetadata
tblMeta, err = dbase.TableMetadata(rc.Context, table)
tblMeta, err = dbase.TableMetadata(ctx, table)
if err != nil {
return err
}
@ -123,7 +126,7 @@ func execInspect(rc *RunContext, cmd *cobra.Command, args []string) error {
return rc.writers.metaw.TableMetadata(tblMeta)
}
meta, err := dbase.SourceMetadata(rc.Context)
meta, err := dbase.SourceMetadata(ctx)
if err != nil {
return errz.Wrapf(err, "failed to read %s source metadata", src.Handle)
}

View File

@ -2,25 +2,23 @@ package cli
import (
"github.com/spf13/cobra"
"github.com/neilotoole/sq/libsq/core/errz"
)
func newSrcListCmd() (*cobra.Command, runFunc) {
func newSrcListCmd() *cobra.Command {
cmd := &cobra.Command{
Use: "ls",
Short: "List data sources",
Args: cobra.ExactArgs(0),
RunE: execSrcList,
}
cmd.Flags().BoolP(flagVerbose, flagVerboseShort, false, flagVerboseUsage)
cmd.Flags().BoolP(flagHeader, flagHeaderShort, false, flagHeaderUsage)
return cmd, execSrcList
return cmd
}
func execSrcList(rc *RunContext, cmd *cobra.Command, args []string) error {
if len(args) != 0 {
return errz.Errorf(msgInvalidArgs)
}
func execSrcList(cmd *cobra.Command, args []string) error {
rc := RunContextFrom(cmd.Context())
return rc.writers.srcw.SourceSet(rc.Config.Sources)
}

View File

@ -14,27 +14,41 @@ import (
"github.com/neilotoole/sq/libsq/source"
)
func newPingCmd() (*cobra.Command, runFunc) {
func newPingCmd() *cobra.Command {
argsFn := func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
// Suggestions are: handles, plus the string "all".
rc := RunContextFrom(cmd.Context())
suggestions := append([]string{"all"}, rc.Config.Sources.Handles()...)
return suggestions, cobra.ShellCompDirectiveNoFileComp
}
cmd := &cobra.Command{
Use: "ping [@HANDLE|all]",
Use: "ping [all|@HANDLE [@HANDLE_N]]",
//Args: cobra.MaximumNArgs(1),
RunE: execPing,
ValidArgsFunction: argsFn,
Short: "Ping data sources",
Long: `Ping data sources to check connection health. If no arguments provided, the
active data source is pinged. Provide the handles of one or more sources
to ping those sources, or "all" to ping all sources.
The exit code is 1 if ping fails for any of the sources.`,
Example: ` # ping active data source
sq ping
$ sq ping
# ping all data sources
sq ping all
# ping @my1 with 2s timeout
sq ping @my1 --timeout=2s
$ sq ping all
# ping @my1 and @pg1
sq ping @my1 @pg1
$ sq ping @my1 @pg1
# ping @my1 with 2s timeout
$ sq ping @my1 --timeout=2s
# output in TSV format
sq ping --tsv @my1`,
Short: "Check data source connection health",
Long: `Ping data sources to check connection health. If no arguments provided, the
active data source is pinged. The exit code is 1 if ping fails for any of the sources.`,
$ sq ping --tsv @my1`,
}
cmd.Flags().BoolP(flagTable, flagTableShort, false, flagTableUsage)
@ -42,10 +56,11 @@ active data source is pinged. The exit code is 1 if ping fails for any of the so
cmd.Flags().BoolP(flagTSV, flagTSVShort, false, flagTSVUsage)
cmd.Flags().Duration(flagTimeout, time.Second*10, flagTimeoutPingUsage)
return cmd, execPing
return cmd
}
func execPing(rc *RunContext, cmd *cobra.Command, args []string) error {
func execPing(cmd *cobra.Command, args []string) error {
rc := RunContextFrom(cmd.Context())
cfg := rc.Config
var srcs []*source.Source
var gotAll bool
@ -93,19 +108,23 @@ func execPing(rc *RunContext, cmd *cobra.Command, args []string) error {
}
}
timeout := cfg.Defaults.Timeout
timeout := cfg.Defaults.PingTimeout
if cmdFlagChanged(cmd, flagTimeout) {
timeout, _ = cmd.Flags().GetDuration(flagTimeout)
}
rc.Log.Debugf("Using timeout value: %s", timeout)
return pingSources(rc.Context, rc.Log, rc.registry, srcs, rc.writers.pingw, timeout)
return pingSources(cmd.Context(), rc.Log, rc.registry, srcs, rc.writers.pingw, timeout)
}
// pingSources pings each of the sources in srcs, and prints results
// to w. If any error occurs pinging any of srcs, that error is printed
// inline as part of the ping results, and an errNoMsg is returned.
//
// NOTE: This ping code has an ancient lineage, in that it was written
// originally laid down before context.Context was a thing. Thus,
// the entire thing could probably be rewritten for simplicity.
func pingSources(ctx context.Context, log lg.Log, dp driver.Provider, srcs []*source.Source, w output.PingWriter, timeout time.Duration) error {
w.Open(srcs)
defer log.WarnIfFuncError(w.Close)

View File

@ -4,26 +4,23 @@ import (
"fmt"
"github.com/spf13/cobra"
"github.com/neilotoole/sq/libsq/core/errz"
)
func newSrcRemoveCmd() (*cobra.Command, runFunc) {
func newSrcRemoveCmd() *cobra.Command {
cmd := &cobra.Command{
Use: "rm @HANDLE",
Example: ` sq rm @my1`,
Aliases: []string{"remove"},
Short: "Remove data source",
Use: "rm @HANDLE",
Example: ` $ sq rm @my1`,
Short: "Remove data source",
Args: cobra.ExactArgs(1),
RunE: execSrcRemove,
ValidArgsFunction: completeHandle(1),
}
return cmd, execSrcRemove
return cmd
}
func execSrcRemove(rc *RunContext, cmd *cobra.Command, args []string) error {
if len(args) != 1 {
return errz.Errorf(msgInvalidArgs)
}
func execSrcRemove(cmd *cobra.Command, args []string) error {
rc := RunContextFrom(cmd.Context())
cfg := rc.Config
src, err := cfg.Sources.Get(args[0])
if err != nil {

View File

@ -19,66 +19,67 @@ output to a database table.
You can query using sq's own jq-like syntax, or in native SQL.
Execute "sq completion --help" for how to install shell completion.
More at https://sq.io
`,
Example: ` # pipe an Excel file and output the first 10 rows from sheet1
cat data.xlsx | sq '.sheet1 | .[0:10]'
$ cat data.xlsx | sq '.sheet1 | .[0:10]'
# add Postgres source identified by handle @sakila_pg
sq add --handle=@sakila_pg 'postgres://user:pass@localhost:5432/sakila?sslmode=disable'
$ sq add --handle=@sakila_pg 'postgres://user:pass@localhost:5432/sakila?sslmode=disable'
# add SQL Server source; will have generated handle @sakila_mssql
sq add 'sqlserver://user:pass@localhost?database=sakila'
$ sq add 'sqlserver://user:pass@localhost?database=sakila'
# list available data sources
sq ls
$ sq ls
# ping all data sources
sq ping all
$ sq ping all
# set active data source
sq src @sakila_pg
$ sq src @sakila_pg
# get specified cols from table address in active data source
sq '.address | .address_id, .city, .country'
$ sq '.address | .address_id, .city, .country'
# get metadata (schema, stats etc) for data source
sq inspect @sakila_pg
$ sq inspect @sakila_pg
# get metadata for a table
sq inspect @pg1.person
$ sq inspect @pg1.person
# output in JSON
sq -j '.person | .uid, .username, .email'
$ sq -j '.person | .uid, .username, .email'
# output in table format (with header)
sq -th '.person | .uid, .username, .email'
$ sq -th '.person | .uid, .username, .email'
# output in table format (no header)
sq -t '.person | .uid, .username, .email'
$ sq -t '.person | .uid, .username, .email'
# output to a HTML file
sq --html '@sakila_sl3.actor' -o actor.html
$ sq --html '@sakila_sl3.actor' -o actor.html
# join across data sources
sq '@my1.person, @pg1.address | join(.uid) | .username, .email, .city'
$ sq '@my1.person, @pg1.address | join(.uid) | .username, .email, .city'
# insert query results into a table in another data source
sq --insert=@pg1.person '@my1.person | .username, .email'
$ sq --insert=@pg1.person '@my1.person | .username, .email'
# execute a database-native SQL query, specifying the source
sq sql --src=@pg1 'SELECT uid, username, email FROM person LIMIT 2'
$ sq sql --src=@pg1 'SELECT uid, username, email FROM person LIMIT 2'
# copy a table (in the same source)
sq tbl copy @sakila_sl3.actor .actor2
$ sq tbl copy @sakila_sl3.actor .actor2
# truncate tables
sq tbl truncate @sakila_sl3.actor2
$ sq tbl truncate @sakila_sl3.actor2
# drop table
sq tbl drop @sakila_sl3.actor2
$ sq tbl drop @sakila_sl3.actor2
`,
BashCompletionFunction: bashCompletionFunc,
}
addQueryCmdFlags(cmd)

View File

@ -2,7 +2,6 @@ package cli
import (
"github.com/neilotoole/sq/drivers/sqlite3"
"github.com/neilotoole/sq/libsq/core/errz"
"github.com/neilotoole/sq/libsq/source"
"github.com/spf13/cobra"
@ -10,24 +9,26 @@ import (
// TODO: dump all this "internal" stuff: make the options as follows: @HANDLE, file, memory
func newScratchCmd() (*cobra.Command, runFunc) {
func newScratchCmd() *cobra.Command {
cmd := &cobra.Command{
Use: "scratch [@HANDLE|internal|internal:file|internal:mem|@scratch]",
// This command is likely to be ditched in favor of a generalized "config" cmd
// such as "sq config scratchdb=@my1"
Hidden: true,
Args: cobra.ExactArgs(1),
RunE: execScratch,
Example: ` # get scratch data source
sq scratch
$ sq scratch
# set @my1 as scratch data source
sq scratch @my1
$ sq scratch @my1
# use the default embedded db
sq scratch internal
$ sq scratch internal
# explicitly specify use of embedded file db
sq scratch internal:file
$ sq scratch internal:file
# explicitly specify use of embedded memory db
sq scratch internal:mem
$ sq scratch internal:mem
# restore default scratch db (equivalent to "internal")
sq scratch @scratch`,
$ sq scratch @scratch`,
Short: "Get or set scratch data source",
Long: `Get or set scratch data source. The scratch db is used internally by sq for multiple purposes such as
importing non-SQL data, or cross-database joins. If no argument provided, get the current scratch data
@ -35,14 +36,11 @@ source. Otherwise, set @HANDLE or an internal db as the scratch data source. The
`,
}
return cmd, execScratch
return cmd
}
func execScratch(rc *RunContext, cmd *cobra.Command, args []string) error {
if len(args) > 1 {
return errz.Errorf(msgInvalidArgs)
}
func execScratch(cmd *cobra.Command, args []string) error {
rc := RunContextFrom(cmd.Context())
cfg := rc.Config
var src *source.Source

View File

@ -15,24 +15,28 @@ import (
"github.com/neilotoole/sq/libsq/source"
)
func newSLQCmd() (*cobra.Command, runFunc) {
func newSLQCmd() *cobra.Command {
cmd := &cobra.Command{
Use: "slq",
Short: "Execute SLQ query",
Hidden: false,
Use: "slq",
Short: "Execute SLQ query",
Hidden: true,
Args: cobra.MaximumNArgs(1),
RunE: execSLQ,
ValidArgsFunction: completeSLQ,
}
addQueryCmdFlags(cmd)
cmd.Flags().Bool(flagVersion, false, flagVersionUsage)
return cmd, execSLQ
return cmd
}
func execSLQ(rc *RunContext, cmd *cobra.Command, args []string) error {
func execSLQ(cmd *cobra.Command, args []string) error {
rc := RunContextFrom(cmd.Context())
srcs := rc.Config.Sources
// check if there's input on stdin
src, err := checkStdinSource(rc)
src, err := checkStdinSource(cmd.Context(), rc)
if err != nil {
return err
}
@ -65,7 +69,7 @@ func execSLQ(rc *RunContext, cmd *cobra.Command, args []string) error {
if !cmdFlagChanged(cmd, flagInsert) {
// The user didn't specify the --insert=@src.tbl flag,
// so we just want to print the records.
return execSLQPrint(rc)
return execSLQPrint(cmd.Context(), rc)
}
// Instead of printing the records, they will be
@ -85,19 +89,19 @@ func execSLQ(rc *RunContext, cmd *cobra.Command, args []string) error {
return err
}
return execSLQInsert(rc, destSrc, destTbl)
return execSLQInsert(cmd.Context(), rc, destSrc, destTbl)
}
// execSQLInsert executes the SLQ and inserts resulting records
// into destTbl in destSrc.
func execSLQInsert(rc *RunContext, destSrc *source.Source, destTbl string) error {
func execSLQInsert(ctx context.Context, rc *RunContext, destSrc *source.Source, destTbl string) error {
args, srcs, dbases := rc.Args, rc.Config.Sources, rc.databases
slq, err := preprocessUserSLQ(rc, args)
slq, err := preprocessUserSLQ(ctx, rc, args)
if err != nil {
return err
}
ctx, cancelFn := context.WithCancel(rc.Context)
ctx, cancelFn := context.WithCancel(ctx)
defer cancelFn()
destDB, err := dbases.Open(ctx, destSrc)
@ -132,14 +136,14 @@ func execSLQInsert(rc *RunContext, destSrc *source.Source, destTbl string) error
}
// execSLQPrint executes the SLQ query, and prints output to writer.
func execSLQPrint(rc *RunContext) error {
slq, err := preprocessUserSLQ(rc, rc.Args)
func execSLQPrint(ctx context.Context, rc *RunContext) error {
slq, err := preprocessUserSLQ(ctx, rc, rc.Args)
if err != nil {
return err
}
recw := output.NewRecordWriterAdapter(rc.writers.recordw)
execErr := libsq.ExecuteSLQ(rc.Context, rc.Log, rc.databases, rc.databases, rc.Config.Sources, slq, recw)
execErr := libsq.ExecuteSLQ(ctx, rc.Log, rc.databases, rc.databases, rc.Config.Sources, slq, recw)
_, waitErr := recw.Wait()
if execErr != nil {
return execErr
@ -171,8 +175,8 @@ func execSLQPrint(rc *RunContext) error {
// to the query. This allows a query where the first selector
// segment is the table name.
//
// $ sq '.person' --> sq '@active.person'
func preprocessUserSLQ(rc *RunContext, args []string) (string, error) {
// $ sq '.person' --> $ sq '@active.person'
func preprocessUserSLQ(ctx context.Context, rc *RunContext, args []string) (string, error) {
log, reg, dbases, srcs := rc.Log, rc.registry, rc.databases, rc.Config.Sources
activeSrc := srcs.Active()
@ -204,13 +208,13 @@ func preprocessUserSLQ(rc *RunContext, args []string) (string, error) {
// This isn't a monotable src, so we can't
// just select @stdin.data. Instead we'll select
// the first table name, as found in the source meta.
dbase, err := dbases.Open(rc.Context, activeSrc)
dbase, err := dbases.Open(ctx, activeSrc)
if err != nil {
return "", err
}
defer log.WarnIfCloseError(dbase)
srcMeta, err := dbase.SourceMetadata(rc.Context)
srcMeta, err := dbase.SourceMetadata(ctx)
if err != nil {
return "", err
}
@ -299,9 +303,14 @@ func addQueryCmdFlags(cmd *cobra.Command) {
cmd.Flags().BoolP(flagPretty, "", true, flagPrettyUsage)
cmd.Flags().StringP(flagInsert, "", "", flagInsertUsage)
_ = cmd.RegisterFlagCompletionFunc(flagInsert, (&handleTableCompleter{onlySQL: true, handleRequired: true}).complete)
cmd.Flags().StringP(flagActiveSrc, "", "", flagActiveSrcUsage)
_ = cmd.RegisterFlagCompletionFunc(flagActiveSrc, completeHandle(0))
// The driver flag can be used if data is piped to sq over stdin
cmd.Flags().StringP(flagDriver, "", "", flagQueryDriverUsage)
_ = cmd.RegisterFlagCompletionFunc(flagDriver, completeDriverType)
cmd.Flags().StringP(flagSrcOptions, "", "", flagQuerySrcOptionsUsage)
}

View File

@ -16,7 +16,7 @@ import (
"github.com/neilotoole/sq/libsq/core/stringz"
)
func newSQLCmd() (*cobra.Command, runFunc) {
func newSQLCmd() *cobra.Command {
cmd := &cobra.Command{
Use: "sql QUERY|STMT",
Short: "Execute DB-native SQL query or statement",
@ -28,17 +28,18 @@ If flag --query is set, sq will run the input as a query
(SELECT) and return the query rows. If flag --exec is set,
sq will execute the input and return the result. If neither
flag is set, sq attempts to determine the appropriate mode.`,
RunE: execSQL,
Example: ` # Select from active source
sq sql 'SELECT * FROM actor'
$ sq sql 'SELECT * FROM actor'
# Select from a specified source
sq sql --src=@sakila_pg12 'SELECT * FROM actor'
$ sq sql --src=@sakila_pg12 'SELECT * FROM actor'
# Drop table @sakila_pg12.actor
sq sql --exec --src=@sakila_pg12 'DROP TABLE actor'
$ sq sql --exec --src=@sakila_pg12 'DROP TABLE actor'
# Select from active source and write results to @sakila_ms17.actor
sq sql 'SELECT * FROM actor' --insert=@sakila_ms17.actor`,
$ sq sql 'SELECT * FROM actor' --insert=@sakila_ms17.actor`,
}
addQueryCmdFlags(cmd)
@ -48,10 +49,11 @@ flag is set, sq attempts to determine the appropriate mode.`,
// User explicitly wants to execute the SQL using sql.DB.Exec
cmd.Flags().Bool(flagSQLExec, false, flagSQLExecUsage)
return cmd, execSQL
return cmd
}
func execSQL(rc *RunContext, cmd *cobra.Command, args []string) error {
func execSQL(cmd *cobra.Command, args []string) error {
rc := RunContextFrom(cmd.Context())
switch len(args) {
default:
// FIXME: we should allow multiple args and concat them
@ -64,7 +66,7 @@ func execSQL(rc *RunContext, cmd *cobra.Command, args []string) error {
}
}
err := determineSources(rc)
err := determineSources(cmd.Context(), rc)
if err != nil {
return err
}
@ -77,7 +79,7 @@ func execSQL(rc *RunContext, cmd *cobra.Command, args []string) error {
if !cmdFlagChanged(cmd, flagInsert) {
// The user didn't specify the --insert=@src.tbl flag,
// so we just want to print the records.
return execSQLPrint(rc, activeSrc)
return execSQLPrint(cmd.Context(), rc, activeSrc)
}
// Instead of printing the records, they will be
@ -97,20 +99,20 @@ func execSQL(rc *RunContext, cmd *cobra.Command, args []string) error {
return err
}
return execSQLInsert(rc, activeSrc, destSrc, destTbl)
return execSQLInsert(cmd.Context(), rc, activeSrc, destSrc, destTbl)
}
// execSQLPrint executes the SQL and prints resulting records
// to the configured writer.
func execSQLPrint(rc *RunContext, fromSrc *source.Source) error {
func execSQLPrint(ctx context.Context, rc *RunContext, fromSrc *source.Source) error {
args := rc.Args
dbase, err := rc.databases.Open(rc.Context, fromSrc)
dbase, err := rc.databases.Open(ctx, fromSrc)
if err != nil {
return err
}
recw := output.NewRecordWriterAdapter(rc.writers.recordw)
err = libsq.QuerySQL(rc.Context, rc.Log, dbase, recw, args[0])
err = libsq.QuerySQL(ctx, rc.Log, dbase, recw, args[0])
if err != nil {
return err
}
@ -120,10 +122,10 @@ func execSQLPrint(rc *RunContext, fromSrc *source.Source) error {
// execSQLInsert executes the SQL and inserts resulting records
// into destTbl in destSrc.
func execSQLInsert(rc *RunContext, fromSrc, destSrc *source.Source, destTbl string) error {
func execSQLInsert(ctx context.Context, rc *RunContext, fromSrc, destSrc *source.Source, destTbl string) error {
args := rc.Args
dbases := rc.databases
ctx, cancelFn := context.WithCancel(rc.Context)
ctx, cancelFn := context.WithCancel(ctx)
defer cancelFn()
fromDB, err := dbases.Open(ctx, fromSrc)

View File

@ -2,35 +2,29 @@ package cli
import (
"github.com/spf13/cobra"
"github.com/neilotoole/sq/libsq/core/errz"
)
func newSrcCommand() (*cobra.Command, runFunc) {
func newSrcCommand() *cobra.Command {
cmd := &cobra.Command{
Use: "src [@HANDLE]",
Use: "src [@HANDLE]",
RunE: execSrc,
Example: ` # get active data source
sq src
$ sq src
# set @my1 as active data source
sq src @my1`,
// RunE: execSrc,
Short: "Get or set active data source",
$ sq src @my1`,
Args: cobra.MaximumNArgs(1),
ValidArgsFunction: completeHandle(1),
Short: "Get or set active data source",
Long: `Get or set active data source. If no argument provided, get the active data
source. Otherwise, set @HANDLE as the active data source.`,
}
//cmd.Flags().BoolP(flagJSON, flagJSONShort, false, flagJSONUsage)
//cmd.Flags().BoolP(flagTable, flagTableShort, false, flagTableUsage)
//cmd.Flags().BoolP(flagHeader, flagHeaderShort, false, flagHeaderUsage)
return cmd, execSrc
return cmd
}
func execSrc(rc *RunContext, cmd *cobra.Command, args []string) error {
if len(args) > 1 {
return errz.Errorf(msgInvalidArgs)
}
func execSrc(cmd *cobra.Command, args []string) error {
rc := RunContextFrom(cmd.Context())
cfg := rc.Config
if len(args) == 0 {

View File

@ -11,52 +11,55 @@ import (
"github.com/neilotoole/sq/libsq/source"
)
func newTblCmd() (*cobra.Command, runFunc) {
func newTblCmd() *cobra.Command {
cmd := &cobra.Command{
Use: "tbl",
Short: "Common actions on tables (copy, truncate, drop)",
RunE: func(cmd *cobra.Command, args []string) error {
return cmd.Help()
},
Example: ` # Copy table actor to new table actor2
sq tbl copy @sakila_sl3.actor actor2
$ sq tbl copy @sakila_sl3.actor actor2
# Truncate table actor2
sq tbl truncate @sakila_sl3.actor2
$ sq tbl truncate @sakila_sl3.actor2
# Drop table actor2
sq tbl drop @sakila_sl3.actor2`,
$ sq tbl drop @sakila_sl3.actor2`,
}
return cmd, func(rc *RunContext, cmd *cobra.Command, args []string) error {
return cmd.Help()
}
return cmd
}
func newTblCopyCmd() (*cobra.Command, runFunc) {
func newTblCopyCmd() *cobra.Command {
cmd := &cobra.Command{
Use: "copy @HANDLE.TABLE NEWTABLE",
Short: "Make a copy of a table",
Long: `Make a copy of a table in the same database. The table data is also copied by default.`,
Example: ` # Copy table "actor" in @sakila_sl3" to new table "actor2"
sq tbl copy @sakila_sl3.actor .actor2
Use: "copy @HANDLE.TABLE NEWTABLE",
Short: "Make a copy of a table",
Long: `Make a copy of a table in the same database. The table data is also copied by default.`,
ValidArgsFunction: completeTblCopy,
RunE: execTblCopy,
Example: ` # Copy table "actor" in @sakila_sl3 to new table "actor2"
$ sq tbl copy @sakila_sl3.actor .actor2
# Copy table "actor" in active src to table "actor2"
sq tbl copy .actor .actor2
$ sq tbl copy .actor .actor2
# Copy table "actor" in active src to generated table name (e.g. "@sakila_sl3.actor_copy__1ae03e9b")
sq tbl copy .actor
$ sq tbl copy .actor
# Copy table structure, but don't copy table data
sq tbl copy --data=false .actor
$ sq tbl copy --data=false .actor
`,
}
cmd.Flags().BoolP(flagJSON, flagJSONShort, false, flagJSONUsage)
cmd.Flags().Bool(flagTblData, true, flagTblDataUsage)
return cmd, execTblCopy
return cmd
}
func execTblCopy(rc *RunContext, cmd *cobra.Command, args []string) error {
func execTblCopy(cmd *cobra.Command, args []string) error {
rc := RunContextFrom(cmd.Context())
if len(args) == 0 || len(args) > 2 {
return errz.New("one or two table args required")
}
@ -108,12 +111,12 @@ func execTblCopy(rc *RunContext, cmd *cobra.Command, args []string) error {
}
var dbase driver.Database
dbase, err = rc.databases.Open(rc.Context, tblHandles[0].src)
dbase, err = rc.databases.Open(cmd.Context(), tblHandles[0].src)
if err != nil {
return err
}
copied, err := sqlDrvr.CopyTable(rc.Context, dbase.DB(), tblHandles[0].tbl, tblHandles[1].tbl, copyData)
copied, err := sqlDrvr.CopyTable(cmd.Context(), dbase.DB(), tblHandles[0].tbl, tblHandles[1].tbl, copyData)
if err != nil {
return errz.Wrapf(err, "failed tbl copy %s.%s --> %s.%s",
tblHandles[0].handle, tblHandles[0].tbl,
@ -137,28 +140,35 @@ func execTblCopy(rc *RunContext, cmd *cobra.Command, args []string) error {
return nil
}
func newTblTruncateCmd() (*cobra.Command, runFunc) {
func newTblTruncateCmd() *cobra.Command {
cmd := &cobra.Command{
Use: "truncate @HANDLE.TABLE",
Use: "truncate @HANDLE.TABLE|.TABLE",
Short: "Truncate one or more tables",
Long: `Truncate one or more tables. Note that this command
only applies to SQL sources.`,
RunE: execTblTruncate,
ValidArgsFunction: (&handleTableCompleter{
onlySQL: true,
}).complete,
Example: ` # truncate table "actor"" in source @sakila_sl3
sq tbl truncate @sakila_sl3.actor
$ sq tbl truncate @sakila_sl3.actor
# truncate table "payment"" in the active src
sq tbl truncate .payment
$ sq tbl truncate .payment
# truncate multiple tables
sq tbl truncate .payment @sakila_sl3.actor
$ sq tbl truncate .payment @sakila_sl3.actor
`,
}
cmd.Flags().BoolP(flagJSON, flagJSONShort, false, flagJSONUsage)
cmd.Flags().BoolP(flagTable, flagTableShort, false, flagTableUsage)
return cmd, execTblTruncate
return cmd
}
func execTblTruncate(rc *RunContext, cmd *cobra.Command, args []string) (err error) {
func execTblTruncate(cmd *cobra.Command, args []string) (err error) {
rc := RunContextFrom(cmd.Context())
var tblHandles []tblHandle
tblHandles, err = parseTableHandleArgs(rc.registry, rc.Config.Sources, args)
if err != nil {
@ -167,7 +177,7 @@ func execTblTruncate(rc *RunContext, cmd *cobra.Command, args []string) (err err
for _, tblH := range tblHandles {
var affected int64
affected, err = tblH.drvr.Truncate(rc.Context, tblH.src, tblH.tbl, true)
affected, err = tblH.drvr.Truncate(cmd.Context(), tblH.src, tblH.tbl, true)
if err != nil {
return err
}
@ -180,25 +190,32 @@ func execTblTruncate(rc *RunContext, cmd *cobra.Command, args []string) (err err
return nil
}
func newTblDropCmd() (*cobra.Command, runFunc) {
func newTblDropCmd() *cobra.Command {
cmd := &cobra.Command{
Use: "drop @HANDLE.TABLE",
Short: "Drop one or more tables",
Long: `Drop one or more tables. Note that this command
only applies to SQL sources.`,
RunE: execTblDrop,
ValidArgsFunction: (&handleTableCompleter{
onlySQL: true,
}).complete,
Example: `# drop table "actor" in src @sakila_sl3
sq tbl drop @sakila_sl3.actor
$ sq tbl drop @sakila_sl3.actor
# drop table "payment"" in the active src
sq tbl drop .payment
$ sq tbl drop .payment
# drop multiple tables
sq drop .payment @sakila_sl3.actor
$ sq drop .payment @sakila_sl3.actor
`,
}
return cmd, execTblDrop
return cmd
}
func execTblDrop(rc *RunContext, cmd *cobra.Command, args []string) (err error) {
func execTblDrop(cmd *cobra.Command, args []string) (err error) {
rc := RunContextFrom(cmd.Context())
var tblHandles []tblHandle
tblHandles, err = parseTableHandleArgs(rc.registry, rc.Config.Sources, args)
if err != nil {
@ -212,11 +229,11 @@ func execTblDrop(rc *RunContext, cmd *cobra.Command, args []string) (err error)
}
var dbase driver.Database
dbase, err = rc.databases.Open(rc.Context, tblH.src)
dbase, err = rc.databases.Open(cmd.Context(), tblH.src)
if err != nil {
return err
}
err = sqlDrvr.DropTable(rc.Context, dbase.DB(), tblH.tbl, false)
err = sqlDrvr.DropTable(cmd.Context(), dbase.DB(), tblH.tbl, false)
if err != nil {
return err
}

View File

@ -8,16 +8,18 @@ import (
"github.com/neilotoole/sq/cli/buildinfo"
)
func newVersionCmd() (*cobra.Command, runFunc) {
func newVersionCmd() *cobra.Command {
cmd := &cobra.Command{
Use: "version",
Short: "Print sq version",
RunE: execVersion,
}
return cmd, execVersion
return cmd
}
func execVersion(rc *RunContext, cmd *cobra.Command, args []string) error {
func execVersion(cmd *cobra.Command, args []string) error {
rc := RunContextFrom(cmd.Context())
rc.writers.fmt.Hilite.Fprintf(rc.Out, "sq %s", buildinfo.Version)
if len(buildinfo.Commit) > 0 {

362
cli/completion.go Normal file
View File

@ -0,0 +1,362 @@
package cli
import (
"context"
"strings"
"github.com/spf13/cobra"
"github.com/neilotoole/sq/libsq/source"
)
// completionFunc is a shell completion function.
type completionFunc func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective)
var (
_ completionFunc = completeDriverType
_ completionFunc = completeSLQ
_ completionFunc = new(handleTableCompleter).complete
)
// completeHandle is a completionFunc that suggests handles.
// The max arg is the maximum number of completions. Set to 0
// for no limit.
func completeHandle(max int) completionFunc {
return func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
if max > 0 && len(args) >= max {
return nil, cobra.ShellCompDirectiveNoFileComp
}
rc := RunContextFrom(cmd.Context())
handles := rc.Config.Sources.Handles()
return handles, cobra.ShellCompDirectiveNoFileComp
}
}
// completeSLQ is a completionFunc that completes SLQ queries.
// The completion functionality is rudimentary: it only
// completes the "table select" segment (that is, the @HANDLE.NAME)
// segment.
func completeSLQ(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
if len(args) != 0 {
return nil, cobra.ShellCompDirectiveError
}
c := &handleTableCompleter{}
return c.complete(cmd, args, toComplete)
}
// completeDriverType is a completionFunc that suggests drivers.
func completeDriverType(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
rc := RunContextFrom(cmd.Context())
if rc.databases == nil {
err := rc.init()
if err != nil {
rc.Log.Error(err)
return nil, cobra.ShellCompDirectiveError
}
}
drivers := rc.registry.Drivers()
types := make([]string, len(drivers))
for i, driver := range rc.registry.Drivers() {
types[i] = string(driver.DriverMetadata().Type)
}
return types, cobra.ShellCompDirectiveNoFileComp
}
// completeTblCopy is a completionFunc for the "tbl copy" command.
func completeTblCopy(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
// Example invocation:
//
// sq tbl copy @sakila_sl3.actor .new_table
//
// Note that the second arg can only be a table name (and
// not a @HANDLE.TABLE), and it must also be a _new_ table
// (because we can't copy over an existing table), thus
// we only suggest "." for the second arg, forcing the user
// to supply the rest of that new table name.
switch len(args) {
case 0:
c := &handleTableCompleter{onlySQL: true}
return c.complete(cmd, args, toComplete)
case 1:
return []string{"."}, cobra.ShellCompDirectiveNoFileComp | cobra.ShellCompDirectiveNoSpace
default:
return nil, cobra.ShellCompDirectiveError
}
}
// handleTableCompleter encapsulates completion of a handle
// ("@sakila_sl3"), table (".actor"), or @HANDLE.TABLE
// ("@sakila_sl3.actor"). Its complete method is a completionFunc.
type handleTableCompleter struct {
// onlySQL, when true, filters out non-SQL sources.
onlySQL bool
// handleRequired, when true, means that only @HANDLE.TABLE
// suggestions are offered. That is, naked .TABLE suggestions
// will not be offered.
handleRequired bool
// max indicates the maximum number of completions
// to offer. Use 0 to indicate no limit. Frequently this
// is set to 1 to if the command accepts only one argument.
max int
}
// complete is the completionFunc for handleTableCompleter.
func (c *handleTableCompleter) complete(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
rc := RunContextFrom(cmd.Context())
if err := rc.init(); err != nil {
rc.Log.Error(err)
return nil, cobra.ShellCompDirectiveError
}
// We don't want the user to wait around forever for
// shell completion, so we set a timeout. Typically
// this is something like 500ms.
ctx, cancelFn := context.WithTimeout(cmd.Context(), rc.Config.Defaults.ShellCompletionTimeout)
defer cancelFn()
if c.max > 0 && len(args) >= c.max {
return nil, cobra.ShellCompDirectiveNoFileComp
}
if toComplete == "" {
if c.handleRequired {
return c.completeHandle(ctx, rc, args, toComplete)
}
return c.completeEither(ctx, rc, args, toComplete)
}
// There's some input. We expect the input to be of the
// the form "@handle" or ".table". That is, the input should
// start with either '@' or '.'.
switch toComplete[0] {
default:
// User input was something other than '@' or '.'
return nil, cobra.ShellCompDirectiveError
case '@':
return c.completeHandle(ctx, rc, args, toComplete)
case '.':
if c.handleRequired {
return nil, cobra.ShellCompDirectiveError
}
return c.completeTableOnly(ctx, rc, args, toComplete)
}
}
// completeTableOnly returns suggestions given input beginning with
// a period. Effectively this is completion for tables in the
// active src.
func (c *handleTableCompleter) completeTableOnly(ctx context.Context, rc *RunContext, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
activeSrc := rc.Config.Sources.Active()
if activeSrc == nil {
rc.Log.Error("Active source is nil")
return nil, cobra.ShellCompDirectiveError
}
if c.onlySQL {
isSQL, err := handleIsSQLDriver(rc, activeSrc.Handle)
if err != nil {
rc.Log.Error(err)
return nil, cobra.ShellCompDirectiveError
}
if !isSQL {
return nil, cobra.ShellCompDirectiveNoFileComp
}
}
tables, err := getTableNamesForHandle(ctx, rc, activeSrc.Handle)
if err != nil {
rc.Log.Error(err)
return nil, cobra.ShellCompDirectiveError
}
var suggestions []string
for _, table := range tables {
if strings.HasPrefix(table, toComplete[1:]) {
suggestions = append(suggestions, "."+table)
}
}
return suggestions, cobra.ShellCompDirectiveNoFileComp
}
// completeHandle returns suggestions given input beginning with
// a '@'. The returned suggestions could be @HANDLE, or @HANDLE.TABLE.
func (c *handleTableCompleter) completeHandle(ctx context.Context, rc *RunContext, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
// We're dealing with a handle.
// But we could be dealing with just the handle ("@sakila_sl3")
// or a @HANDLE.TABLE ("@sakila_sl3.actor").
if strings.ContainsRune(toComplete, '.') {
if strings.Count(toComplete, ".") > 1 {
// Can only have one period
return nil, cobra.ShellCompDirectiveError
}
// It's a handle with a full handle and at least a
// partial table name, such as "@sakila_sl3.fil"
handle, partialTbl, err := source.ParseTableHandle(strings.TrimSuffix(toComplete, "."))
if err != nil {
rc.Log.Error(err)
return nil, cobra.ShellCompDirectiveError
}
if c.onlySQL {
isSQL, err := handleIsSQLDriver(rc, handle)
if err != nil {
rc.Log.Error(err)
return nil, cobra.ShellCompDirectiveError
}
if !isSQL {
return nil, cobra.ShellCompDirectiveNoFileComp
}
}
tables, err := getTableNamesForHandle(ctx, rc, handle)
if err != nil {
rc.Log.Error(err)
return nil, cobra.ShellCompDirectiveError
}
var suggestions []string
for _, table := range tables {
if strings.HasPrefix(table, partialTbl) {
suggestions = append(suggestions, handle+"."+table)
}
}
return suggestions, cobra.ShellCompDirectiveNoFileComp
}
handles := rc.Config.Sources.Handles()
// Else, we're dealing with just a handle so far
var matchingHandles []string
for _, handle := range handles {
if strings.HasPrefix(handle, toComplete) {
if c.onlySQL {
isSQL, err := handleIsSQLDriver(rc, handle)
if err != nil {
rc.Log.Error(err)
return nil, cobra.ShellCompDirectiveError
}
if !isSQL {
continue
}
}
matchingHandles = append(matchingHandles, handle)
}
}
switch len(matchingHandles) {
default:
return matchingHandles, cobra.ShellCompDirectiveNoFileComp | cobra.ShellCompDirectiveNoSpace
case 0:
return nil, cobra.ShellCompDirectiveNoFileComp
case 1:
// Only one handle match, so we will present that complete
// handle, plus a suggestion (@HANDLE.TABLE) for each of the tables
// for that handle
}
tables, err := getTableNamesForHandle(ctx, rc, matchingHandles[0])
if err != nil {
rc.Log.Error(err)
return nil, cobra.ShellCompDirectiveError
}
suggestions := []string{matchingHandles[0]}
for _, table := range tables {
suggestions = append(suggestions, matchingHandles[0]+"."+table)
}
return suggestions, cobra.ShellCompDirectiveNoFileComp
}
func (c *handleTableCompleter) completeEither(ctx context.Context, rc *RunContext, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
// There's no input yet.
// Therefore we want to return a union of all handles
// plus the tables from the active source.
activeSrc := rc.Config.Sources.Active()
if activeSrc == nil {
rc.Log.Error("Active source is nil")
return nil, cobra.ShellCompDirectiveError
}
var activeSrcTables []string
isSQL, err := handleIsSQLDriver(rc, activeSrc.Handle)
if err != nil {
rc.Log.Error(err)
return nil, cobra.ShellCompDirectiveError
}
if !c.onlySQL || isSQL {
activeSrcTables, err = getTableNamesForHandle(ctx, rc, activeSrc.Handle)
if err != nil {
rc.Log.Error(err)
return nil, cobra.ShellCompDirectiveError
}
}
var suggestions []string
for _, table := range activeSrcTables {
suggestions = append(suggestions, "."+table)
}
for _, src := range rc.Config.Sources.Items() {
if c.onlySQL {
isSQL, err = handleIsSQLDriver(rc, src.Handle)
if err != nil {
rc.Log.Error(err)
return nil, cobra.ShellCompDirectiveError
}
if !isSQL {
continue
}
}
suggestions = append(suggestions, src.Handle)
}
return suggestions, cobra.ShellCompDirectiveNoFileComp | cobra.ShellCompDirectiveNoSpace
}
func handleIsSQLDriver(rc *RunContext, handle string) (bool, error) {
src, err := rc.Config.Sources.Get(handle)
if err != nil {
return false, err
}
driver, err := rc.registry.DriverFor(src.Type)
if err != nil {
return false, err
}
return driver.DriverMetadata().IsSQL, nil
}
func getTableNamesForHandle(ctx context.Context, rc *RunContext, handle string) ([]string, error) {
src, err := rc.Config.Sources.Get(handle)
if err != nil {
return nil, err
}
db, err := rc.databases.Open(ctx, src)
if err != nil {
return nil, err
}
md, err := db.SourceMetadata(ctx)
if err != nil {
return nil, err
}
return md.TableNames(), nil
}

View File

@ -36,11 +36,21 @@ type Ext struct {
UserDrivers []*userdriver.DriverDef `yaml:"user_drivers" json:"user_drivers"`
}
// Defaults contains sq default values.
// Defaults contains default config values.
type Defaults struct {
Timeout time.Duration `yaml:"timeout" json:"timeout"`
Format Format `yaml:"output_format" json:"output_format"`
Header bool `yaml:"output_header" json:"output_header"`
// Format is the default output format: json, table, etc.
Format Format `yaml:"output_format" json:"output_format"`
// Header determines if a header should be printed (if relevant
// for the output format).
Header bool `yaml:"output_header" json:"output_header"`
// PingTimeout is the allowed time for a ping.
PingTimeout time.Duration `yaml:"ping_timeout" json:"ping_timeout"`
// ShellCompletionTimeout is the time allowed for the shell
// completion callback to execute.
ShellCompletionTimeout time.Duration `yaml:"shell_completion_timeout" json:"shell_completion_timeout"`
}
// New returns a config instance with default options set.
@ -66,11 +76,15 @@ func initCfg(cfg *Config) {
cfg.Defaults.Format = FormatTable
}
if cfg.Defaults.Timeout == 0 {
if cfg.Defaults.PingTimeout == 0 {
// Probably should be setting this in the New function,
// but we haven't yet defined cli's behavior wrt
// a zero timeout. Does it mean no timeout?
cfg.Defaults.Timeout = 10 * time.Second
cfg.Defaults.PingTimeout = 10 * time.Second
}
if cfg.Defaults.ShellCompletionTimeout == 0 {
cfg.Defaults.ShellCompletionTimeout = time.Millisecond * 500
}
}

View File

@ -9,6 +9,7 @@ import (
"github.com/stretchr/testify/require"
"github.com/neilotoole/sq/cli/config"
"github.com/neilotoole/sq/testh"
"github.com/neilotoole/sq/testh/proj"
)
@ -70,14 +71,21 @@ func TestFileStore_Load(t *testing.T) {
fs := &config.YAMLFileStore{HookLoad: hookExpand}
for _, match := range good {
fs.Path = match
_, err = fs.Load()
require.NoError(t, err, match)
match := match
t.Run(testh.Name(match), func(t *testing.T) {
fs.Path = match
_, err = fs.Load()
require.NoError(t, err, match)
})
}
for _, match := range bad {
fs.Path = match
_, err = fs.Load()
require.Error(t, err, match)
match := match
t.Run(testh.Name(match), func(t *testing.T) {
fs.Path = match
_, err = fs.Load()
require.Error(t, err, match)
})
}
}

View File

@ -1,2 +1,2 @@
defaults:
timeout: not_a_duration
ping_timeout: not_a_duration

2
cli/config/testdata/bad.08.sq.yml vendored Normal file
View File

@ -0,0 +1,2 @@
defaults:
shell_completion_timeout: not_a_duration

View File

@ -1,5 +1,6 @@
defaults:
timeout: 10s
ping_timeout: 10s
shell_completion_timeout: 1s
output_format: table
output_header: true
sources:

View File

@ -96,7 +96,7 @@ func (w *mdWriter) SourceMetadata(meta *source.Metadata) error {
meta.Name,
stringz.ByteSized(meta.Size, 1, ""),
fmt.Sprintf("%d", len(meta.Tables)),
meta.Location,
source.RedactLocation(meta.Location),
}
} else {
headers = []string{"HANDLE", "DRIVER", "NAME", "FQ NAME", "SIZE", "TABLES", "LOCATION"}
@ -110,7 +110,7 @@ func (w *mdWriter) SourceMetadata(meta *source.Metadata) error {
meta.FQName,
stringz.ByteSized(meta.Size, 1, ""),
fmt.Sprintf("%d", len(meta.Tables)),
meta.Location,
source.RedactLocation(meta.Location),
}
}

View File

@ -29,7 +29,6 @@ func newTestRunCtx(log lg.Log) (rc *cli.RunContext, out, errOut *bytes.Buffer) {
errOut = &bytes.Buffer{}
rc = &cli.RunContext{
Context: context.Background(),
Stdin: os.Stdin,
Out: out,
ErrOut: errOut,
@ -102,11 +101,7 @@ func (ru *run) exec(args ...string) error {
return err
}
if len(args) > 0 && args[0] != "sq" {
args = append([]string{"sq"}, args...)
}
execErr := cli.ExecuteWith(ru.rc, args)
execErr := cli.ExecuteWith(context.Background(), ru.rc, args)
if !ru.hushOutput {
// We log sq's output now (before calling rc.Close) because

View File

@ -1,6 +1,7 @@
package cli
import (
"context"
"strings"
"github.com/neilotoole/lg"
@ -17,7 +18,7 @@ import (
// mutate rc.Config.Sources as necessary. If no error
// is returned, it is guaranteed that there's an active
// source on the source set.
func determineSources(rc *RunContext) error {
func determineSources(ctx context.Context, rc *RunContext) error {
cmd, srcs := rc.Cmd, rc.Config.Sources
activeSrc, err := activeSrcFromFlagsOrConfig(cmd, srcs)
if err != nil {
@ -26,7 +27,7 @@ func determineSources(rc *RunContext) error {
// Note: ^ activeSrc could still be nil
// check if there's input on stdin
stdinSrc, err := checkStdinSource(rc)
stdinSrc, err := checkStdinSource(ctx, rc)
if err != nil {
return err
}
@ -95,7 +96,7 @@ func activeSrcFromFlagsOrConfig(cmd *cobra.Command, srcs *source.Set) (*source.S
// If there is, that pipe is inspected, and if it has recognizable
// input, a new source instance with handle @stdin is constructed
// and returned.
func checkStdinSource(rc *RunContext) (*source.Source, error) {
func checkStdinSource(ctx context.Context, rc *RunContext) (*source.Source, error) {
cmd := rc.Cmd
f := rc.Stdin
@ -140,7 +141,7 @@ func checkStdinSource(rc *RunContext) (*source.Source, error) {
}
if typ == source.TypeNone {
typ, err = rc.files.TypeStdin(rc.Context)
typ, err = rc.files.TypeStdin(ctx)
if err != nil {
return nil, err
}

View File

@ -171,7 +171,7 @@ func (d *driveri) CopyTable(ctx context.Context, db sqlz.DB, fromTable, toTable
stmt += " WHERE 0"
}
affected, err := sqlz.ExecResult(ctx, db, stmt)
affected, err := sqlz.ExecAffected(ctx, db, stmt)
if err != nil {
return 0, errz.Err(err)
}
@ -324,7 +324,7 @@ func (d *driveri) Truncate(ctx context.Context, src *source.Source, tbl string,
return 0, errz.Append(err, errz.Err(tx.Rollback()))
}
affected, err = sqlz.ExecResult(ctx, tx, fmt.Sprintf("TRUNCATE TABLE `%s`", tbl))
affected, err = sqlz.ExecAffected(ctx, tx, fmt.Sprintf("TRUNCATE TABLE `%s`", tbl))
if err != nil {
return affected, errz.Append(err, errz.Err(tx.Rollback()))
}

View File

@ -243,7 +243,7 @@ func (d *driveri) CopyTable(ctx context.Context, db sqlz.DB, fromTable, toTable
stmt += " WITH NO DATA"
}
affected, err := sqlz.ExecResult(ctx, db, stmt)
affected, err := sqlz.ExecAffected(ctx, db, stmt)
if err != nil {
return 0, errz.Err(err)
}

View File

@ -103,16 +103,27 @@ func (d *driveri) Truncate(ctx context.Context, src *source.Source, tbl string,
return 0, errz.Err(err)
}
affected, err = sqlz.ExecResult(ctx, tx, fmt.Sprintf("DELETE FROM %q", tbl))
affected, err = sqlz.ExecAffected(ctx, tx, fmt.Sprintf("DELETE FROM %q", tbl))
if err != nil {
return affected, errz.Append(err, errz.Err(tx.Rollback()))
}
if reset {
_, err = sqlz.ExecResult(ctx, tx, "UPDATE sqlite_sequence SET seq = 0 WHERE name = ?", tbl)
// First check that the sqlite_sequence table event exists. It
// may not exist if there are no auto-increment columns?
const q = `SELECT COUNT(name) FROM sqlite_master WHERE type='table' AND name='sqlite_sequence'`
var count int64
err = tx.QueryRowContext(ctx, q).Scan(&count)
if err != nil {
return 0, errz.Append(err, errz.Err(tx.Rollback()))
}
if count > 0 {
_, err = tx.ExecContext(ctx, "UPDATE sqlite_sequence SET seq = 0 WHERE name = ?", tbl)
if err != nil {
return 0, errz.Append(err, errz.Err(tx.Rollback()))
}
}
}
return affected, errz.Err(tx.Commit())
@ -188,7 +199,7 @@ func (d *driveri) CopyTable(ctx context.Context, db sqlz.DB, fromTable, toTable
}
stmt := fmt.Sprintf("INSERT INTO %q SELECT * FROM %q", toTable, fromTable)
affected, err := sqlz.ExecResult(ctx, db, stmt)
affected, err := sqlz.ExecAffected(ctx, db, stmt)
if err != nil {
return 0, errz.Err(err)
}

View File

@ -159,7 +159,7 @@ func (d *driveri) Truncate(ctx context.Context, src *source.Source, tbl string,
}
defer d.log.WarnIfFuncError(db.Close)
affected, err = sqlz.ExecResult(ctx, db, fmt.Sprintf("DELETE FROM %q", tbl))
affected, err = sqlz.ExecAffected(ctx, db, fmt.Sprintf("DELETE FROM %q", tbl))
if err != nil {
return affected, errz.Wrapf(err, "truncate: failed to delete from %q", tbl)
}
@ -285,7 +285,7 @@ func (d *driveri) CopyTable(ctx context.Context, db sqlz.DB, fromTable, toTable
stmt = fmt.Sprintf("SELECT TOP(0) * INTO %q FROM %q", toTable, fromTable)
}
affected, err := sqlz.ExecResult(ctx, db, stmt)
affected, err := sqlz.ExecAffected(ctx, db, stmt)
if err != nil {
return 0, errz.Err(err)
}

38
go.mod
View File

@ -1,60 +1,48 @@
module github.com/neilotoole/sq
go 1.14
go 1.15
// Using forked cobra for now because v1.1.3 does not pass Context
// to valid args completion funcs. There's an open PR for
// this: https://github.com/spf13/cobra/pull/1265
replace github.com/spf13/cobra v1.1.3 => github.com/neilotoole/cobra v1.1.4-0.20210220092732-c11dbd416310
require (
github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751 // indirect
github.com/alecthomas/units v0.0.0-20201120081800-1786d5ef83d4 // indirect
github.com/antlr/antlr4 v0.0.0-20191011202612-ad2bd05285ca
github.com/aws/aws-sdk-go v1.12.10 // indirect
github.com/bgentry/go-netrc v0.0.0-20140422174119-9fd32a8b3d3d // indirect
github.com/buger/jsonparser v1.0.0
github.com/armon/consul-api v0.0.0-20180202201655-eb2c6b5be1b6 // indirect
github.com/c2h5oh/datasize v0.0.0-20170519143321-54516c931ae9
github.com/coreos/go-etcd v2.0.0+incompatible // indirect
github.com/cpuguy83/go-md2man v1.0.10 // indirect
github.com/denisenkom/go-mssqldb v0.0.0-20200620013148-b91950f658ec
github.com/djherbis/fscache v0.10.1
github.com/dustin/go-humanize v1.0.0 // indirect
github.com/emirpasic/gods v1.9.0
github.com/fatih/color v1.9.0
github.com/go-ini/ini v1.30.0 // indirect
github.com/go-sql-driver/mysql v1.5.0
github.com/google/go-querystring v0.0.0-20170111101155-53e6ce116135 // indirect
github.com/google/uuid v1.1.1
github.com/h2non/filetype v1.1.0
github.com/hashicorp/go-cleanhttp v0.0.0-20170211013415-3573b8b52aa7 // indirect
github.com/hashicorp/go-getter v0.0.0-20171007181130-2f449c791e6a
github.com/hashicorp/go-version v0.0.0-20170914154128-fc61389e27c7 // indirect
github.com/jackc/pgconn v1.5.0
github.com/jackc/pgx/v4 v4.6.0
github.com/jmespath/go-jmespath v0.0.0-20160202185014-0b12d6b521d8 // indirect
github.com/jondot/goweight v1.0.5 // indirect
github.com/kr/pretty v0.2.1 // indirect
github.com/kr/text v0.2.0 // indirect
github.com/magefile/mage v1.9.0
github.com/mattn/go-colorable v0.1.4
github.com/mattn/go-isatty v0.0.12
github.com/mattn/go-runewidth v0.0.4
github.com/mattn/go-sqlite3 v2.0.3+incompatible
github.com/mattn/go-zglob v0.0.3 // indirect
github.com/mitchellh/go-homedir v1.1.0
github.com/mitchellh/go-testing-interface v0.0.0-20171004221916-a61a99592b77 // indirect
github.com/neilotoole/errgroup v0.1.5
github.com/neilotoole/lg v0.3.0
github.com/nlopes/slack v0.1.0
github.com/pkg/errors v0.9.1
github.com/ryboe/q v1.0.12
github.com/satori/go.uuid v1.2.0
github.com/segmentio/encoding v0.1.14
github.com/shopspring/decimal v0.0.0-20180709203117-cd690d0c9e24
github.com/smartystreets/goconvey v0.0.0-20190731233626-505e41936337 // indirect
github.com/spf13/cobra v0.0.5
github.com/spf13/pflag v1.0.3
github.com/spf13/cobra v1.1.3
github.com/spf13/pflag v1.0.5
github.com/stretchr/testify v1.5.1
github.com/tbruyelle/hipchat-go v0.0.0-20160921153256-749fb9e14beb
github.com/tealeg/xlsx/v2 v2.0.1
github.com/testcontainers/testcontainers-go v0.5.0
github.com/thoas/go-funk v0.7.0 // indirect
github.com/ulikunitz/xz v0.5.4 // indirect
github.com/xo/dburl v0.0.0-20200124232849-e9ec94f52bc3
github.com/xordataexchange/crypt v0.0.3-0.20170626215501-b2862e3d0a77 // indirect
go.uber.org/atomic v1.5.0
go.uber.org/multierr v1.4.0
golang.org/x/crypto v0.0.0-20200728195943-123391ffb6de
@ -62,5 +50,5 @@ require (
golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208
gopkg.in/djherbis/atime.v1 v1.0.0 // indirect
gopkg.in/djherbis/stream.v1 v1.3.1 // indirect
gopkg.in/yaml.v2 v2.2.8
gopkg.in/yaml.v2 v2.4.0
)

267
go.sum
View File

@ -1,45 +1,60 @@
cloud.google.com/go v0.26.0 h1:e0WKqKTd5BnrG8aKH3J3h+QvEIQtSUcf2n5UZ5ZgLtQ=
cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
cloud.google.com/go v0.38.0/go.mod h1:990N+gfupTy94rShfmMCWGDn0LpTmnzTp2qbd1dvSRU=
cloud.google.com/go v0.44.1/go.mod h1:iSa0KzasP4Uvy3f1mN/7PiObzGgflwredwwASm/v6AU=
cloud.google.com/go v0.44.2/go.mod h1:60680Gw3Yr4ikxnPRS/oxxkBccT6SA1yMk63TGekxKY=
cloud.google.com/go v0.45.1/go.mod h1:RpBamKRgapWJb87xiFSdk4g1CME7QZg3uwTez+TSTjc=
cloud.google.com/go v0.46.3/go.mod h1:a6bKKbmY7er1mI7TEI4lsAkts/mkhTSZK8w33B4RAg0=
cloud.google.com/go/bigquery v1.0.1/go.mod h1:i/xbL2UlR5RvWAURpBYZTtm/cXjCha9lbfbpx4poX+o=
cloud.google.com/go/datastore v1.0.0/go.mod h1:LXYbyblFSglQ5pkeyhO+Qmw7ukd3C+pD7TKLgZqpHYE=
cloud.google.com/go/firestore v1.1.0/go.mod h1:ulACoGHTpvq5r8rxGJ4ddJZBZqakUQqClKRT5SZwBmk=
cloud.google.com/go/pubsub v1.0.1/go.mod h1:R0Gpsv3s54REJCy4fxDixWD93lHJMoZTyQ2kNxGRt3I=
cloud.google.com/go/storage v1.0.0/go.mod h1:IhtSnM/ZTZV8YYJWCY8RULGVqBDmpoyjwiyrjsg+URw=
dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU=
github.com/Azure/go-ansiterm v0.0.0-20170929234023-d6e3b3328b78 h1:w+iIsaOQNcT7OZ575w+acHgRric5iCyQh+xv+KJ4HB8=
github.com/Azure/go-ansiterm v0.0.0-20170929234023-d6e3b3328b78/go.mod h1:LmzpDX56iTiv29bbRTIsUNlaFfuhWRQBWjQdVyAevI8=
github.com/BurntSushi/toml v0.3.1 h1:WXkYYl6Yr3qBf1K79EBnL4mak0OimBfB0XUf9Vl28OQ=
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo=
github.com/Microsoft/go-winio v0.4.11 h1:zoIOcVf0xPN1tnMVbTtEdI+P8OofVk3NObnwOQ6nK2Q=
github.com/Microsoft/go-winio v0.4.11/go.mod h1:VhR8bwka0BXejwEJY73c50VrPtXAaKcyvVC4A4RozmA=
github.com/Microsoft/hcsshim v0.8.6 h1:ZfF0+zZeYdzMIVMZHKtDKJvLHj76XCuVae/jNkjj0IA=
github.com/Microsoft/hcsshim v0.8.6/go.mod h1:Op3hHsoHPAvb6lceZHDtd9OkTew38wNoXnJs8iY7rUg=
github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc h1:cAKDfWh5VpdgMhJosfJnn5/FoN2SRZ4p7fJNX58YPaU=
github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU=
github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751 h1:JYp7IbQjafoB+tBA3gMyHYHrpOtNuDiK/uB5uXxq5wM=
github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf h1:qet1QNfXsQxTZqLG4oE62mJzwPIB8+Tee4RNCL9ulrY=
github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0=
github.com/alecthomas/units v0.0.0-20201120081800-1786d5ef83d4 h1:EBTWhcAX7rNQ80RLwLCpHZBBrJuzallFHnF+yMXo928=
github.com/alecthomas/units v0.0.0-20201120081800-1786d5ef83d4/go.mod h1:OMCwj8VM1Kc9e19TLln2VL61YJF0x1XFtfdL4JdbSyE=
github.com/antlr/antlr4 v0.0.0-20191011202612-ad2bd05285ca h1:QHbltbNkVcw97h4zA/L8gA4o3dJiFvBZ0gyZHrYXHbs=
github.com/antlr/antlr4 v0.0.0-20191011202612-ad2bd05285ca/go.mod h1:T7PbCXFs94rrTttyxjbyT5+/1V8T2TYDejxUfHJjw1Y=
github.com/armon/circbuf v0.0.0-20150827004946-bbbad097214e/go.mod h1:3U/XgcO3hCbHZ8TKRvWD2dDTCfh9M9ya+I9JpbB7O8o=
github.com/armon/consul-api v0.0.0-20180202201655-eb2c6b5be1b6/go.mod h1:grANhF5doyWs3UAsr3K4I6qtAmlQcZDesFNEHPZAzj8=
github.com/aws/aws-sdk-go v1.12.10 h1:ihg0UOujHVcFciyc6zs/q5VLhoG1K+oDLqgpCxkAh04=
github.com/aws/aws-sdk-go v1.12.10/go.mod h1:ZRmQr0FajVIyZ4ZzBYKG5P3ZqPz9IHG41ZoMu1ADI3k=
github.com/bgentry/go-netrc v0.0.0-20140422174119-9fd32a8b3d3d h1:xDfNPAt8lFiC1UJrqV3uuy861HCTo708pDMbjHHdCas=
github.com/bgentry/go-netrc v0.0.0-20140422174119-9fd32a8b3d3d/go.mod h1:6QX/PXZ00z/TKoufEY6K/a0k6AhaJrQKdFe6OfVXsa4=
github.com/buger/jsonparser v1.0.0 h1:etJTGF5ESxjI0Ic2UaLQs2LQQpa8G9ykQScukbh4L8A=
github.com/buger/jsonparser v1.0.0/go.mod h1:tgcrVJ81GPSF0mz+0nu1Xaz0fazGPrmmJfJtxjbHhUQ=
github.com/armon/go-metrics v0.0.0-20180917152333-f0300d1749da/go.mod h1:Q73ZrmVTwzkszR9V5SSuryQ31EELlFMUz1kKyl939pY=
github.com/armon/go-radix v0.0.0-20180808171621-7fddfc383310/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8=
github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q=
github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8=
github.com/bgentry/speakeasy v0.1.0/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kBD4zp0CCIs=
github.com/bketelsen/crypt v0.0.3-0.20200106085610-5cbc8cc4026c/go.mod h1:MKsuJmJgSg28kpZDP6UIiPt0e0Oz0kqKNGyRaWEPv84=
github.com/c2h5oh/datasize v0.0.0-20170519143321-54516c931ae9 h1:N6In6gI4jApKZ6F8QqeVgPti7612P5EdLISBNbjQ8tk=
github.com/c2h5oh/datasize v0.0.0-20170519143321-54516c931ae9/go.mod h1:S/7n9copUssQ56c7aAgHqftWO4LTf4xY6CGWt8Bc+3M=
github.com/cenkalti/backoff v2.2.1+incompatible h1:tNowT99t7UNflLxfYYSlKYsBpXdEet03Pg2g16Swow4=
github.com/cenkalti/backoff v2.2.1+incompatible/go.mod h1:90ReRw6GdpyfrHakVjL/QHaoyV4aDUVVkXQJJJ3NXXM=
github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc=
github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
github.com/cockroachdb/apd v1.1.0 h1:3LFP3629v+1aKXU5Q37mxmRxX/pIu1nijXydLShEq5I=
github.com/cockroachdb/apd v1.1.0/go.mod h1:8Sl8LxpKi29FqWXR16WEFZRNSz3SoPzUzeMeY4+DwBQ=
github.com/containerd/continuity v0.0.0-20190426062206-aaeac12a7ffc h1:TP+534wVlf61smEIq1nwLLAjQVEK2EADoW3CX9AuT+8=
github.com/containerd/continuity v0.0.0-20190426062206-aaeac12a7ffc/go.mod h1:GL3xCUCBDV3CZiTSEKksMWbLE66hEyuu9qyDOOqM47Y=
github.com/coreos/bbolt v1.3.2/go.mod h1:iRUV2dpdMOn7Bo10OQBFzIJO9kkE559Wcmn+qkEiiKk=
github.com/coreos/etcd v3.3.10+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE=
github.com/coreos/etcd v3.3.13+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE=
github.com/coreos/go-etcd v2.0.0+incompatible/go.mod h1:Jez6KQU2B/sWsbdaef3ED8NzMklzPG4d5KIOhIy30Tk=
github.com/coreos/go-semver v0.2.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk=
github.com/coreos/go-semver v0.3.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk=
github.com/coreos/go-systemd v0.0.0-20190321100706-95778dfbb74e/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4=
github.com/coreos/go-systemd v0.0.0-20190719114852-fd7a80b32e1f/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4=
github.com/coreos/pkg v0.0.0-20180928190104-399ea9e2e55f/go.mod h1:E3G3o1h8I7cfcXa63jLwjI0eiQQMgzzUDFVpN/nH/eA=
github.com/cpuguy83/go-md2man v1.0.10/go.mod h1:SmD6nW6nTyfqj6ABTjUi3V3JVMnlJmwcJI5acqYI6dE=
github.com/cpuguy83/go-md2man/v2 v2.0.0/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU=
github.com/creack/pty v1.1.7/go.mod h1:lj5s0c3V2DBrqTV7llrYr5NG6My20zk30Fl46Y7DoTY=
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
@ -47,6 +62,8 @@ github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/denisenkom/go-mssqldb v0.0.0-20200620013148-b91950f658ec h1:NfhRXXFDPxcF5Cwo06DzeIaE7uuJtAUhsDwH3LNsjos=
github.com/denisenkom/go-mssqldb v0.0.0-20200620013148-b91950f658ec/go.mod h1:xbL0rPBG9cCiLr28tMa8zpbdarY27NDyej4t/EjAShU=
github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ=
github.com/dgryski/go-sip13 v0.0.0-20181026042036-e10d5fee7954/go.mod h1:vAd38F8PWV+bWy6jNmig1y/TA+kYO4g3RSRF0IAv0no=
github.com/djherbis/fscache v0.10.1 h1:hDv+RGyvD+UDKyRYuLoVNbuRTnf2SrA2K3VyR1br9lk=
github.com/djherbis/fscache v0.10.1/go.mod h1:yyPYtkNnnPXsW+81lAcQS6yab3G2CRfnPLotBvtbf0c=
github.com/docker/distribution v2.7.1-0.20190205005809-0d3efadf0154+incompatible h1:dvc1KSkIYTVjZgHf/CTC2diTYC8PzhaA5sFISRfNVrE=
@ -57,22 +74,22 @@ github.com/docker/go-connections v0.4.0 h1:El9xVISelRB7BuFusrZozjnkIM5YnzCViNKoh
github.com/docker/go-connections v0.4.0/go.mod h1:Gbd7IOopHjR8Iph03tsViu4nIes5XhDvyHbTtUxmeec=
github.com/docker/go-units v0.3.3 h1:Xk8S3Xj5sLGlG5g67hJmYMmUgXv5N4PhkjJHHqrwnTk=
github.com/docker/go-units v0.3.3/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk=
github.com/dustin/go-humanize v0.0.0-20180713052910-9f541cc9db5d h1:lDrio3iIdNb0Gw9CgH7cQF+iuB5mOOjdJ9ERNJCBgb4=
github.com/dustin/go-humanize v0.0.0-20180713052910-9f541cc9db5d/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk=
github.com/dustin/go-humanize v1.0.0 h1:VSnTsYCnlFHaM2/igO1h6X3HA71jcobQuxemgkq4zYo=
github.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk=
github.com/emirpasic/gods v1.9.0 h1:rUF4PuzEjMChMiNsVjdI+SyLu7rEqpQ5reNFnhC7oFo=
github.com/emirpasic/gods v1.9.0/go.mod h1:YfzfFFoVP/catgzJb4IKIqXjX78Ha8FMSDh3ymbK86o=
github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4=
github.com/fatih/color v1.9.0 h1:8xPHl4/q1VyqGIPif1F+1V3Y3lSmrq01EabUW3CoW5s=
github.com/fatih/color v1.9.0/go.mod h1:eQcE1qtQxscV5RaZvpXrrb8Drkc3/DdQ+uUYCNjL+zU=
github.com/frankban/quicktest v1.5.0 h1:Tb4jWdSpdjKzTUicPnY61PZxKbDoGa7ABbrReT3gQVY=
github.com/frankban/quicktest v1.5.0/go.mod h1:jaStnuzAqU1AJdCO0l53JDCJrVDKcS03DbaAcR7Ks/o=
github.com/fsnotify/fsnotify v1.4.7 h1:IXs+QLmnXW2CcXuY+8Mzv/fWEsPGWxqefPtCP5CnV9I=
github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=
github.com/gin-contrib/sse v0.1.0/go.mod h1:RHrZQHXnP2xjPF+u1gW/2HnVO7nvIa9PG3Gm+fLHvGI=
github.com/gin-gonic/gin v1.6.2/go.mod h1:75u5sXoLsGZoRN5Sgbi1eraJ4GU3++wFwWzhwvtwp4M=
github.com/go-ini/ini v1.30.0 h1:bcFeUQUA+99t1cZPXmtc7HpGv2KTlZGIFeBDWQh2DRw=
github.com/go-ini/ini v1.30.0/go.mod h1:ByCAeIL28uOIIG0E3PJtZPDL8WnHpFKFOtgjp+3Ies8=
github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU=
github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as=
github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE=
github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk=
github.com/go-playground/assert/v2 v2.0.1/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4=
github.com/go-playground/locales v0.13.0/go.mod h1:taPMhCMXrRLJO55olJkUXHZBHCxTMfnGwq/HNwmWNS8=
github.com/go-playground/universal-translator v0.17.0/go.mod h1:UkSxE5sNxxRwHyU+Scu5vgOQjsIJAF8j9muTVoKLVtA=
@ -84,39 +101,69 @@ github.com/go-sql-driver/mysql v1.5.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LB
github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY=
github.com/gofrs/uuid v3.2.0+incompatible h1:y12jRkkFxsd7GpqdSZ+/KCs/fJbqpEXSGd4+jfEaewE=
github.com/gofrs/uuid v3.2.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM=
github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ=
github.com/gogo/protobuf v1.2.0 h1:xU6/SpYbvkNYiptHJYEDRseDLvYE7wSqhYYNy0QSUzI=
github.com/gogo/protobuf v1.2.0/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ=
github.com/gogo/protobuf v1.2.1 h1:/s5zKNz0uPFCZ5hddgPdo2TK2TVrUNMn0OOX8/aZMTE=
github.com/gogo/protobuf v1.2.1/go.mod h1:hp+jE20tsWTFYpLwKvXlhS1hjn+gTNwPg2I6zVXpSg4=
github.com/golang-sql/civil v0.0.0-20190719163853-cb61b32ac6fe h1:lXe2qZdvpiX5WZkZR4hgp4KJVfY3nMkvmwbVkpv1rVY=
github.com/golang-sql/civil v0.0.0-20190719163853-cb61b32ac6fe/go.mod h1:8vg3r2VgvsThLBIFL93Qb5yWzgyZWhEmBwUJWevAkK0=
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
github.com/golang/groupcache v0.0.0-20190129154638-5b532d6fd5ef/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
github.com/golang/mock v1.3.1/go.mod h1:sBzyDLLjw3U8JLTeZvSv8jJB+tU5PVekmnlKIyFUx0Y=
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/golang/protobuf v1.3.3 h1:gyjaxf+svBWX08ZjK86iN9geUJF0H6gp2IRKX6Nf6/I=
github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw=
github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
github.com/google/go-cmp v0.3.1 h1:Xye71clBPdm5HgqGwUkwhbynsUJZhDbS20FvLhQ2izg=
github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
github.com/google/go-querystring v0.0.0-20170111101155-53e6ce116135 h1:zLTLjkaOFEFIOxY5BWLFLwh+cL8vOBW4XJ2aqLE/Tf0=
github.com/google/go-querystring v0.0.0-20170111101155-53e6ce116135/go.mod h1:odCYkC5MyYFN7vkCjXpyrEuKhc/BUO6wN/zVPAxq5ck=
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs=
github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc=
github.com/google/pprof v0.0.0-20190515194954-54271f7e092f/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc=
github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI=
github.com/google/uuid v1.1.1 h1:Gkbcsh/GbpXz7lPftLA3P6TYMwjCLYm83jiFQZF/3gY=
github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1 h1:EGx4pi6eqNxGaHF6qqu48+N2wcFQ5qg5FXgOdqsJ5d8=
github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg=
github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk=
github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY=
github.com/gorilla/context v1.1.1 h1:AWwleXJkX/nhcU9bZSnZoi3h/qGYqQAGhq6zZe/aQW8=
github.com/gorilla/context v1.1.1/go.mod h1:kBGZzfjB9CEq2AlWe17Uuf7NDRt0dE0s8S51q0aT7Yg=
github.com/gorilla/mux v1.6.2 h1:Pgr17XVTNXAk3q/r4CpKzC5xBM/qW1uVLV+IhRZpIIk=
github.com/gorilla/mux v1.6.2/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs=
github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
github.com/grpc-ecosystem/go-grpc-middleware v1.0.0/go.mod h1:FiyG127CGDf3tlThmgyCl78X/SZQqEOJBCDaAfeWzPs=
github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0/go.mod h1:8NvIoxWQoOIhqOTXgfV/d3M/q6VIi02HzZEHgUlZvzk=
github.com/grpc-ecosystem/grpc-gateway v1.9.0/go.mod h1:vNeuVxBJEsws4ogUvrchl83t/GYV9WGTSLVdBhOQFDY=
github.com/h2non/filetype v1.1.0 h1:Or/gjocJrJRNK/Cri/TDEKFjAR+cfG6eK65NGYB6gBA=
github.com/h2non/filetype v1.1.0/go.mod h1:319b3zT68BvV+WRj7cwy856M2ehB3HqNOt6sy1HndBY=
github.com/hashicorp/go-cleanhttp v0.0.0-20170211013415-3573b8b52aa7 h1:67fHcS+inUoiIqWCKIqeDuq2AlPHNHPiTqp97LdQ+bc=
github.com/hashicorp/go-cleanhttp v0.0.0-20170211013415-3573b8b52aa7/go.mod h1:JpRdi6/HCYpAwUzNwuwqhbovhLtngrth3wmdIIUrZ80=
github.com/hashicorp/go-getter v0.0.0-20171007181130-2f449c791e6a h1:deqk3CV92a9CYhyfYjIJPAB2X4/vheVghmoBCno6WQM=
github.com/hashicorp/go-getter v0.0.0-20171007181130-2f449c791e6a/go.mod h1:6rdJFnhkXnzGOJbvkrdv4t9nLwKcVA+tmbQeUlkIzrU=
github.com/hashicorp/go-version v0.0.0-20170914154128-fc61389e27c7 h1:Tijq+ZHupzK8WfomfH2s5dpKkpZd2TcN2i1LDbzWbwk=
github.com/hashicorp/go-version v0.0.0-20170914154128-fc61389e27c7/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA=
github.com/hashicorp/consul/api v1.1.0/go.mod h1:VmuI/Lkw1nC05EYQWNKwWGbkg+FbDBtguAZLlVdkD9Q=
github.com/hashicorp/consul/sdk v0.1.1/go.mod h1:VKf9jXwCTEY1QZP2MOLRhb5i/I/ssyNV1vwHyQBF0x8=
github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4=
github.com/hashicorp/go-cleanhttp v0.5.1/go.mod h1:JpRdi6/HCYpAwUzNwuwqhbovhLtngrth3wmdIIUrZ80=
github.com/hashicorp/go-immutable-radix v1.0.0/go.mod h1:0y9vanUI8NX6FsYoO3zeMjhV/C5i9g4Q3DwcSNZ4P60=
github.com/hashicorp/go-msgpack v0.5.3/go.mod h1:ahLV/dePpqEmjfWmKiqvPkv/twdG7iPBM1vqhUKIvfM=
github.com/hashicorp/go-multierror v1.0.0/go.mod h1:dHtQlpGsu+cZNNAkkCN/P3hoUDHhCYQXV3UM06sGGrk=
github.com/hashicorp/go-rootcerts v1.0.0/go.mod h1:K6zTfqpRlCUIjkwsN4Z+hiSfzSTQa6eBIzfwKfwNnHU=
github.com/hashicorp/go-sockaddr v1.0.0/go.mod h1:7Xibr9yA9JjQq1JpNB2Vw7kxv8xerXegt+ozgdvDeDU=
github.com/hashicorp/go-syslog v1.0.0/go.mod h1:qPfqrKkXGihmCqbJM2mZgkZGvKG1dFdvsLplgctolz4=
github.com/hashicorp/go-uuid v1.0.0/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro=
github.com/hashicorp/go-uuid v1.0.1/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro=
github.com/hashicorp/go.net v0.0.1/go.mod h1:hjKkEWcCURg++eb33jQU7oqQcI9XDCnUzHA0oac0k90=
github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ=
github.com/hashicorp/logutils v1.0.0/go.mod h1:QIAnNjmIWmVIIkWDTG1z5v++HQmx9WQRO+LraFDTW64=
github.com/hashicorp/mdns v1.0.0/go.mod h1:tL+uN++7HEJ6SQLQ2/p+z2pH24WQKWjBPkE0mNTz8vQ=
github.com/hashicorp/memberlist v0.1.3/go.mod h1:ajVTdAv/9Im8oMAAj5G31PhhMCZJV2pPBoIllUwCN7I=
github.com/hashicorp/serf v0.8.2/go.mod h1:6hOLApaqBFA1NXqRQAsxw9QxuDEvNxSQRwA/JwenrHc=
github.com/hpcloud/tail v1.0.0 h1:nfCOvKYfkgYP8hkirhJocXT2+zOD8yUNjXaWfTlyFKI=
github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU=
github.com/inconshreveable/mousetrap v1.0.0 h1:Z8tu5sraLXCXIcARxBp/8cbvlwVa7Z1NHg9XEKhtSvM=
@ -163,17 +210,18 @@ github.com/jackc/pgx/v4 v4.6.0/go.mod h1:vPh43ZzxijXUVJ+t/EmXBtFmbFVO72cuneCT9oA
github.com/jackc/puddle v0.0.0-20190413234325-e4ced69a3a2b/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk=
github.com/jackc/puddle v0.0.0-20190608224051-11cab39313c9/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk=
github.com/jackc/puddle v1.1.0/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk=
github.com/jmespath/go-jmespath v0.0.0-20160202185014-0b12d6b521d8 h1:12VvqtR6Aowv3l/EQUlocDHW2Cp4G9WJVH7uyH8QFJE=
github.com/jmespath/go-jmespath v0.0.0-20160202185014-0b12d6b521d8/go.mod h1:Nht3zPeWKUH0NzdCt2Blrr5ys8VGpn0CEB0cQHVjt7k=
github.com/jondot/goweight v1.0.5 h1:aRpnyj1G8BLLNhem8xezuuV0GlFz4G11e3/UtBU/FlQ=
github.com/jondot/goweight v1.0.5/go.mod h1:3PRcpOwkyspe1t4+KCNgauas+aNDTSSCwZ6AQ4kDD/A=
github.com/jonboulle/clockwork v0.1.0/go.mod h1:Ii8DK3G1RaLaWxj9trq07+26W01tbo22gdxWY5EU2bo=
github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU=
github.com/json-iterator/go v1.1.9/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
github.com/jtolds/gls v4.20.0+incompatible h1:xdiiI2gbIgH/gLH7ADydsJ1uDOEzR8yvV7C0MuV77Wo=
github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU=
github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU=
github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w=
github.com/kisielk/errcheck v1.1.0/go.mod h1:EZBBE59ingxPouuu3KfxchcWSUPOHkagtvWXihfKN4Q=
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
github.com/konsorten/go-windows-terminal-sequences v1.0.2 h1:DB17ag19krx9CFsz4o3enTrPXyIXCl+2iCXH/aMAp9s=
github.com/konsorten/go-windows-terminal-sequences v1.0.2/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc=
github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI=
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
github.com/kr/pretty v0.2.1 h1:Fmg33tUaq4/8ym9TJN1x7sLJnHVwhP33CNkpYV/7rwI=
@ -192,10 +240,13 @@ github.com/lib/pq v1.2.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo=
github.com/magefile/mage v1.9.0 h1:t3AU2wNwehMCW97vuqQLtw6puppWXHO+O2MHo5a50XE=
github.com/magefile/mage v1.9.0/go.mod h1:z5UZb/iS3GoOSn0JgWuiw7dxlurVYTu+/jHXqQg881A=
github.com/magiconair/properties v1.8.0/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ=
github.com/magiconair/properties v1.8.1/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ=
github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU=
github.com/mattn/go-colorable v0.1.1/go.mod h1:FuOcm+DKB9mbwrcAfNl7/TZVBZ6rcnceauSikq3lYCQ=
github.com/mattn/go-colorable v0.1.2/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE=
github.com/mattn/go-colorable v0.1.4 h1:snbPLB8fVfU9iwbbo30TPtbLRzwWu6aJS6Xh4eaaviA=
github.com/mattn/go-colorable v0.1.4/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE=
github.com/mattn/go-isatty v0.0.3/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4=
github.com/mattn/go-isatty v0.0.5/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s=
github.com/mattn/go-isatty v0.0.7/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s=
github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s=
@ -208,25 +259,31 @@ github.com/mattn/go-runewidth v0.0.4 h1:2BvfKmzob6Bmd4YsL0zygOqfdFnK7GR4QL06Do4/
github.com/mattn/go-runewidth v0.0.4/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU=
github.com/mattn/go-sqlite3 v2.0.3+incompatible h1:gXHsfypPkaMZrKbD5209QV9jbUTJKjyR5WD3HYQSd+U=
github.com/mattn/go-sqlite3 v2.0.3+incompatible/go.mod h1:FPy6KqzDD04eiIsT53CuJW3U88zkxoIYsOqkbpncsNc=
github.com/mattn/go-zglob v0.0.0-20180803001819-2ea3427bfa53 h1:tGfIHhDghvEnneeRhODvGYOt305TPwingKt6p90F4MU=
github.com/mattn/go-zglob v0.0.0-20180803001819-2ea3427bfa53/go.mod h1:9fxibJccNxU2cnpIKLRRFA7zX7qhkJIQWBb449FYHOo=
github.com/mattn/go-zglob v0.0.3 h1:6Ry4EYsScDyt5di4OI6xw1bYhOqfE5S33Z1OPy+d+To=
github.com/mattn/go-zglob v0.0.3/go.mod h1:9fxibJccNxU2cnpIKLRRFA7zX7qhkJIQWBb449FYHOo=
github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0=
github.com/miekg/dns v1.0.14/go.mod h1:W1PPwlIAgtquWBMBEV9nkV9Cazfe8ScdGz/Lj7v3Nrg=
github.com/mitchellh/cli v1.0.0/go.mod h1:hNIlj7HEI86fIcpObd7a0FcrxTWetlwJDGcceTlRvqc=
github.com/mitchellh/go-homedir v1.0.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0=
github.com/mitchellh/go-homedir v1.1.0 h1:lukF9ziXFxDFPkA1vsr5zpc1XuPDn/wFntq5mG+4E0Y=
github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0=
github.com/mitchellh/go-testing-interface v0.0.0-20171004221916-a61a99592b77 h1:7GoSOOW2jpsfkntVKaS2rAr1TJqfcxotyaUcuxoZSzg=
github.com/mitchellh/go-testing-interface v0.0.0-20171004221916-a61a99592b77/go.mod h1:kRemZodwjscx+RGhAo8eIhFbs2+BFgRtFPeD/KE+zxI=
github.com/mitchellh/go-testing-interface v1.0.0/go.mod h1:kRemZodwjscx+RGhAo8eIhFbs2+BFgRtFPeD/KE+zxI=
github.com/mitchellh/gox v0.4.0/go.mod h1:Sd9lOJ0+aimLBi73mGofS1ycjY8lL3uZM3JPS42BGNg=
github.com/mitchellh/iochan v1.0.0/go.mod h1:JwYml1nuB7xOzsp52dPpHFffvOCDupsG0QubkSMEySY=
github.com/mitchellh/mapstructure v0.0.0-20160808181253-ca63d7c062ee/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y=
github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y=
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
github.com/morikuni/aec v0.0.0-20170113033406-39771216ff4c h1:nXxl5PrvVm2L/wCy8dQu6DMTwH4oIuGN8GJDAlqDdVE=
github.com/morikuni/aec v0.0.0-20170113033406-39771216ff4c/go.mod h1:BbKIizmSmc5MMPqRYbxO4ZU0S0+P200+tUnFx7PXmsc=
github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U=
github.com/neilotoole/cobra v1.1.4-0.20210220092732-c11dbd416310 h1:++ilwBgxD5woFaMRfHmbJDbZ4mpoJT/ghS2I5pjrn+A=
github.com/neilotoole/cobra v1.1.4-0.20210220092732-c11dbd416310/go.mod h1:pGADOWyqRD/YMrPZigI/zbliZ2wVD/23d+is3pSWzOo=
github.com/neilotoole/errgroup v0.1.5 h1:DxEGoIfFm5ooGicidR+okiHjoOaGRKFaSxDPVZuuu2I=
github.com/neilotoole/errgroup v0.1.5/go.mod h1:Q2nLGf+594h0CLBs/Mbg6qOr7GtqDK7C2S41udRnToE=
github.com/neilotoole/lg v0.3.0 h1:2/IESY8l903AeK9nvH3AWVI/p8up+8wmKWIuts7PpxY=
github.com/neilotoole/lg v0.3.0/go.mod h1:CUHyAinxegpXWV2uPbGP8R3OzRMlAd78YxtA9xiOeFA=
github.com/nlopes/slack v0.1.0 h1:YnVhdQvWT/m0TDh3VNpSoCBDlD7Y4pz1qUqb/NrNyUs=
github.com/nlopes/slack v0.1.0/go.mod h1:jVI4BBK3lSktibKahxBF74txcK2vyvkza1z/+rRnVAM=
github.com/oklog/ulid v1.3.1/go.mod h1:CirwcVhetQ6Lv90oh/F+FBtV6XMibvdAFo93nm5qn4U=
github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
github.com/onsi/ginkgo v1.8.0 h1:VkHVNpR4iVnU8XQR6DBm8BqYjN7CRzw+xKUbVVbbW9w=
github.com/onsi/ginkgo v1.8.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
@ -238,6 +295,7 @@ github.com/opencontainers/image-spec v1.0.1 h1:JMemWkRwHx4Zj+fVxWoMCFm/8sYGGrUVo
github.com/opencontainers/image-spec v1.0.1/go.mod h1:BtxoFyWECRxE4U/7sNtV5W15zMzWCbyJoFRP3s7yZA0=
github.com/opencontainers/runc v0.1.1 h1:GlxAyO6x8rfZYN9Tt0Kti5a/cP41iuiO2yYT0IJGY8Y=
github.com/opencontainers/runc v0.1.1/go.mod h1:qT5XzbpPznkRYVz/mWwUaVBUv2rmF59PVA73FjuZG0U=
github.com/pascaldekloe/goe v0.0.0-20180627143212-57f6aae5913c/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc=
github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic=
github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pkg/errors v0.8.1 h1:iURUrRGxPUNPdy5/HRSm+Yj6okJ6UtLINN0Q9M4+h3I=
@ -246,36 +304,55 @@ github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/posener/complete v1.1.1/go.mod h1:em0nMJCgc9GFtwrmVmEMR/ZL6WyhyjMBndrE9hABlRI=
github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw=
github.com/prometheus/client_golang v0.9.3/go.mod h1:/TN21ttK/J9q6uSwhBd54HahCDft0ttaMvbicHlPoso=
github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo=
github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
github.com/prometheus/common v0.0.0-20181113130724-41aa239b4cce/go.mod h1:daVV7qP5qjZbuso7PdcryaAu0sAZbrN9i7WWcTMWvro=
github.com/prometheus/common v0.4.0/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4=
github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk=
github.com/prometheus/procfs v0.0.0-20190507164030-5867b95ac084/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA=
github.com/prometheus/tsdb v0.7.1/go.mod h1:qhTCs0VvXwvX/y3TZrWD7rabWM+ijKTux40TwIPHuXU=
github.com/rogpeppe/fastuuid v0.0.0-20150106093220-6724a57986af/go.mod h1:XWv6SoW27p1b0cqNHllgS5HIMJraePCO15w5zCzIWYg=
github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
github.com/rs/xid v1.2.1/go.mod h1:+uKXf+4Djp6Md1KODXJxgGQPKngRmWyn10oCKFzNHOQ=
github.com/rs/zerolog v1.13.0/go.mod h1:YbFCdg8HfsridGWAh22vktObvhZbQsZXe4/zB0OKkWU=
github.com/rs/zerolog v1.15.0/go.mod h1:xYTKnLHcpfU2225ny5qZjxnj9NvkumZYjJHlAThCjNc=
github.com/russross/blackfriday v1.5.2/go.mod h1:JO/DiYxRf+HjHt06OyowR9PTA263kcR/rfWxYHBV53g=
github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
github.com/ryanuber/columnize v0.0.0-20160712163229-9b3edd62028f/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts=
github.com/ryboe/q v1.0.12 h1:Ehx4fAUEaZ5KW9Cf9w+ShQ41r5Znc5076jPdnqbfZ3o=
github.com/ryboe/q v1.0.12/go.mod h1:6xM6Lm3ZAh5H8d85zLohckSJcqIjJspf1gGkWWS8YGs=
github.com/satori/go.uuid v1.2.0 h1:0uYX9dsZ2yD7q2RtLRtPSdGDWzjeM3TbMJP9utgA0ww=
github.com/satori/go.uuid v1.2.0/go.mod h1:dA0hQrYB0VpLJoorglMZABFdXlWrHn1NEOzdhQKdks0=
github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529/go.mod h1:DxrIzT+xaE7yg65j358z/aeFdxmN0P9QXhEzd20vsDc=
github.com/segmentio/encoding v0.1.14 h1:BfnglNbNRohLaBLf93uP5/IwKqeWrezXK/g6IRnj75c=
github.com/segmentio/encoding v0.1.14/go.mod h1:RWhr02uzMB9gQC1x+MfYxedtmBibb9cZ6Vv9VxRSSbw=
github.com/shopspring/decimal v0.0.0-20180709203117-cd690d0c9e24 h1:pntxY8Ary0t43dCZ5dqY4YTJCObLY1kIXl0uzMv+7DE=
github.com/shopspring/decimal v0.0.0-20180709203117-cd690d0c9e24/go.mod h1:M+9NzErvs504Cn4c5DxATwIqPbtswREoFCre64PpcG4=
github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc=
github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo=
github.com/sirupsen/logrus v1.4.1/go.mod h1:ni0Sbl8bgC9z8RoU9G6nDWqqs/fq4eDPysMBDgk/93Q=
github.com/sirupsen/logrus v1.4.2 h1:SPIRibHv4MatM3XXNO2BJeFLZwZ2LvZgfQ5+UNI2im4=
github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE=
github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d h1:zE9ykElWQ6/NYmHa3jpm/yHnI4xSofP+UP6SpjHcSeM=
github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc=
github.com/smartystreets/goconvey v0.0.0-20190731233626-505e41936337 h1:WN9BUFbdyOsSH/XohnWpXOlq9NBD5sGAB2FciQMUEe8=
github.com/smartystreets/goconvey v0.0.0-20190731233626-505e41936337/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA=
github.com/smartystreets/goconvey v1.6.4/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA=
github.com/soheilhy/cmux v0.1.4/go.mod h1:IM3LyeVVIOuxMH7sFAkER9+bJ4dT7Ms6E4xg4kGIyLM=
github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA=
github.com/spf13/afero v1.1.2/go.mod h1:j4pytiNVoe2o6bmDsKpLACNPDBIoEAkihy7loJ1B0CQ=
github.com/spf13/cast v1.3.0/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE=
github.com/spf13/cobra v0.0.5 h1:f0B+LkLX6DtmRH1isoNA9VTtNUK9K8xYd28JNNfOv/s=
github.com/spf13/cobra v0.0.5/go.mod h1:3K3wKZymM7VvHMDS9+Akkh4K60UwM26emMESw8tLCHU=
github.com/spf13/cobra v1.1.3 h1:xghbfqPkxzxP3C/f3n5DdpAbdKLj4ZE4BWQI362l53M=
github.com/spf13/cobra v1.1.3/go.mod h1:pGADOWyqRD/YMrPZigI/zbliZ2wVD/23d+is3pSWzOo=
github.com/spf13/jwalterweatherman v1.0.0/go.mod h1:cQK4TGJAtQXfYWX+Ddv3mKDzgVb68N+wFjFa4jdeBTo=
github.com/spf13/pflag v1.0.2/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4=
github.com/spf13/pflag v1.0.3 h1:zPAT6CGy6wXeQ7NtTnaTerfKOsV6V6F8agHXFiazDkg=
github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4=
github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA=
github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
github.com/spf13/viper v1.3.2/go.mod h1:ZiWeW+zYFKm7srdB9IoDzzZXaJaI5eL9QjNiN/DMA2s=
github.com/spf13/viper v1.7.0/go.mod h1:8WkrPz2fc9jxqZNCJI/76HCieCp4Q8HaLFoCha5qpdg=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.2.0/go.mod h1:qt09Ya8vawLte6SNmTgCsAVtYtaKzEcn8ATUoHMkEqE=
@ -287,25 +364,23 @@ github.com/stretchr/testify v1.4.0 h1:2E4SXV/wtOkTonXsotYi4li6zVWxYlZuYNCXe9XRJy
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
github.com/stretchr/testify v1.5.1 h1:nOGnQDM7FYENwehXlg/kFVnos3rEvtKTjRvOWSzb6H4=
github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=
github.com/tbruyelle/hipchat-go v0.0.0-20160921153256-749fb9e14beb h1:mb7xv0kx9XpGsLy5kCCa6+3HqSj495cEBQNMgljqZ48=
github.com/tbruyelle/hipchat-go v0.0.0-20160921153256-749fb9e14beb/go.mod h1:CJEWrlDz1qHCF/nywogFd3AqHUWbKCdpu9pSAdf1OzY=
github.com/subosito/gotenv v1.2.0/go.mod h1:N0PQaV/YGNqwC0u51sEeR/aUtSLEXKX9iv69rRypqCw=
github.com/tealeg/xlsx/v2 v2.0.1 h1:RP+VEscpPFjH2FnpKh1p9HVLAk1htqb9Urcxi2AU1ns=
github.com/tealeg/xlsx/v2 v2.0.1/go.mod h1:l9GvhCCjdaIGkAyZcFedDALcYcXUOei55f6umRMOz9c=
github.com/testcontainers/testcontainers-go v0.5.0 h1:u7jCdf130QHBY5KK9xkFzZpkep24Rtl8HdWscMO9H/8=
github.com/testcontainers/testcontainers-go v0.5.0/go.mod h1:BXwe1JilTOLT8cmVyPMDbIw7e+8UCGeAhxjBwguG5wQ=
github.com/thoas/go-funk v0.0.0-20180716193722-1060394a7713 h1:knaxjm6QMbUMNvuaSnJZmw0gRX4V/79JVUQiziJGM84=
github.com/thoas/go-funk v0.0.0-20180716193722-1060394a7713/go.mod h1:mlR+dHGb+4YgXkf13rkQTuzrneeHANxOm6+ZnEV9HsA=
github.com/thoas/go-funk v0.7.0 h1:GmirKrs6j6zJbhJIficOsz2aAI7700KsU/5YrdHRM1Y=
github.com/thoas/go-funk v0.7.0/go.mod h1:+IWnUfUmFO1+WVYQWQtIJHeRRdaIyyYglZN7xzUPe4Q=
github.com/tmc/grpc-websocket-proxy v0.0.0-20190109142713-0ad062ec5ee5/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U=
github.com/ugorji/go v1.1.7/go.mod h1:kZn38zHttfInRq0xu/PH0az30d+z6vm202qpg1oXVMw=
github.com/ugorji/go/codec v0.0.0-20181204163529-d75b2dcb6bc8/go.mod h1:VFNgLljTbGfSG7qAOspJ7OScBnGdDN/yBr0sguwnwf0=
github.com/ugorji/go/codec v1.1.7/go.mod h1:Ax+UKWsSmolVDwsd+7N3ZtXu+yMGCf907BLYF3GoBXY=
github.com/ulikunitz/xz v0.5.4 h1:zATC2OoZ8H1TZll3FpbX+ikwmadbO699PE06cIkm9oU=
github.com/ulikunitz/xz v0.5.4/go.mod h1:2bypXElzHzzJZwzH67Y6wb67pO62Rzfn7BSiF4ABRW8=
github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2/go.mod h1:UETIi67q53MR2AWcXfiuqkDkRtnGDLqkBTpCHuJHxtU=
github.com/xo/dburl v0.0.0-20200124232849-e9ec94f52bc3 h1:NC3CI7do3KHtiuYhk1CdS9V2qS3jNa7Fs2Afcnnt+IE=
github.com/xo/dburl v0.0.0-20200124232849-e9ec94f52bc3/go.mod h1:A47W3pdWONaZmXuLZgfKLAVgUY0qvfTRM5vVDKS40S4=
github.com/xordataexchange/crypt v0.0.3-0.20170626215501-b2862e3d0a77/go.mod h1:aYKd//L2LvnjZzWKhF00oedf4jCCReLcmhLdhm1A27Q=
github.com/zenazn/goji v0.9.0/go.mod h1:7S9M489iMyHBNxwZnk9/EHS098H4/F6TATF2mIxtB1Q=
go.etcd.io/bbolt v1.3.2/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU=
go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU=
go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8=
go.uber.org/atomic v1.3.2/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE=
go.uber.org/atomic v1.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE=
go.uber.org/atomic v1.5.0 h1:OI5t8sDa1Or+q8AeE+yKeB/SDYioSHAgcVljj9JIETY=
@ -321,6 +396,7 @@ go.uber.org/zap v1.10.0/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q=
go.uber.org/zap v1.13.0 h1:nR6NoDBgAf67s68NhaXbsojM+2gxp3S1hWkHDl27pVU=
go.uber.org/zap v1.13.0/go.mod h1:zwrFLgMcdUuIBviXEYEH1YKNaOBnKXsx2IPda5bBwHM=
golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
golang.org/x/crypto v0.0.0-20181029021203-45a5f77698d3/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
golang.org/x/crypto v0.0.0-20181203042331-505ab145d0a9/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2 h1:VklqNMn3ovrHsnt90PveolxSbWFaJdECFbxSq0Mqo2M=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
@ -328,6 +404,7 @@ golang.org/x/crypto v0.0.0-20190325154230-a5d413f7728c/go.mod h1:djNgcEr1/C05ACk
golang.org/x/crypto v0.0.0-20190411191339-88737f569e3a/go.mod h1:WFFai1msRO1wXaEeE5yQxYXgSfI8pQAWXbQop6sCtWE=
golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529 h1:iMGN4xG0cnqj3t+zOM8wUB0BiPKHEwSxEZCvzcbZuvk=
golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20190820162420-60c769a6c586/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20200117160349-530e935923ad h1:Jh8cai0fqIK+f6nG0UgPW5wFk8wmiMhM3AyciDBdtQg=
golang.org/x/crypto v0.0.0-20200117160349-530e935923ad/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
@ -335,36 +412,75 @@ golang.org/x/crypto v0.0.0-20200323165209-0ec3e9974c59 h1:3zb4D3T4G8jdExgVU/95+v
golang.org/x/crypto v0.0.0-20200323165209-0ec3e9974c59/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.0.0-20200728195943-123391ffb6de h1:ikNHVSjEfnvz6sxdSPCaPt572qowuyMDMJLLm3Db3ig=
golang.org/x/crypto v0.0.0-20200728195943-123391ffb6de/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8=
golang.org/x/exp v0.0.0-20190829153037-c13cbed26979/go.mod h1:86+5VVa7VpoJ4kLfm080zCjGlMRFzhUhsZKEZO7MGek=
golang.org/x/exp v0.0.0-20191030013958-a1ab85dbe136/go.mod h1:JXzH8nQsPlswgeRAPE3MuO9GYsAcnJvJ4vnMwN/5qkY=
golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js=
golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU=
golang.org/x/lint v0.0.0-20190301231843-5614ed5bae6f/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
golang.org/x/lint v0.0.0-20190409202823-959b441ac422/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
golang.org/x/lint v0.0.0-20190909230951-414d861bb4ac/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
golang.org/x/lint v0.0.0-20190930215403-16217165b5de h1:5hukYrvBGR8/eNkX5mdUezrA6JiaEZDtJb9Ei+1LlBs=
golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
golang.org/x/mobile v0.0.0-20190312151609-d3739f865fa6/go.mod h1:z+o9i4GpDbdi3rU15maQ/Ox0txvL9dWGYEHz965HBQE=
golang.org/x/mobile v0.0.0-20190719004257-d2bd2a29d028/go.mod h1:E/iHnbuqvinMTCcRqshq8CkpyQDoeVncDDYHnLhea+o=
golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc=
golang.org/x/mod v0.1.0/go.mod h1:0QHyrYULN0/3qlju5TqG8bIK38QM8yzMo5ekMj3DlcY=
golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20181023162649-9b4f9f5ad519/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20181201002055-351d144fa1fc/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20181220203305-927f97764cc3/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20190311183353-d8887717615a h1:oWX7TPOiFAMXLq8o0ikBYfCJVlRHBcsciT5bXOrH628=
golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190501004415-9ce7a6920f09/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190503192946-f4e77d36d62c/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks=
golang.org/x/net v0.0.0-20190620200207-3b0461eec859 h1:R/3boaszxrf1GEUWTVDzSKVwLmSJpwZ1yqXm8j0v2QI=
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20190813141303-74dc4d7220e7 h1:fHDIZ2oxGnUZRN6WgWFCbYBjH9uqVPRCUVUDhs0wnbA=
golang.org/x/net v0.0.0-20190813141303-74dc4d7220e7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20190423024810-112230192c58 h1:8gQV6CLnAEikrhgkHFbMAEhagSSnXWGV915qUMm9mrU=
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208 h1:qwRHBd0NqMbJxfbotnDhm2ByMI1Shq4Y6oRJo21SGJA=
golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sys v0.0.0-20180823144017-11551d06cbcc/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20181026203630-95b1ffbd15a5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20181107165924-66b7b1311ac8/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20181205085412-a5c9d58dba9a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a h1:1BGLXjeY4akVXGgbC9HugT3Jv3hCI0z56oJR5vAMgBU=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190403152447-81d4e9dc473e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190412213103-97732733099d h1:+R4KGOnez64A81RvjARKc4UT5/tI9ujCIVX+P5KiHuI=
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190502145724-3ef323f4f1fd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190606165138-5da285871e9c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190813064441-fde4db37ae7a/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190826190057-c7b8b68b1456/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191026070338-33540a1f6037 h1:YyJpGZS1sBuBCzLAR1VEpK193GlqGZbnPFnPV/5Rsb4=
@ -372,32 +488,67 @@ golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7w
golang.org/x/sys v0.0.0-20200116001909-b77594299b42 h1:vEOn+mP2zCOVzKckCZy6YsCtDblrpj/w7B9nxGNELpg=
golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.2 h1:tW2bmiBqwgJj/UpqtC8EpXEZVYOwU0yG4iWbprSVAcs=
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
golang.org/x/time v0.0.0-20181108054448-85acf8d2951c h1:fqgJT0MGcGpPgpWU7VRdRjuArfcOvC4AoJmILihzhDg=
golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/tools v0.0.0-20180221164845-07fd8470d635/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20180810170437-e96c4e24768d/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20180828015842-6cd1fcedba52/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY=
golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
golang.org/x/tools v0.0.0-20190312151545-0bb0c0a6e846/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
golang.org/x/tools v0.0.0-20190312170243-e65039ee4138/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
golang.org/x/tools v0.0.0-20190328211700-ab21143f2384/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
golang.org/x/tools v0.0.0-20190425150028-36563e24a262/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
golang.org/x/tools v0.0.0-20190425163242-31fd60d6bfdc/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
golang.org/x/tools v0.0.0-20190506145303-2d16b83fe98c/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
golang.org/x/tools v0.0.0-20190606124116-d0a3d012864b/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
golang.org/x/tools v0.0.0-20190628153133-6cdbf07be9d0/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
golang.org/x/tools v0.0.0-20190816200558-6889da9d5479/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20190823170909-c4a336ef6a2f/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20190911174233-4f2ddba30aff/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20191012152004-8de300cfc20a/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20191029041327-9cc4af7d6b2c/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20191029190741-b9c20aec41a5 h1:hKsoRgsbwY1NafxrwTs+k64bikrLBkAgPir1TNCj3Zs=
golang.org/x/tools v0.0.0-20191029190741-b9c20aec41a5/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20191112195655-aa38f8e97acc/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/xerrors v0.0.0-20190410155217-1f06c39b4373/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20190513163551-3ee3066db522/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7 h1:9zdDQZ7Thm29KFXgAX/+yaf3eVbP7djjWp/dXAppNCc=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE=
google.golang.org/api v0.7.0/go.mod h1:WtwebWUNSVBH/HAw79HIFXZNqEvBhG+Ra+ax0hx3E3M=
google.golang.org/api v0.8.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg=
google.golang.org/api v0.9.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg=
google.golang.org/api v0.13.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI=
google.golang.org/appengine v1.1.0 h1:igQkv0AAhEIvTEpD5LIpAfav2eeVO9HBTjvKHVJPRSs=
google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
google.golang.org/appengine v1.6.1/go.mod h1:i06prIuMbXzDqacNJfV5OdTW448YApPu5ww/cMBSeb0=
google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8 h1:Nw54tB0rB7hY/N0NQvRW8DG4Yk3Q6T9cu9RcFQDu1tc=
google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
google.golang.org/genproto v0.0.0-20190307195333-5fe7a883aa19/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
google.golang.org/genproto v0.0.0-20190418145605-e7d98fc518a7/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
google.golang.org/genproto v0.0.0-20190425155659-357c62f0e4bb/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
google.golang.org/genproto v0.0.0-20190502173448-54afdca5d873/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
google.golang.org/genproto v0.0.0-20190801165951-fa694d86fc64/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc=
google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc=
google.golang.org/genproto v0.0.0-20190911173649-1774047e7e51/go.mod h1:IbNlFCBrqXvoKpeg0TB2l7cyZUmoaFKYIwrEpbDKLA8=
google.golang.org/genproto v0.0.0-20191108220845-16a3f7862a1a h1:Ob5/580gVHBJZgXnff1cZDbG+xLtMVE5mDRTe+nIsX4=
google.golang.org/genproto v0.0.0-20191108220845-16a3f7862a1a/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
google.golang.org/grpc v1.17.0 h1:TRJYBgMclJvGYn2rIMjj+h9KtMt5r1Ij7ODVRIZkwhk=
google.golang.org/grpc v1.17.0/go.mod h1:6QZJwpn2B+Zp71q/5VxRsJ6NXXVCE5NRUHRo+f3cWCs=
gopkg.in/alecthomas/kingpin.v2 v2.2.6 h1:jMFz6MfLP0/4fUyZle81rXUoxOBFi19VUFKVDOQfozc=
google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38=
google.golang.org/grpc v1.21.1 h1:j6XxA85m/6txkUCHvzlV5f+HBNl/1r5cZ2A/3IEFOO8=
google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM=
gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
@ -411,15 +562,25 @@ gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI=
gopkg.in/fsnotify.v1 v1.4.7 h1:xOHLXZwVvI9hhs+cLKq5+I5onOuwQLhQwiu63xxlHs4=
gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys=
gopkg.in/inconshreveable/log15.v2 v2.0.0-20180818164646-67afb5ed74ec/go.mod h1:aPpfJ7XW+gOuirDoZ8gHhLh3kZ1B08FtV2bbmy7Jv3s=
gopkg.in/ini.v1 v1.51.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
gopkg.in/resty.v1 v1.12.0/go.mod h1:mDo4pnntr5jdWRML875a/NmxYqAlA73dVijT2AXvQQo=
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ=
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw=
gopkg.in/yaml.v2 v2.0.0-20170812160011-eb3733d160e7/go.mod h1:JAlM8MvJe8wmxCU4Bli9HhUf9+ttbYbLASfIpnQbh74=
gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.2 h1:ZCJp+EgiOT7lHqUV2J862kp8Qj64Jo6az82+3Td9dZw=
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.8 h1:obN1ZagJSUGI0Ek/LBmuj4SNLPfIny3KsKFopxRdj10=
gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
gotest.tools v0.0.0-20181223230014-1083505acf35 h1:zpdCK+REwbk+rqjJmHhiCN6iBIigrZ39glqSF0P3KF0=
gotest.tools v0.0.0-20181223230014-1083505acf35/go.mod h1:R//lfYlUuTOTfblYI3lGoAAAebUdzjvbmQsuB7Ykd90=
honnef.co/go/tools v0.0.0-20180728063816-88497007e858/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
honnef.co/go/tools v0.0.1-2019.2.3 h1:3JgtbtFHMiCmsznwGVTUWbgGov+pVqnlf1dEJTNAXeM=
honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg=
rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8=

View File

@ -42,9 +42,9 @@ type DB interface {
Preparer
}
// ExecResult invokes db.ExecContext, returning the count of rows
// ExecAffected invokes db.ExecContext, returning the count of rows
// affected and any error.
func ExecResult(ctx context.Context, db Execer, query string, args ...interface{}) (affected int64, err error) {
func ExecAffected(ctx context.Context, db Execer, query string, args ...interface{}) (affected int64, err error) {
var res sql.Result
res, err = db.ExecContext(ctx, query, args...)
if err != nil {

View File

@ -135,7 +135,7 @@ type SQLDriver interface {
// must honor the table name and column names and kinds from tblDef.
CreateTable(ctx context.Context, db sqlz.DB, tblDef *sqlmodel.TableDef) error
// TableExists returns true if there's an exist table tbl in db.
// TableExists returns true if there's an existing table tbl in db.
TableExists(ctx context.Context, db sqlz.DB, tbl string) (bool, error)
// CopyTable copies fromTable into a new table toTable.

View File

@ -90,14 +90,6 @@ func (s *Set) Add(src *Source) error {
return nil
}
// IndexOf returns the index of handle in s.
func (s *Set) IndexOf(handle string) (int, *Source) {
s.mu.Lock()
defer s.mu.Unlock()
return s.indexOf(handle)
}
// Exists returns true if handle already exists in the set.
func (s *Set) Exists(handle string) bool {
s.mu.Lock()
@ -263,6 +255,19 @@ func (s *Set) Remove(handle string) error {
return nil
}
// Handles returns the set of source handles.
func (s *Set) Handles() []string {
s.mu.Lock()
defer s.mu.Unlock()
handles := make([]string, len(s.data.Items))
for i := range s.data.Items {
handles[i] = s.data.Items[i].Handle
}
return handles
}
// VerifySetIntegrity verifies the internal state of s.
// Typically this func is invoked after s has been loaded
// from config, verifying that the config is not corrupt.

View File

@ -67,8 +67,13 @@ func (s *Source) RedactedLocation() string {
if s == nil {
return ""
}
loc := s.Location
return RedactLocation(s.Location)
}
// RedactLocation returns a redacted version of the source
// location loc, with the password component (if any) of
// the location masked.
func RedactLocation(loc string) string {
switch {
case loc == "":
return ""

View File

@ -21,7 +21,7 @@ func main() {
cancelFn()
}()
err := cli.Execute(ctx, os.Stdin, os.Stdout, os.Stderr, os.Args)
err := cli.Execute(ctx, os.Stdin, os.Stdout, os.Stderr, os.Args[1:])
if err != nil {
cancelFn()
os.Exit(1)