fix: don't assume bash when displaying interactive CLI prompts (#21839)

pull/21852/head
Daniel Moran 2021-07-14 18:12:56 -04:00 committed by GitHub
parent ba8f91f1d9
commit fa1c352132
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
9 changed files with 70 additions and 325 deletions

View File

@ -43,6 +43,7 @@ This release adds an embedded SQLite database for storing metadata required by t
1. [21840](https://github.com/influxdata/influxdb/pull/21840): Run migrations on restored bolt & SQLite metadata databases as part of the restore process.
1. [21844](https://github.com/influxdata/influxdb/pull/21844): Upgrade to latest version of `influxdata/cron` so that tasks can be created with interval of `every: 1w`.
1. [21849](https://github.com/influxdata/influxdb/pull/21849): Specify which fields are missing when rejecting an incomplete onboarding request.
1. [21839](https://github.com/influxdata/influxdb/pull/21839): Fix display and parsing of `influxd upgrade` CLI prompts in PowerShell.
## v2.0.7 [2021-06-04]

View File

@ -482,13 +482,23 @@ func (tl *TestLauncher) TaskService(tb testing.TB) taskmodel.TaskService {
func (tl *TestLauncher) BackupService(tb testing.TB) *clibackup.Client {
tb.Helper()
return &clibackup.Client{CLI: clients.CLI{}, BackupApi: tl.APIClient(tb).BackupApi}
client := tl.APIClient(tb)
return &clibackup.Client{
CLI: clients.CLI{},
BackupApi: client.BackupApi,
HealthApi: client.HealthApi,
}
}
func (tl *TestLauncher) RestoreService(tb testing.TB) *clirestore.Client {
tb.Helper()
client := tl.APIClient(tb)
return &clirestore.Client{CLI: clients.CLI{}, RestoreApi: client.RestoreApi, OrganizationsApi: client.OrganizationsApi}
return &clirestore.Client{
CLI: clients.CLI{},
RestoreApi: client.RestoreApi,
OrganizationsApi: client.OrganizationsApi,
HealthApi: client.HealthApi,
}
}
func (tl *TestLauncher) HTTPClient(tb testing.TB) *httpc.Client {

View File

@ -8,19 +8,17 @@ import (
"path/filepath"
"strings"
"github.com/influxdata/influxdb/v2/kit/platform"
"github.com/dustin/go-humanize"
"github.com/influxdata/influx-cli/v2/clients"
"github.com/influxdata/influxdb/v2"
"github.com/influxdata/influxdb/v2/cmd/internal"
"github.com/influxdata/influxdb/v2/kit/platform"
"github.com/influxdata/influxdb/v2/pkg/fs"
"github.com/influxdata/influxdb/v2/v1/services/meta"
"github.com/tcnksm/go-input"
"go.uber.org/zap"
)
// upgradeDatabases creates databases, buckets, retention policies and shard info according to 1.x meta and copies data
func upgradeDatabases(ctx context.Context, ui *input.UI, v1 *influxDBv1, v2 *influxDBv2, opts *options, orgID platform.ID, log *zap.Logger) (map[string][]platform.ID, error) {
func upgradeDatabases(ctx context.Context, cli clients.CLI, v1 *influxDBv1, v2 *influxDBv2, opts *options, orgID platform.ID, log *zap.Logger) (map[string][]platform.ID, error) {
v1opts := opts.source
v2opts := opts.target
db2BucketIds := make(map[string][]platform.ID)
@ -40,7 +38,7 @@ func upgradeDatabases(ctx context.Context, ui *input.UI, v1 *influxDBv1, v2 *inf
log.Info("No database found in the 1.x meta")
return db2BucketIds, nil
}
if err := checkDiskSpace(ui, opts, log); err != nil {
if err := checkDiskSpace(cli, opts, log); err != nil {
return nil, err
}
@ -199,7 +197,7 @@ func upgradeDatabases(ctx context.Context, ui *input.UI, v1 *influxDBv1, v2 *inf
// checkDiskSpace ensures there is enough room at the target path to store
// a full copy of all V1 data.
func checkDiskSpace(ui *input.UI, opts *options, log *zap.Logger) error {
func checkDiskSpace(cli clients.CLI, opts *options, log *zap.Logger) error {
log.Info("Checking available disk space")
size, err := DirSize(opts.source.dataDir)
@ -227,12 +225,10 @@ func checkDiskSpace(ui *input.UI, opts *options, log *zap.Logger) error {
return fmt.Errorf("not enough space on target disk of %s: need %d, available %d", v2dir, size, diskInfo.Free)
}
if !opts.force {
if confirmed := internal.GetConfirm(ui, func() string {
return fmt.Sprintf(`Proceeding will copy all V1 data to %q
if confirmed := cli.StdIO.GetConfirm(fmt.Sprintf(`Proceeding will copy all V1 data to %q
Space available: %s
Space required: %s
`, v2dir, freeBytes, requiredBytes)
}); !confirmed {
`, v2dir, freeBytes, requiredBytes)); !confirmed {
return errors.New("upgrade was canceled")
}
}

View File

@ -4,15 +4,12 @@ import (
"context"
"errors"
"fmt"
"math"
"path/filepath"
"strconv"
"time"
"github.com/influxdata/influx-cli/v2/clients"
"github.com/influxdata/influx-cli/v2/clients/setup"
"github.com/influxdata/influx-cli/v2/config"
"github.com/influxdata/influxdb/v2"
"github.com/influxdata/influxdb/v2/cmd/internal"
"github.com/tcnksm/go-input"
"go.uber.org/zap"
)
@ -25,81 +22,35 @@ func setupAdmin(ctx context.Context, v2 *influxDBv2, req *influxdb.OnboardingReq
return res, nil
}
func onboardingRequest(ui *input.UI, options *options) (*influxdb.OnboardingRequest, error) {
if (options.force || len(options.target.password) > 0) && len(options.target.password) < internal.MinPasswordLen {
return nil, internal.ErrPasswordIsTooShort
func onboardingRequest(cli clients.CLI, options *options) (*influxdb.OnboardingRequest, error) {
setupClient := setup.Client{CLI: cli}
cliReq, err := setupClient.OnboardingRequest(&setup.Params{
Username: options.target.userName,
Password: options.target.password,
AuthToken: options.target.token,
Org: options.target.orgName,
Bucket: options.target.bucket,
Retention: options.target.retention,
Force: options.force,
})
if err != nil {
return nil, err
}
req := &influxdb.OnboardingRequest{
User: options.target.userName,
Password: options.target.password,
Org: options.target.orgName,
Bucket: options.target.bucket,
RetentionPeriodSeconds: influxdb.InfiniteRetention,
Token: options.target.token,
req := influxdb.OnboardingRequest{
User: cliReq.Username,
Org: cliReq.Org,
Bucket: cliReq.Bucket,
}
if options.target.retention != "" {
dur, err := internal.RawDurationToTimeDuration(options.target.retention)
if err != nil {
return nil, err
}
secs, nanos := math.Modf(dur.Seconds())
if nanos > 0 {
return nil, fmt.Errorf("retention policy %q is too precise, must be divisible by 1s", options.target.retention)
}
req.RetentionPeriodSeconds = int64(secs)
if cliReq.Password != nil {
req.Password = *cliReq.Password
}
if options.force {
return req, nil
if cliReq.RetentionPeriodSeconds != nil {
req.RetentionPeriodSeconds = *cliReq.RetentionPeriodSeconds
}
fmt.Fprintln(ui.Writer, string(internal.PromptWithColor("Welcome to InfluxDB 2.0!", internal.ColorYellow)))
if req.User == "" {
req.User = internal.GetInput(ui, "Please type your primary username", "")
if cliReq.Token != nil {
req.Token = *cliReq.Token
}
if req.Password == "" {
req.Password = internal.GetPassword(ui, false)
}
if req.Org == "" {
req.Org = internal.GetInput(ui, "Please type your primary organization name", "")
}
if req.Bucket == "" {
req.Bucket = internal.GetInput(ui, "Please type your primary bucket name", "")
}
// Check the initial opts instead of the req to distinguish not-set from explicit 0 over the CLI.
if options.target.retention == "" {
infiniteStr := strconv.Itoa(influxdb.InfiniteRetention)
for {
rpStr := internal.GetInput(ui,
"Please type your retention period in hours.\nOr press ENTER for infinite", infiniteStr)
rp, err := strconv.Atoi(rpStr)
if rp >= 0 && err == nil {
req.RetentionPeriodSeconds = int64((time.Duration(rp) * time.Hour).Seconds())
break
}
}
}
if confirmed := internal.GetConfirm(ui, func() string {
rp := "infinite"
if req.RetentionPeriodSeconds > 0 {
rp = (time.Duration(req.RetentionPeriodSeconds) * time.Second).String()
}
return fmt.Sprintf(`
You have entered:
Username: %s
Organization: %s
Bucket: %s
Retention Period: %s
`, req.User, req.Org, req.Bucket, rp)
}); !confirmed {
return nil, errors.New("setup was canceled")
}
return req, nil
return &req, nil
}
func saveLocalConfig(sourceOptions *optionsV1, targetOptions *optionsV2, log *zap.Logger) error {

View File

@ -11,8 +11,8 @@ import (
"path/filepath"
"strings"
"github.com/influxdata/influxdb/v2/kit/platform"
"github.com/influxdata/influx-cli/v2/clients"
"github.com/influxdata/influx-cli/v2/pkg/stdio"
"github.com/influxdata/influxdb/v2"
"github.com/influxdata/influxdb/v2/authorization"
"github.com/influxdata/influxdb/v2/bolt"
@ -20,6 +20,7 @@ import (
"github.com/influxdata/influxdb/v2/internal/fs"
"github.com/influxdata/influxdb/v2/kit/cli"
"github.com/influxdata/influxdb/v2/kit/metric"
"github.com/influxdata/influxdb/v2/kit/platform"
"github.com/influxdata/influxdb/v2/kit/prom"
"github.com/influxdata/influxdb/v2/kv"
"github.com/influxdata/influxdb/v2/kv/migration"
@ -31,7 +32,6 @@ import (
"github.com/influxdata/influxdb/v2/v1/services/meta/filestore"
"github.com/spf13/cobra"
"github.com/spf13/viper"
"github.com/tcnksm/go-input"
"go.uber.org/zap"
"go.uber.org/zap/zapcore"
)
@ -155,8 +155,7 @@ func NewCommand(ctx context.Context, v *viper.Viper) (*cobra.Command, error) {
if err != nil {
return err
}
ui := &input.UI{Writer: cmd.OutOrStdout(), Reader: cmd.InOrStdin()}
return runUpgradeE(ctx, ui, options, logger)
return runUpgradeE(ctx, clients.CLI{StdIO: stdio.TerminalStdio}, options, logger)
},
Args: cobra.NoArgs,
}
@ -234,7 +233,7 @@ func NewCommand(ctx context.Context, v *viper.Viper) (*cobra.Command, error) {
DestP: &options.target.retention,
Flag: "retention",
Default: "",
Desc: "optional: duration bucket will retain data. 0 is infinite. The default is 0.",
Desc: "optional: duration bucket will retain data (i.e '1w' or '72h'). Default is infinite.",
Short: 'r',
},
{
@ -351,7 +350,7 @@ func buildLogger(options *logOptions, verbose bool) (*zap.Logger, error) {
return log, nil
}
func runUpgradeE(ctx context.Context, ui *input.UI, options *options, log *zap.Logger) error {
func runUpgradeE(ctx context.Context, cli clients.CLI, options *options, log *zap.Logger) error {
if options.source.configFile != "" && options.source.dbDir != "" {
return errors.New("only one of --v1-dir or --config-file may be specified")
}
@ -445,7 +444,7 @@ func runUpgradeE(ctx context.Context, ui *input.UI, options *options, log *zap.L
return errors.New("InfluxDB has been already set up")
}
req, err := onboardingRequest(ui, options)
req, err := onboardingRequest(cli, options)
if err != nil {
return err
}
@ -463,7 +462,7 @@ func runUpgradeE(ctx context.Context, ui *input.UI, options *options, log *zap.L
return err
}
db2BucketIds, err := upgradeDatabases(ctx, ui, v1, v2, options, or.Org.ID, log)
db2BucketIds, err := upgradeDatabases(ctx, cli, v1, v2, options, or.Org.ID, log)
if err != nil {
// remove all files
log.Error("Database upgrade error, removing data", zap.Error(err))

View File

@ -1,7 +1,6 @@
package upgrade
import (
"bytes"
"context"
"fmt"
"io/ioutil"
@ -13,6 +12,7 @@ import (
"github.com/BurntSushi/toml"
"github.com/dustin/go-humanize"
"github.com/google/go-cmp/cmp"
"github.com/influxdata/influx-cli/v2/clients"
"github.com/influxdata/influxdb/v2"
"github.com/influxdata/influxdb/v2/bolt"
"github.com/influxdata/influxdb/v2/cmd/influxd/launcher"
@ -22,7 +22,6 @@ import (
"github.com/spf13/cobra"
"github.com/spf13/viper"
"github.com/stretchr/testify/require"
"github.com/tcnksm/go-input"
"go.uber.org/zap"
"go.uber.org/zap/zaptest"
)
@ -225,10 +224,8 @@ func TestUpgradeRealDB(t *testing.T) {
}
opts := &options{source: *v1opts, target: *v2opts, force: true}
ui := &input.UI{Writer: &bytes.Buffer{}, Reader: &bytes.Buffer{}}
log := zaptest.NewLogger(t, zaptest.Level(zap.InfoLevel))
err = runUpgradeE(ctx, ui, opts, log)
err = runUpgradeE(ctx, clients.CLI{}, opts, log)
require.NoError(t, err)
v := viper.New()

View File

@ -1,209 +0,0 @@
package internal
import (
"errors"
"fmt"
"os"
"strings"
"time"
"github.com/influxdata/influxdb/v2/task/options"
"github.com/tcnksm/go-input"
)
const MinPasswordLen int = 8
var (
ErrPasswordNotMatch = fmt.Errorf("passwords do not match")
ErrPasswordIsTooShort = fmt.Errorf("password is too short")
)
// vt100EscapeCodes
var (
KeyEscape = byte(27)
ColorRed = []byte{KeyEscape, '[', '3', '1', 'm'}
ColorYellow = []byte{KeyEscape, '[', '3', '3', 'm'}
ColorCyan = []byte{KeyEscape, '[', '3', '6', 'm'}
KeyReset = []byte{KeyEscape, '[', '0', 'm'}
)
func PromptWithColor(s string, color []byte) []byte {
bb := append(color, []byte(s)...)
return append(bb, KeyReset...)
}
func GetConfirm(ui *input.UI, promptFunc func() string) bool {
prompt := PromptWithColor("Confirm? (y/n)", ColorRed)
for {
ui.Writer.Write(PromptWithColor(promptFunc(), ColorCyan))
result, err := ui.Ask(string(prompt), &input.Options{
HideOrder: true,
})
if err != nil {
return false
}
switch result {
case "y":
return true
case "n":
return false
default:
continue
}
}
}
func GetSecret(ui *input.UI) (secret string) {
var err error
query := string(PromptWithColor("Please type your secret", ColorCyan))
for {
secret, err = ui.Ask(query, &input.Options{
Required: true,
HideOrder: true,
Hide: true,
Mask: false,
})
switch err {
case input.ErrInterrupted:
os.Exit(1)
default:
if secret = strings.TrimSpace(secret); secret == "" {
continue
}
}
break
}
return secret
}
func GetPassword(ui *input.UI, showNew bool) (password string) {
newStr := ""
if showNew {
newStr = " new"
}
var err error
enterPassword:
query := string(PromptWithColor("Please type your"+newStr+" password", ColorCyan))
for {
password, err = ui.Ask(query, &input.Options{
Required: true,
HideOrder: true,
Hide: true,
Mask: false,
ValidateFunc: func(s string) error {
if len(s) < 8 {
return ErrPasswordIsTooShort
}
return nil
},
})
switch err {
case input.ErrInterrupted:
os.Exit(1)
case ErrPasswordIsTooShort:
ui.Writer.Write(PromptWithColor("Password too short - minimum length is 8 characters.\n\r", ColorRed))
continue
default:
if password = strings.TrimSpace(password); password == "" {
continue
}
}
break
}
query = string(PromptWithColor("Please type your"+newStr+" password again", ColorCyan))
for {
_, err = ui.Ask(query, &input.Options{
Required: true,
HideOrder: true,
Hide: true,
ValidateFunc: func(s string) error {
if s != password {
return ErrPasswordNotMatch
}
return nil
},
})
switch err {
case input.ErrInterrupted:
os.Exit(1)
case nil:
// Nothing.
default:
ui.Writer.Write(PromptWithColor("Passwords do not match.\n", ColorRed))
goto enterPassword
}
break
}
return password
}
func GetInput(ui *input.UI, prompt, defaultValue string) string {
option := &input.Options{
Required: true,
HideOrder: true,
}
if defaultValue != "" {
option.Default = defaultValue
option.HideDefault = true
}
prompt = string(PromptWithColor(prompt, ColorCyan))
for {
line, err := ui.Ask(prompt, option)
switch err {
case input.ErrInterrupted:
os.Exit(1)
default:
if line = strings.TrimSpace(line); line == "" {
continue
}
return line
}
}
}
func RawDurationToTimeDuration(raw string) (time.Duration, error) {
if raw == "" {
return 0, nil
}
if dur, err := time.ParseDuration(raw); err == nil {
return dur, nil
}
retention, err := options.ParseSignedDuration(raw)
if err != nil {
return 0, err
}
const (
day = 24 * time.Hour
week = 7 * day
)
var dur time.Duration
for _, d := range retention.Values {
if d.Magnitude < 0 {
return 0, errors.New("must be greater than 0")
}
mag := time.Duration(d.Magnitude)
switch d.Unit {
case "w":
dur += mag * week
case "d":
dur += mag * day
case "m":
dur += mag * time.Minute
case "s":
dur += mag * time.Second
case "ms":
dur += mag * time.Minute
case "us":
dur += mag * time.Microsecond
case "ns":
dur += mag * time.Nanosecond
default:
return 0, errors.New("duration must be week(w), day(d), hour(h), min(m), sec(s), millisec(ms), microsec(us), or nanosec(ns)")
}
}
return dur, nil
}

7
go.mod
View File

@ -26,7 +26,7 @@ require (
github.com/glycerine/goconvey v0.0.0-20180728074245-46e3a41ad493 // indirect
github.com/go-chi/chi v4.1.0+incompatible
github.com/go-stack/stack v1.8.0
github.com/gogo/protobuf v1.3.1
github.com/gogo/protobuf v1.3.2
github.com/golang/gddo v0.0.0-20181116215533-9bd4a3295021
github.com/golang/mock v1.5.0
github.com/golang/protobuf v1.3.3
@ -45,7 +45,7 @@ require (
github.com/influxdata/cron v0.0.0-20201006132531-4bb0a200dcbe
github.com/influxdata/flux v0.122.0
github.com/influxdata/httprouter v1.3.1-0.20191122104820-ee83e2772f69
github.com/influxdata/influx-cli/v2 v2.0.0-20210702141951-3ca681b1dd48
github.com/influxdata/influx-cli/v2 v2.0.0-20210713195937-a69f06b41b45
github.com/influxdata/influxql v0.0.0-20180925231337-1cbfca8e56b6
github.com/influxdata/pkg-config v0.2.7
github.com/influxdata/usage-client v0.0.0-20160829180054-6d3895376368
@ -80,7 +80,6 @@ require (
github.com/spf13/pflag v1.0.5
github.com/spf13/viper v1.6.1
github.com/stretchr/testify v1.7.0
github.com/tcnksm/go-input v0.0.0-20180404061846-548a7d7a8ee8
github.com/testcontainers/testcontainers-go v0.0.0-20190108154635-47c0da630f72
github.com/tinylib/msgp v1.1.0
github.com/tylerb/graceful v1.2.15
@ -90,7 +89,7 @@ require (
github.com/yudai/gojsondiff v1.0.0
github.com/yudai/golcs v0.0.0-20170316035057-ecda9a501e82 // indirect
github.com/yudai/pp v2.0.1+incompatible // indirect
go.etcd.io/bbolt v1.3.5
go.etcd.io/bbolt v1.3.6
go.uber.org/multierr v1.5.0
go.uber.org/zap v1.14.1
golang.org/x/crypto v0.0.0-20201221181555-eec23a3978ad

21
go.sum
View File

@ -212,8 +212,8 @@ github.com/gofrs/uuid v3.3.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRx
github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ=
github.com/gogo/protobuf v1.2.0/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ=
github.com/gogo/protobuf v1.2.1/go.mod h1:hp+jE20tsWTFYpLwKvXlhS1hjn+gTNwPg2I6zVXpSg4=
github.com/gogo/protobuf v1.3.1 h1:DqDEcV5aeaTmdFBePNpYsp3FlcVH/2ISVVM9Qf8PSls=
github.com/gogo/protobuf v1.3.1/go.mod h1:SlYgWuQ5SjCEi6WLHjHCa1yvBfUnHcTbrrZtXPKa29o=
github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q=
github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q=
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/freetype v0.0.0-20170609003504-e2365dfdc4a0/go.mod h1:E/TSTwGwJL78qG/PmXZO1EjYhfJinVAhrmmHX6Z8B9k=
@ -343,8 +343,8 @@ github.com/influxdata/flux v0.122.0 h1:nAOezylrjyJ7FAzuCUaTC2+mxsq4Oy0Lde5WZ1j9I
github.com/influxdata/flux v0.122.0/go.mod h1:pGSAvyAA5d3et7SSzajaYShWYXmnRnJJq2qWi+WWZ2I=
github.com/influxdata/httprouter v1.3.1-0.20191122104820-ee83e2772f69 h1:WQsmW0fXO4ZE/lFGIE84G6rIV5SJN3P3sjIXAP1a8eU=
github.com/influxdata/httprouter v1.3.1-0.20191122104820-ee83e2772f69/go.mod h1:pwymjR6SrP3gD3pRj9RJwdl1j5s3doEEV8gS4X9qSzA=
github.com/influxdata/influx-cli/v2 v2.0.0-20210702141951-3ca681b1dd48 h1:UjL3ISuDYyy3wLXzFdsuO9deesA9C0LP4mQXcVGDivc=
github.com/influxdata/influx-cli/v2 v2.0.0-20210702141951-3ca681b1dd48/go.mod h1:Hf3fGQost4LKnFDvkvwRBoVuwui7S+sC4JUDd5JYDJ8=
github.com/influxdata/influx-cli/v2 v2.0.0-20210713195937-a69f06b41b45 h1:81jaYQW3KeukYoKFAJzGQyqVRv49a9wIl2KmyvJmRUo=
github.com/influxdata/influx-cli/v2 v2.0.0-20210713195937-a69f06b41b45/go.mod h1:xjodigDVNVCkwvJt+ePXI0s5tMPv8rWUA8xJmqsLSTE=
github.com/influxdata/influxdb-client-go/v2 v2.3.1-0.20210518120617-5d1fff431040 h1:MBLCfcSsUyFPDJp6T7EoHp/Ph3Jkrm4EuUKLD2rUWHg=
github.com/influxdata/influxdb-client-go/v2 v2.3.1-0.20210518120617-5d1fff431040/go.mod h1:vLNHdxTJkIf2mSLvGrpj8TCcISApPoXkaxP8g9uRlW8=
github.com/influxdata/influxql v0.0.0-20180925231337-1cbfca8e56b6 h1:CFx+pP90q/qg3spoiZjf8donE4WpAdjeJfPOcoNqkWo=
@ -385,7 +385,7 @@ github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51/go.mod h1:C
github.com/kevinburke/go-bindata v3.22.0+incompatible h1:/JmqEhIWQ7GRScV0WjX/0tqBrC5D21ALg0H0U/KZ/ts=
github.com/kevinburke/go-bindata v3.22.0+incompatible/go.mod h1:/pEEZ72flUW2p0yi30bslSp9YqD9pysLxunQDdb2CPM=
github.com/kisielk/errcheck v1.1.0/go.mod h1:EZBBE59ingxPouuu3KfxchcWSUPOHkagtvWXihfKN4Q=
github.com/kisielk/errcheck v1.2.0/go.mod h1:/BMXB+zMLi60iA8Vv6Ksmxu/1UDYcXs4uQLJ+jE2L00=
github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8=
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/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc=
@ -584,8 +584,6 @@ github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5Cc
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/subosito/gotenv v1.2.0 h1:Slr1R9HxAlEKefgq5jn9U+DnETlIUa6HfgEzj0g5d7s=
github.com/subosito/gotenv v1.2.0/go.mod h1:N0PQaV/YGNqwC0u51sEeR/aUtSLEXKX9iv69rRypqCw=
github.com/tcnksm/go-input v0.0.0-20180404061846-548a7d7a8ee8 h1:RB0v+/pc8oMzPsN97aZYEwNuJ6ouRJ2uhjxemJ9zvrY=
github.com/tcnksm/go-input v0.0.0-20180404061846-548a7d7a8ee8/go.mod h1:IlWNj9v/13q7xFbaK4mbyzMNwrZLaWSHx/aibKIZuIg=
github.com/testcontainers/testcontainers-go v0.0.0-20190108154635-47c0da630f72 h1:3dsrMloqeog2f5ZoQCWJbTPR/tKIDFePkB0zg3GLjY8=
github.com/testcontainers/testcontainers-go v0.0.0-20190108154635-47c0da630f72/go.mod h1:wt/nMz68+kIO4RoguOZzsdv1B3kTYw+SuIKyJYRQpgE=
github.com/tinylib/msgp v1.1.0 h1:9fQd+ICuRIu/ue4vxJZu6/LzxN0HwMds2nq/0cFvxHU=
@ -618,11 +616,12 @@ github.com/yudai/golcs v0.0.0-20170316035057-ecda9a501e82 h1:BHyfKlQyqbsFN5p3Ifn
github.com/yudai/golcs v0.0.0-20170316035057-ecda9a501e82/go.mod h1:lgjkn3NuSvDfVJdfcVVdX+jpBxNmX4rDAzaS45IcYoM=
github.com/yudai/pp v2.0.1+incompatible h1:Q4//iY4pNF6yPLZIigmvcl7k/bPgrcTPIFIcmawg5bI=
github.com/yudai/pp v2.0.1+incompatible/go.mod h1:PuxR/8QJ7cyCkFp/aUDS+JY727OFEZkTdatxwunjIkc=
github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
go.etcd.io/bbolt v1.3.2/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU=
go.etcd.io/bbolt v1.3.5 h1:XAzx9gjCb0Rxj7EoqcClPD1d5ZBxZJk0jbuoPHenBt0=
go.etcd.io/bbolt v1.3.5/go.mod h1:G5EMThwa9y8QZGBClrRx5EY+Yw9kAhnjy3bSjsnlVTQ=
go.etcd.io/bbolt v1.3.6 h1:/ecaJf0sk1l4l6V4awd65v2C3ILy7MSj+s/x1ADCIMU=
go.etcd.io/bbolt v1.3.6/go.mod h1:qXsaaIqmgQH0T+OPdb99Bf+PKfBBQVAdyD6TY9G8XM4=
go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU=
go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8=
go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
@ -774,6 +773,7 @@ golang.org/x/sys v0.0.0-20200212091648-12a6c2dcc1e4/go.mod h1:h1NjWce9XRLGQEsW7w
golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200826173525-f9321e4c35a6/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200923182605-d9f96fdee20d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210119212857-b64e53b001e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
@ -799,7 +799,6 @@ golang.org/x/tools v0.0.0-20180221164845-07fd8470d635/go.mod h1:n7NCudcB/nEzxVGm
golang.org/x/tools v0.0.0-20180525024113-a5b4c53f6e8b/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-20181030221726-6c7e314b6563/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-20190206041539-40960b6deb8e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY=
@ -833,8 +832,10 @@ golang.org/x/tools v0.0.0-20200204074204-1cc6d1ef6c74/go.mod h1:TB2adYChydJhpapK
golang.org/x/tools v0.0.0-20200207183749-b753a1ba74fa/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/tools v0.0.0-20200212150539-ea181f53ac56/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/tools v0.0.0-20200304024140-c4206d458c3f/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw=
golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
golang.org/x/tools v0.0.0-20200721032237-77f530d86f9a/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA=
golang.org/x/tools v0.0.0-20201118003311-bd56c0adb394/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
golang.org/x/tools v0.1.0 h1:po9/4sTYwZU9lPhi1tOrb4hCv3qrhiQ77LZfGa2OjwY=
golang.org/x/tools v0.1.0/go.mod h1:xkSsbof2nBLbhDlRMhhhyNLN/zl3eTqcnHD5viDpcZ0=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=