diff --git a/.gitignore b/.gitignore index af98402..070d996 100644 --- a/.gitignore +++ b/.gitignore @@ -1,5 +1,8 @@ +go.work +go.work.sum + CHANGELOG.md -VERSION +/VERSION /test/results /dist /snapshot diff --git a/.goreleaser.yaml b/.goreleaser.yaml index 89e701e..b066d06 100644 --- a/.goreleaser.yaml +++ b/.goreleaser.yaml @@ -24,10 +24,10 @@ builds: -w -s -extldflags '-static' - -X github.com/anchore/quill/internal/version.version={{.Version}} - -X github.com/anchore/quill/internal/version.gitCommit={{.Commit}} - -X github.com/anchore/quill/internal/version.buildDate={{.Date}} - -X github.com/anchore/quill/internal/version.gitDescription={{.Summary}} + -X main.version={{.Version}} + -X main.gitCommit={{.Commit}} + -X main.buildDate={{.Date}} + -X main.gitDescription={{.Summary}} - id: darwin-build dir: *dir diff --git a/DEVELOPING.md b/DEVELOPING.md index 07914ea..dd7132c 100644 --- a/DEVELOPING.md +++ b/DEVELOPING.md @@ -115,7 +115,7 @@ The code signature data at the end of the `__LINKEDIT` segment can contain the f All of these blobs are contained within a single "super blob". -## Historical Notes +### Historical Notes - plist with CD hashes as a signed CMS attribute does not appear to be required (was implemented, and now removed) - sha256 nested set as a signed CMS attribute does not appear to be required (was implemented, and now removed) @@ -133,3 +133,67 @@ All of these blobs are contained within a single "super blob". - Apple's docs on notarization: - https://developer.apple.com/documentation/security/notarizing_macos_software_before_distribution - https://developer.apple.com/documentation/notaryapi/submitting_software_for_notarization_over_the_web + +## Architecture + +Quill is designed to be used as a CLI application as well as a library. When used as a library, there are two main +ways to use it: + +- Core API: the `github.com/anchore/quill/quill` package, which is where all of the core functionality resides. This is most common when using quill as a library. + +- CLI API: the `github.com/anchore/quill/cmd/quill/cli` package, which is a wrapper around the core functionality that + provides a CLI interface. This is helpful if you are trying to create a wrapping application that extends quill's functionality or are using the core API but want the same bubbletea UI interactions. + +Here's the 10,000 foot view of the quill repo: + +``` +quill/ +│ +├── cmd/ +│ └── quill/ +│ ├── cli/ # the CLI API +│ │ ├── commands/ # produce cobra commands +│ │ ├── options/ # composable CLI options used by commands +│ │ ├── ui/ # reusable bubbletea event handler (plugs into a bubbletea application) +│ │ └── cli.go +│ ├── internal/ # internal concerns for the CLI +│ │ └── ui/ # the bubbletea UI +│ └── main.go # entrypoint and target for ldflags (version info) +│ +├── internal/ # internal concerns needed by both the CLI and core API +│ ├── bus/ # global/singleton event bus +│ ├── log/ # globa/singleton logger +│ ├── test/ # common test assets and utilities +│ └── constants.go +│ +├── quill # the "top-level" or "core" API +│ ├── event/ # all events emitted by quill, with parsers, and parsed types +│ ├── extract/ # string display utils for macho binaries +│ ├── macho/ # all macho binary types, parsing, and manipulation utils +│ ├── notary/ # api client for Apple's notarization service +│ ├── pki/ # all PKI manipulation utils +│ │ ├── apple/ # Apple's PKI material +│ │ ├── certchain/ # utils for searching, sorting, and representing certificate chains +│ │ └── load/ # utils for reading certificates and key material safely +│ ├── sign/ # functions for creating the code signature data for macho binaries +│ ├── notarize.go # notarization API +│ ├── sign.go # signing API +│ └── lib.go +│ +└── test # high-level tests +``` + +# Known issues + +Unit test failure on main when using go 1.20+ with no changes (see [issue #35](https://github.com/anchore/quill/issues/35)): +``` +--- FAIL: TestSign (4.84s) + --- FAIL: TestSign/sign_the_syft_binary_(with_a_password) (0.02s) + sign_test.go:219: + Error Trace: /Users/wagoodman/code/quill/quill/sign_test.go:219 + Error: Received unexpected error: + unable to parse certificate 1 of 1: x509: certificate contains duplicate extensions + Test: TestSign/sign_the_syft_binary_(with_a_password) +``` +This is [new behavior in starting in go 1.20](https://groups.google.com/g/golang-checkins/c/nishT5TtWeo). The workaround +is to use go 1.19 or earlier, but this test fixture will need to be regenerated. diff --git a/cmd/quill/cli/application/application.go b/cmd/quill/cli/application/application.go deleted file mode 100644 index ff6cda1..0000000 --- a/cmd/quill/cli/application/application.go +++ /dev/null @@ -1,180 +0,0 @@ -package application - -import ( - "context" - "fmt" - "os" - "strings" - - "github.com/gookit/color" - "github.com/pkg/profile" - "github.com/spf13/cobra" - "github.com/wagoodman/go-partybus" - "gopkg.in/yaml.v3" - - "github.com/anchore/go-logger/adapter/logrus" - "github.com/anchore/quill/cmd/quill/cli/options" - "github.com/anchore/quill/internal" - "github.com/anchore/quill/internal/bus" - "github.com/anchore/quill/internal/eventloop" - "github.com/anchore/quill/internal/log" - "github.com/anchore/quill/internal/ui" - "github.com/anchore/quill/internal/utils" - "github.com/anchore/quill/internal/version" -) - -type Application struct { - Config *Config - subscription *partybus.Subscription -} - -func New() *Application { - return &Application{ - Config: &Config{}, - } -} - -func (a *Application) Setup(opts options.Interface) func(cmd *cobra.Command, args []string) error { - v := utils.NewViper() - return func(cmd *cobra.Command, args []string) error { - // bind options to viper - if opts != nil { - if err := opts.BindFlags(cmd.Flags(), v); err != nil { - return err - } - } - - if err := a.Config.BindFlags(cmd.Root().PersistentFlags(), v); err != nil { - return fmt.Errorf("unable to bind persistent flags: %w", err) - } - - if err := a.Config.Load(v); err != nil { - return fmt.Errorf("invalid application config: %v", err) - } - - // setup command config... - if opts != nil { - err := v.Unmarshal(opts) - if err != nil { - return fmt.Errorf("unable to unmarshal command configuration for cmd=%q: %w", strings.TrimSpace(cmd.CommandPath()), err) - } - } - - // setup logger... - if err := setupLogger(a.Config); err != nil { - return err - } - - // redact sensitive config values... - if opts != nil { - opts.Redact() - } - - // show the app version and configuration... - logVersion() - logConfiguration(a.Config, opts) - - // setup the event bus (before any publishers in the workers run)... - b := partybus.NewBus() - bus.SetPublisher(b) - a.subscription = b.Subscribe() - - return nil - } -} - -func (a Application) Run(ctx context.Context, errs <-chan error) error { - if a.Config.Dev.ProfileCPU { - defer profile.Start(profile.CPUProfile).Stop() - } else if a.Config.Dev.ProfileMem { - defer profile.Start(profile.MemProfile).Stop() - } - err := eventloop.Run( - ctx, - errs, - a.subscription, - nil, - ui.Select(ui.Config{ - Verbose: isVerbose(a.Config.Log.Verbosity), - Quiet: a.Config.Log.Quiet, - Debug: false, - })..., - ) - - if err != nil { - log.Error(err.Error()) - } - return err -} - -func logConfiguration(app *Config, opts interface{}) { - var optsStr string - - if opts != nil { - if stringer, ok := opts.(fmt.Stringer); ok { - optsStr = stringer.String() - } else { - // yaml is pretty human friendly (at least when compared to json) - cfgBytes, err := yaml.Marshal(&opts) - if err != nil { - optsStr = fmt.Sprintf("%+v", opts) - } else { - optsStr = string(cfgBytes) - } - } - } - - log.Debugf("config:\n%+v", formatConfig(app.String())+"\n"+formatConfig(optsStr)) -} - -func logVersion() { - versionInfo := version.FromBuild() - log.Infof("%s version: %+v", internal.ApplicationName, versionInfo.Version) -} - -func setupLogger(app *Config) error { - cfg := logrus.Config{ - //EnableConsole: (app.Log.FileLocation == "" || app.Log.Verbosity > 0) && !app.Log.Quiet, - EnableConsole: app.Log.Verbosity > 0 && !app.Log.Quiet, - FileLocation: app.Log.FileLocation, - Level: app.Log.Level, - } - - l, err := logrus.New(cfg) - if err != nil { - return err - } - - log.Set(l) - - return nil -} - -func formatConfig(config string) string { - return color.Magenta.Sprint(utils.Indent(strings.TrimSpace(config), " ")) -} - -func isVerbose(verbosity int) (result bool) { - pipedInput, err := isPipedInput() - if err != nil { - // since we can't tell if there was piped input we assume that there could be to disable the ETUI - log.Warnf("unable to determine if there is piped input: %+v", err) - return true - } - // verbosity should consider if there is piped input (in which case we should not show the ETUI) - return verbosity > 0 || pipedInput -} - -// isPipedInput returns true if there is no input device, which means the user **may** be providing input via a pipe. -func isPipedInput() (bool, error) { - fi, err := os.Stdin.Stat() - if err != nil { - return false, fmt.Errorf("unable to determine if there is piped input: %w", err) - } - - // note: we should NOT use the absence of a character device here as the hint that there may be input expected - // on stdin, as running this application as a subprocess you would expect no character device to be present but input can - // be from either stdin or indicated by the CLI. Checking if stdin is a pipe is the most direct way to determine - // if there *may* be bytes that will show up on stdin that should be used for the analysis source. - return fi.Mode()&os.ModeNamedPipe != 0, nil -} diff --git a/cmd/quill/cli/application/config.go b/cmd/quill/cli/application/config.go deleted file mode 100644 index 3e42395..0000000 --- a/cmd/quill/cli/application/config.go +++ /dev/null @@ -1,213 +0,0 @@ -package application - -import ( - "errors" - "fmt" - "path" - "reflect" - - "github.com/adrg/xdg" - "github.com/mitchellh/go-homedir" - "github.com/spf13/pflag" - "github.com/spf13/viper" - "gopkg.in/yaml.v2" - - "github.com/anchore/quill/cmd/quill/cli/options" - "github.com/anchore/quill/internal" -) - -var ConfigSearchLocations = []string{ - fmt.Sprintf(".%s.yaml", internal.ApplicationName), - fmt.Sprintf("%s.yaml", internal.ApplicationName), - fmt.Sprintf(".%s/config.yaml", internal.ApplicationName), - fmt.Sprintf("~/.%s.yaml", internal.ApplicationName), - fmt.Sprintf("~/%s.yaml", internal.ApplicationName), - fmt.Sprintf("$XDG_CONFIG_HOME/%s/config.yaml", internal.ApplicationName), -} - -var ErrConfigNotFound = fmt.Errorf("config not found") - -type defaultValueLoader interface { - loadDefaultValues(*viper.Viper) -} - -type parser interface { - parseConfigValues() error -} - -// Config is the main application configuration. -type Config struct { - ConfigPath string `yaml:"config,omitempty" json:"config"` // the location where the application config was read from (either from -c or discovered while loading) - Dev Development `yaml:"dev" json:"dev" mapstructure:"dev"` - Log Logging `yaml:"log" json:"log" mapstructure:"log"` // all logging-related options - - DisableLoadFromDisk bool `yaml:"-" json:"-" mapstructure:"-"` -} - -func (cfg *Config) BindFlags(flags *pflag.FlagSet, v *viper.Viper) error { - options.BindOrExit(v, "config", flags.Lookup("config")) - options.BindOrExit(v, "verbosity", flags.Lookup("verbose")) - options.BindOrExit(v, "quiet", flags.Lookup("quiet")) - - return nil -} - -// Load populates the given viper object with application configuration discovered on disk -func (cfg *Config) Load(v *viper.Viper) error { - // read the config from a specified path from the environment (only if not already preconfigured) - if cfg.ConfigPath == "" { - // unmarshal only control from viper in order to get config file path - var control struct { - ConfigPath string `yaml:"config" json:"config" mapstructure:"config"` - } - err := v.Unmarshal(&control) - if err != nil { - return fmt.Errorf("unable to unmarshal control section of application config: %w", err) - } - if control.ConfigPath != "" { - cfg.ConfigPath = control.ConfigPath - } - } - - // check if user specified config; otherwise read all possible paths - if !cfg.DisableLoadFromDisk && cfg.ConfigPath != "-" { - if err := readFromDisk(v, cfg.ConfigPath); err != nil { - return err - } - } - - // load default config values into viper - cfg.loadDefaultValues(v) - - // unmarshal fully populated viper object onto config - if err := v.Unmarshal(cfg); err != nil { - return fmt.Errorf("unable to unmarshal application config: %w", err) - } - - // Convert all populated config options to their internal application values ex: scope string => scopeOpt source.Scope - return cfg.parseConfigValues() -} - -// init loads the default configuration values into the viper instance (before the config values are read and parsed). -func (cfg Config) loadDefaultValues(v *viper.Viper) { - // for each field in the configuration struct, see if the field implements the defaultValueLoader interface and invoke it if it does - value := reflect.ValueOf(cfg) - for i := 0; i < value.NumField(); i++ { - // note: the defaultValueLoader method receiver is NOT a pointer receiver. - if loadable, ok := value.Field(i).Interface().(defaultValueLoader); ok { - // the field implements defaultValueLoader, call it - loadable.loadDefaultValues(v) - } - } -} - -func (cfg *Config) parseConfigValues() error { - // parse nested config options - // for each field in the configuration struct, see if the field implements the parser interface - // note: the app config is a pointer, so we need to grab the elements explicitly (to traverse the address) - value := reflect.ValueOf(cfg).Elem() - for i := 0; i < value.NumField(); i++ { - // note: since the interface method of parser is a pointer receiver we need to get the value of the field as a pointer. - if parsable, ok := value.Field(i).Addr().Interface().(parser); ok { - // the field implements parser, call it - if err := parsable.parseConfigValues(); err != nil { - return err - } - } - } - return nil -} - -func (cfg Config) String() string { - // yaml is pretty human friendly (at least when compared to json) - appCfgStr, err := yaml.Marshal(&cfg) - - if err != nil { - return err.Error() - } - - return string(appCfgStr) -} - -// readConfig attempts to read the given config path from disk or discover an alternate store location -// - -func readFromDisk(v *viper.Viper, configPath string) error { - var err error - // use explicitly the given user config - if configPath != "" { - v.SetConfigFile(configPath) - if err := v.ReadInConfig(); err != nil { - return fmt.Errorf("unable to read application config=%q: %w", configPath, err) - } - v.Set("config", v.ConfigFileUsed()) - // don't fall through to other options if the config path was explicitly provided - return nil - } - - // start searching for valid configs in order... - // 1. look for ..yaml (in the current directory) - v.AddConfigPath(".") - v.SetConfigName("." + internal.ApplicationName) - if err = v.ReadInConfig(); err == nil { - v.Set("config", v.ConfigFileUsed()) - return nil - } else if !errors.As(err, &viper.ConfigFileNotFoundError{}) { - return fmt.Errorf("unable to parse config=%q: %w", v.ConfigFileUsed(), err) - } - - // 2. look for .yaml (in the current directory) - v.AddConfigPath(".") - v.SetConfigName(internal.ApplicationName) - if err = v.ReadInConfig(); err == nil { - v.Set("config", v.ConfigFileUsed()) - return nil - } else if !errors.As(err, &viper.ConfigFileNotFoundError{}) { - return fmt.Errorf("unable to parse config=%q: %w", v.ConfigFileUsed(), err) - } - - // 3. look for ./config.yaml (in the current directory) - v.AddConfigPath("." + internal.ApplicationName) - v.SetConfigName("config") - if err = v.ReadInConfig(); err == nil { - v.Set("config", v.ConfigFileUsed()) - return nil - } else if !errors.As(err, &viper.ConfigFileNotFoundError{}) { - return fmt.Errorf("unable to parse config=%q: %w", v.ConfigFileUsed(), err) - } - - // 4. look for ~/..yaml && ~/.yaml - home, err := homedir.Dir() - if err == nil { - v.AddConfigPath(home) - v.SetConfigName("." + internal.ApplicationName) - if err = v.ReadInConfig(); err == nil { - v.Set("config", v.ConfigFileUsed()) - return nil - } else if !errors.As(err, &viper.ConfigFileNotFoundError{}) { - return fmt.Errorf("unable to parse config=%q: %w", v.ConfigFileUsed(), err) - } - - v.SetConfigName(internal.ApplicationName) - if err = v.ReadInConfig(); err == nil { - v.Set("config", v.ConfigFileUsed()) - return nil - } else if !errors.As(err, &viper.ConfigFileNotFoundError{}) { - return fmt.Errorf("unable to parse config=%q: %w", v.ConfigFileUsed(), err) - } - } - - // 5. look for /config.yaml in xdg locations (starting with xdg home config dir, then moving upwards) - v.AddConfigPath(path.Join(xdg.ConfigHome, internal.ApplicationName)) - for _, dir := range xdg.ConfigDirs { - v.AddConfigPath(path.Join(dir, internal.ApplicationName)) - } - v.SetConfigName("config") - if err = v.ReadInConfig(); err == nil { - v.Set("config", v.ConfigFileUsed()) - return nil - } else if !errors.As(err, &viper.ConfigFileNotFoundError{}) { - return fmt.Errorf("unable to parse config=%q: %w", v.ConfigFileUsed(), err) - } - return nil -} diff --git a/cmd/quill/cli/application/development.go b/cmd/quill/cli/application/development.go deleted file mode 100644 index 39da935..0000000 --- a/cmd/quill/cli/application/development.go +++ /dev/null @@ -1,13 +0,0 @@ -package application - -import "github.com/spf13/viper" - -type Development struct { - ProfileCPU bool `yaml:"profile-cpu" json:"profile-cpu" mapstructure:"profile-cpu"` - ProfileMem bool `yaml:"profile-mem" json:"profile-mem" mapstructure:"profile-mem"` -} - -func (c Development) loadDefaultValues(v *viper.Viper) { - v.SetDefault("dev.profile-cpu", c.ProfileCPU) // zero-value (false) or the current instance value - v.SetDefault("dev.profile-mem", c.ProfileMem) // zero-value (false) or the current instance value -} diff --git a/cmd/quill/cli/application/logging.go b/cmd/quill/cli/application/logging.go deleted file mode 100644 index 9f593f6..0000000 --- a/cmd/quill/cli/application/logging.go +++ /dev/null @@ -1,53 +0,0 @@ -package application - -import ( - "github.com/spf13/viper" - - "github.com/anchore/go-logger" -) - -// Logging contains all logging-related configuration options available to the user via the application config. -type Logging struct { - Quiet bool `yaml:"quiet" json:"quiet" mapstructure:"quiet" description:"suppress logging output"` // -q, indicates to not show any status output to stderr - Verbosity int `yaml:"-" json:"-" mapstructure:"verbosity"` // -v or -vv , controlling which UI (ETUI vs logging) and what the log level should be - Level logger.Level `yaml:"level" json:"level" mapstructure:"level" description:"error, warn, info, debug, trace"` // the log level string hint - FileLocation string `yaml:"file" json:"file" mapstructure:"file" description:"file to write all loge entries to"` // the file path to write logs to - - // not implemented upstream - // Structured bool `yaml:"structured" json:"structured" mapstructure:"structured"` // show all log entries as JSON formatted strings -} - -func (cfg Logging) loadDefaultValues(v *viper.Viper) { - v.SetDefault("log.level", string(logger.WarnLevel)) - v.SetDefault("log.file", cfg.FileLocation) -} - -func (cfg *Logging) parseConfigValues() error { - switch { - case cfg.Quiet: - // TODO: this is bad: quiet option trumps all other logging options (such as to a file on disk) - // we should be able to quiet the console logging and leave file logging alone... - // ... this will be an enhancement for later - cfg.Level = logger.DisabledLevel - - case cfg.Verbosity > 0: - // TODO: there is a panic in this function when specifying more verbosity than whats available - cfg.Level = logger.LevelFromVerbosity(cfg.Verbosity, logger.WarnLevel, logger.InfoLevel, logger.DebugLevel, logger.TraceLevel) - - case cfg.Level != "": - var err error - cfg.Level, err = logger.LevelFromString(string(cfg.Level)) - if err != nil { - return err - } - - if logger.IsVerbose(cfg.Level) { - cfg.Verbosity = 1 - } - default: - // TODO: set default warn - cfg.Level = logger.WarnLevel - } - - return nil -} diff --git a/cmd/quill/cli/cli.go b/cmd/quill/cli/cli.go index 121dd32..73c8277 100644 --- a/cmd/quill/cli/cli.go +++ b/cmd/quill/cli/cli.go @@ -1,33 +1,53 @@ package cli import ( + "os" + "github.com/spf13/cobra" - "github.com/anchore/quill/cmd/quill/cli/application" + "github.com/anchore/clio" "github.com/anchore/quill/cmd/quill/cli/commands" + "github.com/anchore/quill/cmd/quill/internal/ui" + "github.com/anchore/quill/internal/bus" + "github.com/anchore/quill/internal/log" + "github.com/anchore/quill/internal/redact" ) -type config struct { - app *application.Application -} +func New(id clio.Identification) *cobra.Command { + clioCfg := clio.NewSetupConfig(id). + WithGlobalConfigFlag(). // add persistent -c for reading an application config from + WithGlobalLoggingFlags(). // add persistent -v and -q flags tied to the logging config + WithConfigInRootHelp(). // --help on the root command renders the full application config in the help text + WithUIConstructor( + // select a UI based on the logging configuration and state of stdin (if stdin is a tty) + func(cfg clio.Config) ([]clio.UI, error) { + noUI := ui.None() + if !cfg.Log.AllowUI(os.Stdin) { + return []clio.UI{noUI}, nil + } -type Option func(*config) + return []clio.UI{ + ui.New(false, cfg.Log.Quiet), + noUI, + }, nil + }, + ). + WithInitializers( + func(state *clio.State) error { + // clio is setting up and providing the bus, redact store, and logger to the application. Once loaded, + // we can hoist them into the internal packages for global use. -func WithApplication(app *application.Application) Option { - return func(config *config) { - config.app = app - } -} + bus.Set(state.Bus) + redact.Set(state.RedactStore) + log.Set(state.Logger) -func New(opts ...Option) *cobra.Command { - cfg := &config{ - app: application.New(), - } - for _, fn := range opts { - fn(cfg) - } + return nil + }, + ) - app := cfg.app + app := clio.New(*clioCfg) + + root := commands.Root(app) submission := commands.Submission(app) submission.AddCommand(commands.SubmissionList(app)) @@ -41,8 +61,7 @@ func New(opts ...Option) *cobra.Command { p12.AddCommand(commands.P12AttachChain(app)) p12.AddCommand(commands.P12Describe(app)) - root := commands.Root(app) - root.AddCommand(commands.Version(app)) + root.AddCommand(clio.VersionCommand(id)) root.AddCommand(commands.Sign(app)) root.AddCommand(commands.Notarize(app)) root.AddCommand(commands.SignAndNotarize(app)) diff --git a/cmd/quill/cli/commands/describe.go b/cmd/quill/cli/commands/describe.go index 281c3b4..dce8b27 100644 --- a/cmd/quill/cli/commands/describe.go +++ b/cmd/quill/cli/commands/describe.go @@ -5,36 +5,20 @@ import ( "strings" "github.com/spf13/cobra" - "github.com/spf13/pflag" - "github.com/spf13/viper" - "github.com/anchore/quill/cmd/quill/cli/application" + "github.com/anchore/clio" "github.com/anchore/quill/cmd/quill/cli/options" "github.com/anchore/quill/internal/bus" "github.com/anchore/quill/quill/extract" ) -var _ options.Interface = &describeConfig{} - type describeConfig struct { - Path string `yaml:"path" json:"path" mapstructure:"path"` + Path string `yaml:"path" json:"path" mapstructure:"-"` options.Format `yaml:",inline" json:",inline" mapstructure:",squash"` options.Describe `yaml:"describe" json:"describe" mapstructure:"describe"` } -func (d *describeConfig) Redact() { - options.RedactAll(&d.Format, &d.Describe) -} - -func (d *describeConfig) AddFlags(flags *pflag.FlagSet) { - options.AddAllFlags(flags, &d.Format, &d.Describe) -} - -func (d *describeConfig) BindFlags(flags *pflag.FlagSet, v *viper.Viper) error { - return options.BindAllFlags(flags, v, &d.Format, &d.Describe) -} - -func Describe(app *application.Application) *cobra.Command { +func Describe(app clio.Application) *cobra.Command { opts := &describeConfig{ Format: options.Format{ Output: "text", @@ -42,7 +26,7 @@ func Describe(app *application.Application) *cobra.Command { }, } - cmd := &cobra.Command{ + return app.SetupCommand(&cobra.Command{ Use: "describe PATH", Short: "show the details of a macho binary", Example: options.FormatPositionalArgsHelp( @@ -57,32 +41,27 @@ func Describe(app *application.Application) *cobra.Command { return nil }, ), - PreRunE: app.Setup(opts), RunE: func(cmd *cobra.Command, args []string) error { - return app.Run(cmd.Context(), async(func() error { - var err error - buf := &strings.Builder{} - switch strings.ToLower(opts.Output) { - case "text": - err = extract.ShowText(opts.Path, buf, !opts.Detail) - case "json": - err = extract.ShowJSON(opts.Path, buf) - default: - err = fmt.Errorf("unknown format: %s", opts.Output) - } - - if err != nil { - return err - } + defer bus.Exit() - bus.Report(buf.String()) + var err error + buf := &strings.Builder{} + switch strings.ToLower(opts.Output) { + case "text": + err = extract.ShowText(opts.Path, buf, !opts.Detail) + case "json": + err = extract.ShowJSON(opts.Path, buf) + default: + err = fmt.Errorf("unknown format: %s", opts.Output) + } - return nil - })) - }, - } + if err != nil { + return err + } - commonConfiguration(app, cmd, opts) + bus.Report(buf.String()) - return cmd + return nil + }, + }, opts) } diff --git a/cmd/quill/cli/commands/embedded_certificates.go b/cmd/quill/cli/commands/embedded_certificates.go index 6277206..356777b 100644 --- a/cmd/quill/cli/commands/embedded_certificates.go +++ b/cmd/quill/cli/commands/embedded_certificates.go @@ -7,41 +7,36 @@ import ( "github.com/spf13/cobra" - "github.com/anchore/quill/cmd/quill/cli/application" + "github.com/anchore/clio" "github.com/anchore/quill/internal/bus" "github.com/anchore/quill/quill/pki/apple" ) -func EmbeddedCerts(app *application.Application) *cobra.Command { - cmd := &cobra.Command{ +func EmbeddedCerts(app clio.Application) *cobra.Command { + return app.SetupCommand(&cobra.Command{ Aliases: []string{ "embedded-certs", }, - Use: "embedded-certificates", - Short: "show the certificates embedded into quill (typically the Apple root and intermediate certs)", - Args: cobra.NoArgs, - PreRunE: app.Setup(nil), + Use: "embedded-certificates", + Short: "show the certificates embedded into quill (typically the Apple root and intermediate certs)", + Args: cobra.NoArgs, RunE: func(cmd *cobra.Command, args []string) error { - return app.Run(cmd.Context(), async(func() error { - var err error - buf := &strings.Builder{} + defer bus.Exit() - err = showAppleCerts(buf) + var err error + buf := &strings.Builder{} - if err != nil { - return err - } + err = showAppleCerts(buf) - bus.Report(buf.String()) + if err != nil { + return err + } - return nil - })) - }, - } + bus.Report(buf.String()) - commonConfiguration(app, cmd, nil) - - return cmd + return nil + }, + }) } func showAppleCerts(buf io.Writer) error { diff --git a/cmd/quill/cli/commands/extract.go b/cmd/quill/cli/commands/extract.go index d788888..6fac551 100644 --- a/cmd/quill/cli/commands/extract.go +++ b/cmd/quill/cli/commands/extract.go @@ -3,16 +3,13 @@ package commands import ( "github.com/spf13/cobra" - "github.com/anchore/quill/cmd/quill/cli/application" + "github.com/anchore/clio" ) -func Extract(_ *application.Application) *cobra.Command { - cmd := &cobra.Command{ +func Extract(app clio.Application) *cobra.Command { + return app.SetupCommand(&cobra.Command{ Use: "extract", Short: "extract information from a macho binary", Args: cobra.NoArgs, - } - - commonConfiguration(nil, cmd, nil) - return cmd + }) } diff --git a/cmd/quill/cli/commands/extract_certificates.go b/cmd/quill/cli/commands/extract_certificates.go index e1d383e..cea7906 100644 --- a/cmd/quill/cli/commands/extract_certificates.go +++ b/cmd/quill/cli/commands/extract_certificates.go @@ -7,24 +7,22 @@ import ( "github.com/scylladb/go-set/strset" "github.com/spf13/cobra" - "github.com/anchore/quill/cmd/quill/cli/application" + "github.com/anchore/clio" "github.com/anchore/quill/cmd/quill/cli/options" "github.com/anchore/quill/internal/bus" "github.com/anchore/quill/internal/log" "github.com/anchore/quill/quill/extract" ) -var _ options.Interface = &extractCertificatesConfig{} - type extractCertificatesConfig struct { - Path string `yaml:"path" json:"path" mapstructure:"path"` + Path string `yaml:"path" json:"path" mapstructure:"-"` options.ExtractCertificates `yaml:"extract-certificates" json:"extract-certificates" mapstructure:"extract-certificates"` } -func ExtractCertificates(app *application.Application) *cobra.Command { +func ExtractCertificates(app clio.Application) *cobra.Command { opts := &extractCertificatesConfig{} - cmd := &cobra.Command{ + return app.SetupCommand(&cobra.Command{ Aliases: []string{ "certs", }, @@ -42,25 +40,20 @@ func ExtractCertificates(app *application.Application) *cobra.Command { return nil }, ), - PreRunE: app.Setup(opts), RunE: func(cmd *cobra.Command, args []string) error { - return app.Run(cmd.Context(), async(func() error { - certs, err := extractCertificates(opts.Path, opts.Leaf) - if err != nil { - return err - } - - bus.Report(certs) - bus.Notify("Try running 'openssl x509 -text -in .pem' to view the certificate details") + defer bus.Exit() - return nil - })) - }, - } + certs, err := extractCertificates(opts.Path, opts.Leaf) + if err != nil { + return err + } - commonConfiguration(app, cmd, opts) + bus.Report(certs) + bus.Notify("Try running 'openssl x509 -text -in .pem' to view the certificate details") - return cmd + return nil + }, + }, opts) } func extractCertificates(binPath string, leaf bool) (string, error) { diff --git a/cmd/quill/cli/commands/notarize.go b/cmd/quill/cli/commands/notarize.go index 409468e..55b73b3 100644 --- a/cmd/quill/cli/commands/notarize.go +++ b/cmd/quill/cli/commands/notarize.go @@ -4,47 +4,35 @@ import ( "time" "github.com/spf13/cobra" - "github.com/spf13/pflag" - "github.com/spf13/viper" - "github.com/anchore/quill/cmd/quill/cli/application" + "github.com/anchore/clio" + "github.com/anchore/fangs" "github.com/anchore/quill/cmd/quill/cli/options" + "github.com/anchore/quill/internal/bus" "github.com/anchore/quill/internal/log" "github.com/anchore/quill/quill" "github.com/anchore/quill/quill/notary" ) -var _ options.Interface = ¬arizeConfig{} +var _ fangs.FlagAdder = (*notarizeConfig)(nil) type notarizeConfig struct { - Path string `yaml:"path" json:"path" mapstructure:"path"` + Path string `yaml:"path" json:"path" mapstructure:"-"` options.Notary `yaml:"notary" json:"notary" mapstructure:"notary"` options.Status `yaml:"status" json:"status" mapstructure:"status"` DryRun bool `yaml:"dry-run" json:"dry-run" mapstructure:"dry-run"` } -func (o *notarizeConfig) Redact() { - options.RedactAll(&o.Notary, &o.Status) +func (o *notarizeConfig) AddFlags(flags fangs.FlagSet) { + flags.BoolVarP(&o.DryRun, "dry-run", "", "dry run mode (do not actually notarize)") } -func (o *notarizeConfig) AddFlags(flags *pflag.FlagSet) { - flags.BoolVar(&o.DryRun, "dry-run", o.DryRun, "dry run mode (do not actually notarize)") - options.AddAllFlags(flags, &o.Notary, &o.Status) -} - -func (o *notarizeConfig) BindFlags(flags *pflag.FlagSet, v *viper.Viper) error { - if err := options.Bind(v, "dry-run", flags.Lookup("dry-run")); err != nil { - return err - } - return options.BindAllFlags(flags, v, &o.Notary, &o.Status) -} - -func Notarize(app *application.Application) *cobra.Command { +func Notarize(app clio.Application) *cobra.Command { opts := ¬arizeConfig{ Status: options.DefaultStatus(), } - cmd := &cobra.Command{ + return app.SetupCommand(&cobra.Command{ Use: "notarize PATH", Short: "notarize a signed a macho binary with Apple's Notary service", Example: options.FormatPositionalArgsHelp( @@ -59,24 +47,19 @@ func Notarize(app *application.Application) *cobra.Command { return nil }, ), - PreRunE: app.Setup(opts), RunE: func(cmd *cobra.Command, args []string) error { - return app.Run(cmd.Context(), async(func() error { - // TODO: verify path is a signed darwin binary - // ... however, we may want to allow notarization of other kinds of assets (zip with darwin binary, etc) - if opts.DryRun { - log.Warn("[DRY RUN] skipping notarization...") - return nil - } - _, err := notarize(opts.Path, opts.Notary, opts.Status) - return err - })) - }, - } - - commonConfiguration(app, cmd, opts) + defer bus.Exit() - return cmd + // TODO: verify path is a signed darwin binary + // ... however, we may want to allow notarization of other kinds of assets (zip with darwin binary, etc) + if opts.DryRun { + log.Warn("[DRY RUN] skipping notarization...") + return nil + } + _, err := notarize(opts.Path, opts.Notary, opts.Status) + return err + }, + }, opts) } func notarize(binPath string, notaryCfg options.Notary, statusCfg options.Status) (notary.SubmissionStatus, error) { diff --git a/cmd/quill/cli/commands/p12.go b/cmd/quill/cli/commands/p12.go index 76bb102..abaece7 100644 --- a/cmd/quill/cli/commands/p12.go +++ b/cmd/quill/cli/commands/p12.go @@ -3,16 +3,13 @@ package commands import ( "github.com/spf13/cobra" - "github.com/anchore/quill/cmd/quill/cli/application" + "github.com/anchore/clio" ) -func P12(_ *application.Application) *cobra.Command { - cmd := &cobra.Command{ +func P12(app clio.Application) *cobra.Command { + return app.SetupCommand(&cobra.Command{ Use: "p12", Short: "describe and manipulate p12 files", Args: cobra.NoArgs, - } - - commonConfiguration(nil, cmd, nil) - return cmd + }) } diff --git a/cmd/quill/cli/commands/p12_attach_chain.go b/cmd/quill/cli/commands/p12_attach_chain.go index 67a04da..dc0851d 100644 --- a/cmd/quill/cli/commands/p12_attach_chain.go +++ b/cmd/quill/cli/commands/p12_attach_chain.go @@ -2,52 +2,36 @@ package commands import ( "crypto/rand" + "crypto/x509" "fmt" "os" "strings" "github.com/spf13/cobra" - "github.com/spf13/pflag" - "github.com/spf13/viper" "software.sslmate.com/src/go-pkcs12" - "github.com/anchore/quill/cmd/quill/cli/application" + "github.com/anchore/clio" "github.com/anchore/quill/cmd/quill/cli/options" "github.com/anchore/quill/internal/bus" "github.com/anchore/quill/internal/log" "github.com/anchore/quill/quill/pki/apple" "github.com/anchore/quill/quill/pki/certchain" - "github.com/anchore/quill/quill/pki/load" ) -var _ options.Interface = &p12AttachChainConfig{} - type p12AttachChainConfig struct { - Path string `yaml:"path" json:"path" mapstructure:"path"` + Path string `yaml:"path" json:"path" mapstructure:"-"` options.Keychain `yaml:"keychain" json:"keychain" mapstructure:"keychain"` options.P12 `yaml:"p12" json:"p12" mapstructure:"p12"` } -func (p *p12AttachChainConfig) Redact() { - options.RedactAll(&p.P12, &p.Keychain) -} - -func (p *p12AttachChainConfig) AddFlags(set *pflag.FlagSet) { - options.AddAllFlags(set, &p.P12, &p.Keychain) -} - -func (p *p12AttachChainConfig) BindFlags(set *pflag.FlagSet, viper *viper.Viper) error { - return options.BindAllFlags(set, viper, &p.P12, &p.Keychain) -} - -func P12AttachChain(app *application.Application) *cobra.Command { +func P12AttachChain(app clio.Application) *cobra.Command { opts := &p12AttachChainConfig{ Keychain: options.Keychain{ Path: "/System/Library/Keychains/SystemRootCertificates.keychain", }, } - cmd := &cobra.Command{ + return app.SetupCommand(&cobra.Command{ Use: "attach-chain PATH", Short: "pack full Apple certificate chain into a p12 file", Long: "The p12 file you download from Apple contains a single private key and signing certificate. In order for " + @@ -69,48 +53,43 @@ func P12AttachChain(app *application.Application) *cobra.Command { return nil }, ), - PreRunE: app.Setup(opts), RunE: func(cmd *cobra.Command, args []string) error { - return app.Run(cmd.Context(), async(func() error { - newFilename, err := writeP12WithChain(opts.Path, opts.P12.Password, opts.Keychain.Path, true) - if err != nil { - return fmt.Errorf("unable to write new p12 with chain attached file=%q : %w", opts.Path, err) - } - - description, err := describeP12(newFilename, opts.P12.Password) - if err != nil { - return fmt.Errorf("unable to describe p12 file=%q : %w", newFilename, err) - } + defer bus.Exit() - bus.Report(description) - bus.Notify(fmt.Sprintf("Wrote new p12 file with certificate chain to %q", newFilename)) + newFilename, err := writeP12WithChain(opts.Path, opts.P12.Password, opts.Keychain.Path, true) + if err != nil { + return fmt.Errorf("unable to write new p12 with chain attached file=%q : %w", opts.Path, err) + } - return nil - })) - }, - } + description, err := describeP12(newFilename, opts.P12.Password) + if err != nil { + return fmt.Errorf("unable to describe p12 file=%q : %w", newFilename, err) + } - commonConfiguration(app, cmd, opts) + bus.Report(description) + bus.Notify(fmt.Sprintf("Wrote new p12 file with certificate chain to %q", newFilename)) - return cmd + return nil + }, + }, opts) } func writeP12WithChain(p12Path, password, keychainPath string, failWithoutFullChain bool) (string, error) { log.WithFields("file", p12Path).Info("attaching certificate chain to p12 file") - key, cert, certs, err := load.P12(p12Path, password) + p12Contents, err := loadP12Interactively(p12Path, password) if err != nil { return "", err } - if cert == nil { + if p12Contents.Certificate == nil { return "", fmt.Errorf("unable to find signing certificate in p12") } - log.WithFields("chain-certs", len(certs), "signing-cert", fmt.Sprintf("%q", cert.Subject.CommonName)).Debug("existing p12 contents") + log.WithFields("chain-certs", len(p12Contents.Certificates), "signing-cert", fmt.Sprintf("%q", p12Contents.Certificate.Subject.CommonName)).Debug("existing p12 contents") - if len(certs) > 0 { - return "", fmt.Errorf("p12 file already has the certificate chain embedded (chain length %d + 1 signing certificate)", len(certs)) + if len(p12Contents.Certificates) > 0 { + return "", fmt.Errorf("p12 file already has the certificate chain embedded (chain length %d + 1 signing certificate)", len(p12Contents.Certificates)) } store := certchain.NewCollection().WithStores(apple.GetEmbeddedCertStore()) @@ -118,19 +97,20 @@ func writeP12WithChain(p12Path, password, keychainPath string, failWithoutFullCh store = store.WithSearchers(apple.NewKeychainSearcher(keychainPath)) } - remainingCerts, err := certchain.Find(store, cert) + var certs = append([]*x509.Certificate{}, p12Contents.Certificates...) + remainingCerts, err := certchain.Find(store, p12Contents.Certificate) if err != nil { return "", fmt.Errorf("unable to find remaining chain certificates: %w", err) } certs = append(certs, remainingCerts...) - p12Bytes, err := pkcs12.Encode(rand.Reader, key, cert, certs, password) + p12Bytes, err := pkcs12.Encode(rand.Reader, p12Contents.PrivateKey, p12Contents.Certificate, certs, password) if err != nil { return "", fmt.Errorf("unable to encode p12 file: %w", err) } // verify the cert chain before writing... - if err := certchain.VerifyForCodeSigning(append(certs, cert), failWithoutFullChain); err != nil { + if err := certchain.VerifyForCodeSigning(append(certs, p12Contents.Certificate), failWithoutFullChain); err != nil { return "", err } diff --git a/cmd/quill/cli/commands/p12_describe.go b/cmd/quill/cli/commands/p12_describe.go index e0e16f3..cd00b00 100644 --- a/cmd/quill/cli/commands/p12_describe.go +++ b/cmd/quill/cli/commands/p12_describe.go @@ -8,23 +8,20 @@ import ( "github.com/spf13/cobra" - "github.com/anchore/quill/cmd/quill/cli/application" + "github.com/anchore/clio" "github.com/anchore/quill/cmd/quill/cli/options" "github.com/anchore/quill/internal/bus" - "github.com/anchore/quill/quill/pki/load" ) -var _ options.Interface = &p12DescribeConfig{} - type p12DescribeConfig struct { - Path string `yaml:"path" json:"path" mapstructure:"path"` + Path string `yaml:"path" json:"path" mapstructure:"-"` options.P12 `yaml:"p12" json:"p12" mapstructure:"p12"` } -func P12Describe(app *application.Application) *cobra.Command { +func P12Describe(app clio.Application) *cobra.Command { opts := &p12DescribeConfig{} - cmd := &cobra.Command{ + return app.SetupCommand(&cobra.Command{ Use: "describe PATH", Short: "describe the contents of a p12 file", Example: options.FormatPositionalArgsHelp( @@ -39,37 +36,32 @@ func P12Describe(app *application.Application) *cobra.Command { return nil }, ), - PreRunE: app.Setup(opts), RunE: func(cmd *cobra.Command, args []string) error { - return app.Run(cmd.Context(), async(func() error { - description, err := describeP12(opts.Path, opts.Password) - if err != nil { - return err - } - - bus.Report(description) + defer bus.Exit() - return nil - })) - }, - } + description, err := describeP12(opts.Path, opts.Password) + if err != nil { + return err + } - commonConfiguration(app, cmd, opts) + bus.Report(description) - return cmd + return nil + }, + }, opts) } func describeP12(file, password string) (string, error) { - key, cert, certs, err := load.P12(file, password) + p12Contents, err := loadP12Interactively(file, password) if err != nil { return "", err } buf := strings.Builder{} - if key != nil { + if p12Contents.PrivateKey != nil { buf.WriteString("Private Key:\n") - buf.WriteString(fmt.Sprintf(" - %+v exists\n", reflect.TypeOf(key).Elem().String())) + buf.WriteString(fmt.Sprintf(" - %+v exists\n", reflect.TypeOf(p12Contents.PrivateKey).Elem().String())) } else { buf.WriteString("Private Key: (none)\n") } @@ -80,15 +72,15 @@ func describeP12(file, password string) (string, error) { buf.WriteString(fmt.Sprintf(" Authority-Key-ID: %x\n", c.AuthorityKeyId)) } - if cert != nil { + if p12Contents.Certificate != nil { buf.WriteString("Signing Certificate:\n") - summarizeCert(cert) + summarizeCert(p12Contents.Certificate) } else { buf.WriteString("Signing Certificate: (none)\n") } - buf.WriteString(fmt.Sprintf("Certificate Chain: (%d)\n", len(certs))) - for _, c := range certs { + buf.WriteString(fmt.Sprintf("Certificate Chain: (%d)\n", len(p12Contents.Certificates))) + for _, c := range p12Contents.Certificates { summarizeCert(c) } return buf.String(), nil diff --git a/cmd/quill/cli/commands/root.go b/cmd/quill/cli/commands/root.go index 7a18487..f190c87 100644 --- a/cmd/quill/cli/commands/root.go +++ b/cmd/quill/cli/commands/root.go @@ -1,52 +1,11 @@ package commands import ( - "fmt" - "strings" - "github.com/spf13/cobra" - "github.com/spf13/viper" - "github.com/anchore/quill/cmd/quill/cli/application" - "github.com/anchore/quill/cmd/quill/cli/options" - "github.com/anchore/quill/internal" - "github.com/anchore/quill/internal/utils" - "github.com/anchore/quill/internal/version" + "github.com/anchore/clio" ) -func Root(app *application.Application) *cobra.Command { - opts := app.Config - - cmd := &cobra.Command{ - Use: "", - Version: version.FromBuild().Version, - PreRunE: app.Setup(nil), - Example: formatRootExamples(), - } - - commonConfiguration(nil, cmd, nil) - - cmd.SetVersionTemplate(fmt.Sprintf("%s {{.Version}}\n", internal.ApplicationName)) - - flags := cmd.PersistentFlags() - - flags.StringVarP(&opts.ConfigPath, "config", "c", "", "application config file") - flags.CountVarP(&opts.Log.Verbosity, "verbose", "v", "increase verbosity (-v = info, -vv = debug)") - flags.BoolVarP(&opts.Log.Quiet, "quiet", "q", false, "suppress all logging output") - - return cmd -} - -func formatRootExamples() string { - cfg := application.Config{ - DisableLoadFromDisk: true, - } - // best effort to load current or default values - // intentionally don't read from the environment - _ = cfg.Load(viper.New()) - - cfgString := utils.Indent(options.Summarize(cfg, nil), " ") - return fmt.Sprintf(`Application Config: - (search locations: %+v) -%s`, strings.Join(application.ConfigSearchLocations, ", "), strings.TrimSuffix(cfgString, "\n")) +func Root(app clio.Application) *cobra.Command { + return app.SetupRootCommand(&cobra.Command{}) } diff --git a/cmd/quill/cli/commands/sign.go b/cmd/quill/cli/commands/sign.go index f6351ec..3f7f0b7 100644 --- a/cmd/quill/cli/commands/sign.go +++ b/cmd/quill/cli/commands/sign.go @@ -5,25 +5,24 @@ import ( "github.com/spf13/cobra" - "github.com/anchore/quill/cmd/quill/cli/application" + "github.com/anchore/clio" "github.com/anchore/quill/cmd/quill/cli/options" + "github.com/anchore/quill/internal/bus" "github.com/anchore/quill/internal/log" "github.com/anchore/quill/quill" ) -var _ options.Interface = &signConfig{} - type signConfig struct { - Path string `yaml:"path" json:"path" mapstructure:"path"` + Path string `yaml:"path" json:"path" mapstructure:"-"` options.Signing `yaml:"sign" json:"sign" mapstructure:"sign"` } -func Sign(app *application.Application) *cobra.Command { +func Sign(app clio.Application) *cobra.Command { opts := &signConfig{ Signing: options.DefaultSigning(), } - cmd := &cobra.Command{ + return app.SetupCommand(&cobra.Command{ Use: "sign PATH", Short: "sign a macho (darwin) executable binary", Example: options.FormatPositionalArgsHelp( @@ -38,17 +37,12 @@ func Sign(app *application.Application) *cobra.Command { return nil }, ), - PreRunE: app.Setup(opts), RunE: func(cmd *cobra.Command, args []string) error { - return app.Run(cmd.Context(), async(func() error { - return sign(opts.Path, opts.Signing) - })) - }, - } + defer bus.Exit() - commonConfiguration(app, cmd, opts) - - return cmd + return sign(opts.Path, opts.Signing) + }, + }, opts) } func sign(binPath string, opts options.Signing) error { @@ -60,7 +54,15 @@ func sign(binPath string, opts options.Signing) error { if opts.AdHoc { log.Warn("ad-hoc signing is enabled, but a p12 file was also provided. The p12 file will be ignored.") } else { - replacement, err := quill.NewSigningConfigFromP12(binPath, opts.P12, opts.Password, opts.FailWithoutFullChain) + p12Content, err := loadP12Interactively(opts.P12, opts.Password) + if err != nil { + return fmt.Errorf("unable to decode p12 file: %w", err) + } + if p12Content == nil { + return fmt.Errorf("no content found in the p12 file") + } + + replacement, err := quill.NewSigningConfigFromP12(binPath, *p12Content, opts.FailWithoutFullChain) if err != nil { return fmt.Errorf("unable to read p12: %w", err) } diff --git a/cmd/quill/cli/commands/sign_and_notarize.go b/cmd/quill/cli/commands/sign_and_notarize.go index 89e8b74..822ab20 100644 --- a/cmd/quill/cli/commands/sign_and_notarize.go +++ b/cmd/quill/cli/commands/sign_and_notarize.go @@ -4,47 +4,35 @@ import ( "fmt" "github.com/spf13/cobra" - "github.com/spf13/pflag" - "github.com/spf13/viper" - "github.com/anchore/quill/cmd/quill/cli/application" + "github.com/anchore/clio" + "github.com/anchore/fangs" "github.com/anchore/quill/cmd/quill/cli/options" + "github.com/anchore/quill/internal/bus" "github.com/anchore/quill/internal/log" ) -var _ options.Interface = &signConfig{} +var _ fangs.FlagAdder = &signAndNotarizeConfig{} type signAndNotarizeConfig struct { - Path string `yaml:"path" json:"path" mapstructure:"path"` + Path string `yaml:"path" json:"path" mapstructure:"-"` options.Signing `yaml:"sign" json:"sign" mapstructure:"sign"` options.Notary `yaml:"notary" json:"notary" mapstructure:"notary"` options.Status `yaml:"status" json:"status" mapstructure:"status"` DryRun bool `yaml:"dry-run" json:"dry-run" mapstructure:"dry-run"` } -func (o *signAndNotarizeConfig) Redact() { - options.RedactAll(&o.Notary, &o.Status, &o.Signing) +func (o *signAndNotarizeConfig) AddFlags(flags fangs.FlagSet) { + flags.BoolVarP(&o.DryRun, "dry-run", "", "dry run mode (do not actually notarize)") } -func (o *signAndNotarizeConfig) AddFlags(flags *pflag.FlagSet) { - flags.BoolVar(&o.DryRun, "dry-run", o.DryRun, "dry run mode (do not actually notarize)") - options.AddAllFlags(flags, &o.Notary, &o.Status, &o.Signing) -} - -func (o *signAndNotarizeConfig) BindFlags(flags *pflag.FlagSet, v *viper.Viper) error { - if err := options.Bind(v, "dry-run", flags.Lookup("dry-run")); err != nil { - return err - } - return options.BindAllFlags(flags, v, &o.Notary, &o.Status, &o.Signing) -} - -func SignAndNotarize(app *application.Application) *cobra.Command { +func SignAndNotarize(app clio.Application) *cobra.Command { opts := &signAndNotarizeConfig{ Status: options.DefaultStatus(), Signing: options.DefaultSigning(), } - cmd := &cobra.Command{ + return app.SetupCommand(&cobra.Command{ Use: "sign-and-notarize PATH", Short: "sign and notarize a macho (darwin) executable binary", Example: options.FormatPositionalArgsHelp( @@ -59,30 +47,25 @@ func SignAndNotarize(app *application.Application) *cobra.Command { return nil }, ), - PreRunE: app.Setup(opts), RunE: func(cmd *cobra.Command, args []string) error { - return app.Run(cmd.Context(), async(func() error { - err := sign(opts.Path, opts.Signing) - if err != nil { - return fmt.Errorf("signing failed: %w", err) - } + defer bus.Exit() - if opts.DryRun { - log.Warn("[DRY RUN] skipping notarization...") - return nil - } - - _, err = notarize(opts.Path, opts.Notary, opts.Status) - if err != nil { - return fmt.Errorf("notarization failed: %w", err) - } + err := sign(opts.Path, opts.Signing) + if err != nil { + return fmt.Errorf("signing failed: %w", err) + } + if opts.DryRun { + log.Warn("[DRY RUN] skipping notarization...") return nil - })) - }, - } + } - commonConfiguration(app, cmd, opts) + _, err = notarize(opts.Path, opts.Notary, opts.Status) + if err != nil { + return fmt.Errorf("notarization failed: %w", err) + } - return cmd + return nil + }, + }, opts) } diff --git a/cmd/quill/cli/commands/submission.go b/cmd/quill/cli/commands/submission.go index cb41dee..2725b1e 100644 --- a/cmd/quill/cli/commands/submission.go +++ b/cmd/quill/cli/commands/submission.go @@ -3,16 +3,13 @@ package commands import ( "github.com/spf13/cobra" - "github.com/anchore/quill/cmd/quill/cli/application" + "github.com/anchore/clio" ) -func Submission(_ *application.Application) *cobra.Command { - cmd := &cobra.Command{ +func Submission(app clio.Application) *cobra.Command { + return app.SetupCommand(&cobra.Command{ Use: "submission", Short: "query Apple's Notary service for submission information", Args: cobra.NoArgs, - } - - commonConfiguration(nil, cmd, nil) - return cmd + }) } diff --git a/cmd/quill/cli/commands/submission_list.go b/cmd/quill/cli/commands/submission_list.go index a9f7e28..439d2e2 100644 --- a/cmd/quill/cli/commands/submission_list.go +++ b/cmd/quill/cli/commands/submission_list.go @@ -6,7 +6,7 @@ import ( "github.com/jedib0t/go-pretty/table" "github.com/spf13/cobra" - "github.com/anchore/quill/cmd/quill/cli/application" + "github.com/anchore/clio" "github.com/anchore/quill/cmd/quill/cli/options" "github.com/anchore/quill/internal/bus" "github.com/anchore/quill/internal/log" @@ -14,63 +14,56 @@ import ( "github.com/anchore/quill/quill/notary" ) -var _ options.Interface = &submissionListConfig{} - type submissionListConfig struct { options.Notary `yaml:"notary" json:"notary" mapstructure:"notary"` } -func SubmissionList(app *application.Application) *cobra.Command { +func SubmissionList(app clio.Application) *cobra.Command { opts := &submissionListConfig{} - cmd := &cobra.Command{ - Use: "list", - Short: "list previous submissions to Apple's Notary service", - Args: cobra.NoArgs, - PreRunE: app.Setup(opts), + return app.SetupCommand(&cobra.Command{ + Use: "list", + Short: "list previous submissions to Apple's Notary service", + Args: cobra.NoArgs, RunE: func(cmd *cobra.Command, args []string) error { - return app.Run(cmd.Context(), async(func() error { - log.Info("fetching previous submissions") - - cfg := quill.NewNotarizeConfig( - opts.Notary.Issuer, - opts.Notary.PrivateKeyID, - opts.Notary.PrivateKey, - ) + defer bus.Exit() - token, err := notary.NewSignedToken(cfg.TokenConfig) - if err != nil { - return err - } + log.Info("fetching previous submissions") - a := notary.NewAPIClient(token, cfg.HTTPTimeout) + cfg := quill.NewNotarizeConfig( + opts.Notary.Issuer, + opts.Notary.PrivateKeyID, + opts.Notary.PrivateKey, + ) - sub := notary.ExistingSubmission(a, "") + token, err := notary.NewSignedToken(cfg.TokenConfig) + if err != nil { + return err + } - submissions, err := sub.List(context.Background()) - if err != nil { - return err - } + a := notary.NewAPIClient(token, cfg.HTTPTimeout) - // show list report + sub := notary.ExistingSubmission(a, "") - t := table.NewWriter() - t.SetStyle(table.StyleLight) + submissions, err := sub.List(context.Background()) + if err != nil { + return err + } - t.AppendHeader(table.Row{"ID", "Name", "Status", "Created"}) + // show list report - for _, item := range submissions { - t.AppendRow(table.Row{item.ID, item.Name, item.Status, item.CreatedDate}) - } + t := table.NewWriter() + t.SetStyle(table.StyleLight) - bus.Report(t.Render()) + t.AppendHeader(table.Row{"ID", "Name", "Status", "Created"}) - return nil - })) - }, - } + for _, item := range submissions { + t.AppendRow(table.Row{item.ID, item.Name, item.Status, item.CreatedDate}) + } - commonConfiguration(app, cmd, opts) + bus.Report(t.Render()) - return cmd + return nil + }, + }, opts) } diff --git a/cmd/quill/cli/commands/submission_logs.go b/cmd/quill/cli/commands/submission_logs.go index aada6e1..cbc06c5 100644 --- a/cmd/quill/cli/commands/submission_logs.go +++ b/cmd/quill/cli/commands/submission_logs.go @@ -3,7 +3,7 @@ package commands import ( "github.com/spf13/cobra" - "github.com/anchore/quill/cmd/quill/cli/application" + "github.com/anchore/clio" "github.com/anchore/quill/cmd/quill/cli/options" "github.com/anchore/quill/internal/bus" "github.com/anchore/quill/internal/log" @@ -11,17 +11,15 @@ import ( "github.com/anchore/quill/quill/notary" ) -var _ options.Interface = &submissionLogsConfig{} - type submissionLogsConfig struct { - ID string `yaml:"id" json:"id" mapstructure:"id"` + ID string `yaml:"id" json:"id" mapstructure:"-"` options.Notary `yaml:"notary" json:"notary" mapstructure:"notary"` } -func SubmissionLogs(app *application.Application) *cobra.Command { +func SubmissionLogs(app clio.Application) *cobra.Command { opts := &submissionLogsConfig{} - cmd := &cobra.Command{ + return app.SetupCommand(&cobra.Command{ Use: "logs SUBMISSION_ID", Short: "fetch logs for an existing submission from Apple's Notary service", Example: options.FormatPositionalArgsHelp( @@ -36,39 +34,34 @@ func SubmissionLogs(app *application.Application) *cobra.Command { return nil }, ), - PreRunE: app.Setup(opts), RunE: func(cmd *cobra.Command, args []string) error { - return app.Run(cmd.Context(), async(func() error { - log.Infof("fetching submission logs for %q", opts.ID) + defer bus.Exit() - cfg := quill.NewNotarizeConfig( - opts.Notary.Issuer, - opts.Notary.PrivateKeyID, - opts.Notary.PrivateKey, - ) + log.Infof("fetching submission logs for %q", opts.ID) - token, err := notary.NewSignedToken(cfg.TokenConfig) - if err != nil { - return err - } + cfg := quill.NewNotarizeConfig( + opts.Notary.Issuer, + opts.Notary.PrivateKeyID, + opts.Notary.PrivateKey, + ) - a := notary.NewAPIClient(token, cfg.HTTPTimeout) + token, err := notary.NewSignedToken(cfg.TokenConfig) + if err != nil { + return err + } - sub := notary.ExistingSubmission(a, opts.ID) + a := notary.NewAPIClient(token, cfg.HTTPTimeout) - content, err := sub.Logs(cmd.Context()) - if err != nil { - return err - } + sub := notary.ExistingSubmission(a, opts.ID) - bus.Report(content) + content, err := sub.Logs(cmd.Context()) + if err != nil { + return err + } - return nil - })) - }, - } + bus.Report(content) - commonConfiguration(app, cmd, opts) - - return cmd + return nil + }, + }, opts) } diff --git a/cmd/quill/cli/commands/submission_status.go b/cmd/quill/cli/commands/submission_status.go index bbb844c..0b52549 100644 --- a/cmd/quill/cli/commands/submission_status.go +++ b/cmd/quill/cli/commands/submission_status.go @@ -4,10 +4,8 @@ import ( "time" "github.com/spf13/cobra" - "github.com/spf13/pflag" - "github.com/spf13/viper" - "github.com/anchore/quill/cmd/quill/cli/application" + "github.com/anchore/clio" "github.com/anchore/quill/cmd/quill/cli/options" "github.com/anchore/quill/internal/bus" "github.com/anchore/quill/internal/log" @@ -15,34 +13,20 @@ import ( "github.com/anchore/quill/quill/notary" ) -var _ options.Interface = &submissionStatusConfig{} - type submissionStatusConfig struct { - ID string `yaml:"id" json:"id" mapstructure:"id"` + ID string `yaml:"id" json:"id" mapstructure:"-"` options.Notary `yaml:"notary" json:"notary" mapstructure:"notary"` options.Status `yaml:"status" json:"status" mapstructure:"status"` } -func (o *submissionStatusConfig) Redact() { - options.RedactAll(&o.Notary, &o.Status) -} - -func (o *submissionStatusConfig) AddFlags(flags *pflag.FlagSet) { - options.AddAllFlags(flags, &o.Notary, &o.Status) -} - -func (o *submissionStatusConfig) BindFlags(flags *pflag.FlagSet, v *viper.Viper) error { - return options.BindAllFlags(flags, v, &o.Notary, &o.Status) -} - -func SubmissionStatus(app *application.Application) *cobra.Command { +func SubmissionStatus(app clio.Application) *cobra.Command { opts := &submissionStatusConfig{ Status: options.Status{ Wait: false, }, } - cmd := &cobra.Command{ + return app.SetupCommand(&cobra.Command{ Use: "status SUBMISSION_ID", Short: "check against Apple's Notary service to see the status of a notarization submission request", Example: options.FormatPositionalArgsHelp( @@ -57,50 +41,45 @@ func SubmissionStatus(app *application.Application) *cobra.Command { return nil }, ), - PreRunE: app.Setup(opts), RunE: func(cmd *cobra.Command, args []string) error { - return app.Run(cmd.Context(), async(func() error { - log.Infof("checking submission status for %q", opts.ID) - - cfg := quill.NewNotarizeConfig( - opts.Notary.Issuer, - opts.Notary.PrivateKeyID, - opts.Notary.PrivateKey, - ).WithStatusConfig( - notary.StatusConfig{ - Timeout: time.Duration(int64(opts.TimeoutSeconds) * int64(time.Second)), - Poll: time.Duration(int64(opts.PollSeconds) * int64(time.Second)), - Wait: opts.Wait, - }, - ) - - token, err := notary.NewSignedToken(cfg.TokenConfig) - if err != nil { - return err - } - - a := notary.NewAPIClient(token, cfg.HTTPTimeout) - - sub := notary.ExistingSubmission(a, opts.ID) - - var status notary.SubmissionStatus - if opts.Wait { - status, err = notary.PollStatus(cmd.Context(), sub, cfg.StatusConfig) - } else { - status, err = sub.Status(cmd.Context()) - } - if err != nil { - return err - } - - bus.Report(string(status)) - - return nil - })) + defer bus.Exit() + + log.Infof("checking submission status for %q", opts.ID) + + cfg := quill.NewNotarizeConfig( + opts.Notary.Issuer, + opts.Notary.PrivateKeyID, + opts.Notary.PrivateKey, + ).WithStatusConfig( + notary.StatusConfig{ + Timeout: time.Duration(int64(opts.TimeoutSeconds) * int64(time.Second)), + Poll: time.Duration(int64(opts.PollSeconds) * int64(time.Second)), + Wait: opts.Wait, + }, + ) + + token, err := notary.NewSignedToken(cfg.TokenConfig) + if err != nil { + return err + } + + a := notary.NewAPIClient(token, cfg.HTTPTimeout) + + sub := notary.ExistingSubmission(a, opts.ID) + + var status notary.SubmissionStatus + if opts.Wait { + status, err = notary.PollStatus(cmd.Context(), sub, cfg.StatusConfig) + } else { + status, err = sub.Status(cmd.Context()) + } + if err != nil { + return err + } + + bus.Report(string(status)) + + return nil }, - } - - commonConfiguration(app, cmd, opts) - - return cmd + }, opts) } diff --git a/cmd/quill/cli/commands/utils.go b/cmd/quill/cli/commands/utils.go index 2194072..6e6f445 100644 --- a/cmd/quill/cli/commands/utils.go +++ b/cmd/quill/cli/commands/utils.go @@ -1,24 +1,51 @@ package commands import ( + "context" + "errors" + "fmt" + "github.com/spf13/cobra" + "software.sslmate.com/src/go-pkcs12" - "github.com/anchore/quill/cmd/quill/cli/application" - "github.com/anchore/quill/cmd/quill/cli/options" "github.com/anchore/quill/internal/bus" + "github.com/anchore/quill/internal/redact" + "github.com/anchore/quill/quill/pki/load" ) -func async(f func() error) <-chan error { - errs := make(chan error) - go func() { - defer close(errs) - if err := f(); err != nil { - errs <- err - } - bus.Exit() - }() +func loadP12Interactively(p12Path, password string) (*load.P12Contents, error) { + p12Content, err := load.P12(p12Path, password) + if err == nil { + return p12Content, nil + } + + if !errors.Is(err, load.ErrNeedPassword) { + return nil, err + } + + by, err := load.BytesFromFileOrEnv(p12Path) + if err != nil { + return nil, fmt.Errorf("unable to read p12 bytes: %w", err) + } - return errs + prompter := bus.PromptForInput("Enter P12 password:", true) + newPassword, err := prompter.Response(context.Background()) + if err != nil { + return nil, fmt.Errorf("unable to get password from prompt: %w", err) + } + + redact.Add(newPassword) + + key, cert, certs, err := pkcs12.DecodeChain(by, newPassword) + if err != nil { + return nil, fmt.Errorf("unable to decode p12 file: %w", err) + } + + return &load.P12Contents{ + PrivateKey: key, + Certificate: cert, + Certificates: certs, + }, nil } func chainArgs(processors ...func(cmd *cobra.Command, args []string) error) func(cmd *cobra.Command, args []string) error { @@ -31,45 +58,3 @@ func chainArgs(processors ...func(cmd *cobra.Command, args []string) error) func return nil } } - -func commonConfiguration(app *application.Application, cmd *cobra.Command, opts options.Interface) { - if opts != nil { - opts.AddFlags(cmd.Flags()) - - if app != nil { - // we want to be able to attach config binding information to the help output - cmd.SetHelpFunc(func(cmd *cobra.Command, args []string) { - _ = app.Setup(opts)(cmd, args) - cmd.Parent().HelpFunc()(cmd, args) - }) - } - } - - cmd.SilenceUsage = true - cmd.SilenceErrors = true - cmd.SetHelpTemplate(`{{if (or .Long .Short)}}{{.Long}}{{if not .Long}}{{.Short}}{{end}} - -{{end}}Usage:{{if .Runnable}} - {{.UseLine}}{{end}}{{if .HasAvailableSubCommands}} - {{.CommandPath}} [command]{{end}}{{if .HasExample}} - -{{.Example}}{{end}}{{if gt (len .Aliases) 0}} - -Aliases: - {{.NameAndAliases}}{{end}}{{if .HasAvailableSubCommands}} - -Available Commands:{{range .Commands}}{{if (or .IsAvailableCommand (eq .Name "help"))}} - {{rpad .Name .NamePadding }} {{.Short}}{{end}}{{end}}{{end}}{{if .HasAvailableLocalFlags}} - -{{if not .CommandPath}}Global {{end}}Flags: -{{.LocalFlags.FlagUsages | trimTrailingWhitespaces}}{{end}}{{if (and .HasAvailableInheritedFlags (not .CommandPath))}} - -Global Flags: -{{.InheritedFlags.FlagUsages | trimTrailingWhitespaces}}{{end}}{{if .HasHelpSubCommands}} - -Additional help topics:{{range .Commands}}{{if .IsAdditionalHelpTopicCommand}} - {{rpad .CommandPath .CommandPathPadding}} {{.Short}}{{end}}{{end}}{{end}}{{if .HasAvailableSubCommands}} - -Use "{{if .CommandPath}}{{.CommandPath}} {{end}}[command] --help" for more information about a command.{{end}} -`) -} diff --git a/cmd/quill/cli/commands/version.go b/cmd/quill/cli/commands/version.go deleted file mode 100644 index da8bb34..0000000 --- a/cmd/quill/cli/commands/version.go +++ /dev/null @@ -1,73 +0,0 @@ -package commands - -import ( - "encoding/json" - "fmt" - "os" - - "github.com/spf13/cobra" - - "github.com/anchore/quill/cmd/quill/cli/application" - "github.com/anchore/quill/internal" - "github.com/anchore/quill/internal/version" -) - -func Version(_ *application.Application) *cobra.Command { - var format string - - cmd := &cobra.Command{ - Use: "version", - Short: fmt.Sprintf("show %s version information", internal.ApplicationName), - Args: func(cmd *cobra.Command, args []string) error { - if err := cobra.NoArgs(cmd, args); err != nil { - return err - } - // note: we intentionally do not execute through the application infrastructure (no app config is required for this command) - - return nil - }, - RunE: func(cmd *cobra.Command, args []string) error { - // note: we intentionally do not execute through the application infrastructure (no app config is required for this command) - - versionInfo := version.FromBuild() - - switch format { - case "text": - fmt.Println("Application: ", internal.ApplicationName) - fmt.Println("Version: ", versionInfo.Version) - fmt.Println("BuildDate: ", versionInfo.BuildDate) - fmt.Println("GitCommit: ", versionInfo.GitCommit) - fmt.Println("GitDescription: ", versionInfo.GitDescription) - fmt.Println("Platform: ", versionInfo.Platform) - fmt.Println("GoVersion: ", versionInfo.GoVersion) - fmt.Println("Compiler: ", versionInfo.Compiler) - - case "json": - enc := json.NewEncoder(os.Stdout) - enc.SetEscapeHTML(false) - enc.SetIndent("", " ") - err := enc.Encode(&struct { - version.Version - Application string `json:"application"` - }{ - Version: versionInfo, - Application: internal.ApplicationName, - }) - if err != nil { - return fmt.Errorf("failed to show version information: %w", err) - } - default: - return fmt.Errorf("unsupported output format: %s", format) - } - - return nil - }, - } - - flags := cmd.Flags() - flags.StringVarP(&format, "output", "o", "text", "the format to show the results (allowable: [text json])") - - commonConfiguration(nil, cmd, nil) - - return cmd -} diff --git a/cmd/quill/cli/options/describe.go b/cmd/quill/cli/options/describe.go index 249bc84..071700d 100644 --- a/cmd/quill/cli/options/describe.go +++ b/cmd/quill/cli/options/describe.go @@ -1,27 +1,19 @@ package options import ( - "github.com/spf13/pflag" - "github.com/spf13/viper" + "github.com/anchore/fangs" ) -var _ Interface = &Describe{} +var _ fangs.FlagAdder = (*Describe)(nil) type Describe struct { Detail bool `yaml:"detail" json:"detail" mapstructure:"detail"` } -func (o *Describe) Redact() { -} - -func (o *Describe) AddFlags(flags *pflag.FlagSet) { +func (o *Describe) AddFlags(flags fangs.FlagSet) { flags.BoolVarP( &o.Detail, - "detail", "d", o.Detail, + "detail", "d", "show additional detail of description", ) } - -func (o *Describe) BindFlags(flags *pflag.FlagSet, v *viper.Viper) error { - return Bind(v, "describe.detail", flags.Lookup("detail")) -} diff --git a/cmd/quill/cli/options/extract_certificates.go b/cmd/quill/cli/options/extract_certificates.go index abb420c..ba28d06 100644 --- a/cmd/quill/cli/options/extract_certificates.go +++ b/cmd/quill/cli/options/extract_certificates.go @@ -1,27 +1,19 @@ package options import ( - "github.com/spf13/pflag" - "github.com/spf13/viper" + "github.com/anchore/fangs" ) -var _ Interface = &ExtractCertificates{} +var _ fangs.FlagAdder = (*ExtractCertificates)(nil) type ExtractCertificates struct { Leaf bool `yaml:"leaf" json:"leaf" mapstructure:"leaf"` } -func (o *ExtractCertificates) Redact() { -} - -func (o *ExtractCertificates) AddFlags(flags *pflag.FlagSet) { +func (o *ExtractCertificates) AddFlags(flags fangs.FlagSet) { flags.BoolVarP( &o.Leaf, - "leaf", "l", o.Leaf, + "leaf", "l", "only extract the leaf certificate", ) } - -func (o *ExtractCertificates) BindFlags(flags *pflag.FlagSet, v *viper.Viper) error { - return Bind(v, "extract-certificates.leaf", flags.Lookup("leaf")) -} diff --git a/cmd/quill/cli/options/format.go b/cmd/quill/cli/options/format.go index 3cbce21..33fb1f3 100644 --- a/cmd/quill/cli/options/format.go +++ b/cmd/quill/cli/options/format.go @@ -3,29 +3,20 @@ package options import ( "fmt" - "github.com/spf13/pflag" - "github.com/spf13/viper" + "github.com/anchore/fangs" ) -var _ Interface = &Format{} +var _ fangs.FlagAdder = (*Format)(nil) type Format struct { Output string `yaml:"output" json:"output" mapstructure:"output"` AllowableFormats []string `yaml:"-" json:"-" mapstructure:"-"` } -func (o *Format) Redact() { - -} - -func (o *Format) AddFlags(flags *pflag.FlagSet) { +func (o *Format) AddFlags(flags fangs.FlagSet) { flags.StringVarP( &o.Output, - "output", "o", o.Output, + "output", "o", fmt.Sprintf("output format to report results in (allowable values: %s)", o.AllowableFormats), ) } - -func (o *Format) BindFlags(flags *pflag.FlagSet, v *viper.Viper) error { - return Bind(v, "output", flags.Lookup("output")) -} diff --git a/cmd/quill/cli/options/keychain.go b/cmd/quill/cli/options/keychain.go index 9202cf4..d0c8627 100644 --- a/cmd/quill/cli/options/keychain.go +++ b/cmd/quill/cli/options/keychain.go @@ -1,27 +1,19 @@ package options import ( - "github.com/spf13/pflag" - "github.com/spf13/viper" + "github.com/anchore/fangs" ) -var _ Interface = &Keychain{} +var _ fangs.FlagAdder = (*Keychain)(nil) type Keychain struct { Path string `yaml:"path" json:"path" mapstructure:"path"` } -func (o *Keychain) Redact() { -} - -func (o *Keychain) AddFlags(flags *pflag.FlagSet) { +func (o *Keychain) AddFlags(flags fangs.FlagSet) { flags.StringVarP( &o.Path, - "keychain-path", "", o.Path, + "keychain-path", "", "path to the mac system keychain", ) } - -func (o *Keychain) BindFlags(flags *pflag.FlagSet, v *viper.Viper) error { - return Bind(v, "keychain.path", flags.Lookup("keychain-path")) -} diff --git a/cmd/quill/cli/options/notary.go b/cmd/quill/cli/options/notary.go index 215b544..fff2eea 100644 --- a/cmd/quill/cli/options/notary.go +++ b/cmd/quill/cli/options/notary.go @@ -1,11 +1,13 @@ package options import ( - "github.com/spf13/pflag" - "github.com/spf13/viper" + "github.com/anchore/fangs" ) -var _ Interface = (*Notary)(nil) +var _ interface { + fangs.FlagAdder + fangs.PostLoader +} = (*Notary)(nil) type Notary struct { // bound options @@ -16,36 +18,27 @@ type Notary struct { // unbound options } -func (o *Notary) Redact() { +func (o *Notary) PostLoad() error { redactNonFileOrEnvHint(o.PrivateKey) + return nil } -func (o *Notary) AddFlags(flags *pflag.FlagSet) { +func (o *Notary) AddFlags(flags fangs.FlagSet) { flags.StringVarP( &o.Issuer, - "notary-issuer", "", o.Issuer, + "notary-issuer", "", "App Store Connect API Issuer ID. The issuer ID is a UUID format string.", ) flags.StringVarP( &o.PrivateKeyID, - "notary-key-id", "", o.PrivateKeyID, + "notary-key-id", "", "App Store Connect API Key ID. For most teams this will be a 10 character alphanumeric string (e.g. 23425865-85ea-2b62-f043-1082a2081d24).", ) flags.StringVarP( &o.PrivateKey, - "notary-key", "", o.PrivateKey, - "App Store Connect API key. File system path to the private key. This can also be the base64-encoded contents of the key file, or 'env:ENV_VAR_NAME' to read the key from a different environment variable", + "notary-key", "", + "App Store Connect API key. File system path to the private key.\nThis can also be the base64-encoded contents of the key file, or 'env:ENV_VAR_NAME' to read the key from a different environment variable", ) } - -func (o *Notary) BindFlags(flags *pflag.FlagSet, v *viper.Viper) error { - if err := Bind(v, "notary.issuer", flags.Lookup("notary-issuer")); err != nil { - return err - } - if err := Bind(v, "notary.key-id", flags.Lookup("notary-key-id")); err != nil { - return err - } - return Bind(v, "notary.key", flags.Lookup("notary-key")) -} diff --git a/cmd/quill/cli/options/options.go b/cmd/quill/cli/options/options.go deleted file mode 100644 index 796cc72..0000000 --- a/cmd/quill/cli/options/options.go +++ /dev/null @@ -1,35 +0,0 @@ -package options - -import ( - "github.com/hashicorp/go-multierror" - "github.com/spf13/pflag" - "github.com/spf13/viper" -) - -type Interface interface { - Redact() - AddFlags(*pflag.FlagSet) - BindFlags(*pflag.FlagSet, *viper.Viper) error -} - -func AddAllFlags(flags *pflag.FlagSet, i ...Interface) { - for _, o := range i { - o.AddFlags(flags) - } -} - -func BindAllFlags(flags *pflag.FlagSet, v *viper.Viper, i ...Interface) error { - var errs error - for _, o := range i { - if err := o.BindFlags(flags, v); err != nil { - errs = multierror.Append(errs, err) - } - } - return errs -} - -func RedactAll(i ...Interface) { - for _, o := range i { - o.Redact() - } -} diff --git a/cmd/quill/cli/options/p12.go b/cmd/quill/cli/options/p12.go index fca994d..bad36c8 100644 --- a/cmd/quill/cli/options/p12.go +++ b/cmd/quill/cli/options/p12.go @@ -1,28 +1,24 @@ package options import ( - "github.com/spf13/pflag" - "github.com/spf13/viper" - - "github.com/anchore/quill/internal/log" + "github.com/anchore/fangs" + "github.com/anchore/quill/internal/redact" ) -var _ Interface = &P12{} +var _ interface { + fangs.PostLoader + fangs.FieldDescriber +} = (*P12)(nil) type P12 struct { Password string `yaml:"password" json:"password" mapstructure:"password"` } -func (o *P12) Redact() { - log.Redact(o.Password) -} - -func (o *P12) AddFlags(_ *pflag.FlagSet) { +func (o *P12) PostLoad() error { + redact.Add(o.Password) + return nil } -func (o *P12) BindFlags(_ *pflag.FlagSet, v *viper.Viper) error { - // set default values for non-bound struct items - v.SetDefault("p12.password", o.Password) - - return nil +func (o *P12) DescribeFields(d fangs.FieldDescriptionSet) { + d.Add(&o.Password, "password to decrypt the p12 file") } diff --git a/cmd/quill/cli/options/signing.go b/cmd/quill/cli/options/signing.go index 6123ebb..db51271 100644 --- a/cmd/quill/cli/options/signing.go +++ b/cmd/quill/cli/options/signing.go @@ -1,13 +1,15 @@ package options import ( - "github.com/spf13/pflag" - "github.com/spf13/viper" - - "github.com/anchore/quill/internal/log" + "github.com/anchore/fangs" + "github.com/anchore/quill/internal/redact" ) -var _ Interface = &Signing{} +var _ interface { + fangs.FlagAdder + fangs.PostLoader + fangs.FieldDescriber +} = (*Signing)(nil) type Signing struct { // bound options @@ -28,54 +30,39 @@ func DefaultSigning() Signing { } } -func (o *Signing) Redact() { - log.Redact(o.Password) +func (o *Signing) PostLoad() error { + redact.Add(o.Password) redactNonFileOrEnvHint(o.P12) + return nil } -func (o *Signing) AddFlags(flags *pflag.FlagSet) { +func (o *Signing) AddFlags(flags fangs.FlagSet) { flags.StringVarP( &o.Identity, - "identity", "", o.Identity, + "identity", "", "identifier to encode into the code directory of the code signing super block (default is derived from the name of the binary being solved)", ) flags.StringVarP( &o.P12, - "p12", "", o.P12, - "path to a PKCS12 file containing the private key, (leaf) signing certificate, remaining certificate chain. This can also be the base64-encoded contents of the p12 file, or 'env:ENV_VAR_NAME' to read the p12 from a different environment variable", + "p12", "", + "path to a PKCS12 file containing the private key, (leaf) signing certificate, remaining certificate chain.\nThis can also be the base64-encoded contents of the p12 file, or 'env:ENV_VAR_NAME' to read the p12 from a different environment variable", ) flags.StringVarP( &o.TimestampServer, - "timestamp-server", "", o.TimestampServer, + "timestamp-server", "", "URL to a timestamp server to use for timestamping the signature", ) flags.BoolVarP( &o.AdHoc, - "ad-hoc", "", o.AdHoc, + "ad-hoc", "", "perform ad-hoc signing. No cryptographic signature is included and --p12 key and certificate input are not needed. Do NOT use this option for production builds.", ) } -func (o *Signing) BindFlags(flags *pflag.FlagSet, v *viper.Viper) error { - if err := Bind(v, "sign.override-identity", flags.Lookup("identity")); err != nil { - return err - } - if err := Bind(v, "sign.p12", flags.Lookup("p12")); err != nil { - return err - } - if err := Bind(v, "sign.timestamp-server", flags.Lookup("timestamp-server")); err != nil { - return err - } - if err := Bind(v, "sign.ad-hoc", flags.Lookup("ad-hoc")); err != nil { - return err - } - - // set default values for non-bound struct items - v.SetDefault("sign.password", o.Password) - v.SetDefault("sign.fail-without-full-chain", o.FailWithoutFullChain) - - return nil +func (o *Signing) DescribeFields(d fangs.FieldDescriptionSet) { + d.Add(&o.FailWithoutFullChain, "fail without the full certificate chain present in the p12 file") + d.Add(&o.Password, "password for the p12 file") } diff --git a/cmd/quill/cli/options/status.go b/cmd/quill/cli/options/status.go index 9f07bd0..82baa91 100644 --- a/cmd/quill/cli/options/status.go +++ b/cmd/quill/cli/options/status.go @@ -3,15 +3,18 @@ package options import ( "time" - "github.com/spf13/pflag" - "github.com/spf13/viper" + "github.com/anchore/fangs" ) -var _ Interface = &Status{} +var _ interface { + fangs.FlagAdder + fangs.PostLoader + fangs.FieldDescriber +} = (*Status)(nil) type Status struct { // bound options - Wait bool `yaml:"wait" json:"wait" mapstructure:"status.wait"` + Wait bool `yaml:"wait" json:"wait" mapstructure:"wait"` // unbound options PollSeconds int `yaml:"poll-seconds" json:"poll-seconds" mapstructure:"poll-seconds"` @@ -20,29 +23,25 @@ type Status struct { func DefaultStatus() Status { return Status{ - Wait: true, + Wait: true, + PollSeconds: int((10 * time.Second).Seconds()), + TimeoutSeconds: int((15 * time.Minute).Seconds()), } } -func (o *Status) Redact() { +func (o *Status) PostLoad() error { + return nil } -func (o *Status) AddFlags(flags *pflag.FlagSet) { +func (o *Status) AddFlags(flags fangs.FlagSet) { flags.BoolVarP( &o.Wait, - "wait", "w", o.Wait, + "wait", "w", "wait for a conclusive status before exiting (accepted, rejected, or invalid status)", ) } -func (o *Status) BindFlags(flags *pflag.FlagSet, v *viper.Viper) error { - if err := Bind(v, "status.wait", flags.Lookup("wait")); err != nil { - return err - } - - // set default values for non-bound struct items - v.SetDefault("status.poll-seconds", int((10 * time.Second).Seconds())) - v.SetDefault("status.timeout-seconds", int((15 * time.Minute).Seconds())) - - return nil +func (o *Status) DescribeFields(d fangs.FieldDescriptionSet) { + d.Add(&o.PollSeconds, "how often to poll for status") + d.Add(&o.TimeoutSeconds, "maximum time to wait for a response for a status request before cancelling with error") } diff --git a/cmd/quill/cli/options/utils.go b/cmd/quill/cli/options/utils.go index a8aece2..84d718f 100644 --- a/cmd/quill/cli/options/utils.go +++ b/cmd/quill/cli/options/utils.go @@ -3,41 +3,12 @@ package options import ( "fmt" "os" - "reflect" "sort" "strings" - "github.com/iancoleman/strcase" - "github.com/spf13/pflag" - "github.com/spf13/viper" - - "github.com/anchore/quill/internal" - "github.com/anchore/quill/internal/log" - "github.com/anchore/quill/internal/utils" + "github.com/anchore/quill/internal/redact" ) -func Bind(v *viper.Viper, configKey string, flag *pflag.Flag) error { - if flag == nil { - return fmt.Errorf("unable to bind config to CLI flag: no flag given for config-key=%q", configKey) - } - - if err := v.BindPFlag(configKey, flag); err != nil { - return fmt.Errorf("unable to bind config-key=%q to CLI flag=%q: %w", configKey, flag.Name, err) - } - - envVar := strings.ToUpper(strings.NewReplacer(".", "_", "-", "_").Replace(internal.ApplicationName + "_" + configKey)) - - flag.Usage += fmt.Sprintf(" (env var: %q)", envVar) - - return nil -} - -func BindOrExit(v *viper.Viper, configKey string, flag *pflag.Flag) { - if err := Bind(v, configKey, flag); err != nil { - utils.ExitWithErrorf("%+v", err) - } -} - func FormatPositionalArgsHelp(args map[string]string) string { var keys []string for k := range args { @@ -59,64 +30,6 @@ func FormatPositionalArgsHelp(args map[string]string) string { return "Arguments:\n" + strings.TrimSuffix(ret, "\n") } -func Summarize(itf interface{}, currentPath []string) string { - var desc []string - - t := reflect.TypeOf(itf) - v := reflect.ValueOf(itf) - - if t.Kind() == reflect.Struct { - for i := 0; i < t.NumField(); i++ { - field := t.Field(i) - description := field.Tag.Get("description") - yamlName := field.Tag.Get("yaml") - - tag := field.Tag.Get("mapstructure") - switch tag { - case "-", "": - continue - } - - fieldVal := v.Field(i) - - var newPath []string - newPath = append(newPath, currentPath...) - newPath = append(newPath, tag) - - envVar := strcase.ToScreamingSnake(strings.Join(append([]string{internal.ApplicationName}, newPath...), "_")) - - if description != "" { - var section string - section += fmt.Sprintf("# %s (env var: %q)\n", description, envVar) - - var val string - switch field.Type.Kind() { - case reflect.String: - val = fmt.Sprintf("%q", fieldVal) - default: - val = fmt.Sprintf("%+v", fieldVal) - } - - section += fmt.Sprintf("%s: %s", yamlName, val) - - desc = append(desc, section) - } else { - d := Summarize(fieldVal.Interface(), newPath) - if d != "" { - section := yamlName + ":\n" + utils.Indent(d, strings.Repeat(" ", len(newPath))) - desc = append(desc, strings.TrimSpace(section)) - } - } - } - } - - if len(desc) == 0 { - return "" - } - - return strings.Join(desc, "\n\n") -} - func redactNonFileOrEnvHint(value string) { if strings.HasPrefix(value, "env:") { // this is an env hint, the real value will be read downstream of config processing @@ -127,5 +40,5 @@ func redactNonFileOrEnvHint(value string) { return } // path does not exist OR there was an access issue and we cannot verify... either way, redact - log.Redact(value) + redact.Add(value) } diff --git a/cmd/quill/cli/ui/handler.go b/cmd/quill/cli/ui/handler.go new file mode 100644 index 0000000..ff0a0c5 --- /dev/null +++ b/cmd/quill/cli/ui/handler.go @@ -0,0 +1,87 @@ +package ui + +import ( + "sync" + + tea "github.com/charmbracelet/bubbletea" + "github.com/wagoodman/go-partybus" + "github.com/wagoodman/go-progress" + + "github.com/anchore/bubbly" + "github.com/anchore/bubbly/bubbles/prompt" + "github.com/anchore/bubbly/bubbles/taskprogress" + "github.com/anchore/quill/internal/log" + "github.com/anchore/quill/quill/event" +) + +var _ bubbly.EventHandler = (*Handler)(nil) + +type Handler struct { + state *State + bubbly.EventHandler +} + +type State struct { + WindowSize tea.WindowSizeMsg + Running *sync.WaitGroup +} + +func New() *Handler { + d := bubbly.NewEventDispatcher() + + h := &Handler{ + EventHandler: d, + state: &State{ + Running: &sync.WaitGroup{}, + }, + } + + d.AddHandlers(map[partybus.EventType]bubbly.EventHandlerFn{ + event.CLIInputPromptType: h.handleInputPrompt, + event.TaskType: h.handleTask, + }) + + return h +} + +func (m *Handler) State() *State { + return m.state +} + +func (m *Handler) handleInputPrompt(e partybus.Event) []tea.Model { + writer, err := event.ParseCLIInputPromptType(e) + if err != nil { + log.Warnf("unable to parse event: %+v", err) + return nil + } + + return []tea.Model{prompt.New(writer)} +} + +func (m *Handler) handleTask(e partybus.Event) []tea.Model { + cmd, prog, err := event.ParseTaskType(e) + if err != nil { + log.Warnf("unable to parse event: %+v", err) + return nil + } + + return m.handleStagedProgressable(prog, taskprogress.Title{ + Default: cmd.Title.Default, + Running: cmd.Title.WhileRunning, + Success: cmd.Title.OnSuccess, + Failed: cmd.Title.OnFail, + }, cmd.Context) +} + +func (m *Handler) handleStagedProgressable(prog progress.StagedProgressable, title taskprogress.Title, context ...string) []tea.Model { + tsk := taskprogress.New( + m.state.Running, + taskprogress.WithStagedProgressable(prog), + ) + tsk.HideProgressOnSuccess = true + tsk.TitleOptions = title + tsk.Context = context + tsk.WindowSize = m.state.WindowSize + + return []tea.Model{tsk} +} diff --git a/cmd/quill/internal/ui/no_ui.go b/cmd/quill/internal/ui/no_ui.go new file mode 100644 index 0000000..4b6e574 --- /dev/null +++ b/cmd/quill/internal/ui/no_ui.go @@ -0,0 +1,40 @@ +package ui + +import ( + "github.com/wagoodman/go-partybus" + + "github.com/anchore/clio" + "github.com/anchore/quill/quill/event" +) + +var _ clio.UI = (*NoUI)(nil) + +type NoUI struct { + finalizeEvents []partybus.Event + subscription partybus.Unsubscribable +} + +func None() *NoUI { + return &NoUI{} +} + +func (n *NoUI) Setup(subscription partybus.Unsubscribable) error { + n.subscription = subscription + return nil +} + +func (n *NoUI) Handle(e partybus.Event) error { + switch e.Type { + case event.CLIReportType, event.CLINotificationType: + // keep these for when the UI is terminated to show to the screen (or perform other events) + n.finalizeEvents = append(n.finalizeEvents, e) + case event.CLIExitType: + return n.subscription.Unsubscribe() + } + return nil +} + +func (n NoUI) Teardown(_ bool) error { + postUIEvents(false, n.finalizeEvents...) + return nil +} diff --git a/cmd/quill/internal/ui/ui.go b/cmd/quill/internal/ui/ui.go new file mode 100644 index 0000000..e0c41a1 --- /dev/null +++ b/cmd/quill/internal/ui/ui.go @@ -0,0 +1,202 @@ +package ui + +import ( + "fmt" + "os" + "strings" + "sync" + + tea "github.com/charmbracelet/bubbletea" + "github.com/charmbracelet/lipgloss" + "github.com/wagoodman/go-partybus" + + "github.com/anchore/bubbly/bubbles/frame" + "github.com/anchore/clio" + "github.com/anchore/go-logger" + handler "github.com/anchore/quill/cmd/quill/cli/ui" + "github.com/anchore/quill/internal/bus" + "github.com/anchore/quill/internal/log" + "github.com/anchore/quill/quill/event" +) + +var _ interface { + tea.Model + partybus.Responder + clio.UI +} = (*UI)(nil) + +type UI struct { + program *tea.Program + running *sync.WaitGroup + quiet bool + subscription partybus.Unsubscribable + finalizeEvents []partybus.Event + + handler *handler.Handler + frame tea.Model +} + +func New(_, quiet bool) *UI { + h := handler.New() + return &UI{ + handler: h, + frame: frame.New(), + running: &sync.WaitGroup{}, + quiet: quiet, + } +} + +func (m *UI) Setup(subscription partybus.Unsubscribable) error { + // we still want to collect log messages, however, we also the logger shouldn't write to the screen directly + if logWrapper, ok := log.Get().(logger.Controller); ok { + logWrapper.SetOutput(m.frame.(*frame.Frame).Footer()) + } + + m.subscription = subscription + m.program = tea.NewProgram(m, tea.WithOutput(os.Stderr), tea.WithInput(os.Stdin)) + m.running.Add(1) + + go func() { + defer m.running.Done() + if err := m.program.Start(); err != nil { + log.Errorf("unable to start UI: %+v", err) + m.exit() + } + }() + + return nil +} + +func (m *UI) exit() { + // stop the event loop + bus.Exit() +} + +func (m *UI) Handle(e partybus.Event) error { + if m.program != nil { + m.program.Send(e) + if e.Type == event.CLIExitType { + return m.subscription.Unsubscribe() + } + } + return nil +} + +func (m *UI) Teardown(force bool) error { + if !force { + m.handler.State().Running.Wait() + m.program.Quit() + } else { + m.program.Kill() + } + + m.running.Wait() + + // TODO: allow for writing out the full log output to the screen (only a partial log is shown currently) + // this needs coordination to know what the last frame event is to change the state accordingly (which isn't possible now) + + postUIEvents(m.quiet, m.finalizeEvents...) + + return nil +} + +// bubbletea.Model functions + +func (m UI) Init() tea.Cmd { + return m.frame.Init() +} + +func (m UI) RespondsTo() []partybus.EventType { + return append([]partybus.EventType{ + event.CLIReportType, + event.CLINotificationType, + event.CLIExitType, + }, m.handler.RespondsTo()...) +} + +func (m *UI) Update(msg tea.Msg) (tea.Model, tea.Cmd) { + // note: we need a pointer receiver such that the same instance of UI used in Teardown is referenced (to keep finalize events) + + var cmds []tea.Cmd + + switch msg := msg.(type) { + case tea.KeyMsg: + switch msg.String() { + case "esc", "ctrl+c": + m.exit() + return m, tea.Quit + } + + case partybus.Event: + switch msg.Type { + case event.CLIReportType, event.CLINotificationType, event.CLIExitType: + // keep these for when the UI is terminated to show to the screen (or perform other events) + m.finalizeEvents = append(m.finalizeEvents, msg) + + // why not return tea.Quit here for exit events? because there may be UI components that still need the update-render loop. + // for this reason we'll let the quill event loop call Teardown() which will explicitly wait for these components + return m, nil + } + + for _, newModel := range m.handler.Handle(msg) { + if newModel == nil { + continue + } + cmds = append(cmds, newModel.Init()) + m.frame.(*frame.Frame).AppendModel(newModel) + } + // intentionally fallthrough to update the frame model + } + + frameModel, cmd := m.frame.Update(msg) + m.frame = frameModel + cmds = append(cmds, cmd) + + return m, tea.Batch(cmds...) +} + +func (m UI) View() string { + return m.frame.View() +} + +func postUIEvents(quiet bool, events ...partybus.Event) { + // TODO: add partybus event filter to filter down to events matching a type + + // show all accumulated reports to stdout + var reports []string + for _, e := range events { + if e.Type != event.CLIReportType { + continue + } + + source, report, err := event.ParseCLIReportType(e) + if err != nil { + log.WithFields("error", err). + Warn("failed to gather final report for %q", source) + } else { + // remove all whitespace padding from the end of the report + reports = append(reports, strings.TrimRight(report, "\n ")+"\n") + } + } + + // prevent the double new-line at the end of the report + fmt.Print(strings.Join(reports, "\n")) + + if !quiet { + // show all notifications reports to stderr + for _, e := range events { + if e.Type != event.CLINotificationType { + continue + } + + source, notification, err := event.ParseCLINotificationType(e) + if err != nil { + log.WithFields("error", err). + Warnf("failed to gather notification for %q", source) + } else { + // 13 = high intensity magenta (ANSI 16 bit code) + _, _ = fmt.Fprintln(os.Stderr, lipgloss.NewStyle().Foreground(lipgloss.Color("13")).Render(notification)) + } + } + } +} diff --git a/cmd/quill/main.go b/cmd/quill/main.go index 70091b0..ceb4680 100644 --- a/cmd/quill/main.go +++ b/cmd/quill/main.go @@ -4,14 +4,34 @@ import ( "context" "os" "os/signal" + "strings" + "github.com/gookit/color" + + "github.com/anchore/clio" "github.com/anchore/quill/cmd/quill/cli" + "github.com/anchore/quill/internal" "github.com/anchore/quill/internal/log" - "github.com/anchore/quill/internal/utils" ) +const valueNotProvided = "[not provided]" + +// all variables here are provided as build-time arguments, with clear default values +var version = valueNotProvided +var gitCommit = valueNotProvided +var gitDescription = valueNotProvided +var buildDate = valueNotProvided + func main() { - cmd := cli.New() + cmd := cli.New( + clio.Identification{ + Name: internal.ApplicationName, + Version: version, + GitCommit: gitCommit, + GitDescription: gitDescription, + BuildDate: buildDate, + }, + ) // drive application control from a single context which can be cancelled (notifying the event loop to stop) ctx, cancel := context.WithCancel(context.Background()) @@ -23,6 +43,14 @@ func main() { signals := make(chan os.Signal, 10) // Note: A buffered channel is recommended for this; see https://golang.org/pkg/os/signal/#Notify signal.Notify(signals, os.Interrupt) + var exitCode int + + defer func() { + if exitCode != 0 { + os.Exit(exitCode) + } + }() + defer func() { signal.Stop(signals) cancel() @@ -40,5 +68,9 @@ func main() { os.Exit(1) }() - utils.FatalOnError(cmd.Execute(), "error") + if err := cmd.Execute(); err != nil { + // report an issue on stdout + color.Red.Println(strings.TrimSpace(err.Error())) + exitCode = 1 + } } diff --git a/go.mod b/go.mod index ea40783..467e53a 100644 --- a/go.mod +++ b/go.mod @@ -5,97 +5,90 @@ go 1.18 require ( github.com/PuerkitoBio/goquery v1.8.1 github.com/acarl005/stripansi v0.0.0-20180116102854-5a71ef0e047d - github.com/adrg/xdg v0.2.1 - github.com/anchore/go-logger v0.0.0-20220728155337-03b66a5207d8 + github.com/anchore/bubbly v0.0.0-20230518153401-87b6af8ccf22 + github.com/anchore/clio v0.0.0-20230630162005-9535e9dc2817 + github.com/anchore/fangs v0.0.0-20230628163043-a51c5a39b097 + github.com/anchore/go-logger v0.0.0-20230531193951-db5ae83e7dbe github.com/anchore/go-macholibre v0.0.0-20220308212642-53e6d0aaf6fb github.com/aws/aws-sdk-go v1.44.114 github.com/blacktop/go-macho v1.1.161 - github.com/charmbracelet/bubbles v0.11.0 github.com/charmbracelet/bubbletea v0.22.1 github.com/charmbracelet/lipgloss v0.7.1 - github.com/erikgeiser/promptkit v0.7.0 github.com/gabriel-vasile/mimetype v1.4.2 github.com/github/smimesign v0.2.0 - github.com/gkampitakis/go-snaps v0.4.5 github.com/go-restruct/restruct v1.2.0-alpha github.com/golang-jwt/jwt/v4 v4.5.0 github.com/google/go-cmp v0.5.9 github.com/gookit/color v1.5.3 - github.com/hashicorp/go-multierror v1.1.1 - github.com/iancoleman/strcase v0.2.0 github.com/jedib0t/go-pretty v4.3.0+incompatible github.com/klauspost/compress v1.16.6 - github.com/mitchellh/go-homedir v1.1.0 - github.com/pkg/profile v1.7.0 github.com/scylladb/go-set v1.0.2 github.com/spf13/cobra v1.7.0 - github.com/spf13/pflag v1.0.5 - github.com/spf13/viper v1.7.0 github.com/stretchr/testify v1.8.4 - github.com/wagoodman/go-partybus v0.0.0-20210627031916-db1f5573bbc5 + github.com/wagoodman/go-partybus v0.0.0-20230516145632-8ccac152c651 github.com/wagoodman/go-progress v0.0.0-20220614130704-4b1c25a33c7c - golang.org/x/term v0.8.0 - gopkg.in/yaml.v2 v2.4.0 - gopkg.in/yaml.v3 v3.0.1 software.sslmate.com/src/go-pkcs12 v0.2.0 ) require ( + github.com/adrg/xdg v0.4.0 // indirect github.com/andybalholm/cascadia v1.3.1 // indirect github.com/asaskevich/govalidator v0.0.0-20200907205600-7a23bdc65eef // indirect github.com/atotto/clipboard v0.1.4 // indirect github.com/aymanbagabas/go-osc52/v2 v2.0.1 // indirect github.com/blacktop/go-dwarf v1.0.9 // indirect + github.com/charmbracelet/bubbles v0.11.0 // indirect github.com/charmbracelet/harmonica v0.2.0 // indirect github.com/containerd/console v1.0.3 // indirect github.com/davecgh/go-spew v1.1.1 // indirect + github.com/erikgeiser/promptkit v0.7.0 // indirect github.com/felixge/fgprof v0.9.3 // indirect - github.com/fsnotify/fsnotify v1.4.9 // indirect - github.com/gkampitakis/ciinfo v0.2.4 // indirect - github.com/gkampitakis/go-diff v1.3.2 // indirect + github.com/fsnotify/fsnotify v1.6.0 // indirect + github.com/gkampitakis/go-snaps v0.4.5 // indirect github.com/go-openapi/errors v0.20.2 // indirect github.com/go-openapi/strfmt v0.21.3 // indirect github.com/google/pprof v0.0.0-20211214055906-6f57359322fd // indirect + github.com/google/uuid v1.3.0 // indirect github.com/hashicorp/errwrap v1.0.0 // indirect + github.com/hashicorp/go-multierror v1.1.1 // indirect github.com/hashicorp/hcl v1.0.0 // indirect github.com/inconshreveable/mousetrap v1.1.0 // indirect github.com/jmespath/go-jmespath v0.4.0 // indirect - github.com/kr/pretty v0.3.1 // indirect - github.com/kr/text v0.2.0 // indirect github.com/lucasb-eyer/go-colorful v1.2.0 // indirect - github.com/magiconair/properties v1.8.1 // indirect + github.com/magiconair/properties v1.8.7 // indirect github.com/mattn/go-colorable v0.1.12 // indirect github.com/mattn/go-isatty v0.0.17 // indirect github.com/mattn/go-localereader v0.0.1 // indirect github.com/mattn/go-runewidth v0.0.14 // indirect github.com/mgutz/ansi v0.0.0-20200706080929-d51e80ef957d // indirect - github.com/mitchellh/mapstructure v1.3.3 // indirect + github.com/mitchellh/go-homedir v1.1.0 // indirect + github.com/mitchellh/mapstructure v1.5.0 // indirect github.com/muesli/ansi v0.0.0-20211031195517-c9f0611b6c70 // indirect github.com/muesli/cancelreader v0.2.2 // indirect github.com/muesli/reflow v0.3.0 // indirect github.com/muesli/termenv v0.15.1 // indirect github.com/oklog/ulid v1.3.1 // indirect - github.com/pelletier/go-toml v1.8.1 // indirect + github.com/pborman/indent v1.2.1 // indirect + github.com/pelletier/go-toml/v2 v2.0.6 // indirect github.com/pkg/errors v0.9.1 // indirect + github.com/pkg/profile v1.7.0 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect github.com/rivo/uniseg v0.2.0 // indirect - github.com/rogpeppe/go-internal v1.9.0 // indirect github.com/sirupsen/logrus v1.8.1 // indirect - github.com/smartystreets/assertions v1.0.0 // indirect - github.com/spf13/afero v1.2.2 // indirect - github.com/spf13/cast v1.3.1 // indirect + github.com/spf13/afero v1.9.3 // indirect + github.com/spf13/cast v1.5.0 // indirect github.com/spf13/jwalterweatherman v1.1.0 // indirect - github.com/stretchr/objx v0.5.0 // indirect - github.com/subosito/gotenv v1.2.0 // indirect - github.com/tidwall/gjson v1.14.4 // indirect - github.com/tidwall/match v1.1.1 // indirect + github.com/spf13/pflag v1.0.5 // indirect + github.com/spf13/viper v1.15.0 // indirect + github.com/subosito/gotenv v1.4.2 // indirect github.com/tidwall/pretty v1.2.1 // indirect - github.com/tidwall/sjson v1.2.5 // indirect github.com/xo/terminfo v0.0.0-20210125001918-ca9a967f8778 // indirect go.mongodb.org/mongo-driver v1.10.0 // indirect golang.org/x/crypto v0.0.0-20221012134737-56aed061732a // indirect golang.org/x/net v0.8.0 // indirect - golang.org/x/sys v0.8.0 // indirect + golang.org/x/sys v0.9.0 // indirect + golang.org/x/term v0.9.0 // indirect golang.org/x/text v0.8.0 // indirect - gopkg.in/ini.v1 v1.56.0 // indirect + gopkg.in/ini.v1 v1.67.0 // indirect + gopkg.in/yaml.v3 v3.0.1 // indirect ) diff --git a/go.sum b/go.sum index 5ac2cc5..eadb21d 100644 --- a/go.sum +++ b/go.sum @@ -3,35 +3,60 @@ cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMT 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.44.3/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 v0.50.0/go.mod h1:r9sluTvynVuxRIOHXQEHMFffphuXHOMZMycpNR5e6To= +cloud.google.com/go v0.52.0/go.mod h1:pXajvRH/6o3+F9jDHZWQ5PbGhn+o8w9qiu/CffaVdO4= +cloud.google.com/go v0.53.0/go.mod h1:fp/UouUEsRkN6ryDKNW/Upv/JBKnv6WDthjR6+vze6M= +cloud.google.com/go v0.54.0/go.mod h1:1rq2OEkV3YMf6n/9ZvGWI3GWw0VoqH/1x2nd8Is/bPc= +cloud.google.com/go v0.56.0/go.mod h1:jr7tqZxxKOVYizybht9+26Z/gUq7tiRzu+ACVAMbKVk= +cloud.google.com/go v0.57.0/go.mod h1:oXiQ6Rzq3RAkkY7N6t3TcE6jE+CIBBbA36lwQ1JyzZs= +cloud.google.com/go v0.62.0/go.mod h1:jmCYTdRCQuc1PHIIJ/maLInMho30T/Y0M4hTdTShOYc= +cloud.google.com/go v0.65.0/go.mod h1:O5N8zS7uWy9vkA9vayVHs65eM1ubvY4h553ofrNHObY= +cloud.google.com/go v0.72.0/go.mod h1:M+5Vjvlc2wnp6tjzE102Dw08nGShTscUx2nZMufOKPI= +cloud.google.com/go v0.74.0/go.mod h1:VV1xSbzvo+9QJOxLDaJfTjx5e+MePCpCWwvftOeQmWk= +cloud.google.com/go v0.75.0/go.mod h1:VGuuCn7PG0dwsd5XPVm2Mm3wlh3EL55/79EKB6hlPTY= cloud.google.com/go/bigquery v1.0.1/go.mod h1:i/xbL2UlR5RvWAURpBYZTtm/cXjCha9lbfbpx4poX+o= +cloud.google.com/go/bigquery v1.3.0/go.mod h1:PjpwJnslEMmckchkHFfq+HTD2DmtT67aNFKH1/VBDHE= +cloud.google.com/go/bigquery v1.4.0/go.mod h1:S8dzgnTigyfTmLBfrtrhyYhwRxG72rYxvftPBK2Dvzc= +cloud.google.com/go/bigquery v1.5.0/go.mod h1:snEHRnqQbz117VIFhE8bmtwIDY80NLUZUMb4Nv6dBIg= +cloud.google.com/go/bigquery v1.7.0/go.mod h1://okPTzCYNXSlb24MZs83e2Do+h+VXtc4gLoIoXIAPc= +cloud.google.com/go/bigquery v1.8.0/go.mod h1:J5hqkt3O0uAFnINi6JXValWIb1v0goeZM77hZzJN/fQ= 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/datastore v1.1.0/go.mod h1:umbIZjpQpHh4hmRpGhH4tLFup+FVzqBi1b3c64qFpCk= cloud.google.com/go/pubsub v1.0.1/go.mod h1:R0Gpsv3s54REJCy4fxDixWD93lHJMoZTyQ2kNxGRt3I= +cloud.google.com/go/pubsub v1.1.0/go.mod h1:EwwdRX2sKPjnvnqCa270oGRyludottCI76h+R3AArQw= +cloud.google.com/go/pubsub v1.2.0/go.mod h1:jhfEVHT8odbXTkndysNHCcx0awwzvfOlguIAii9o8iA= +cloud.google.com/go/pubsub v1.3.1/go.mod h1:i+ucay31+CNRpDW4Lu78I4xXG+O1r/MAHgjpRVR+TSU= cloud.google.com/go/storage v1.0.0/go.mod h1:IhtSnM/ZTZV8YYJWCY8RULGVqBDmpoyjwiyrjsg+URw= +cloud.google.com/go/storage v1.5.0/go.mod h1:tpKbwo567HUNpVclU5sGELwQWBDZ8gh0ZeosJ0Rtdos= +cloud.google.com/go/storage v1.6.0/go.mod h1:N7U0C8pVQ/+NIKOBQyamJIeKQKkZ+mxpohlUTyfDhBk= +cloud.google.com/go/storage v1.8.0/go.mod h1:Wv1Oy7z6Yz3DshWRJFhqM/UCfaWIRTdp0RXyy7KQOVs= +cloud.google.com/go/storage v1.10.0/go.mod h1:FLPqc6j+Ki4BU591ie1oL6qBQGu2Bl/tZ9ullr3+Kg0= +cloud.google.com/go/storage v1.14.0/go.mod h1:GrKmX003DSIwi9o29oFT7YDnHYwZoctc3fOKtUw0Xmo= dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU= 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/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU= github.com/PuerkitoBio/goquery v1.8.1 h1:uQxhNlArOIdbrH1tr0UXwdVFgDcZDrZVdcpygAcwmWM= github.com/PuerkitoBio/goquery v1.8.1/go.mod h1:Q8ICL1kNUJ2sXGoAhPGUdYDJvgQgHzJsnnd3H7Ho5jQ= github.com/acarl005/stripansi v0.0.0-20180116102854-5a71ef0e047d h1:licZJFw2RwpHMqeKTCYkitsPqHNxTmd4SNR5r94FGM8= github.com/acarl005/stripansi v0.0.0-20180116102854-5a71ef0e047d/go.mod h1:asat636LX7Bqt5lYEZ27JNDcqxfjdBQuJ/MM4CN/Lzo= -github.com/adrg/xdg v0.2.1 h1:VSVdnH7cQ7V+B33qSJHTCRlNgra1607Q8PzEmnvb2Ic= -github.com/adrg/xdg v0.2.1/go.mod h1:ZuOshBmzV4Ta+s23hdfFZnBsdzmoR3US0d7ErpqSbTQ= -github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= -github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= -github.com/anchore/go-logger v0.0.0-20220728155337-03b66a5207d8 h1:imgMA0gN0TZx7PSa/pdWqXadBvrz8WsN6zySzCe4XX0= -github.com/anchore/go-logger v0.0.0-20220728155337-03b66a5207d8/go.mod h1:+gPap4jha079qzRTUaehv+UZ6sSdaNwkH0D3b6zhTuk= +github.com/adrg/xdg v0.4.0 h1:RzRqFcjH4nE5C6oTAxhBtoE2IRyjBSa62SCbyPidvls= +github.com/adrg/xdg v0.4.0/go.mod h1:N6ag73EX4wyxeaoeHctc1mas01KZgsj5tYiAIwqJE/E= +github.com/anchore/bubbly v0.0.0-20230518153401-87b6af8ccf22 h1:5NFK6VGgqBUOAX2SYyzFYvNdOiYDxzim8jga386FlZY= +github.com/anchore/bubbly v0.0.0-20230518153401-87b6af8ccf22/go.mod h1:Kv+Mm9CdtnV8iem48iEPIwy7/N4Wmk0hpxYNH5gTwKQ= +github.com/anchore/clio v0.0.0-20230630162005-9535e9dc2817 h1:YsE91GT81FQOAOKByAnJVeJY2q8AunJ1eNf1bDC/o8g= +github.com/anchore/clio v0.0.0-20230630162005-9535e9dc2817/go.mod h1:H5f7dtqPQ6kbL0OHcLrc5N0zkIxLZPBL2oKUE03fLgA= +github.com/anchore/fangs v0.0.0-20230628163043-a51c5a39b097 h1:79jSyWO6WOV8HPEpOQBOr7WsC2DnBRpyl7zsdaahCcg= +github.com/anchore/fangs v0.0.0-20230628163043-a51c5a39b097/go.mod h1:E3zNHEz7mizIFGJhuX+Ga7AbCmEN5TfzVDxmOfj7XZw= +github.com/anchore/go-logger v0.0.0-20230531193951-db5ae83e7dbe h1:Df867YMmymdMG6z5IW8pR0/2CRpLIjYnaTXLp6j+s0k= +github.com/anchore/go-logger v0.0.0-20230531193951-db5ae83e7dbe/go.mod h1:ubLFmlsv8/DFUQrZwY5syT5/8Er3ugSr4rDFwHsE3hg= github.com/anchore/go-macholibre v0.0.0-20220308212642-53e6d0aaf6fb h1:iDMnx6LIjtjZ46C0akqveX83WFzhpTD3eqOthawb5vU= github.com/anchore/go-macholibre v0.0.0-20220308212642-53e6d0aaf6fb/go.mod h1:DmTY2Mfcv38hsHbG78xMiTDdxFtkHpgYNVDPsF2TgHk= github.com/andreyvit/diff v0.0.0-20170406064948-c7f18ee00883/go.mod h1:rCTlJbsFo29Kk6CurOXKm700vrz8f0KW0JNfpkRJY/8= github.com/andybalholm/cascadia v1.3.1 h1:nhxRkql1kdYCc8Snf7D5/D3spOX+dBgjA6u8x004T2c= github.com/andybalholm/cascadia v1.3.1/go.mod h1:R4bJ1UQfqADjvDa4P6HZHLh/3OxWWEqc0Sk8XGwHqvA= -github.com/armon/circbuf v0.0.0-20150827004946-bbbad097214e/go.mod h1:3U/XgcO3hCbHZ8TKRvWD2dDTCfh9M9ya+I9JpbB7O8o= -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/asaskevich/govalidator v0.0.0-20200907205600-7a23bdc65eef h1:46PFijGLmAjMPwCCCo7Jf0W6f9slllCkkv7vyc1yOSg= github.com/asaskevich/govalidator v0.0.0-20200907205600-7a23bdc65eef/go.mod h1:WaHUgvxTVq04UNunO+XhnAqY/wQc+bxr74GqbsZ/Jqw= github.com/atotto/clipboard v0.1.4 h1:EH0zSVneZPSuFR11BlR9YppQTVDbh5+16AmcJi4g1z4= @@ -40,16 +65,12 @@ github.com/aws/aws-sdk-go v1.44.114 h1:plIkWc/RsHr3DXBj4MEw9sEW4CcL/e2ryokc+CKyq github.com/aws/aws-sdk-go v1.44.114/go.mod h1:y4AeaBuwd2Lk+GepC1E9v0qOiTws0MIWAX4oIKwKHZo= github.com/aymanbagabas/go-osc52/v2 v2.0.1 h1:HwpRHbFMcZLEVr42D4p7XBqjyuxQH5SMiErDT4WkJ2k= github.com/aymanbagabas/go-osc52/v2 v2.0.1/go.mod h1:uYgXzlJ7ZpABp8OJ+exZzJJhRNQ2ASbcXHWsFqH8hp8= -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/blacktop/go-dwarf v1.0.9 h1:eT/L7gt0gllvvgnRXY0MFKjNB6+jtOY5DTm2ynVX2dY= github.com/blacktop/go-dwarf v1.0.9/go.mod h1:4W2FKgSFYcZLDwnR7k+apv5i3nrau4NGl9N6VQ9DSTo= github.com/blacktop/go-macho v1.1.161 h1:LLxfxS/XF5p9BzouQSgF+fU95gZBJCzW1FhLMLmCd8s= github.com/blacktop/go-macho v1.1.161/go.mod h1:f2X4noFBob4G5bWUrzvPBKDVcFWZgDCM7rIn7ygTID0= +github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= github.com/certifi/gocertifi v0.0.0-20180118203423-deb3ae2ef261/go.mod h1:GJKEexRPVJrBSOjoqN5VNOIKJ5Q3RViH6eu3puDRwx4= -github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc= github.com/charmbracelet/bubbles v0.11.0 h1:fBLyY0PvJnd56Vlu5L84JJH6f4axhgIJ9P3NET78f0Q= github.com/charmbracelet/bubbles v0.11.0/go.mod h1:bbeTiXwPww4M031aGi8UK2HT9RDWoiNibae+1yCMtcc= github.com/charmbracelet/bubbletea v0.21.0/go.mod h1:GgmJMec61d08zXsOhqRC/AiOx4K4pmz+VIcRIm1FKr4= @@ -64,33 +85,33 @@ github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWR github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI= github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU= github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= +github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc= +github.com/cncf/udpa/go v0.0.0-20200629203442-efcf912fb354/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk= +github.com/cncf/udpa/go v0.0.0-20201120205902-5459f2c99403/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk= github.com/containerd/console v1.0.3 h1:lIr7SlA5PxZyMV30bDW0MGbiOPXwc63yRuCP0ARubLw= github.com/containerd/console v1.0.3/go.mod h1:7LqA/THxQ86k76b8c/EMSiaJ3h1eZkMkXar0TQ1gf3U= -github.com/coreos/bbolt v1.3.2/go.mod h1:iRUV2dpdMOn7Bo10OQBFzIJO9kkE559Wcmn+qkEiiKk= -github.com/coreos/etcd v3.3.13+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE= -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/pkg v0.0.0-20180928190104-399ea9e2e55f/go.mod h1:E3G3o1h8I7cfcXa63jLwjI0eiQQMgzzUDFVpN/nH/eA= github.com/cpuguy83/go-md2man/v2 v2.0.2/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= 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= 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/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/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= +github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= +github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98= +github.com/envoyproxy/go-control-plane v0.9.7/go.mod h1:cwu0lG7PUMfa9snN8LXBig5ynNVH9qI8YYLbd1fK2po= +github.com/envoyproxy/go-control-plane v0.9.9-0.20201210154907-fd9021fe5dad/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk= +github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= github.com/erikgeiser/promptkit v0.7.0 h1:Yi28iN6JRs8/0x+wjQRPfWb+vWz1pFmZ5fu2uoFipD8= github.com/erikgeiser/promptkit v0.7.0/go.mod h1:Jj9bhN+N8RbMjB1jthkr9A4ydmczZ1WZJ8xTXnP12dg= -github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4= github.com/fatih/set v0.2.1 h1:nn2CaJyknWE/6txyUDGwysr3G5QC6xWB/PtVjPBbeaA= github.com/fatih/set v0.2.1/go.mod h1:+RKtMCH+favT2+3YecHGxcc0b4KyVWA1QWWJUs4E0CI= github.com/felixge/fgprof v0.9.3 h1:VvyZxILNuCiUCSXtPtYmmtGvb65nqXh2QFWc0Wpf2/g= github.com/felixge/fgprof v0.9.3/go.mod h1:RdbpDgzqYVh/T9fPELJyV7EYJuHB55UTEULNun8eiPw= -github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= -github.com/fsnotify/fsnotify v1.4.9 h1:hsms1Qyu0jgnwNXIxa+/V/PDsU6CfLf6CNO8H7IWoS4= -github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ= +github.com/frankban/quicktest v1.14.3 h1:FJKSZTDHjyhriyC81FLQ0LY93eSai0ZyR/ZIkd3ZUKE= +github.com/fsnotify/fsnotify v1.6.0 h1:n+5WquG0fcWoWp6xPWfHdbskMCQaFnG6PfBrh1Ky4HY= +github.com/fsnotify/fsnotify v1.6.0/go.mod h1:sl3t1tCWJFWoRz9R8WJCbQihKKwmorjAbSClcnxKAGw= github.com/gabriel-vasile/mimetype v1.4.2 h1:w5qFW6JKBz9Y393Y4q372O9A7cUSequkh1Q7OhCmWKU= github.com/gabriel-vasile/mimetype v1.4.2/go.mod h1:zApsH/mKG4w07erKIaJPFiX0Tsq9BFQgN3qGY5GnNgA= -github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= github.com/github/smimesign v0.2.0 h1:Hho4YcX5N1I9XNqhq0fNx0Sts8MhLonHd+HRXVGNjvk= github.com/github/smimesign v0.2.0/go.mod h1:iZiiwNT4HbtGRVqCQu7uJPEZCuEE5sfSSttcnePkDl4= github.com/gkampitakis/ciinfo v0.2.4 h1:Ip1hf4K7ISRuVlDrheuhaeffg1VOhlyeFGaQ/vTxrtE= @@ -100,82 +121,92 @@ github.com/gkampitakis/go-diff v1.3.2/go.mod h1:LLgOrpqleQe26cte8s36HTWcTmMEur6O github.com/gkampitakis/go-snaps v0.4.5 h1:balDeoor0XSLhhFtqBHajVgU8AF6eKOFmIoNhH1NqsQ= github.com/gkampitakis/go-snaps v0.4.5/go.mod h1:oEL8WdkP4EpHdMA3wrNRjfRiV9wV3OhU/JpX9l4joA0= 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-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= +github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= github.com/go-openapi/errors v0.20.2 h1:dxy7PGTqEh94zj2E3h1cUmQQWiM1+aeCROfAr02EmK8= github.com/go-openapi/errors v0.20.2/go.mod h1:cM//ZKUKyO06HSwqAelJ5NsEMMcpa6VpXe8DOa1Mi1M= github.com/go-openapi/strfmt v0.21.3 h1:xwhj5X6CjXEZZHMWy1zKJxvW9AfHC9pkyUjLvHtKG7o= github.com/go-openapi/strfmt v0.21.3/go.mod h1:k+RzNO0Da+k3FrrynSNN8F7n/peCmQQqbbXjtDfvmGg= github.com/go-restruct/restruct v1.2.0-alpha h1:2Lp474S/9660+SJjpVxoKuWX09JsXHSrdV7Nv3/gkvc= github.com/go-restruct/restruct v1.2.0-alpha/go.mod h1:KqrpKpn4M8OLznErihXTGLlsXFGeLxHUrLRRI/1YjGk= -github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= -github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= -github.com/gogo/protobuf v1.2.1/go.mod h1:hp+jE20tsWTFYpLwKvXlhS1hjn+gTNwPg2I6zVXpSg4= github.com/golang-jwt/jwt/v4 v4.5.0 h1:7cYmW1XlMY7h7ii7UhUyChSgS5wUJEnm9uZVTGqOWzg= github.com/golang-jwt/jwt/v4 v4.5.0/go.mod h1:m21LjoU+eqJr34lmDMbreY2eSTRJ1cv77w39/MY0Ch0= 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/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= +github.com/golang/groupcache v0.0.0-20191227052852-215e87163ea7/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= +github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e/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/mock v1.4.0/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= +github.com/golang/mock v1.4.1/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= +github.com/golang/mock v1.4.3/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= +github.com/golang/mock v1.4.4/go.mod h1:l3mdAwkq5BuhzHwde/uurv3sEJeZMXNpwsxVWU71h+4= 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/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw= +github.com/golang/protobuf v1.3.4/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw= +github.com/golang/protobuf v1.3.5/go.mod h1:6O5/vntMXwX2lRkT1hjjk0nAC1IDOTvTlVgjlRvqsdk= +github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8= +github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA= +github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs= +github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w= +github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0= +github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8= +github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= +github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= github.com/golang/snappy v0.0.1/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= 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/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= +github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.4.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38= github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs= +github.com/google/martian/v3 v3.0.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0= +github.com/google/martian/v3 v3.1.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0= 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/pprof v0.0.0-20191218002539-d4f498aebedc/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= +github.com/google/pprof v0.0.0-20200212024743-f11f1df84d12/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= +github.com/google/pprof v0.0.0-20200229191704-1ebb73c60ed3/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= +github.com/google/pprof v0.0.0-20200430221834-fc25d7d30c6d/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= +github.com/google/pprof v0.0.0-20200708004538-1a94d8640e99/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= +github.com/google/pprof v0.0.0-20201023163331-3e6fc7fc9c4c/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= +github.com/google/pprof v0.0.0-20201203190320-1bf35d6f28c2/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= +github.com/google/pprof v0.0.0-20201218002935-b9804c9f04c2/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= github.com/google/pprof v0.0.0-20211214055906-6f57359322fd h1:1FjCyPC+syAzJ5/2S8fqdZK1R22vvA0J7JZKcuOIQ7Y= github.com/google/pprof v0.0.0-20211214055906-6f57359322fd/go.mod h1:KgnwoLYCZ8IQu3XUZ8Nc/bM9CCZFOyjUNOSygVozoDg= 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/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I= +github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= 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/googleapis/google-cloud-go-testing v0.0.0-20200911160855-bcd43fbb19e8/go.mod h1:dvDLG8qkwmyD9a/MJJN3XJcT3xFxOKAvTZGvuZmac9g= github.com/gookit/color v1.2.5/go.mod h1:AhIE+pS6D4Ql0SQWbBeXPHw7gY0/sjHoA4s/n1KB7xg= github.com/gookit/color v1.5.3 h1:twfIhZs4QLCtimkP7MOxlF3A0U/5cDPseRT9M/+2SCE= github.com/gookit/color v1.5.3/go.mod h1:NUzwzeehUfl7GIb36pqId+UGmRfQcU/WiiyTTeNjHtE= -github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1 h1:EGx4pi6eqNxGaHF6qqu48+N2wcFQ5qg5FXgOdqsJ5d8= -github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY= -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/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 h1:hLrqtEDnRye3+sgx6z4qVLNuviH3MR5aQ0ykNJa/UYA= 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-multierror v1.1.0/go.mod h1:spPvp8C1qA32ftKqdAHm4hHTbPw+vmowP0z+KUhOZdA= github.com/hashicorp/go-multierror v1.1.1 h1:H5DkEtf6CXdFp0N0Em5UCwQpXMWke8IA0+lD48awMYo= github.com/hashicorp/go-multierror v1.1.1/go.mod h1:iw975J/qwKPdAO1clOe2L8331t/9/fmwbPZ6JB6eMoM= -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 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4= 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/iancoleman/strcase v0.2.0 h1:05I4QRnGpI0m37iZQRuskXh+w77mr6Z41lwQzuHLwW0= -github.com/iancoleman/strcase v0.2.0/go.mod h1:iwCmte+B7n89clKwxIoIXy/HfoL7AsD47ZCWhYzw7ho= +github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= +github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= github.com/ianlancetaylor/demangle v0.0.0-20210905161508-09a460cdf81d/go.mod h1:aYm2/VgdVmcIU8iMfdMvDMsRAQjcfZSKFby6HOFvi/w= github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8= github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw= @@ -185,19 +216,13 @@ github.com/jmespath/go-jmespath v0.4.0 h1:BEgLn5cpjn8UN1mAw4NjwDrS35OdebyEtFe+9Y github.com/jmespath/go-jmespath v0.4.0/go.mod h1:T8mJZnbsbmF+m6zOOFylbeCJqk5+pHWvzYPziyZiYoo= github.com/jmespath/go-jmespath/internal/testify v1.5.1 h1:shLQSRRSCCPj3f2gpwzGwWFoC7ycTf1rcQZHOlsJ6N8= github.com/jmespath/go-jmespath/internal/testify v1.5.1/go.mod h1:L3OGu8Wl2/fWfCI6z80xFu9LTZmf1ZRjMHUOPmWr69U= -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/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU= -github.com/jtolds/gls v4.20.0+incompatible h1:xdiiI2gbIgH/gLH7ADydsJ1uDOEzR8yvV7C0MuV77Wo= -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/jstemmer/go-junit-report v0.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/XSXhF0NWZEnDohbsk= github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= github.com/klauspost/compress v1.13.6/go.mod h1:/3/Vjq9QcHkK5uEr5lBEmyoZ1iFhe47etQ6QUkpK6sk= github.com/klauspost/compress v1.16.6 h1:91SKEy4K37vkp255cJ8QesJhjyRO0hn9i9G0GoUwLsk= github.com/klauspost/compress v1.16.6/go.mod h1:ntbaceVETuRiXiv4DpjP66DpAtAGkEQskQzEyD//IeE= -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= +github.com/kr/fs v0.1.0/go.mod h1:FFnZGqtBN9Gxj7eW1uZ42v5BccTP0vu6NEaFoC2HwRg= github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= @@ -208,12 +233,10 @@ github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw= github.com/lucasb-eyer/go-colorful v1.2.0 h1:1nnpGOrhyZZuNyfu1QjKiUICQ74+3FNCN69Aj6K7nkY= github.com/lucasb-eyer/go-colorful v1.2.0/go.mod h1:R4dSotOR9KMtayYi1e77YzuveK+i7ruzyGqttikkLy0= -github.com/magiconair/properties v1.8.1 h1:ZC2Vc7/ZFkGmsVC9KvOjumD+G5lXy2RtTKyzRKO2BQ4= -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/magiconair/properties v1.8.7 h1:IeQXZAiQcpL9mgcAe1Nu6cX9LLw6ExEHKjN0VQdvPDY= +github.com/magiconair/properties v1.8.7/go.mod h1:Dhd985XPs7jluiymwWYZ0G4Z61jb3vdS329zhj2hYo0= github.com/mattn/go-colorable v0.1.12 h1:jF+Du6AlPIjs2BiUiQlKOX0rt3SujHxPnksPKZbaA40= github.com/mattn/go-colorable v0.1.12/go.mod h1:u5H1YNBxpqRaxsYJYSkiCWKzEfiAb1Gb520KVy5xxl4= -github.com/mattn/go-isatty v0.0.3/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4= github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94= github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= github.com/mattn/go-isatty v0.0.17 h1:BTarxUcIeDqL27Mc+vyvdWYSL28zpIhv3RoTdsLMPng= @@ -225,23 +248,13 @@ github.com/mattn/go-runewidth v0.0.12/go.mod h1:RAqKPSqVFrSLVXbA8x7dzmKdmGzieGRC github.com/mattn/go-runewidth v0.0.13/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w= github.com/mattn/go-runewidth v0.0.14 h1:+xnbZSEeDbOIg5/mE6JF0w6n9duR1l3/WmbinWVwUuU= github.com/mattn/go-runewidth v0.0.14/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w= -github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0= github.com/mgutz/ansi v0.0.0-20200706080929-d51e80ef957d h1:5PJl274Y63IEHC+7izoQE9x6ikvDFZS2mDVS3drnohI= github.com/mgutz/ansi v0.0.0-20200706080929-d51e80ef957d/go.mod h1:01TrycV0kFyexm33Z7vhZRXopbI8J3TDReVlkTgMUxE= -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 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/mitchellh/mapstructure v1.3.3 h1:SzB1nHZ2Xi+17FP0zVQBHIZqvwRN9408fJO8h+eeNA8= github.com/mitchellh/mapstructure v1.3.3/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= -github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= -github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= +github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY= +github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= github.com/montanaflynn/stats v0.0.0-20171201202039-1bf9dbcd8cbe/go.mod h1:wL8QJuTMNUDYhXwkmfOly8iTdp5TEcJFWZD2D7SIkUc= github.com/muesli/ansi v0.0.0-20211018074035-2e021307bc4b/go.mod h1:fQuZ0gauxyBcmsdE3ZT4NasjaRdxmbCS0jRHsrWu3Ho= github.com/muesli/ansi v0.0.0-20211031195517-c9f0611b6c70 h1:kMlmsLSbjkikxQJ1IPwaM+7LJ9ltFu/fi8CRzvSnQmA= @@ -257,91 +270,68 @@ github.com/muesli/termenv v0.11.1-0.20220212125758-44cd13922739/go.mod h1:Bd5NYQ github.com/muesli/termenv v0.12.0/go.mod h1:WCCv32tusQ/EEZ5S8oUIIrC/nIuBcxCVqlN4Xfkv+7A= github.com/muesli/termenv v0.15.1 h1:UzuTb/+hhlBugQz28rpzey4ZuKcZ03MeKsoG7IJZIxs= github.com/muesli/termenv v0.15.1/go.mod h1:HeAQPTzpfs016yGtA4g00CsdYnVLJvxsS4ANqrZs2sQ= -github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e h1:fD57ERR4JtEqsWbfPhv4DMiApHyliiK5xCTNVSPiaAs= github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno= github.com/oklog/ulid v1.3.1 h1:EGfNDEx6MqHz8B3uNV6QAib1UR2Lm97sHi3ocA6ESJ4= github.com/oklog/ulid v1.3.1/go.mod h1:CirwcVhetQ6Lv90oh/F+FBtV6XMibvdAFo93nm5qn4U= -github.com/pascaldekloe/goe v0.0.0-20180627143212-57f6aae5913c/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc= github.com/pborman/getopt v0.0.0-20180811024354-2b5b3bfb099b/go.mod h1:85jBQOZwpVEaDAr341tbn15RS4fCAsIst0qp7i8ex1o= -github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic= -github.com/pelletier/go-toml v1.8.1 h1:1Nf83orprkJyknT6h7zbuEGUEjcyVlCxSUGTENmNCRM= -github.com/pelletier/go-toml v1.8.1/go.mod h1:T2/BmBdy8dvIRq1a/8aqjN41wvWlN4lrapLU/GW4pbc= +github.com/pborman/indent v1.2.1 h1:lFiviAbISHv3Rf0jcuh489bi06hj98JsVMtIDZQb9yM= +github.com/pborman/indent v1.2.1/go.mod h1:FitS+t35kIYtB5xWTZAPhnmrxcciEEOdbyrrpz5K6Vw= +github.com/pelletier/go-toml/v2 v2.0.6 h1:nrzqCb7j9cDFj2coyLNLaZuJTLjWjlaz6nvTvIwycIU= +github.com/pelletier/go-toml/v2 v2.0.6/go.mod h1:eumQOmlWiOPt5WriQQqoM5y18pDHwha2N+QD+EUNTek= github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA= -github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/profile v1.7.0 h1:hnbDkaNWPCLMO9wGLdBFTIZvzDrDfBM2072E1S9gJkA= github.com/pkg/profile v1.7.0/go.mod h1:8Uer0jas47ZQMJ7VD+OHknK4YDY07LPUC6dEvqDjvNo= +github.com/pkg/sftp v1.13.1/go.mod h1:3HaPG6Dq1ILlpPZRO0HVMrsydcdLt6HRDccSgb87qRg= 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/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= github.com/rivo/uniseg v0.1.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc= github.com/rivo/uniseg v0.2.0 h1:S1pD9weZBuJdFmowNwbpi7BJ8TNftyUImj/0WQi72jY= github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc= -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/rogpeppe/go-internal v1.9.0 h1:73kH8U+JUqXU8lRuOHeVHaa/SZPifC7BkcraZVejAe8= github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs= github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= -github.com/ryanuber/columnize v0.0.0-20160712163229-9b3edd62028f/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts= github.com/sahilm/fuzzy v0.1.0/go.mod h1:VFvziUEIMCrT6A6tw2RFIXPXXmzXbOsSHF0DOI8ZK9Y= github.com/scylladb/go-set v1.0.2 h1:SkvlMCKhP0wyyct6j+0IHJkBkSZL+TDzZ4E7f7BCcRE= github.com/scylladb/go-set v1.0.2/go.mod h1:DkpGd78rljTxKAnTDPFqXSGxvETQnJyuSOQwsHycqfs= -github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529/go.mod h1:DxrIzT+xaE7yg65j358z/aeFdxmN0P9QXhEzd20vsDc= github.com/sergi/go-diff v1.1.0 h1:we8PVUC3FE2uYfodKH/nBHMSetSfHDR6scGdBi+erh0= github.com/sergi/go-diff v1.1.0/go.mod h1:STckp+ISIX8hZLjrqAeVduY0gWCT9IjLuqbuNXdaHfM= -github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo= github.com/sirupsen/logrus v1.8.1 h1:dJKuHgqk1NNQlqoA6BTlM1Wf9DOH3NBjQyu0h9+AZZE= github.com/sirupsen/logrus v1.8.1/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0= -github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc= -github.com/smartystreets/assertions v1.0.0 h1:UVQPSSmc3qtTi+zPPkCXvZX9VvW/xT/NsRvKfwY81a8= -github.com/smartystreets/assertions v1.0.0/go.mod h1:kHHU4qYBaI3q23Pp3VPrmWhuIUrLW/7eUrw0BU5VaoM= -github.com/smartystreets/goconvey v1.6.4 h1:fv0U8FUIMPNf1L9lnHLvLhgicrIVChEkdzIKYqbNC9s= -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/afero v1.2.2 h1:5jhuqJyZCZf2JRofRvN/nIFgIWNzPa3/Vz8mYylgbWc= -github.com/spf13/afero v1.2.2/go.mod h1:9ZxEEn6pIJ8Rxe320qSDBk6AsU0r9pR7Q4OcevTdifk= -github.com/spf13/cast v1.3.0/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE= -github.com/spf13/cast v1.3.1 h1:nFm6S0SMdyzrzcmThSipiEubIDy8WEXKNZ0UOgiRpng= -github.com/spf13/cast v1.3.1/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE= +github.com/spf13/afero v1.9.3 h1:41FoI0fD7OR7mGcKE/aOiLkGreyf8ifIOQmJANWogMk= +github.com/spf13/afero v1.9.3/go.mod h1:iUV7ddyEEZPO5gA3zD4fJt6iStLlL+Lg4m2cihcDf8Y= +github.com/spf13/cast v1.5.0 h1:rj3WzYc11XZaIZMPKmwP96zkFEnnAmV8s6XbB2aY32w= +github.com/spf13/cast v1.5.0/go.mod h1:SpXXQ5YoyJw6s3/6cMTQuxvgRl3PCJiyaX9p6b155UU= github.com/spf13/cobra v1.7.0 h1:hyqWnYt1ZQShIddO5kBpj3vu05/++x6tJ6dg8EC572I= github.com/spf13/cobra v1.7.0/go.mod h1:uLxZILRyS/50WlhOIKD7W6V5bgeIt+4sICxh6uRMrb0= -github.com/spf13/jwalterweatherman v1.0.0/go.mod h1:cQK4TGJAtQXfYWX+Ddv3mKDzgVb68N+wFjFa4jdeBTo= github.com/spf13/jwalterweatherman v1.1.0 h1:ue6voC5bR5F8YxI5S67j9i582FU4Qvo2bmqnqMYADFk= github.com/spf13/jwalterweatherman v1.1.0/go.mod h1:aNWZUN0dPAAO/Ljvb5BEdw96iTZ0EXowPYD95IqWIGo= -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.7.0 h1:xVKxvI7ouOI5I+U9s2eeiUfMaWBVoXA3AWskkrqK0VM= -github.com/spf13/viper v1.7.0/go.mod h1:8WkrPz2fc9jxqZNCJI/76HCieCp4Q8HaLFoCha5qpdg= +github.com/spf13/viper v1.15.0 h1:js3yy885G8xwJa6iOISGFwd+qlUo5AvyXb7CiihdtiU= +github.com/spf13/viper v1.15.0/go.mod h1:fFcTBJxvhhzSJiZy8n+PeW6t8l+KeT/uTARa0jHOQLA= 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.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= github.com/stretchr/objx v0.5.0 h1:1zr/of2m5FGMsad5YfcqgdqdWrIhu+EBEJRhR1U7z/c= github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= +github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= +github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk= github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= -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/subosito/gotenv v1.4.2 h1:X1TuBLAMDFbaTAChgCBLu3DU3UPyELpnF2jjJ2cz/S8= +github.com/subosito/gotenv v1.4.2/go.mod h1:ayKnFf/c6rvx/2iiLrJUk1e6plDbT3edrFNGqEflhK0= github.com/tidwall/gjson v1.14.2/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk= github.com/tidwall/gjson v1.14.4 h1:uo0p8EbA09J7RQaflQ1aBRffTR7xedD2bcIVSYxLnkM= github.com/tidwall/gjson v1.14.4/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk= @@ -353,34 +343,38 @@ github.com/tidwall/pretty v1.2.1 h1:qjsOFOWWQl+N3RsoF5/ssm1pHmJJwhjlSbZ51I6wMl4= github.com/tidwall/pretty v1.2.1/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhsoaGGjNU= github.com/tidwall/sjson v1.2.5 h1:kLy8mja+1c9jlljvWTlSazM7cKDRfJuR/bOJhcY5NcY= github.com/tidwall/sjson v1.2.5/go.mod h1:Fvgq9kS/6ociJEDnK0Fk1cpYF4FIW6ZF7LAe+6jwd28= -github.com/tmc/grpc-websocket-proxy v0.0.0-20190109142713-0ad062ec5ee5/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U= -github.com/wagoodman/go-partybus v0.0.0-20210627031916-db1f5573bbc5 h1:phTLPgMRDYTizrBSKsNSOa2zthoC2KsJsaY/8sg3rD8= -github.com/wagoodman/go-partybus v0.0.0-20210627031916-db1f5573bbc5/go.mod h1:JPirS5jde/CF5qIjcK4WX+eQmKXdPc6vcZkJ/P0hfPw= +github.com/wagoodman/go-partybus v0.0.0-20230516145632-8ccac152c651 h1:jIVmlAFIqV3d+DOxazTR9v+zgj8+VYuQBzPgBZvWBHA= +github.com/wagoodman/go-partybus v0.0.0-20230516145632-8ccac152c651/go.mod h1:b26F2tHLqaoRQf8DywqzVaV1MQ9yvjb0OMcNl7Nxu20= github.com/wagoodman/go-progress v0.0.0-20220614130704-4b1c25a33c7c h1:gFwUKtkv6QzQsFdIjvPqd0Qdw42DHUEbbUdiUTI1uco= github.com/wagoodman/go-progress v0.0.0-20220614130704-4b1c25a33c7c/go.mod h1:jLXFoL31zFaHKAAyZUh+sxiTDFe1L1ZHrcK2T1itVKA= github.com/xdg-go/pbkdf2 v1.0.0/go.mod h1:jrpuAogTd400dnrH08LKmI/xc1MbPOebTwRqcT5RDeI= github.com/xdg-go/scram v1.1.1/go.mod h1:RaEWvsqvNKKvBPvcKeFjrG2cJqOkHTiyTpzz23ni57g= github.com/xdg-go/stringprep v1.0.3/go.mod h1:W3f5j4i+9rC0kuIEJL0ky1VpHXQU3ocBgklLGvcBnW8= -github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2/go.mod h1:UETIi67q53MR2AWcXfiuqkDkRtnGDLqkBTpCHuJHxtU= github.com/xo/terminfo v0.0.0-20210125001918-ca9a967f8778 h1:QldyIu/L63oPpyvQmHgvgickp1Yw510KJOqX7H24mg8= github.com/xo/terminfo v0.0.0-20210125001918-ca9a967f8778/go.mod h1:2MuV+tbUrU1zIOPMxZ5EncGwgmMJsa+9ucAQZXxsObs= github.com/youmark/pkcs8 v0.0.0-20181117223130-1be2e3e5546d/go.mod h1:rHwXgn7JulP+udvsHwJoVG1YGAP6VLg4y9I5dyZdqmA= +github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= +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= github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= -go.etcd.io/bbolt v1.3.2/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU= go.mongodb.org/mongo-driver v1.10.0 h1:UtV6N5k14upNp4LTduX0QCufG124fSu25Wz9tu94GLg= go.mongodb.org/mongo-driver v1.10.0/go.mod h1:wsihk0Kdgv8Kqu1Anit4sfK+22vSFbUrAVEYRhCXrA8= 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.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE= -go.uber.org/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0= -go.uber.org/zap v1.10.0/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q= -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= +go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= +go.opencensus.io v0.22.3/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= +go.opencensus.io v0.22.4/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= +go.opencensus.io v0.22.5/go.mod h1:5pWMHQbX5EPX2/62yrJeAkowc+lfs/XD7Uxpq3pI6kk= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20190426145343-a29dc8fdc734/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= 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-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/crypto v0.0.0-20210421170649-83a5a9bb288b/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4= golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= +golang.org/x/crypto v0.0.0-20211108221036-ceb1ce70b4fa/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= golang.org/x/crypto v0.0.0-20220331220935-ae2d96664a29/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= golang.org/x/crypto v0.0.0-20220622213112-05595931fe9d/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= golang.org/x/crypto v0.0.0-20221012134737-56aed061732a h1:NmSIgad6KjE6VvHciPZuNRTKxGhlPfD6OA87W/PLkqg= @@ -390,6 +384,11 @@ golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL 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/exp v0.0.0-20191129062945-2f5052295587/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= +golang.org/x/exp v0.0.0-20191227195350-da58074b4299/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= +golang.org/x/exp v0.0.0-20200119233911-0405dc783f0a/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= +golang.org/x/exp v0.0.0-20200207192155-f17229e696bd/go.mod h1:J/WKrq2StrnmMY6+EHIKF9dgMWnmCNThgcyBT1FY9mM= +golang.org/x/exp v0.0.0-20200224162631-6cc2880d07d6/go.mod h1:3jZMyOhIsHpP37uCMkUooju7aAi5cS1Q23tOzKc+0MU= 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= @@ -399,17 +398,23 @@ golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHl 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/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= +golang.org/x/lint v0.0.0-20191125180803-fdd1cda4f05f/go.mod h1:5qLYkcX4OjUUV8bRuDixDT3tpyyb+LUpUlRWLxfhWrs= +golang.org/x/lint v0.0.0-20200130185559-910be7a94367/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= +golang.org/x/lint v0.0.0-20200302205851-738671d3881b/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= +golang.org/x/lint v0.0.0-20201208152925-83fdc39ff7b5/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= 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/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= +golang.org/x/mod v0.1.1-0.20191107180719-034126e5016b/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= +golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/mod v0.4.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/mod v0.4.1/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= 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-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/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= @@ -418,6 +423,26 @@ golang.org/x/net v0.0.0-20190501004415-9ce7a6920f09/go.mod h1:t9HGtf8HONx5eT2rtn 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/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20190628185345-da137c7871d7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20190724013045-ca1201d0de80/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20191209160850-c0dbc17a3553/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200114155413-6afb5195e5aa/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200202094626-16171245cfb2/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200222125558-5a598a2470a0/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200301022130-244492dfa37a/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200324143707-d3edc9973b7e/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= +golang.org/x/net v0.0.0-20200501053045-e0ff5e5a1de5/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= +golang.org/x/net v0.0.0-20200506145744-7e3656a0809f/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= +golang.org/x/net v0.0.0-20200513185701-a91f0712d120/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= +golang.org/x/net v0.0.0-20200520182314-0ba52f642ac2/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= +golang.org/x/net v0.0.0-20200625001655-4c5254603344/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= +golang.org/x/net v0.0.0-20200707034311-ab3426394381/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= +golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= +golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= +golang.org/x/net v0.0.0-20201031054903-ff519b6c9102/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= +golang.org/x/net v0.0.0-20201209123823-ac852fbbde11/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= +golang.org/x/net v0.0.0-20201224014010-6772e930b67b/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/net v0.0.0-20210916014120-12bc252f5db8/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= @@ -429,19 +454,25 @@ golang.org/x/net v0.8.0/go.mod h1:QVkue5JL9kW//ek3r6jTKnTFis1tRmNAW2P1shuFdJc= 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/oauth2 v0.0.0-20191202225959-858c2ad4c8b6/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= +golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= +golang.org/x/oauth2 v0.0.0-20200902213428-5d25da1a8d43/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= +golang.org/x/oauth2 v0.0.0-20201109201403-9fd604954f58/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= +golang.org/x/oauth2 v0.0.0-20201208152858-08078c50e5b5/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= +golang.org/x/oauth2 v0.0.0-20210218202405-ba52d332ba99/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= 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/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20200317015054-43a5402ce75a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/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-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-20190215142949-d0b11bdaac8a/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-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= @@ -449,15 +480,39 @@ golang.org/x/sys v0.0.0-20190502145724-3ef323f4f1fd/go.mod h1:h1NjWce9XRLGQEsW7w 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-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190726091711-fc99dfbffb4e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191001151750-bb3f8db39f24/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191228213918-04cbcbbfeed8/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200113162924-86b910548bc1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200122134326-e047566fdf82/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200202164722-d101bd2416d5/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200212091648-12a6c2dcc1e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200302150141-5c8b2ff67527/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200331124033-c3d80250170d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200501052902-10377860bb8e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200511232937-7e40ca221e25/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200515095857-1151b9dac4a9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200523222454-059865788121/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200803210538-64077c9b5642/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200905004654-be1d3432aa8f/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-20201201145000-ef89a241ccb3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210104204734-6f8348627aad/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210119212857-b64e53b001e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210225134936-a50acf3fe073/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210423185535-09eb48e85fd7/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210927094055-39ccf1dd6fa6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20211007075335-d3039528d8ac/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20211025201205-69cdffdb9359/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220204135822-1c1b9b1eba6a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220209214540-3681064d5158/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= @@ -465,19 +520,22 @@ golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBc golang.org/x/sys v0.0.0-20220615213510-4f61da869c0c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220908164124-27713097b956/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.8.0 h1:EBmGv8NaZBZTWvrbjNoL6HVt+IVy3QDQpJs7VRIw3tU= -golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.9.0 h1:KS/R3tvhPqvJvwcKfnBHJwwthS11LRhmM5D59eEXa0s= +golang.org/x/sys v0.9.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= golang.org/x/term v0.0.0-20220526004731-065cf7ba2467/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k= -golang.org/x/term v0.8.0 h1:n5xxQn2i3PC0yLAbjTpNT85q/Kgzcr2gIoX9OrJUols= -golang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo= +golang.org/x/term v0.9.0 h1:GRRCnKYhdQrD8kfRAdQ6Zcw1P0OcELxGLKJvtjVMZ28= +golang.org/x/term v0.9.0/go.mod h1:M6DEAAIenWoTxdKrOltXcmDY3rSplQUkrvaDU5FcQyo= +golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= 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/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.3.4/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= @@ -485,38 +543,86 @@ golang.org/x/text v0.8.0 h1:57P1ETyNKtuIjB4SRd15iJxuhj8Gc416Y78H3qgMh68= golang.org/x/text v0.8.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= 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/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= 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-20190506145303-2d16b83fe98c/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= +golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/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-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-20191112195655-aa38f8e97acc/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191113191852-77e3bb0ad9e7/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191115202509-3a792d9c32b2/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191125144606-a911d9008d1f/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191130070609-6e064ea0cf2d/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191216173652-a0e659d51361/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20191227053925-7b8e75db28f4/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200117161641-43d50277825c/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200122220014-bf1340f18c4a/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200130002326-2f3ba24bd6e7/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200204074204-1cc6d1ef6c74/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +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-20200224181240-023911ca70b2/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200227222343-706bc42d1f0d/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200304193943-95d2e580d8eb/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw= +golang.org/x/tools v0.0.0-20200312045724-11d5b4c81c7d/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw= +golang.org/x/tools v0.0.0-20200331025713-a30bf2db82d4/go.mod h1:Sl4aGygMT6LrqrWclx+PTx3U+LnKx/seiNR+3G19Ar8= +golang.org/x/tools v0.0.0-20200501065659-ab2804fb9c9d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= +golang.org/x/tools v0.0.0-20200512131952-2bc93b1c0c88/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= +golang.org/x/tools v0.0.0-20200515010526-7d3b6ebf133d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= +golang.org/x/tools v0.0.0-20200618134242-20370b0cb4b2/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= +golang.org/x/tools v0.0.0-20200729194436-6467de6f59a7/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= +golang.org/x/tools v0.0.0-20200804011535-6c149bb5ef0d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= +golang.org/x/tools v0.0.0-20200825202427-b303f430e36d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= +golang.org/x/tools v0.0.0-20200904185747-39188db58858/go.mod h1:Cj7w3i3Rnn0Xh82ur9kSqwfTHTeVxaDqrfMjpcNT6bE= +golang.org/x/tools v0.0.0-20201110124207-079ba7bd75cd/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= +golang.org/x/tools v0.0.0-20201201161351-ac6f37ff4c2a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= +golang.org/x/tools v0.0.0-20201208233053-a543418bbed2/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= +golang.org/x/tools v0.0.0-20210105154028-b0ab187a4818/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= +golang.org/x/tools v0.0.0-20210108195828-e2f9c7f1fc8e/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= +golang.org/x/tools v0.1.0/go.mod h1:xkSsbof2nBLbhDlRMhhhyNLN/zl3eTqcnHD5viDpcZ0= golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE= golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20220907171357-04be3eba64a2 h1:H2TDz8ibqkAF6YGhCdN3jS9O0/s90v0rJh3X/OLHEUk= 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/api v0.14.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= +google.golang.org/api v0.15.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= +google.golang.org/api v0.17.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= +google.golang.org/api v0.18.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= +google.golang.org/api v0.19.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= +google.golang.org/api v0.20.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= +google.golang.org/api v0.22.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= +google.golang.org/api v0.24.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE= +google.golang.org/api v0.28.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE= +google.golang.org/api v0.29.0/go.mod h1:Lcubydp8VUV7KeIHD9z2Bys/sm/vGKnG1UHuDBSrHWM= +google.golang.org/api v0.30.0/go.mod h1:QGmEvQ87FHZNiUVJkT14jQNYJ4ZJjdRF23ZXz5138Fc= +google.golang.org/api v0.35.0/go.mod h1:/XrVsuzM0rZmrsbjJutiuftIzeuTQcEeaYcSk/mQ1dg= +google.golang.org/api v0.36.0/go.mod h1:+z5ficQTmoYpPn8LCUNVpK5I7hwkpjbcgqA7I34qYtE= +google.golang.org/api v0.40.0/go.mod h1:fYKFpnQN0DsDSKRVRcQSDQNtqWPfM9i+zNPxepjRCQ8= 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/appengine v1.6.5/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= +google.golang.org/appengine v1.6.6/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= +google.golang.org/appengine v1.6.7/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= 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= @@ -526,27 +632,71 @@ google.golang.org/genproto v0.0.0-20190801165951-fa694d86fc64/go.mod h1:DMBHOl98 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/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= +google.golang.org/genproto v0.0.0-20191115194625-c23dd37a84c9/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= +google.golang.org/genproto v0.0.0-20191216164720-4f79533eabd1/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= +google.golang.org/genproto v0.0.0-20191230161307-f3c370f40bfb/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= +google.golang.org/genproto v0.0.0-20200115191322-ca5a22157cba/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= +google.golang.org/genproto v0.0.0-20200122232147-0452cf42e150/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= +google.golang.org/genproto v0.0.0-20200204135345-fa8e72b47b90/go.mod h1:GmwEX6Z4W5gMy59cAlVYjN9JhxgbQH6Gn+gFDQe2lzA= +google.golang.org/genproto v0.0.0-20200212174721-66ed5ce911ce/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200224152610-e50cd9704f63/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200228133532-8c2c7df3a383/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200305110556-506484158171/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200312145019-da6875a35672/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200331122359-1ee6d9798940/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200430143042-b979b6f78d84/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200511104702-f5ebc3bea380/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200515170657-fc4c6c6a6587/go.mod h1:YsZOwe1myG/8QRHRsmBRE1LrgQY60beZKjly0O1fX9U= +google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo= +google.golang.org/genproto v0.0.0-20200618031413-b414f8b61790/go.mod h1:jDfRM7FcilCzHH/e9qn6dsT145K34l5v+OpcnNgKAAA= +google.golang.org/genproto v0.0.0-20200729003335-053ba62fc06f/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20200804131852-c06518451d9c/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20200825200019-8632dd797987/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20200904004341-0bd0a958aa1d/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20201109203340-2640f1f9cdfb/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20201201144952-b05cb90ed32e/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20201210142538-e3217bee35cc/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20201214200347-8c77b98c765d/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20210108203827-ffc7fda8c3d7/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20210226172003-ab064af71705/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= 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/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM= -gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw= +google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= +google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY= +google.golang.org/grpc v1.26.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= +google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= +google.golang.org/grpc v1.27.1/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= +google.golang.org/grpc v1.28.0/go.mod h1:rpkK4SK4GF4Ach/+MFLZUBavHOvF2JJB5uozKKal+60= +google.golang.org/grpc v1.29.1/go.mod h1:itym6AZVZYACWQqET3MqgPpjcuV5QH3BxFS3IjizoKk= +google.golang.org/grpc v1.30.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= +google.golang.org/grpc v1.31.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= +google.golang.org/grpc v1.31.1/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= +google.golang.org/grpc v1.33.2/go.mod h1:JMHMWHQWaTccqQQlmk3MJZS+GWXOdAesneDmEnv2fbc= +google.golang.org/grpc v1.34.0/go.mod h1:WotjhfgOW/POjDeRt8vscBtXq+2VjORFy659qA51WJ8= +google.golang.org/grpc v1.35.0/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU= +google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= +google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= +google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM= +google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE= +google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo= +google.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= +google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= +google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= +google.golang.org/protobuf v1.24.0/go.mod h1:r/3tXBNzIEhYS9I1OUVjXDlt8tc493IdKGjtUeSXeh4= +google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c= 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= gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f h1:BLraFXnmrev5lT+xlilqcH8XK9/i0At2xKjWk4p6zsU= gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= -gopkg.in/ini.v1 v1.51.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= -gopkg.in/ini.v1 v1.56.0 h1:DPMeDvGTM54DXbPkVIZsp19fp/I2K7zwA/itHYHKo8Y= -gopkg.in/ini.v1 v1.56.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= -gopkg.in/resty.v1 v1.12.0/go.mod h1:mDo4pnntr5jdWRML875a/NmxYqAlA73dVijT2AXvQQo= -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/ini.v1 v1.67.0 h1:Dgnx+6+nfE+IfzjUEISNeydPJh9AXNNsWbGP9KzCsOA= +gopkg.in/ini.v1 v1.67.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= 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/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= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.0-20200605160147-a5ece683394c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= @@ -554,7 +704,12 @@ gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 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.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg= +honnef.co/go/tools v0.0.1-2020.1.3/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k= +honnef.co/go/tools v0.0.1-2020.1.4/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k= rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8= +rsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0= +rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA= software.sslmate.com/src/go-pkcs12 v0.2.0 h1:nlFkj7bTysH6VkC4fGphtjXRbezREPgrHuJG20hBGPE= software.sslmate.com/src/go-pkcs12 v0.2.0/go.mod h1:23rNcYsMabIc1otwLpTkCCPwUq6kQsTyowttG/as0kQ= diff --git a/internal/bus/bus.go b/internal/bus/bus.go index eed58dc..69c804e 100644 --- a/internal/bus/bus.go +++ b/internal/bus/bus.go @@ -15,91 +15,23 @@ package bus import ( "github.com/wagoodman/go-partybus" - "github.com/wagoodman/go-progress" - - "github.com/anchore/quill/quill/event" - "github.com/anchore/quill/quill/event/monitor" ) var publisher partybus.Publisher -var active bool -// SetPublisher sets the singleton event bus publisher. This is optional; if no bus is provided, the library will +// Set sets the singleton event bus publisher. This is optional; if no bus is provided, the library will // behave no differently than if a bus had been provided. -func SetPublisher(p partybus.Publisher) { +func Set(p partybus.Publisher) { publisher = p - if p != nil { - active = true - } -} - -// Publish an event onto the bus. If there is no bus set by the calling application, this does nothing. -func Publish(event partybus.Event) { - if active { - publisher.Publish(event) - } -} - -func Exit() { - Publish(partybus.Event{ - Type: event.Exit, - }) } -func Report(report string) { - Publish(partybus.Event{ - Type: event.Report, - Value: report, - }) +func Get() partybus.Publisher { + return publisher } -func Notify(message string) { - Publish(partybus.Event{ - Type: event.Notification, - Value: message, - }) -} - -func PromptForInput(message string, sensitive bool, validators ...func(string) error) *monitor.Prompter { - p := monitor.NewPrompter(message, sensitive, validators...) - Publish(partybus.Event{ - Type: event.InputPrompt, - Value: monitor.PromptWriter(p), - }) - return p -} - -func PublishTask(titles monitor.Title, context string, total int) *monitor.ManualStagedProgress { - prog := monitor.ManualStagedProgress{ - Manual: progress.Manual{ - Total: int64(total), - }, +// publish an event onto the bus. If there is no bus set by the calling application, this does nothing. +func publish(e partybus.Event) { + if publisher != nil { + publisher.Publish(e) } - - Publish(partybus.Event{ - Type: event.Task, - Source: monitor.Task{ - Title: titles, - Context: context, - }, - Value: progress.StagedProgressable(&struct { - progress.Stager - progress.Progressable - }{ - Stager: &prog.Stage, - Progressable: &prog.Manual, - }), - }) - return &prog -} - -func PublishTaskWithProgress(titles monitor.Title, context string, prog progress.StagedProgressable) { - Publish(partybus.Event{ - Type: event.Task, - Source: monitor.Task{ - Title: titles, - Context: context, - }, - Value: prog, - }) } diff --git a/internal/bus/helpers.go b/internal/bus/helpers.go new file mode 100644 index 0000000..950af8b --- /dev/null +++ b/internal/bus/helpers.go @@ -0,0 +1,77 @@ +package bus + +import ( + "github.com/wagoodman/go-partybus" + "github.com/wagoodman/go-progress" + + "github.com/anchore/bubbly" + "github.com/anchore/quill/internal/redact" + "github.com/anchore/quill/quill/event" +) + +func PublishTask(titles event.Title, context string, total int) *event.ManualStagedProgress { + prog := event.ManualStagedProgress{ + Manual: progress.Manual{ + Total: int64(total), + }, + } + + publish(partybus.Event{ + Type: event.TaskType, + Source: event.Task{ + Title: titles, + Context: context, + }, + Value: progress.StagedProgressable(&struct { + progress.Stager + progress.Progressable + }{ + Stager: &prog.Stage, + Progressable: &prog.Manual, + }), + }) + + return &prog +} + +func Exit() { + publish(partybus.Event{ + Type: event.CLIExitType, + }) +} + +func Report(report string) { + if publisher == nil { + // prevent any further actions taken on the report (such as redaction) since it won't be published anyway + return + } + publish(partybus.Event{ + Type: event.CLIReportType, + Value: redact.Apply(report), + }) +} + +func Notify(message string) { + if publisher == nil { + // prevent any further actions taken on the report (such as redaction) since it won't be published anyway + return + } + publish(partybus.Event{ + Type: event.CLINotificationType, + Value: redact.Apply(message), + }) +} + +func PromptForInput(message string, sensitive bool, validators ...func(string) error) *bubbly.Prompter { + if publisher == nil { + // prevent any further actions taken on the report (such as redaction) since it won't be published anyway + return nil + } + p := bubbly.NewPrompter(redact.Apply(message), sensitive, validators...) + publish(partybus.Event{ + Type: event.CLIInputPromptType, + Value: bubbly.PromptWriter(p), + }) + + return p +} diff --git a/internal/eventloop/eventloop.go b/internal/eventloop/eventloop.go deleted file mode 100644 index e6fb508..0000000 --- a/internal/eventloop/eventloop.go +++ /dev/null @@ -1,118 +0,0 @@ -package eventloop - -import ( - "context" - "errors" - "fmt" - - "github.com/hashicorp/go-multierror" - "github.com/wagoodman/go-partybus" - - "github.com/anchore/quill/internal/log" - "github.com/anchore/quill/internal/ui" -) - -func Run(ctx context.Context, workerErrs <-chan error, subscription *partybus.Subscription, cleanupFn func(), uxs ...ui.UI) error { - return run( - ctx, - workerErrs, - subscription, - cleanupFn, - uxs..., - ) -} - -// Run listens to worker errors (from execution path), worker events (from a partybus subscription), and -// signal interrupts. Is responsible for handling each event relative to a given UI an to coordinate eventing until -// an eventual graceful exit. -// - -func run(ctx context.Context, workerErrs <-chan error, subscription *partybus.Subscription, cleanupFn func(), uxs ...ui.UI) error { - if cleanupFn != nil { - defer cleanupFn() - } - events := subscription.Events() - var err error - var ux ui.UI - - if ux, err = setupUI(subscription.Unsubscribe, uxs...); err != nil { - return err - } - - logger := log.Nested("component", "eventloop") - - var retErr error - var forceTeardown bool - - for { - if workerErrs == nil && events == nil { - break - } - select { - case err, isOpen := <-workerErrs: - if !isOpen { - logger.Trace("worker stopped") - workerErrs = nil - continue - } - if err != nil { - // capture the error from the worker and unsubscribe to complete a graceful shutdown - retErr = multierror.Append(retErr, err) - _ = subscription.Unsubscribe() - // the worker has exited, we may have been mid-handling events for the UI which should now be - // ignored, in which case forcing a teardown of the UI regardless of the state is required. - forceTeardown = true - } - case e, isOpen := <-events: - if !isOpen { - logger.Trace("bus stopped") - events = nil - continue - } - - if err := ux.Handle(e); err != nil { - if errors.Is(err, partybus.ErrUnsubscribe) { - events = nil - } else { - retErr = multierror.Append(retErr, err) - // TODO: should we unsubscribe? should we try to halt execution? or continue? - } - } - case <-ctx.Done(): - logger.Trace("signal interrupt") - - // ignore further results from any event source and exit ASAP, but ensure that all cache is cleaned up. - // we ignore further errors since cleaning up the tmp directories will affect running catalogers that are - // reading/writing from/to their nested temp dirs. This is acceptable since we are bailing without result. - - // TODO: potential future improvement would be to pass context into workers with a cancel function that is - // to the event loop. In this way we can have a more controlled shutdown even at the most nested levels - // of processing. - events = nil - workerErrs = nil - forceTeardown = true - } - } - - if err := ux.Teardown(forceTeardown); err != nil { - retErr = multierror.Append(retErr, err) - } - - return retErr -} - -// setupUI takes one or more UIs that responds to events and takes a event bus unsubscribe function for use -// during teardown. With the given UIs, the first UI which the ui.Setup() function does not return an error -// will be utilized in execution. Providing a set of UIs allows for the caller to provide graceful fallbacks -// when there are environmental problem (e.g. unable to setup a TUI with the current TTY). -func setupUI(unsubscribe func() error, uis ...ui.UI) (ui.UI, error) { - for _, ux := range uis { - if err := ux.Setup(unsubscribe); err != nil { - log.Warnf("unable to setup given UI, falling back to alternative UI: %+v", err) - continue - } - - return ux, nil - } - return nil, fmt.Errorf("unable to setup any UI") -} diff --git a/internal/eventloop/eventloop_test.go b/internal/eventloop/eventloop_test.go deleted file mode 100644 index 33143d7..0000000 --- a/internal/eventloop/eventloop_test.go +++ /dev/null @@ -1,430 +0,0 @@ -package eventloop - -import ( - "context" - "fmt" - "testing" - "time" - - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/mock" - "github.com/wagoodman/go-partybus" - - "github.com/anchore/quill/internal/ui" - "github.com/anchore/quill/quill/event" -) - -var _ ui.UI = (*uiMock)(nil) - -type uiMock struct { - t *testing.T - finalEvent partybus.Event - unsubscribe func() error - mock.Mock -} - -func (u *uiMock) Setup(unsubscribe func() error) error { - u.t.Logf("UI Setup called") - u.unsubscribe = unsubscribe - return u.Called(unsubscribe).Error(0) -} - -func (u *uiMock) Handle(event partybus.Event) error { - u.t.Logf("UI Handle called: %+v", event.Type) - if event == u.finalEvent { - assert.NoError(u.t, u.unsubscribe()) - } - return u.Called(event).Error(0) -} - -func (u *uiMock) Teardown(_ bool) error { - u.t.Logf("UI Teardown called") - return u.Called().Error(0) -} - -func Test_EventLoop_gracefulExit(t *testing.T) { - test := func(t *testing.T) { - - testBus := partybus.NewBus() - subscription := testBus.Subscribe() - t.Cleanup(testBus.Close) - - finalEvent := partybus.Event{ - Type: event.Exit, - } - - worker := func() <-chan error { - ret := make(chan error) - go func() { - t.Log("worker running") - // send an empty item (which is ignored) ensuring we've entered the select statement, - // then close (a partial shutdown). - ret <- nil - t.Log("worker sent nothing") - close(ret) - t.Log("worker closed") - // do the other half of the shutdown - testBus.Publish(finalEvent) - t.Log("worker published final event") - }() - return ret - } - - ux := &uiMock{ - t: t, - finalEvent: finalEvent, - } - - // ensure the mock sees at least the final event - ux.On("Handle", finalEvent).Return(nil) - // ensure the mock sees basic setup/teardown events - ux.On("Setup", mock.AnythingOfType("func() error")).Return(nil) - ux.On("Teardown").Return(nil) - - var cleanupCalled bool - cleanupFn := func() { - t.Log("cleanup called") - cleanupCalled = true - } - - assert.NoError(t, - Run( - context.Background(), - worker(), - subscription, - cleanupFn, - ux, - ), - ) - - assert.True(t, cleanupCalled, "cleanup function not called") - ux.AssertExpectations(t) - } - - // if there is a bug, then there is a risk of the event loop never returning - testWithTimeout(t, 5*time.Second, test) -} - -func Test_EventLoop_workerError(t *testing.T) { - test := func(t *testing.T) { - - testBus := partybus.NewBus() - subscription := testBus.Subscribe() - t.Cleanup(testBus.Close) - - workerErr := fmt.Errorf("worker error") - - worker := func() <-chan error { - ret := make(chan error) - go func() { - t.Log("worker running") - // send an empty item (which is ignored) ensuring we've entered the select statement, - // then close (a partial shutdown). - ret <- nil - t.Log("worker sent nothing") - ret <- workerErr - t.Log("worker sent error") - close(ret) - t.Log("worker closed") - // note: NO final event is fired - }() - return ret - } - - ux := &uiMock{ - t: t, - } - - // ensure the mock sees basic setup/teardown events - ux.On("Setup", mock.AnythingOfType("func() error")).Return(nil) - ux.On("Teardown").Return(nil) - - var cleanupCalled bool - cleanupFn := func() { - t.Log("cleanup called") - cleanupCalled = true - } - - // ensure we see an error returned - assert.ErrorIs(t, - Run( - context.Background(), - worker(), - subscription, - cleanupFn, - ux, - ), - workerErr, - "should have seen a worker error, but did not", - ) - - assert.True(t, cleanupCalled, "cleanup function not called") - ux.AssertExpectations(t) - } - - // if there is a bug, then there is a risk of the event loop never returning - testWithTimeout(t, 5*time.Second, test) -} - -func Test_EventLoop_unsubscribeError(t *testing.T) { - test := func(t *testing.T) { - - testBus := partybus.NewBus() - subscription := testBus.Subscribe() - t.Cleanup(testBus.Close) - - finalEvent := partybus.Event{ - Type: event.Exit, - } - - worker := func() <-chan error { - ret := make(chan error) - go func() { - t.Log("worker running") - // send an empty item (which is ignored) ensuring we've entered the select statement, - // then close (a partial shutdown). - ret <- nil - t.Log("worker sent nothing") - close(ret) - t.Log("worker closed") - // do the other half of the shutdown - testBus.Publish(finalEvent) - t.Log("worker published final event") - }() - return ret - } - - ux := &uiMock{ - t: t, - finalEvent: finalEvent, - } - - // ensure the mock sees at least the final event... note the unsubscribe error here - ux.On("Handle", finalEvent).Return(partybus.ErrUnsubscribe) - // ensure the mock sees basic setup/teardown events - ux.On("Setup", mock.AnythingOfType("func() error")).Return(nil) - ux.On("Teardown").Return(nil) - - var cleanupCalled bool - cleanupFn := func() { - t.Log("cleanup called") - cleanupCalled = true - } - - // unsubscribe errors should be handled and ignored, not propagated. We are additionally asserting that - // this case is handled as a controlled shutdown (this test should not timeout) - assert.NoError(t, - Run( - context.Background(), - worker(), - subscription, - cleanupFn, - ux, - ), - ) - - assert.True(t, cleanupCalled, "cleanup function not called") - ux.AssertExpectations(t) - } - - // if there is a bug, then there is a risk of the event loop never returning - testWithTimeout(t, 5*time.Second, test) -} - -func Test_EventLoop_handlerError(t *testing.T) { - test := func(t *testing.T) { - - testBus := partybus.NewBus() - subscription := testBus.Subscribe() - t.Cleanup(testBus.Close) - - finalEvent := partybus.Event{ - Type: event.Exit, - Error: fmt.Errorf("an exit error occured"), - } - - worker := func() <-chan error { - ret := make(chan error) - go func() { - t.Log("worker running") - // send an empty item (which is ignored) ensuring we've entered the select statement, - // then close (a partial shutdown). - ret <- nil - t.Log("worker sent nothing") - close(ret) - t.Log("worker closed") - // do the other half of the shutdown - testBus.Publish(finalEvent) - t.Log("worker published final event") - }() - return ret - } - - ux := &uiMock{ - t: t, - finalEvent: finalEvent, - } - - // ensure the mock sees at least the final event... note the event error is propagated - ux.On("Handle", finalEvent).Return(finalEvent.Error) - // ensure the mock sees basic setup/teardown events - ux.On("Setup", mock.AnythingOfType("func() error")).Return(nil) - ux.On("Teardown").Return(nil) - - var cleanupCalled bool - cleanupFn := func() { - t.Log("cleanup called") - cleanupCalled = true - } - - // handle errors SHOULD propagate the event loop. We are additionally asserting that this case is - // handled as a controlled shutdown (this test should not timeout) - assert.ErrorIs(t, - Run( - context.Background(), - worker(), - subscription, - cleanupFn, - ux, - ), - finalEvent.Error, - "should have seen a event error, but did not", - ) - - assert.True(t, cleanupCalled, "cleanup function not called") - ux.AssertExpectations(t) - } - - // if there is a bug, then there is a risk of the event loop never returning - testWithTimeout(t, 5*time.Second, test) -} - -func Test_EventLoop_contextCancelStopExecution(t *testing.T) { - test := func(t *testing.T) { - - testBus := partybus.NewBus() - subscription := testBus.Subscribe() - t.Cleanup(testBus.Close) - - worker := func() <-chan error { - // the worker will never return work and the event loop will always be waiting... - return make(chan error) - } - - ctx, cancel := context.WithCancel(context.Background()) - - ux := &uiMock{ - t: t, - } - - // ensure the mock sees basic setup/teardown events - ux.On("Setup", mock.AnythingOfType("func() error")).Return(nil) - ux.On("Teardown").Return(nil) - - var cleanupCalled bool - cleanupFn := func() { - t.Log("cleanup called") - cleanupCalled = true - } - - go cancel() - - assert.NoError(t, - run( - ctx, - worker(), - subscription, - cleanupFn, - ux, - ), - ) - - assert.True(t, cleanupCalled, "cleanup function not called") - ux.AssertExpectations(t) - } - - // if there is a bug, then there is a risk of the event loop never returning - testWithTimeout(t, 5*time.Second, test) -} - -func Test_EventLoop_uiTeardownError(t *testing.T) { - test := func(t *testing.T) { - - testBus := partybus.NewBus() - subscription := testBus.Subscribe() - t.Cleanup(testBus.Close) - - finalEvent := partybus.Event{ - Type: event.Exit, - } - - worker := func() <-chan error { - ret := make(chan error) - go func() { - t.Log("worker running") - // send an empty item (which is ignored) ensuring we've entered the select statement, - // then close (a partial shutdown). - ret <- nil - t.Log("worker sent nothing") - close(ret) - t.Log("worker closed") - // do the other half of the shutdown - testBus.Publish(finalEvent) - t.Log("worker published final event") - }() - return ret - } - - ux := &uiMock{ - t: t, - finalEvent: finalEvent, - } - - teardownError := fmt.Errorf("sorry, dave, the UI doesn't want to be torn down") - - // ensure the mock sees at least the final event... note the event error is propagated - ux.On("Handle", finalEvent).Return(nil) - // ensure the mock sees basic setup/teardown events - ux.On("Setup", mock.AnythingOfType("func() error")).Return(nil) - ux.On("Teardown").Return(teardownError) - - var cleanupCalled bool - cleanupFn := func() { - t.Log("cleanup called") - cleanupCalled = true - } - - // ensure we see an error returned - assert.ErrorIs(t, - Run( - context.Background(), - worker(), - subscription, - cleanupFn, - ux, - ), - teardownError, - "should have seen a UI teardown error, but did not", - ) - - assert.True(t, cleanupCalled, "cleanup function not called") - ux.AssertExpectations(t) - } - - // if there is a bug, then there is a risk of the event loop never returning - testWithTimeout(t, 5*time.Second, test) -} - -func testWithTimeout(t *testing.T, timeout time.Duration, test func(*testing.T)) { - done := make(chan bool) - go func() { - test(t) - done <- true - }() - - select { - case <-time.After(timeout): - t.Fatal("test timed out") - case <-done: - } -} diff --git a/internal/input.go b/internal/input.go deleted file mode 100644 index ccb6dd8..0000000 --- a/internal/input.go +++ /dev/null @@ -1,20 +0,0 @@ -package internal - -import ( - "fmt" - "os" -) - -// IsPipedInput returns true if there is no input device, which means the user **may** be providing input via a pipe. -func IsPipedInput() (bool, error) { - fi, err := os.Stdin.Stat() - if err != nil { - return false, fmt.Errorf("unable to determine if there is piped input: %w", err) - } - - // note: we should NOT use the absence of a character device here as the hint that there may be input expected - // on stdin, as running this application as a subprocess you would expect no character device to be present but input can - // be from either stdin or indicated by the CLI. Checking if stdin is a pipe is the most direct way to determine - // if there *may* be bytes that will show up on stdin that should be used for the analysis source. - return fi.Mode()&os.ModeNamedPipe != 0, nil -} diff --git a/internal/log/log.go b/internal/log/log.go index dc46e62..6c5e27e 100644 --- a/internal/log/log.go +++ b/internal/log/log.go @@ -6,17 +6,22 @@ package log import ( "github.com/anchore/go-logger" "github.com/anchore/go-logger/adapter/discard" + "github.com/anchore/go-logger/adapter/redact" + intRedact "github.com/anchore/quill/internal/redact" ) -// log is the singleton used to facilitate logging internally within var log = discard.New() func Set(l logger.Logger) { - if r, ok := log.(*redactingLogger); ok { - r.log = l - } else { - log = newRedactingLogger(l, globalStaticRedactions) + // though quill the application will automatically have a redaction logger, library consumers may not be doing this. + // for this reason we additionally ensure there is a redaction logger configured for any logger passed. The + // source of truth for redaction values is still in the internal redact package. If the passed logger is already + // redacted, then this is a no-op. + store := intRedact.Get() + if store != nil { + l = redact.New(l, store) } + log = l } func Get() logger.Logger { diff --git a/internal/log/redacting_logger.go b/internal/log/redacting_logger.go deleted file mode 100644 index 859cf4d..0000000 --- a/internal/log/redacting_logger.go +++ /dev/null @@ -1,141 +0,0 @@ -package log - -import ( - "fmt" - "strings" - "sync" - - "github.com/scylladb/go-set/strset" - - "github.com/anchore/go-logger" -) - -var ( - _ logger.Logger = (*redactingLogger)(nil) - _redactions = strset.New() - lock = &sync.RWMutex{} -) - -// all instances of the redaction logger share the same set of things that should be redacted, no matter when the -// end user registers something to be redacted. -func globalStaticRedactions() []string { - lock.RLock() - defer lock.RUnlock() - return _redactions.List() -} - -func Redact(value string) { - lock.Lock() - defer lock.Unlock() - if len(value) <= 1 { - // smallest possible redaction string is larger than 1 character - return - } - _redactions.Add(value) -} - -type redactingLogger struct { - log logger.MessageLogger - redactions func() []string -} - -func newRedactingLogger(log logger.MessageLogger, redactor func() []string) *redactingLogger { - return &redactingLogger{ - log: log, - redactions: redactor, - } -} - -func (r *redactingLogger) Errorf(format string, args ...interface{}) { - r.log.Errorf(r.redactString(format), r.redactFields(args)...) -} - -func (r *redactingLogger) Error(args ...interface{}) { - r.log.Error(r.redactFields(args)...) -} - -func (r *redactingLogger) Warnf(format string, args ...interface{}) { - r.log.Warnf(r.redactString(format), r.redactFields(args)...) -} - -func (r *redactingLogger) Warn(args ...interface{}) { - r.log.Warn(r.redactFields(args)...) -} - -func (r *redactingLogger) Infof(format string, args ...interface{}) { - r.log.Infof(r.redactString(format), r.redactFields(args)...) -} - -func (r *redactingLogger) Info(args ...interface{}) { - r.log.Info(r.redactFields(args)...) -} - -func (r *redactingLogger) Debugf(format string, args ...interface{}) { - r.log.Debugf(r.redactString(format), r.redactFields(args)...) -} - -func (r *redactingLogger) Debug(args ...interface{}) { - r.log.Debug(r.redactFields(args)...) -} - -func (r *redactingLogger) Tracef(format string, args ...interface{}) { - r.log.Tracef(r.redactString(format), r.redactFields(args)...) -} - -func (r *redactingLogger) Trace(args ...interface{}) { - r.log.Trace(r.redactFields(args)...) -} - -func (r *redactingLogger) WithFields(fields ...interface{}) logger.MessageLogger { - if l, ok := r.log.(logger.FieldLogger); ok { - return newRedactingLogger(l.WithFields(r.redactFields(fields)...), r.redactions) - } - return r -} - -func (r *redactingLogger) Nested(fields ...interface{}) logger.Logger { - if l, ok := r.log.(logger.NestedLogger); ok { - return newRedactingLogger(l.Nested(r.redactFields(fields)...), r.redactions) - } - return r -} - -func (r *redactingLogger) redactFields(fields []interface{}) []interface{} { - for i, v := range fields { - switch vv := v.(type) { - case string: - fields[i] = r.redactString(vv) - case int, int32, int64, int16, int8, float32, float64: - // don't coerce non-string primitives to different types - fields[i] = vv - case logger.Fields: - for kkk, vvv := range vv { - delete(vv, kkk) // this key may have data that should be redacted - redactedKey := r.redactString(kkk) - - switch vvvv := vvv.(type) { - case string: - vv[redactedKey] = r.redactString(vvvv) - case int, int32, int64, int16, int8, float32, float64: - // don't coerce non-string primitives to different types (but still redact the key) - vv[redactedKey] = vvvv - default: - vv[redactedKey] = r.redactString(fmt.Sprintf("%+v", vvvv)) - } - } - fields[i] = vv - default: - // coerce to a string and redact - fields[i] = r.redactString(fmt.Sprintf("%+v", vv)) - } - } - return fields -} - -func (r *redactingLogger) redactString(str string) string { - for _, s := range r.redactions() { - // note: we don't use the length of the redaction string to determine the replacement string, as even the length could be considered sensitive - str = strings.ReplaceAll(str, s, strings.Repeat("*", 7)) - } - return str -} diff --git a/internal/log/redacting_logger_test.go b/internal/log/redacting_logger_test.go deleted file mode 100644 index 2c26aa5..0000000 --- a/internal/log/redacting_logger_test.go +++ /dev/null @@ -1,102 +0,0 @@ -package log - -import ( - "bytes" - "fmt" - "testing" - - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" - - "github.com/anchore/go-logger" - "github.com/anchore/go-logger/adapter/logrus" -) - -func Test_RedactingLogger(t *testing.T) { - tests := []struct { - name string - redact []string - }{ - { - name: "single value", - redact: []string{"joe"}, - }, - { - name: "multi value", - redact: []string{"bob", "alice"}, - }, - } - - for _, test := range tests { - t.Run(test.name, func(t *testing.T) { - out, err := logrus.New(logrus.Config{ - Level: logger.TraceLevel, - }) - require.NoError(t, err) - - buff := bytes.Buffer{} - out.(logger.Controller).SetOutput(&buff) - - redactor := newRedactingLogger(out, func() []string { return test.redact }) - - var fieldObj = make(logger.Fields) - for _, v := range test.redact { - fieldObj[v] = v - } - - format := "" - var fields []interface{} - for _, v := range test.redact { - fields = append(fields, v) - format += "%s" - } - - fields = append(fields, 3) - format += "%d" - - fields = append(fields, int32(3)) - format += "%d" - - fields = append(fields, 3.2) - format += "%f" - - fields = append(fields, float32(4.3)) - format += "%f" - - fields = append(fields, fieldObj) - format += "%+v" - - var interlacedFields []interface{} - for i, f := range fields { - interlacedFields = append(interlacedFields, fmt.Sprintf("%d", i), f) - } - - nestedFieldLogger := redactor.Nested(interlacedFields...).WithFields(interlacedFields...) - - nestedFieldLogger.Tracef(format, fields...) - nestedFieldLogger.Trace(fields...) - - nestedFieldLogger.Debugf(format, fields...) - nestedFieldLogger.Debug(fields...) - - nestedFieldLogger.Infof(format, fields...) - nestedFieldLogger.Info(fields...) - - nestedFieldLogger.Warnf(format, fields...) - nestedFieldLogger.Warn(fields...) - - nestedFieldLogger.Errorf(format, fields...) - nestedFieldLogger.Error(fields...) - - result := buff.String() - - // this is a string indicator that we've coerced an instance to a new type that does not match the format type (e.g. %d) - assert.NotContains(t, result, "%") - - assert.NotEmpty(t, result) - for _, v := range test.redact { - assert.NotContains(t, result, v) - } - }) - } -} diff --git a/internal/redact/redact.go b/internal/redact/redact.go new file mode 100644 index 0000000..3bb76e2 --- /dev/null +++ b/internal/redact/redact.go @@ -0,0 +1,36 @@ +package redact + +import "github.com/anchore/go-logger/adapter/redact" + +var store redact.Store + +func Set(s redact.Store) { + if store != nil { + // if someone is trying to set a redaction store and we already have one then something is wrong. The store + // that we're replacing might already have values in it, so we should never replace it. + panic("replace existing redaction store (probably unintentional)") + } + store = s +} + +func Get() redact.Store { + return store +} + +func Add(vs ...string) { + if store == nil { + // if someone is trying to add values that should never be output and we don't have a store then something is wrong. + // we should never accidentally output values that should be redacted, thus we panic here. + panic("cannot add redactions without a store") + } + store.Add(vs...) +} + +func Apply(value string) string { + if store == nil { + // if someone is trying to add values that should never be output and we don't have a store then something is wrong. + // we should never accidentally output values that should be redacted, thus we panic here. + panic("cannot apply redactions without a store") + } + return store.RedactString(value) +} diff --git a/internal/ui/config.go b/internal/ui/config.go deleted file mode 100644 index 2781ebf..0000000 --- a/internal/ui/config.go +++ /dev/null @@ -1,7 +0,0 @@ -package ui - -type Config struct { - Verbose bool - Quiet bool - Debug bool -} diff --git a/internal/ui/handle/handle.go b/internal/ui/handle/handle.go deleted file mode 100644 index 4717a11..0000000 --- a/internal/ui/handle/handle.go +++ /dev/null @@ -1,13 +0,0 @@ -package handle - -import ( - "github.com/wagoodman/go-partybus" - - "github.com/anchore/quill/quill/event/parser" -) - -// handleExit is a UI function for processing the Exit bus event, -// and calling the given function to output the contents. -func Exit(e partybus.Event) error { - return parser.Exit(e) -} diff --git a/internal/ui/handle/post_ui_events.go b/internal/ui/handle/post_ui_events.go deleted file mode 100644 index b597042..0000000 --- a/internal/ui/handle/post_ui_events.go +++ /dev/null @@ -1,70 +0,0 @@ -package handle - -import ( - "fmt" - "os" - "strings" - - "github.com/charmbracelet/lipgloss" - "github.com/wagoodman/go-partybus" - - "github.com/anchore/quill/internal/log" - "github.com/anchore/quill/quill/event" - "github.com/anchore/quill/quill/event/parser" -) - -func PostUIEvents(quiet bool, events ...partybus.Event) { - // TODO: add partybus event filter to filter down to events matching a type - - // show all accumulated reports to stdout - var reports []string - for _, e := range events { - if e.Type != event.Report { - continue - } - - source, report, err := parser.Report(e) - if err != nil { - log.WithFields("error", err). - Warn("failed to gather final report for %q", source) - } else { - // remove all whitespace padding from the end of the report - reports = append(reports, strings.TrimRight(report, "\n ")+"\n") - } - } - - // prevent the double new-line at the end of the report - fmt.Print(strings.Join(reports, "\n")) - - if !quiet { - // show all notifications reports to stderr - for _, e := range events { - if e.Type != event.Notification { - continue - } - - source, notification, err := parser.Notification(e) - if err != nil { - log.WithFields("error", err). - Warnf("failed to gather notification for %q", source) - } else { - // 13 = high intensity magenta (ANSI 16 bit code) - _, _ = fmt.Fprintln(os.Stderr, lipgloss.NewStyle().Foreground(lipgloss.Color("13")).Render(notification)) - } - } - } - - // run exit finalizers - for _, e := range events { - switch e.Type { - case event.Exit: - if err := Exit(e); err != nil { - log.WithFields("error", err). - Warn("failed to handle exit event gracefully") - } - // TODO: add more supported finalizer events... - default: - continue - } - } -} diff --git a/internal/ui/loggerui/input_prompt.go b/internal/ui/loggerui/input_prompt.go deleted file mode 100644 index 0b8182d..0000000 --- a/internal/ui/loggerui/input_prompt.go +++ /dev/null @@ -1,21 +0,0 @@ -package loggerui - -import ( - "fmt" - - "github.com/wagoodman/go-partybus" - - "github.com/anchore/quill/internal/ui/tui/bubbles/prompt" - "github.com/anchore/quill/quill/event/parser" -) - -func (u *UI) handleInputPrompt(e partybus.Event) error { - writer, err := parser.InputPrompt(e) - if err != nil { - return fmt.Errorf("unable to parse event: %+v", err) - } - - model := prompt.New(writer) - _, err = model.RunPrompt() - return err -} diff --git a/internal/ui/loggerui/ui.go b/internal/ui/loggerui/ui.go deleted file mode 100644 index 3265147..0000000 --- a/internal/ui/loggerui/ui.go +++ /dev/null @@ -1,147 +0,0 @@ -package loggerui - -import ( - "context" - "errors" - "fmt" - "sync" - "time" - - "github.com/wagoodman/go-partybus" - "github.com/wagoodman/go-progress" - - "github.com/anchore/go-logger" - "github.com/anchore/quill/internal/log" - "github.com/anchore/quill/internal/ui/handle" - "github.com/anchore/quill/quill/event" -) - -type UI struct { - unsubscribe func() error - logger logger.Logger - debug bool - quiet bool - background *sync.WaitGroup - finalizers []partybus.Event -} - -// New writes all events to the common application logger and writes the final report to the given writer. - -func New(debug, quiet bool) *UI { - return &UI{ - debug: debug, - quiet: quiet, - logger: log.Nested("from", "UI"), - background: &sync.WaitGroup{}, - } -} - -func (u *UI) Setup(unsubscribe func() error) error { - u.unsubscribe = unsubscribe - return nil -} - -func (u *UI) Handle(e partybus.Event) error { - if u.debug { - u.handleEvent(e) - } - - switch e.Type { - case event.Exit: - u.finalizers = append(u.finalizers, e) - return u.unsubscribe() - case event.Report, event.Notification: - u.finalizers = append(u.finalizers, e) - case event.InputPrompt: - if err := u.handleInputPrompt(e); err != nil { - return err - } - } - - return nil -} - -func (u UI) Teardown(force bool) error { - if !force { - u.background.Wait() - } - - handle.PostUIEvents(u.quiet, u.finalizers...) - - return nil -} - -func (u *UI) logEventPoll(localLogger logger.Logger, p progress.Progress, stage string) { - fields := make(logger.Fields) - if p.Size() > 0 { - fields["size"] = p.Size() - fields["ratio"] = fmt.Sprintf("%0.2f", p.Ratio()) - } - if stage != "" { - fields["stage"] = stage - } - if p.Current() > 0 { - fields["n"] = p.Current() - } - err := p.Error() - if err != nil && !errors.Is(err, progress.ErrCompleted) { - fields["error"] = err - } - - if p.Complete() { - fields["finished"] = p.Complete() - } - - localLogger. - WithFields(fields). - Debugf("polling event progress") -} - -func (u *UI) handleEvent(e partybus.Event) { - eventFields := make(logger.Fields) - eventFields["event"] = e.Type - if e.Source != nil { - eventFields["source"] = e.Source - } - - localLogger := u.logger.Nested(eventFields) - - localLogger.Debug("new event") - - prog, ok := e.Value.(progress.Progressable) - if !ok { - return - } - - u.background.Add(1) - go func() { - defer u.background.Done() - - var stager progress.Stager = progress.Stage{} - if s, ok := e.Value.(progress.Stager); ok { - stager = s - } - - var last progress.Progress - var lastStage string - var lastShow = time.Now() - for current := range progress.Stream(context.Background(), prog, time.Second*1) { - stage := stager.Stage() - - // try to only log progress updates when there is either new information, or it's been a while since the last log - hasUpdatedInfo := last != current || lastStage != stage - isStale := lastShow.Add(5 * time.Second).Before(time.Now()) - if hasUpdatedInfo || isStale { - u.logEventPoll(localLogger, current, stage) - - lastShow = time.Now() - } - lastStage = stage - last = current - } - - if !last.Complete() { - localLogger.Debugf("event progress finished in an incomplete state") - } - }() -} diff --git a/internal/ui/select.go b/internal/ui/select.go deleted file mode 100644 index bef1bd2..0000000 --- a/internal/ui/select.go +++ /dev/null @@ -1,31 +0,0 @@ -package ui - -import ( - "os" - - "golang.org/x/term" - - "github.com/anchore/quill/internal/ui/loggerui" - "github.com/anchore/quill/internal/ui/tui" -) - -// Select is responsible for determining the specific UI function given select user option, the current platform -// config values, and environment status (such as a TTY being present). The first UI in the returned slice of UIs -// is intended to be used and the UIs that follow are meant to be attempted only in a fallback posture when there -// are environmental problems (e.g. cannot write to the terminal). A writer is provided to capture the output of -// the final SBOM report. -func Select(cfg Config) (uis []UI) { - isStdoutATty := term.IsTerminal(int(os.Stdout.Fd())) - isStderrATty := term.IsTerminal(int(os.Stderr.Fd())) - notATerminal := !isStderrATty && !isStdoutATty - switch { - case cfg.Verbose || cfg.Quiet || notATerminal || !isStderrATty: - uis = append(uis, loggerui.New(cfg.Debug, cfg.Quiet)) - default: - uis = append(uis, tui.New(cfg.Debug, cfg.Quiet), loggerui.New(cfg.Debug, cfg.Quiet)) - } - - return uis - - // return []UI{loggerui.New(cfg.Debug, cfg.Quiet)} -} diff --git a/internal/ui/tui/bubbles/prompt/prompt.go b/internal/ui/tui/bubbles/prompt/prompt.go deleted file mode 100644 index 3471ab4..0000000 --- a/internal/ui/tui/bubbles/prompt/prompt.go +++ /dev/null @@ -1,95 +0,0 @@ -package prompt - -import ( - "fmt" - "strings" - - tea "github.com/charmbracelet/bubbletea" - "github.com/erikgeiser/promptkit/textinput" - - "github.com/anchore/quill/internal/log" - "github.com/anchore/quill/quill/event/monitor" -) - -type Prompt struct { - complete bool - monitor.PromptWriter - tea.Model - value func() (string, error) - *textinput.TextInput -} - -func New(prompter monitor.PromptWriter) *Prompt { - // candidates: ‣⧗⧖⌛💬ⓘ■⬛⬢◼⧓►❖ - spec := textinput.New(" ❖ " + prompter.PromptMessage()) - spec.Hidden = prompter.IsSensitive() - spec.InputWidth = 12 - spec.HideMask = '●' // candidates: ●•✦*⬤⁕ - spec.Template = ` - {{- Bold .Prompt }} {{ .Input -}} - {{- if .ValidationError }} {{ Foreground "1" (Bold "✘") }} - {{- else }} {{ Foreground "2" (Bold "✔") }} - {{- end -}} - {{- if .ValidationError }} {{ Italic (Foreground "240" (ErrorStr (.ValidationError))) }} - {{- end -}} - ` - spec.Validate = func(s string) error { - if len(strings.TrimSpace(s)) == 0 { - return fmt.Errorf("value required") - } - - return prompter.Validate(s) - } - spec.ExtendedTemplateFuncs = map[string]any{ - "ErrorStr": func(err error) string { - return err.Error() - }, - } - specModel := textinput.NewModel(spec) - teaModel := &Prompt{ - PromptWriter: prompter, - Model: specModel, - value: specModel.Value, - TextInput: spec, - } - return teaModel -} - -func (m *Prompt) View() string { - return strings.TrimRight(m.Model.View(), "\n") -} - -func (m *Prompt) Update(msg tea.Msg) (tea.Model, tea.Cmd) { - if m.complete { - return m, nil - } - - switch msg := msg.(type) { //nolint:gocritic - case tea.KeyMsg: - if msg.String() == "enter" { - v, err := m.value() - if err != nil { - log.Errorf("unable to get prompt value: %+v", err) - } - if err := m.PromptWriter.SetPromptResponse(v); err != nil { - log.Errorf("unable to set prompt: %+v", err) - } else { - m.Template = textinput.DefaultResultTemplate // don't show any validation once something has been entered - m.Model.Update(msg) // update the state but ignore any future messages - m.complete = true // don't respond to any other update events - } - return m, nil - } - } - - _, cmd := m.Model.Update(msg) - return m, cmd -} - -func (m *Prompt) RunPrompt() (string, error) { - value, err := m.TextInput.RunPrompt() - if err == nil { - err = m.PromptWriter.SetPromptResponse(value) - } - return value, err -} diff --git a/internal/ui/tui/bubbles/taskprogress/__snapshots__/model_test.snap b/internal/ui/tui/bubbles/taskprogress/__snapshots__/model_test.snap deleted file mode 100755 index 45e7cab..0000000 --- a/internal/ui/tui/bubbles/taskprogress/__snapshots__/model_test.snap +++ /dev/null @@ -1,28 +0,0 @@ - -[TestModel_View/in_progress_without_progress_bar - 1] - ⠋ Doing work [working] at home ---- - -[TestModel_View/in_progress_with_progress_bar - 1] - ⠋ Doing work -------------------- [working] at home ---- - -[TestModel_View/successfully_finished_hides_progress_bar - 1] - ✔ Did work [done!] at home ---- - -[TestModel_View/successfully_finished_keeps_progress_bar_shown - 1] - ✔ Did work -------------------- [done!] at home ---- - -[TestModel_View/no_context - 1] - ⠋ Doing work [working] ---- - -[TestModel_View/multiple_hints - 1] - ⠋ Doing work [working] [info++] [info!++] at home ---- - -[TestModel_View/error - 1] - ✘ Failed at work :( [working] at home ---- diff --git a/internal/ui/tui/bubbles/taskprogress/id.go b/internal/ui/tui/bubbles/taskprogress/id.go deleted file mode 100644 index a468ed8..0000000 --- a/internal/ui/tui/bubbles/taskprogress/id.go +++ /dev/null @@ -1,18 +0,0 @@ -package taskprogress - -import "sync" - -// Internal ID management for text inputs. Necessary for blink integrity when -// multiple text inputs are involved. -var ( - lastID int - idMtx sync.Mutex -) - -// Return the next ID we should use on the Model. -func nextID() int { - idMtx.Lock() - defer idMtx.Unlock() - lastID++ - return lastID -} diff --git a/internal/ui/tui/bubbles/taskprogress/model.go b/internal/ui/tui/bubbles/taskprogress/model.go deleted file mode 100644 index 88640e5..0000000 --- a/internal/ui/tui/bubbles/taskprogress/model.go +++ /dev/null @@ -1,296 +0,0 @@ -package taskprogress - -import ( - "errors" - "fmt" - "strings" - "sync" - "time" - - "github.com/acarl005/stripansi" - progressBubble "github.com/charmbracelet/bubbles/progress" - "github.com/charmbracelet/bubbles/spinner" - tea "github.com/charmbracelet/bubbletea" - "github.com/charmbracelet/lipgloss" - "github.com/wagoodman/go-progress" -) - -const ( - checkMark = "✔" - xMark = "✘" -) - -type Model struct { - // ui components (view models) - Spinner spinner.Model - ProgressBar progressBubble.Model - title string - hints []string - context []string - - // enums for view model - TitleOptions Title - Hints []string - Context []string - - // state that drives view ui components - progress *progress.Progress - progressor progress.Progressor - stager progress.Stager - WindowSize tea.WindowSizeMsg - completed bool - err error - - UpdateDuration time.Duration - HideProgressOnSuccess bool - - TitleStyle lipgloss.Style - // TitlePendingStyle lipgloss.Style - HintStyle lipgloss.Style - ContextStyle lipgloss.Style - SuccessStyle lipgloss.Style - FailedStyle lipgloss.Style - - id int - sequence int - - // coordinate if there are any live components on the UI - done func() -} - -// New returns a model with default values. -func New(wg *sync.WaitGroup, opts ...Option) Model { - wg.Add(1) - once := sync.Once{} - done := func() { - once.Do(wg.Done) - } - spin := spinner.New() - - // matches the same spinner as syft/grype - spin.Spinner = spinner.Spinner{ - Frames: strings.Split("⠋⠙⠹⠸⠼⠴⠦⠧⠇⠏", ""), - FPS: 150 * time.Millisecond, - } - spin.Style = lipgloss.NewStyle().Foreground(lipgloss.Color("13")) // 13 = high intentity magenta (ANSI 16 bit color code) - - prog := progressBubble.New( - progressBubble.WithoutPercentage(), - progressBubble.WithWidth(20), - ) - // matches the same progress feel as syft/grype - prog.Full = '━' - prog.Empty = '━' - // TODO: make responsive to light/dark themes - prog.EmptyColor = "#777777" - prog.FullColor = "#fcba03" - - m := Model{ - Spinner: spin, - ProgressBar: prog, - UpdateDuration: 250 * time.Millisecond, - id: nextID(), - done: done, - - TitleStyle: lipgloss.NewStyle().Bold(true), - //TitlePendingStyle: lipgloss.NewStyle().Bold(true).Foreground(lipgloss.AdaptiveColor{ - // Light: "#555555", - // Dark: "#AAAAAA", - // }), - ContextStyle: lipgloss.NewStyle().Foreground(lipgloss.Color("#777777")), - HintStyle: lipgloss.NewStyle().Foreground(lipgloss.Color("#777777")), - SuccessStyle: lipgloss.NewStyle().Foreground(lipgloss.Color("10")), // 10 = high intensity green (ANSI 16 bit color code) - FailedStyle: lipgloss.NewStyle().Foreground(lipgloss.Color("9")), // 9 = high intensity red (ANSI 16 bit color code) - } - - for _, opt := range opts { - opt(&m) - } - return m -} - -// Init is the command that effectively starts the continuous update loop. -func (m Model) Init() tea.Cmd { - cmds := []tea.Cmd{ - // this is the periodic update of state information - func() tea.Msg { - return TickMsg{ - // The time at which the tick occurred. - Time: time.Now(), - - // The ID of the spinner that this message belongs to. This can be - // helpful when routing messages, however bear in mind that spinners - // will ignore messages that don't contain ID by default. - ID: m.id, - - sequence: m.sequence, - } - }, - m.Spinner.Tick, - m.ProgressBar.Init(), - } - - // if m.progressor != nil { - // cmds = append(cmds, m.ProgressBar.Init()) - //} - - return tea.Batch( - cmds..., - ) -} - -// ID returns the spinner's unique ID. -func (m Model) ID() int { - return m.id -} - -// Update is the Tea update function. -func (m Model) Update(msg tea.Msg) (tea.Model, tea.Cmd) { - switch msg := msg.(type) { - case tea.WindowSizeMsg: - m.WindowSize = msg - return m, nil - - case TickMsg: - tickCmd := m.handleTick(msg) - if tickCmd == nil { - // this tick is not meant for us - return m, nil - } - - // this tick is meant for us... do an update! - var progCmd tea.Cmd - - title := m.TitleOptions.Default - var prog *progress.Progress - if m.progressor != nil { - c := m.progressor.Progress() - title = m.TitleOptions.Title(c) - if c.Size() > 0 { - prog = &c - ratio := c.Ratio() - if m.ProgressBar.Percent() != ratio { - progCmd = m.ProgressBar.SetPercent(ratio) - } - } - m.completed = c.Complete() - if c.Error() != nil && !errors.Is(c.Error(), progress.ErrCompleted) { - m.err = c.Error() - } - } - m.title = title - m.progress = prog - - if m.stager != nil { - stage := m.stager.Stage() - if stage != "" { - // TODO: how to deal with stages that have custom stats from the results of commands? - // TODO: list is awkward both in usage and display - m.hints = append([]string{stage}, m.Hints...) - } else { - m.hints = m.Hints - } - } - - // TODO: rethink this - m.context = m.Context - - return m, tea.Batch(tickCmd, progCmd) - - case progressBubble.FrameMsg: - progModel, progCmd := m.ProgressBar.Update(msg) - m.ProgressBar = progModel.(progressBubble.Model) - return m, progCmd - - case spinner.TickMsg: - spinModel, spinCmd := m.Spinner.Update(msg) - m.Spinner = spinModel - return m, spinCmd - - default: - return m, nil - } -} - -// View renders the model's view. -func (m Model) View() string { - beforeProgress := " " - if m.completed { - if m.err != nil { - beforeProgress += m.FailedStyle.Render(xMark) + " " - } else { - beforeProgress += m.SuccessStyle.Render(checkMark) + " " - } - } else { - beforeProgress += m.Spinner.View() + " " - } - - if m.title != "" { - beforeProgress += m.TitleStyle.Width(40).Align(lipgloss.Left).Render(m.title) + " " - } - - progressBar := "" - var progressBarWidth int - showProgress := m.progress != nil && (!m.completed || (m.completed && !m.HideProgressOnSuccess && m.err == nil)) - if showProgress { - progressBar += m.ProgressBar.View() + " " - progressBarWidth = m.ProgressBar.Width + 2 - } - - afterProgress := "" - - if len(m.hints) > 0 { - var hints []string - for _, h := range m.hints { - hints = append(hints, fmt.Sprintf("[%s]", h)) - } - hintStr := strings.Join(hints, " ") - afterProgress += m.HintStyle.Render(hintStr) + " " - } - - if len(m.context) > 0 { - width := m.WindowSize.Width - (len(stripansi.Strip(beforeProgress+afterProgress)) + progressBarWidth) - afterProgress += m.ContextStyle.Width(width).Align(lipgloss.Right).Render(strings.Join(m.context, " ") + " ") - } - - if m.completed { - defer m.done() - } - - // force overflow to be ignored - return lipgloss.NewStyle().Inline(true).Render(beforeProgress + progressBar + afterProgress) -} - -func (m Model) queueNextTick(id, sequence int) tea.Cmd { - return tea.Tick(m.UpdateDuration, func(t time.Time) tea.Msg { - return TickMsg{ - Time: t, - ID: id, - sequence: sequence, - } - }) -} - -func (m *Model) handleTick(msg TickMsg) tea.Cmd { - // If an ID is set, and the ID doesn't belong to this spinner, reject - // the message. - if msg.ID > 0 && msg.ID != m.id { - return nil - } - - // If a sequence is set, and it's not the one we expect, reject the message. - // This prevents the spinner from receiving too many messages and - // thus spinning too fast. - if msg.sequence > 0 && msg.sequence != m.sequence { - return nil - } - - m.sequence++ - - // we should still respond to stage changes and window size events - // if m.completed { - // return nil - //} - - return m.queueNextTick(m.id, m.sequence) -} diff --git a/internal/ui/tui/bubbles/taskprogress/model_test.go b/internal/ui/tui/bubbles/taskprogress/model_test.go deleted file mode 100644 index 34f613a..0000000 --- a/internal/ui/tui/bubbles/taskprogress/model_test.go +++ /dev/null @@ -1,146 +0,0 @@ -package taskprogress - -import ( - "errors" - "sync" - "testing" - "time" - - tea "github.com/charmbracelet/bubbletea" - "github.com/gkampitakis/go-snaps/snaps" - "github.com/stretchr/testify/require" - "github.com/wagoodman/go-progress" - - "github.com/anchore/quill/internal/ui/tui/bubbles/testutil" -) - -func subject(t testing.TB) (*progress.Manual, *progress.Stage, Model) { - prog := &progress.Manual{ - N: 40, - Total: -1, - Err: nil, - } - stage := &progress.Stage{ - Current: "working", - } - - tsk := New( - &sync.WaitGroup{}, - WithStagedProgressable(progress.StagedProgressable(&struct { - progress.Stager - progress.Progressable - }{ - Stager: stage, - Progressable: prog, - })), - WithNoStyle(), - ) - tsk.HideProgressOnSuccess = true - tsk.TitleOptions = Title{ - Default: "Do work", - Running: "Doing work", - Success: "Did work", - Failed: "Failed at work :(", - } - tsk.Context = []string{ - "at home", - } - tsk.WindowSize = tea.WindowSizeMsg{ - Width: 100, - Height: 60, - } - - return prog, stage, tsk -} - -func subjectGen(t testing.TB) Model { - _, _, tsk := subject(t) - return tsk -} - -func TestModel_View(t *testing.T) { - - tests := []struct { - name string - taskGen func(testing.TB) Model - iterations int - }{ - { - name: "in progress without progress bar", - taskGen: func(tb testing.TB) Model { - prog, _, tsk := subject(t) - prog.N, prog.Total = 40, -1 - return tsk - }, - }, - { - name: "in progress with progress bar", - taskGen: func(tb testing.TB) Model { - prog, _, tsk := subject(t) - prog.N, prog.Total = 40, 100 - return tsk - }, - }, - { - name: "successfully finished hides progress bar", - taskGen: func(tb testing.TB) Model { - prog, stage, tsk := subject(t) - // note: we set progress to have a total size to ensure it is hidden - prog.N, prog.Total = 100, 100 - stage.Current = "done!" - return tsk - }, - }, - { - name: "successfully finished keeps progress bar shown", - taskGen: func(tb testing.TB) Model { - prog, stage, tsk := subject(t) - tsk.HideProgressOnSuccess = false - // note: we set progress to have a total size to ensure it is hidden - prog.N, prog.Total = 100, 100 - stage.Current = "done!" - return tsk - }, - }, - { - name: "no context", - taskGen: func(tb testing.TB) Model { - _, _, tsk := subject(t) - tsk.Context = nil - return tsk - }, - }, - - { - name: "multiple hints", - taskGen: func(tb testing.TB) Model { - _, _, tsk := subject(t) - tsk.Hints = []string{"info++", "info!++"} - return tsk - }, - }, - { - name: "error", - taskGen: func(tb testing.TB) Model { - prog, _, tsk := subject(t) - prog.SetCompleted() - prog.Err = errors.New("woops") - return tsk - }, - }, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - var m tea.Model = tt.taskGen(t) - tsk, ok := m.(Model) - require.True(t, ok) - got := testutil.RunModel(t, tsk, tt.iterations, TickMsg{ - Time: time.Now(), - sequence: tsk.sequence, - ID: tsk.id, - }) - t.Log(got) - snaps.MatchSnapshot(t, got) - }) - } -} diff --git a/internal/ui/tui/bubbles/taskprogress/option.go b/internal/ui/tui/bubbles/taskprogress/option.go deleted file mode 100644 index a3dba56..0000000 --- a/internal/ui/tui/bubbles/taskprogress/option.go +++ /dev/null @@ -1,41 +0,0 @@ -package taskprogress - -import ( - "github.com/charmbracelet/lipgloss" - "github.com/wagoodman/go-progress" -) - -type Option func(*Model) - -func WithProgress(prog progress.Progressable) Option { - return func(m *Model) { - m.progressor = progress.NewGenerator(prog, prog) - } -} - -func WithStager(s progress.Stager) Option { - return func(m *Model) { - m.stager = s - } -} - -func WithStagedProgressable(s progress.StagedProgressable) Option { - return func(m *Model) { - WithProgress(s)(m) - WithStager(s)(m) - } -} - -func WithNoStyle() Option { - return func(m *Model) { - m.SuccessStyle = lipgloss.NewStyle() - m.ContextStyle = lipgloss.NewStyle() - m.FailedStyle = lipgloss.NewStyle() - m.HintStyle = lipgloss.NewStyle() - m.TitleStyle = lipgloss.NewStyle() - m.ProgressBar.FullColor = "" - m.ProgressBar.EmptyColor = "" - m.ProgressBar.Full = '|' - m.ProgressBar.Empty = '-' - } -} diff --git a/internal/ui/tui/bubbles/taskprogress/tick.go b/internal/ui/tui/bubbles/taskprogress/tick.go deleted file mode 100644 index 4fb8a59..0000000 --- a/internal/ui/tui/bubbles/taskprogress/tick.go +++ /dev/null @@ -1,12 +0,0 @@ -package taskprogress - -import ( - "time" -) - -// TickMsg indicates that the timer has ticked and we should render a frame. -type TickMsg struct { - Time time.Time - sequence int - ID int -} diff --git a/internal/ui/tui/bubbles/taskprogress/title.go b/internal/ui/tui/bubbles/taskprogress/title.go deleted file mode 100644 index c9e36e6..0000000 --- a/internal/ui/tui/bubbles/taskprogress/title.go +++ /dev/null @@ -1,37 +0,0 @@ -package taskprogress - -import ( - "errors" - - "github.com/wagoodman/go-progress" -) - -type Title struct { - Default string - Running string - Success string - Failed string -} - -func (t Title) Title(p progress.Progress) string { - isFailed := p.Complete() && (p.Error() != nil && !errors.Is(p.Error(), progress.ErrCompleted)) - isSuccessful := p.Complete() && (p.Error() == nil || errors.Is(p.Error(), progress.ErrCompleted)) - // isRunning := p.Current() > 0 && !p.Complete() - isRunning := !p.Complete() - - switch { - case isRunning: - if t.Running != "" { - return t.Running - } - case isFailed: - if t.Failed != "" { - return t.Failed - } - case isSuccessful: - if t.Success != "" { - return t.Success - } - } - return t.Default -} diff --git a/internal/ui/tui/bubbles/testutil/run_model.go b/internal/ui/tui/bubbles/testutil/run_model.go deleted file mode 100644 index 8753541..0000000 --- a/internal/ui/tui/bubbles/testutil/run_model.go +++ /dev/null @@ -1,62 +0,0 @@ -package testutil - -import ( - "fmt" - "reflect" - "testing" - "unsafe" - - tea "github.com/charmbracelet/bubbletea" -) - -func RunModel(_ testing.TB, m tea.Model, iterations int, message tea.Msg) string { - if iterations == 0 { - iterations = 1 - } - m.Init() - var cmd tea.Cmd = func() tea.Msg { - return message - } - - for i := 0; cmd != nil && i < iterations; i++ { - msgs := flatten(cmd()) - var nextCmds []tea.Cmd - var next tea.Cmd - for _, msg := range msgs { - fmt.Printf("Message: %+v %+v\n", reflect.TypeOf(msg), msg) - m, next = m.Update(msg) - nextCmds = append(nextCmds, next) - } - cmd = tea.Batch(nextCmds...) - } - return m.View() -} - -func flatten(p tea.Msg) (msgs []tea.Msg) { - if reflect.TypeOf(p).Name() == "batchMsg" { - partials := extractBatchMessages(p) - for _, m := range partials { - msgs = append(msgs, flatten(m)...) - } - } else { - msgs = []tea.Msg{p} - } - return msgs -} - -func extractBatchMessages(m tea.Msg) (ret []tea.Msg) { - sliceMsgType := reflect.SliceOf(reflect.TypeOf(tea.Cmd(nil))) - value := reflect.ValueOf(m) // note: this is technically unaddressable - - // make our own instance that is addressable - valueCopy := reflect.New(value.Type()).Elem() - valueCopy.Set(value) - - cmds := reflect.NewAt(sliceMsgType, unsafe.Pointer(valueCopy.UnsafeAddr())).Elem() - for i := 0; i < cmds.Len(); i++ { - item := cmds.Index(i) - r := item.Call(nil) - ret = append(ret, r[0].Interface().(tea.Msg)) - } - return ret -} diff --git a/internal/ui/tui/event_handler.go b/internal/ui/tui/event_handler.go deleted file mode 100644 index f7c84fe..0000000 --- a/internal/ui/tui/event_handler.go +++ /dev/null @@ -1,27 +0,0 @@ -package tui - -import ( - tea "github.com/charmbracelet/bubbletea" - "github.com/wagoodman/go-partybus" - - "github.com/anchore/quill/quill/event" -) - -func (m *UI) eventHandler(e partybus.Event) (tea.Model, tea.Cmd) { - switch e.Type { - case event.Report, event.Notification, event.Exit: - // keep these for when the UI is terminated to show to the screen (or perform other events) - m.finalize = append(m.finalize, e) - - // why not return tea.Quit here for exit events? because there may be UI components that still need the update-render loop. - // for this reason we'll let the quill event loop call Teardown() which will explicitly wait for these components - return m, nil - case event.InputPrompt: - return m.handleInputPrompt(e) - case event.Task: - return m.handleTask(e) - default: - // TODO: type assert progressable and stager objects generically? - } - return m, nil -} diff --git a/internal/ui/tui/handle_input_prompt.go b/internal/ui/tui/handle_input_prompt.go deleted file mode 100644 index 1b7dc09..0000000 --- a/internal/ui/tui/handle_input_prompt.go +++ /dev/null @@ -1,24 +0,0 @@ -package tui - -import ( - tea "github.com/charmbracelet/bubbletea" - "github.com/wagoodman/go-partybus" - - "github.com/anchore/quill/internal/log" - "github.com/anchore/quill/internal/ui/tui/bubbles/prompt" - "github.com/anchore/quill/quill/event/parser" -) - -func (m *UI) handleInputPrompt(e partybus.Event) (tea.Model, tea.Cmd) { - writer, err := parser.InputPrompt(e) - if err != nil { - log.Warnf("unable to parse event: %+v", err) - return m, nil - } - - teaModel := prompt.New(writer) - - m.uiElements = append(m.uiElements, teaModel) - - return m, teaModel.Init() -} diff --git a/internal/ui/tui/handle_staged_progressable.go b/internal/ui/tui/handle_staged_progressable.go deleted file mode 100644 index 947448c..0000000 --- a/internal/ui/tui/handle_staged_progressable.go +++ /dev/null @@ -1,23 +0,0 @@ -package tui - -import ( - tea "github.com/charmbracelet/bubbletea" - "github.com/wagoodman/go-progress" - - "github.com/anchore/quill/internal/ui/tui/bubbles/taskprogress" -) - -func (m *UI) handleStagedProgressable(prog progress.StagedProgressable, title taskprogress.Title, context ...string) (tea.Model, tea.Cmd) { - tsk := taskprogress.New( - m.liveComponents, - taskprogress.WithStagedProgressable(prog), - ) - tsk.HideProgressOnSuccess = true - tsk.TitleOptions = title - tsk.Context = context - tsk.WindowSize = m.windowSize - - m.uiElements = append(m.uiElements, tsk) - - return m, tsk.Init() -} diff --git a/internal/ui/tui/handle_task.go b/internal/ui/tui/handle_task.go deleted file mode 100644 index 7d15e69..0000000 --- a/internal/ui/tui/handle_task.go +++ /dev/null @@ -1,25 +0,0 @@ -package tui - -import ( - tea "github.com/charmbracelet/bubbletea" - "github.com/wagoodman/go-partybus" - - "github.com/anchore/quill/internal/log" - "github.com/anchore/quill/internal/ui/tui/bubbles/taskprogress" - "github.com/anchore/quill/quill/event/parser" -) - -func (m *UI) handleTask(e partybus.Event) (tea.Model, tea.Cmd) { - cmd, prog, err := parser.Task(e) - if err != nil { - log.Warnf("unable to parse event: %+v", err) - return m, nil - } - - return m.handleStagedProgressable(prog, taskprogress.Title{ - Default: cmd.Title.Default, - Running: cmd.Title.WhileRunning, - Success: cmd.Title.OnSuccess, - Failed: cmd.Title.OnFail, - }, cmd.Context) -} diff --git a/internal/ui/tui/ui.go b/internal/ui/tui/ui.go deleted file mode 100644 index 4d29743..0000000 --- a/internal/ui/tui/ui.go +++ /dev/null @@ -1,163 +0,0 @@ -package tui - -import ( - "bytes" - "fmt" - "os" - "strings" - "sync" - - "github.com/charmbracelet/bubbles/spinner" - tea "github.com/charmbracelet/bubbletea" - "github.com/charmbracelet/lipgloss" - "github.com/wagoodman/go-partybus" - - "github.com/anchore/go-logger" - "github.com/anchore/quill/internal/bus" - "github.com/anchore/quill/internal/log" - "github.com/anchore/quill/internal/ui/handle" - "github.com/anchore/quill/quill/event" -) - -var _ tea.Model = (*UI)(nil) - -type UI struct { - program *tea.Program - logBuffer *bytes.Buffer - windowSize tea.WindowSizeMsg - uiElements []tea.Model - liveComponents *sync.WaitGroup - finalize []partybus.Event - unsubscribe func() error - running *sync.WaitGroup - quiet bool -} - -func New(_, quiet bool) *UI { - s := spinner.New() - s.Spinner = spinner.MiniDot - s.Style = lipgloss.NewStyle().Foreground(lipgloss.Color("205")) - return &UI{ - liveComponents: &sync.WaitGroup{}, - running: &sync.WaitGroup{}, - quiet: quiet, - } -} - -func (m *UI) Setup(unsubscribe func() error) error { - // we still want to collect log messages, however, we also the logger shouldn't write to the screen directly - m.logBuffer = &bytes.Buffer{} - if logWrapper, ok := log.Get().(logger.Controller); ok { - logWrapper.SetOutput(m.logBuffer) - } - - m.unsubscribe = unsubscribe - m.program = tea.NewProgram(m, tea.WithOutput(os.Stderr), tea.WithInput(os.Stdin)) - m.running.Add(1) - - go func() { - defer m.running.Done() - if err := m.program.Start(); err != nil { - log.Errorf("unable to start UI: %+v", err) - m.exit() - } - }() - - return nil -} - -func (m *UI) exit() { - // stop the event loop - bus.Publish(partybus.Event{ - Type: event.Exit, - }) -} - -func (m *UI) Handle(e partybus.Event) error { - if m.program != nil { - m.program.Send(e) - if e.Type == event.Exit { - return m.unsubscribe() - } - } - return nil -} - -func (m *UI) Teardown(force bool) error { - if !force { - m.liveComponents.Wait() - m.program.Quit() - } else { - m.program.Kill() - } - - m.running.Wait() - - _, _ = os.Stderr.WriteString(m.logBuffer.String()) - - handle.PostUIEvents(m.quiet, m.finalize...) - - return nil -} - -// bubbletea.Model functions - -func (m UI) Init() tea.Cmd { - return nil -} - -func (m *UI) Update(msg tea.Msg) (tea.Model, tea.Cmd) { - // note: we need a pointer receiver such that the same instance of UI used in Teardown is referenced (to keep finalize events) - - switch msg := msg.(type) { - case tea.KeyMsg: - switch msg.String() { - case "esc", "ctrl+c": - m.exit() - return m, tea.Quit - } - - case tea.WindowSizeMsg: - m.windowSize = msg - return m.updateUIElements(msg) - - case partybus.Event: - return m.eventHandler(msg) - } - - return m.updateUIElements(msg) -} - -func (m *UI) updateUIElements(msg tea.Msg) (tea.Model, tea.Cmd) { - // note: we need a pointer receiver such that the same instance of UI used in Teardown is referenced (to keep finalize events) - - var cmds []tea.Cmd - for i, el := range m.uiElements { - newEl, cmd := el.Update(msg) - cmds = append(cmds, cmd) - m.uiElements[i] = newEl - } - return m, tea.Batch(cmds...) -} - -func (m UI) View() string { - // all UI elements - str := "" - for _, p := range m.uiElements { - str += p.View() + "\n" - } - - // log events - logLines := strings.Split(m.logBuffer.String(), "\n") - logMax := m.windowSize.Height - strings.Count(str, "\n") - trimLog := len(logLines) - logMax - if trimLog > 0 && len(logLines) >= trimLog { - logLines = logLines[trimLog:] - } - for _, line := range logLines { - if len(line) > 0 { - str += fmt.Sprintf("%s\n", line) - } - } - return str -} diff --git a/internal/ui/ui.go b/internal/ui/ui.go deleted file mode 100644 index e720a8b..0000000 --- a/internal/ui/ui.go +++ /dev/null @@ -1,9 +0,0 @@ -package ui - -import "github.com/wagoodman/go-partybus" - -type UI interface { - Setup(unsubscribe func() error) error - partybus.Handler - Teardown(force bool) error -} diff --git a/internal/utils/exit_helpers.go b/internal/utils/exit_helpers.go deleted file mode 100644 index be515c8..0000000 --- a/internal/utils/exit_helpers.go +++ /dev/null @@ -1,18 +0,0 @@ -package utils - -import ( - "os" - - "github.com/gookit/color" -) - -func FatalOnError(err error, msg string) { - if err != nil { - ExitWithErrorf("%s: %v", msg, err) - } -} - -func ExitWithErrorf(format string, args ...interface{}) { - color.Red.Printf(format+"\n", args...) - os.Exit(1) -} diff --git a/internal/utils/viper.go b/internal/utils/viper.go deleted file mode 100644 index 9bc4d74..0000000 --- a/internal/utils/viper.go +++ /dev/null @@ -1,24 +0,0 @@ -package utils - -import ( - "strings" - - "github.com/spf13/viper" - - "github.com/anchore/quill/internal" -) - -func NewViper() *viper.Viper { - v := viper.NewWithOptions( - viper.EnvKeyReplacer( - strings.NewReplacer(".", "_", "-", "_"), - ), - ) - - // load environment variables - v.SetEnvPrefix(internal.ApplicationName) - v.AllowEmptyEnv(true) - v.AutomaticEnv() - - return v -} diff --git a/internal/version/build.go b/internal/version/build.go deleted file mode 100644 index 526d355..0000000 --- a/internal/version/build.go +++ /dev/null @@ -1,50 +0,0 @@ -/* -Package version contains all build time metadata (version, build time, git commit, etc). -*/ -package version - -import ( - "fmt" - "runtime" - "strings" -) - -const valueNotProvided = "[not provided]" - -// all variables here are provided as build-time arguments, with clear default values -var version = valueNotProvided -var gitCommit = valueNotProvided -var gitDescription = valueNotProvided -var buildDate = valueNotProvided -var platform = fmt.Sprintf("%s/%s", runtime.GOOS, runtime.GOARCH) - -// Version defines the application version details (generally from build information) -type Version struct { - Version string `json:"version"` // application semantic version - GitCommit string `json:"gitCommit"` // git SHA at build-time - GitDescription string `json:"gitDescription"` // indication of git tree (either "clean" or "dirty") at build-time - BuildDate string `json:"buildDate"` // date of the build - GoVersion string `json:"goVersion"` // go runtime version at build-time - Compiler string `json:"compiler"` // compiler used at build-time - Platform string `json:"platform"` // GOOS and GOARCH at build-time -} - -func (v Version) IsProductionBuild() bool { - if strings.Contains(v.Version, "SNAPSHOT") || strings.Contains(v.Version, valueNotProvided) { - return false - } - return true -} - -// FromBuild provides all version details -func FromBuild() Version { - return Version{ - Version: version, - GitCommit: gitCommit, - GitDescription: gitDescription, - BuildDate: buildDate, - GoVersion: runtime.Version(), - Compiler: runtime.Compiler, - Platform: platform, - } -} diff --git a/quill/event/event.go b/quill/event/event.go deleted file mode 100644 index 9540d9b..0000000 --- a/quill/event/event.go +++ /dev/null @@ -1,23 +0,0 @@ -/* -Package event provides event types for all events that the library published onto the event bus. By convention, for each event -defined here there should be a corresponding event parser defined in the parsers/ child package. -*/ -package event - -import ( - "github.com/wagoodman/go-partybus" - - "github.com/anchore/quill/internal" -) - -const ( - prefix = internal.ApplicationName - - // Exit is a partybus event indicating the main process is to exit - Exit partybus.EventType = prefix + "-exit-event" - - Report partybus.EventType = prefix + "-report" - Notification partybus.EventType = prefix + "-notification" - InputPrompt partybus.EventType = prefix + "-input-prompt" - Task partybus.EventType = prefix + "-task" -) diff --git a/quill/event/monitor/input_prompt.go b/quill/event/monitor/input_prompt.go deleted file mode 100644 index 19fb107..0000000 --- a/quill/event/monitor/input_prompt.go +++ /dev/null @@ -1,75 +0,0 @@ -package monitor - -import ( - "context" - "errors" - "fmt" -) - -type PromptReader interface { - GetPromptResponse(ctx context.Context) (string, error) -} - -type PromptWriter interface { - IsSensitive() bool - PromptMessage() string - SetPromptResponse(string) error - Validate(string) error -} - -type Prompter struct { - message string - value *string - responses chan string // prompt responses come from the prompt writer - validators []func(string) error - sensitive bool -} - -func NewPrompter(message string, sensitive bool, validators ...func(string) error) *Prompter { - return &Prompter{ - message: message, - responses: make(chan string, 1), - validators: validators, - sensitive: sensitive, - } -} - -func (p Prompter) PromptMessage() string { - return p.message -} - -func (p Prompter) IsSensitive() bool { - return p.sensitive -} - -func (p *Prompter) Validate(value string) error { - for _, validator := range p.validators { - if err := validator(value); err != nil { - return err - } - } - return nil -} - -func (p *Prompter) SetPromptResponse(value string) error { - if p.value != nil { - return fmt.Errorf("prompt cannot take another value") - } - - p.value = &value - p.responses <- value - close(p.responses) - return nil -} - -func (p *Prompter) GetPromptResponse(ctx context.Context) (string, error) { - if p.value != nil { - return *p.value, nil - } - select { - case v := <-p.responses: - return v, nil - case <-ctx.Done(): - return "", errors.New("timeout") - } -} diff --git a/quill/event/parser/common.go b/quill/event/parser/common.go deleted file mode 100644 index 5787778..0000000 --- a/quill/event/parser/common.go +++ /dev/null @@ -1,35 +0,0 @@ -/* -Package parsers provides parser helpers to extract payloads for each event type that the library publishes onto the event bus. -*/ -package parser - -import ( - "fmt" - - "github.com/wagoodman/go-partybus" -) - -type ErrBadPayload struct { - Type partybus.EventType - Field string - Value interface{} -} - -func (e *ErrBadPayload) Error() string { - return fmt.Sprintf("event='%s' has bad event payload field='%v': '%+v'", string(e.Type), e.Field, e.Value) -} - -func newPayloadErr(t partybus.EventType, field string, value interface{}) error { - return &ErrBadPayload{ - Type: t, - Field: field, - Value: value, - } -} - -func checkEventType(actual, expected partybus.EventType) error { - if actual != expected { - return newPayloadErr(expected, "Type", actual) - } - return nil -} diff --git a/quill/event/parser/exit.go b/quill/event/parser/exit.go deleted file mode 100644 index 4130623..0000000 --- a/quill/event/parser/exit.go +++ /dev/null @@ -1,11 +0,0 @@ -package parser - -import ( - "github.com/wagoodman/go-partybus" - - "github.com/anchore/quill/quill/event" -) - -func Exit(e partybus.Event) error { - return checkEventType(e.Type, event.Exit) -} diff --git a/quill/event/parser/input_prompt.go b/quill/event/parser/input_prompt.go deleted file mode 100644 index a35f7fa..0000000 --- a/quill/event/parser/input_prompt.go +++ /dev/null @@ -1,21 +0,0 @@ -package parser - -import ( - "github.com/wagoodman/go-partybus" - - "github.com/anchore/quill/quill/event" - "github.com/anchore/quill/quill/event/monitor" -) - -func InputPrompt(e partybus.Event) (monitor.PromptWriter, error) { - if err := checkEventType(e.Type, event.InputPrompt); err != nil { - return nil, err - } - - p, ok := e.Value.(monitor.PromptWriter) - if !ok { - return nil, newPayloadErr(e.Type, "Value", e.Value) - } - - return p, nil -} diff --git a/quill/event/parser/notification.go b/quill/event/parser/notification.go deleted file mode 100644 index 5f8dd00..0000000 --- a/quill/event/parser/notification.go +++ /dev/null @@ -1,26 +0,0 @@ -package parser - -import ( - "github.com/wagoodman/go-partybus" - - "github.com/anchore/quill/quill/event" -) - -func Notification(e partybus.Event) (string, string, error) { - if err := checkEventType(e.Type, event.Notification); err != nil { - return "", "", err - } - - context, ok := e.Source.(string) - if !ok { - // this is optional - context = "" - } - - notification, ok := e.Value.(string) - if !ok { - return "", "", newPayloadErr(e.Type, "Value", e.Value) - } - - return context, notification, nil -} diff --git a/quill/event/parser/report.go b/quill/event/parser/report.go deleted file mode 100644 index 7b24b8f..0000000 --- a/quill/event/parser/report.go +++ /dev/null @@ -1,26 +0,0 @@ -package parser - -import ( - "github.com/wagoodman/go-partybus" - - "github.com/anchore/quill/quill/event" -) - -func Report(e partybus.Event) (string, string, error) { - if err := checkEventType(e.Type, event.Report); err != nil { - return "", "", err - } - - context, ok := e.Source.(string) - if !ok { - // this is optional - context = "" - } - - report, ok := e.Value.(string) - if !ok { - return "", "", newPayloadErr(e.Type, "Value", e.Value) - } - - return context, report, nil -} diff --git a/quill/event/parser/task.go b/quill/event/parser/task.go deleted file mode 100644 index 10255ea..0000000 --- a/quill/event/parser/task.go +++ /dev/null @@ -1,27 +0,0 @@ -package parser - -import ( - "github.com/wagoodman/go-partybus" - "github.com/wagoodman/go-progress" - - "github.com/anchore/quill/quill/event" - "github.com/anchore/quill/quill/event/monitor" -) - -func Task(e partybus.Event) (*monitor.Task, progress.StagedProgressable, error) { - if err := checkEventType(e.Type, event.Task); err != nil { - return nil, nil, err - } - - cmd, ok := e.Source.(monitor.Task) - if !ok { - return nil, nil, newPayloadErr(e.Type, "Source", e.Source) - } - - p, ok := e.Value.(progress.StagedProgressable) - if !ok { - return nil, nil, newPayloadErr(e.Type, "Value", e.Value) - } - - return &cmd, p, nil -} diff --git a/quill/event/parsers.go b/quill/event/parsers.go new file mode 100644 index 0000000..581f0db --- /dev/null +++ b/quill/event/parsers.go @@ -0,0 +1,104 @@ +package event + +import ( + "fmt" + + "github.com/wagoodman/go-partybus" + "github.com/wagoodman/go-progress" + + "github.com/anchore/bubbly" +) + +type ErrBadPayload struct { + Type partybus.EventType + Field string + Value interface{} +} + +func (e *ErrBadPayload) Error() string { + return fmt.Sprintf("event='%s' has bad event payload field='%v': '%+v'", string(e.Type), e.Field, e.Value) +} + +func newPayloadErr(t partybus.EventType, field string, value interface{}) error { + return &ErrBadPayload{ + Type: t, + Field: field, + Value: value, + } +} + +func checkEventType(actual, expected partybus.EventType) error { + if actual != expected { + return newPayloadErr(expected, "Type", actual) + } + return nil +} + +func ParseCLIInputPromptType(e partybus.Event) (bubbly.PromptWriter, error) { + if err := checkEventType(e.Type, CLIInputPromptType); err != nil { + return nil, err + } + + p, ok := e.Value.(bubbly.PromptWriter) + if !ok { + return nil, newPayloadErr(e.Type, "Value", e.Value) + } + + return p, nil +} + +func ParseCLIReportType(e partybus.Event) (string, string, error) { + if err := checkEventType(e.Type, CLIReportType); err != nil { + return "", "", err + } + + context, ok := e.Source.(string) + if !ok { + // this is optional + context = "" + } + + report, ok := e.Value.(string) + if !ok { + return "", "", newPayloadErr(e.Type, "Value", e.Value) + } + + return context, report, nil +} + +func ParseTaskType(e partybus.Event) (*Task, progress.StagedProgressable, error) { + if err := checkEventType(e.Type, TaskType); err != nil { + return nil, nil, err + } + + cmd, ok := e.Source.(Task) + if !ok { + return nil, nil, newPayloadErr(e.Type, "Source", e.Source) + } + + p, ok := e.Value.(progress.StagedProgressable) + if !ok { + return nil, nil, newPayloadErr(e.Type, "Value", e.Value) + } + + return &cmd, p, nil +} + +func ParseCLINotificationType(e partybus.Event) (string, string, error) { + if err := checkEventType(e.Type, CLINotificationType); err != nil { + return "", "", err + } + + context, ok := e.Source.(string) + if !ok { + // this is optional + context = "" + } + + notification, ok := e.Value.(string) + if !ok { + return "", "", newPayloadErr(e.Type, "Value", e.Value) + } + + return context, notification, nil +} diff --git a/quill/event/monitor/task.go b/quill/event/task.go similarity index 94% rename from quill/event/monitor/task.go rename to quill/event/task.go index 62dbc0e..bc6b204 100644 --- a/quill/event/monitor/task.go +++ b/quill/event/task.go @@ -1,4 +1,4 @@ -package monitor +package event import "github.com/wagoodman/go-progress" diff --git a/quill/event/types.go b/quill/event/types.go new file mode 100644 index 0000000..9c60a2a --- /dev/null +++ b/quill/event/types.go @@ -0,0 +1,24 @@ +/* +Package event provides event types for all events that the library published onto the event bus. By convention, for each event +defined here there should be a corresponding event parser defined in the parsers/ child package. +*/ +package event + +import ( + "github.com/wagoodman/go-partybus" + + "github.com/anchore/quill/internal" +) + +const ( + typePrefix = internal.ApplicationName + cliTypePrefix = typePrefix + "-cli" + + TaskType partybus.EventType = typePrefix + "-task" + + // CLIExitType is a partybus event indicating the main process is to exit + CLIExitType partybus.EventType = cliTypePrefix + "-exit-event" + CLIReportType partybus.EventType = cliTypePrefix + "-report" + CLINotificationType partybus.EventType = cliTypePrefix + "-notification" + CLIInputPromptType partybus.EventType = cliTypePrefix + "-input-prompt" +) diff --git a/quill/lib.go b/quill/lib.go index 6f29beb..e6f7852 100644 --- a/quill/lib.go +++ b/quill/lib.go @@ -4,16 +4,29 @@ import ( "github.com/wagoodman/go-partybus" "github.com/anchore/go-logger" + "github.com/anchore/go-logger/adapter/redact" "github.com/anchore/quill/internal/bus" "github.com/anchore/quill/internal/log" + intRedact "github.com/anchore/quill/internal/redact" ) // SetLogger sets the logger object used for all logging calls. func SetLogger(logger logger.Logger) { + useOrAddRedactor() log.Set(logger) } // SetBus sets the event bus for all library bus publish events onto (in-library subscriptions are not allowed). func SetBus(b *partybus.Bus) { - bus.SetPublisher(b) + useOrAddRedactor() + bus.Set(b) +} + +func useOrAddRedactor() { + // since it is possible to read secrets from the environment during lib calls, we want to ensure that the logger + // is redacted even if the user did not explicitly set a redaction store. + store := intRedact.Get() + if store == nil { + intRedact.Set(redact.NewStore()) + } } diff --git a/quill/notarize.go b/quill/notarize.go index ffbea23..2619e6a 100644 --- a/quill/notarize.go +++ b/quill/notarize.go @@ -8,7 +8,7 @@ import ( "github.com/anchore/quill/internal/bus" "github.com/anchore/quill/internal/log" - "github.com/anchore/quill/quill/event/monitor" + "github.com/anchore/quill/quill/event" "github.com/anchore/quill/quill/notary" ) @@ -83,7 +83,7 @@ func Notarize(path string, cfg NotarizeConfig) (notary.SubmissionStatus, error) log.WithFields("binary", path).Info("notarizing binary") mon := bus.PublishTask( - monitor.Title{ + event.Title{ Default: "Notarize binary", WhileRunning: "Notarizing binary", OnSuccess: "Notarized binary", diff --git a/quill/pki/load/p12.go b/quill/pki/load/p12.go index ceff1bb..affc60b 100644 --- a/quill/pki/load/p12.go +++ b/quill/pki/load/p12.go @@ -1,7 +1,6 @@ package load import ( - "context" "crypto" "crypto/x509" "errors" @@ -9,35 +8,35 @@ import ( "software.sslmate.com/src/go-pkcs12" - "github.com/anchore/quill/internal/bus" "github.com/anchore/quill/internal/log" ) -func P12(path, password string) (crypto.PrivateKey, *x509.Certificate, []*x509.Certificate, error) { +var ErrNeedPassword = errors.New("need password to decode file") + +type P12Contents struct { + PrivateKey crypto.PrivateKey + Certificate *x509.Certificate + Certificates []*x509.Certificate +} + +func P12(path, password string) (*P12Contents, error) { by, err := BytesFromFileOrEnv(path) if err != nil { - return nil, nil, nil, fmt.Errorf("unable to read p12 bytes: %w", err) + return nil, fmt.Errorf("unable to read p12 bytes: %w", err) } key, cert, certs, err := pkcs12.DecodeChain(by, password) if err != nil { if errors.Is(err, pkcs12.ErrIncorrectPassword) && password == "" { - prompter := bus.PromptForInput("Enter P12 password:", true) - newPassword, err := prompter.GetPromptResponse(context.Background()) - if err != nil { - return nil, nil, nil, fmt.Errorf("unable to get password from prompt: %w", err) - } - - log.Redact(newPassword) - - key, cert, certs, err = pkcs12.DecodeChain(by, newPassword) - if err != nil { - return nil, nil, nil, fmt.Errorf("unable to decode p12 file: %w", err) - } - } else { - return nil, nil, nil, fmt.Errorf("unable to decode p12 file: %w", err) + log.Debug("p12 file requires a password but none provided") + return nil, ErrNeedPassword } + return nil, fmt.Errorf("unable to decode p12 file: %w", err) } - return key.(crypto.PrivateKey), cert, certs, nil + return &P12Contents{ + PrivateKey: key, + Certificate: cert, + Certificates: certs, + }, nil } diff --git a/quill/pki/signing_material.go b/quill/pki/signing_material.go index 8e47000..d983a72 100644 --- a/quill/pki/signing_material.go +++ b/quill/pki/signing_material.go @@ -55,23 +55,18 @@ func NewSigningMaterialFromPEMs(certFile, privateKeyPath, password string, failW }, nil } -func NewSigningMaterialFromP12(p12Path, password string, failWithoutFullChain bool) (*SigningMaterial, error) { - privateKey, cert, certs, err := load.P12(p12Path, password) - if err != nil { - return nil, fmt.Errorf("unable to decode p12 file: %w", err) - } - - if privateKey == nil { +func NewSigningMaterialFromP12(p12Content load.P12Contents, failWithoutFullChain bool) (*SigningMaterial, error) { + if p12Content.PrivateKey == nil { return nil, fmt.Errorf("no private key found in the p12") } - if cert == nil { + if p12Content.Certificate == nil { return nil, fmt.Errorf("no signing certificate found in the p12") } - allCerts := append([]*x509.Certificate{cert}, certs...) + allCerts := append([]*x509.Certificate{p12Content.Certificate}, p12Content.Certificates...) - signer, ok := privateKey.(crypto.Signer) + signer, ok := p12Content.PrivateKey.(crypto.Signer) if !ok { return nil, fmt.Errorf("unable to derive signer from private key") } @@ -81,7 +76,7 @@ func NewSigningMaterialFromP12(p12Path, password string, failWithoutFullChain bo store := certchain.NewCollection().WithStores(apple.GetEmbeddedCertStore()) // verification failed, try again but attempt to find more certs from the embedded certs in quill - remainingCerts, err := certchain.Find(store, cert) + remainingCerts, err := certchain.Find(store, p12Content.Certificate) if err != nil { return nil, fmt.Errorf("unable to find remaining chain certificates: %w", err) } diff --git a/quill/sign.go b/quill/sign.go index c025fb9..3d4cbb3 100644 --- a/quill/sign.go +++ b/quill/sign.go @@ -10,9 +10,10 @@ import ( macholibre "github.com/anchore/go-macholibre" "github.com/anchore/quill/internal/bus" "github.com/anchore/quill/internal/log" - "github.com/anchore/quill/quill/event/monitor" + "github.com/anchore/quill/quill/event" "github.com/anchore/quill/quill/macho" "github.com/anchore/quill/quill/pki" + "github.com/anchore/quill/quill/pki/load" "github.com/anchore/quill/quill/sign" ) @@ -40,8 +41,8 @@ func NewSigningConfigFromPEMs(binaryPath, certificate, privateKey, password stri }, nil } -func NewSigningConfigFromP12(binaryPath, p12, password string, failWithoutFullChain bool) (*SigningConfig, error) { - signingMaterial, err := pki.NewSigningMaterialFromP12(p12, password, failWithoutFullChain) +func NewSigningConfigFromP12(binaryPath string, p12Content load.P12Contents, failWithoutFullChain bool) (*SigningConfig, error) { + signingMaterial, err := pki.NewSigningMaterialFromP12(p12Content, failWithoutFullChain) if err != nil { return nil, err } @@ -77,7 +78,7 @@ func Sign(cfg SigningConfig) error { } mon := bus.PublishTask( - monitor.Title{ + event.Title{ Default: "Sign binary", WhileRunning: "Signing binary", OnSuccess: "Signed binary", @@ -112,7 +113,7 @@ func signMultiarchBinary(cfg SigningConfig) error { defer os.RemoveAll(dir) extractMon := bus.PublishTask( - monitor.Title{ + event.Title{ Default: "Extract universal binary", WhileRunning: "Extracting universal binary", OnSuccess: "Extracted universal binary", @@ -141,7 +142,7 @@ func signMultiarchBinary(cfg SigningConfig) error { } signMon := bus.PublishTask( - monitor.Title{ + event.Title{ Default: "Sign binaries", WhileRunning: "Signing binaries", OnSuccess: "Signed binaries", @@ -150,16 +151,6 @@ func signMultiarchBinary(cfg SigningConfig) error { len(cfgs), ) - packMon := bus.PublishTask( - monitor.Title{ - Default: "Repack universal binary", - WhileRunning: "Repacking universal binary", - OnSuccess: "Repacked universal binary", - }, - cfg.Path, - -1, - ) - defer signMon.SetCompleted() for _, c := range cfgs { @@ -178,7 +169,17 @@ func signMultiarchBinary(cfg SigningConfig) error { paths = append(paths, c.Path) } - log.WithFields("binary", cfg.Path, "arches", len(cfgs)).Debug("packaging signed binaries into single multi-arch binary") + log.WithFields("binary", cfg.Path, "arches", len(cfgs)).Info("packaging signed binaries into single multi-arch binary") + + packMon := bus.PublishTask( + event.Title{ + Default: "Repack universal binary", + WhileRunning: "Repacking universal binary", + OnSuccess: "Repacked universal binary", + }, + cfg.Path, + -1, + ) defer packMon.SetCompleted()