From df05add05b23f244fb1af904a8c47b9619821346 Mon Sep 17 00:00:00 2001 From: Aleksa Milosevic Date: Tue, 31 Aug 2021 01:21:24 +0200 Subject: [PATCH] Migration to Cobra/Viper (#300) * Cobra-preferred organization of the repo. Moved current cli functionality to cmd, made it to fit cobra ecosystem * Added flag and env var support along with binding it to viper. Unmarshal from viper to config struct. Populate flags with defaults and deprecation handling. Started oath struct refactoring * Added skip to nested oauth structures. Removed cli.go * Added support for nested structures * Resolve merge conflicts * Fixed tests. Fixed env variables not getting loaded. Removed unnecessary comments * Enabled both single and double dash flags. Fixed errors printed twice,usage always printed and logging. * New command structure. Removed init functions * Added access limiting for domains-google, groups-gitlab and organizations-github. Changes to config and oauth object structures * Moved login days and JWT secret under Auth * Added jwt secret auto-generation. Refactoring for flags. * logging, help and usage changeS * Separated viper instances and combined repeating parts into one function. Added support for bytes.ByteSize and time.Duration. Fixed tests. * small fixes * removes logrus dependency from profiler package * fixes generate-sample-config script Co-authored-by: Dmitry Filimonov --- cmd/pyroscope/command/agent.go | 21 + {pkg/cli => cmd/pyroscope/command}/banner.go | 2 +- cmd/pyroscope/command/common.go | 123 ++++++ cmd/pyroscope/command/connect.go | 22 + cmd/pyroscope/command/convert.go | 58 +++ cmd/pyroscope/command/dbmanager.go | 25 ++ cmd/pyroscope/command/exec.go | 22 + cmd/pyroscope/command/root.go | 83 ++++ cmd/pyroscope/command/server.go | 21 + {pkg/cli => cmd/pyroscope/command}/usage.go | 48 +-- cmd/pyroscope/command/version.go | 22 + cmd/pyroscope/main.go | 6 +- go.mod | 20 +- go.sum | 226 +++++++++- pkg/cli/agent_unix.go | 2 +- pkg/cli/agent_windows.go | 4 +- pkg/cli/cli.go | 207 ---------- pkg/cli/flags.go | 105 ++++- pkg/cli/flags_test.go | 409 +++++++++---------- pkg/cli/root_cmd_test.go | 19 - pkg/cli/server_unix.go | 9 +- pkg/cli/server_windows.go | 2 +- pkg/cli/sortedflags.go | 70 ---- pkg/cli/testdata/example.yml | 1 + pkg/config/config.go | 203 +++++---- pkg/config/interface.go | 51 +++ pkg/convert/cli.go | 41 -- pkg/exec/cli.go | 4 +- pkg/server/controller.go | 124 ++---- pkg/server/handler.go | 148 +------ pkg/server/oauth_base.go | 111 +++++ pkg/server/oauth_github.go | 124 ++++++ pkg/server/oauth_gitlab.go | 124 ++++++ pkg/server/oauth_google.go | 94 +++++ pkg/storage/jwt.go | 68 +++ pyroscope.go | 4 + scripts/generate-sample-config/main.go | 41 +- 37 files changed, 1693 insertions(+), 971 deletions(-) create mode 100644 cmd/pyroscope/command/agent.go rename {pkg/cli => cmd/pyroscope/command}/banner.go (98%) create mode 100644 cmd/pyroscope/command/common.go create mode 100644 cmd/pyroscope/command/connect.go create mode 100644 cmd/pyroscope/command/convert.go create mode 100644 cmd/pyroscope/command/dbmanager.go create mode 100644 cmd/pyroscope/command/exec.go create mode 100644 cmd/pyroscope/command/root.go create mode 100644 cmd/pyroscope/command/server.go rename {pkg/cli => cmd/pyroscope/command}/usage.go (62%) create mode 100644 cmd/pyroscope/command/version.go delete mode 100644 pkg/cli/cli.go delete mode 100644 pkg/cli/root_cmd_test.go delete mode 100644 pkg/cli/sortedflags.go create mode 100644 pkg/config/interface.go delete mode 100644 pkg/convert/cli.go create mode 100644 pkg/server/oauth_base.go create mode 100644 pkg/server/oauth_github.go create mode 100644 pkg/server/oauth_gitlab.go create mode 100644 pkg/server/oauth_google.go create mode 100644 pkg/storage/jwt.go create mode 100644 pyroscope.go diff --git a/cmd/pyroscope/command/agent.go b/cmd/pyroscope/command/agent.go new file mode 100644 index 0000000000..c6386822dd --- /dev/null +++ b/cmd/pyroscope/command/agent.go @@ -0,0 +1,21 @@ +package command + +import ( + "github.com/pyroscope-io/pyroscope/pkg/cli" + "github.com/pyroscope-io/pyroscope/pkg/config" + "github.com/spf13/cobra" +) + +func newAgentCmd(cfg *config.Agent) *cobra.Command { + vpr := newViper() + agentCmd := &cobra.Command{ + Use: "agent [flags]", + Short: "Start pyroscope agent.", + RunE: createCmdRunFn(cfg, vpr, false, func(cmd *cobra.Command, args []string, logger config.LoggerFunc) error { + return cli.StartAgent(cfg) + }), + } + + cli.PopulateFlagSet(cfg, agentCmd.Flags(), vpr, cli.WithSkip("targets")) + return agentCmd +} diff --git a/pkg/cli/banner.go b/cmd/pyroscope/command/banner.go similarity index 98% rename from pkg/cli/banner.go rename to cmd/pyroscope/command/banner.go index e41dc116ee..488f5bb75e 100644 --- a/pkg/cli/banner.go +++ b/cmd/pyroscope/command/banner.go @@ -1,4 +1,4 @@ -package cli +package command import ( "strings" diff --git a/cmd/pyroscope/command/common.go b/cmd/pyroscope/command/common.go new file mode 100644 index 0000000000..2a2891c69a --- /dev/null +++ b/cmd/pyroscope/command/common.go @@ -0,0 +1,123 @@ +package command + +import ( + "fmt" + "os" + "reflect" + "strings" + + goexec "os/exec" + + "github.com/mitchellh/mapstructure" + "github.com/pyroscope-io/pyroscope/pkg/config" + "github.com/pyroscope-io/pyroscope/pkg/util/bytesize" + "github.com/spf13/cobra" + "github.com/spf13/pflag" + "github.com/spf13/viper" +) + +type cmdRunFn func(cmd *cobra.Command, args []string) error +type cmdStartFn func(cmd *cobra.Command, args []string, logger config.LoggerFunc) error + +func createCmdRunFn(cfg interface{}, vpr *viper.Viper, requiresArgs bool, fn cmdStartFn) cmdRunFn { + return func(cmd *cobra.Command, args []string) error { + var err error + if err = bindFlags(cfg, cmd, vpr); err != nil { + return fmt.Errorf("invalid configuration: %w", err) + } + + var logger func(s string) + if l, ok := cfg.(config.LoggerConfiger); ok { + logger = l.InitializeLogging() + } + + if c, ok := cfg.(config.FileConfiger); ok { + if err = loadConfigFile(c.ConfigFilePath(), cmd, vpr, cfg); err != nil { + return fmt.Errorf("loading configuration file: %w", err) + } + } + + if (requiresArgs && len(args) == 0) || (len(args) > 0 && args[0] == "help") { + _ = cmd.Help() + return nil + } + + if err = fn(cmd, args, logger); err != nil { + cmd.SilenceUsage = true + } + + // Normally, if the program ran, the call should return ExitError and + // the exit code must be preserved. Otherwise, the error originates from + // pyroscope and will be printed. + if e, ok := err.(*goexec.ExitError); ok { + os.Exit(e.ExitCode()) + } + + return err + } +} + +func bindFlags(cfg interface{}, cmd *cobra.Command, vpr *viper.Viper) error { + if err := vpr.BindPFlags(cmd.Flags()); err != nil { + return err + } + return viperUnmarshalWithBytesHook(vpr, cfg) +} + +func newViper() *viper.Viper { + v := viper.New() + v.SetEnvPrefix("PYROSCOPE") + v.AutomaticEnv() + v.SetEnvKeyReplacer(strings.NewReplacer("-", "_", ".", "_")) + return v +} + +func loadConfigFile(path string, cmd *cobra.Command, vpr *viper.Viper, v interface{}) error { + if path == "" { + return nil + } + + vpr.SetConfigFile(path) + err := vpr.ReadInConfig() + switch { + case err == nil: + return viperUnmarshalWithBytesHook(vpr, v) + case isUserDefined(cmd.Flag("config"), vpr): + // User-defined configuration can not be read. + return err + case os.IsNotExist(err): + // Default configuration file not found. + return nil + default: + return err + } +} + +func viperUnmarshalWithBytesHook(vpr *viper.Viper, cfg interface{}) error { + return vpr.Unmarshal(cfg, viper.DecodeHook( + mapstructure.ComposeDecodeHookFunc( + // Function to add a special type for «env. mode» + func(f reflect.Type, t reflect.Type, data interface{}) (interface{}, error) { + if t != reflect.TypeOf(bytesize.Byte) { + return data, nil + } + + stringData, ok := data.(string) + if !ok { + return data, nil + } + + return bytesize.Parse(stringData) + }, + // Function to support net.IP + mapstructure.StringToIPHookFunc(), + // Appended by the two default functions + mapstructure.StringToTimeDurationHookFunc(), + mapstructure.StringToSliceHookFunc(","), + ), + )) +} + +func isUserDefined(f *pflag.Flag, v *viper.Viper) bool { + return f.Changed || (f.DefValue != "" && f.DefValue != v.GetString(f.Name)) +} diff --git a/cmd/pyroscope/command/connect.go b/cmd/pyroscope/command/connect.go new file mode 100644 index 0000000000..a5240eaf6c --- /dev/null +++ b/cmd/pyroscope/command/connect.go @@ -0,0 +1,22 @@ +package command + +import ( + "github.com/pyroscope-io/pyroscope/pkg/cli" + "github.com/pyroscope-io/pyroscope/pkg/config" + "github.com/pyroscope-io/pyroscope/pkg/exec" + "github.com/spf13/cobra" +) + +func newConnectCmd(cfg *config.Exec) *cobra.Command { + vpr := newViper() + connectCmd := &cobra.Command{ + Use: "connect [flags]", + Short: "Connect to an existing process and profile it", + RunE: createCmdRunFn(cfg, vpr, true, func(cmd *cobra.Command, args []string, logger config.LoggerFunc) error { + return exec.Cli(cfg, args) + }), + } + + cli.PopulateFlagSet(cfg, connectCmd.Flags(), vpr, cli.WithSkip("group-name", "user-name", "no-root-drop")) + return connectCmd +} diff --git a/cmd/pyroscope/command/convert.go b/cmd/pyroscope/command/convert.go new file mode 100644 index 0000000000..9ff0750dd2 --- /dev/null +++ b/cmd/pyroscope/command/convert.go @@ -0,0 +1,58 @@ +package command + +import ( + "fmt" + "io" + "os" + + "github.com/pyroscope-io/pyroscope/pkg/cli" + "github.com/pyroscope-io/pyroscope/pkg/config" + "github.com/pyroscope-io/pyroscope/pkg/convert" + "github.com/spf13/cobra" + + "github.com/pyroscope-io/pyroscope/pkg/storage/tree" + "github.com/pyroscope-io/pyroscope/pkg/structs/transporttrie" +) + +func newConvertCmd(cfg *config.Convert) *cobra.Command { + vpr := newViper() + convertCmd := &cobra.Command{ + Use: "convert [flags] ", + Short: "Convert between different profiling formats", + RunE: createCmdRunFn(cfg, vpr, false, func(cmd *cobra.Command, args []string, logger config.LoggerFunc) error { + var input io.Reader + if len(args) == 0 { + input = os.Stdin + } else { + logger("not implemented yet") + return nil + } + + parser := convert.ParseGroups + switch cfg.Format { + case "tree": + t := tree.New() + parser(input, func(name []byte, val int) { + t.Insert(name, uint64(val)) + }) + + t.SerializeNoDict(4096, os.Stdout) + case "trie": + t := transporttrie.New() + parser(input, func(name []byte, val int) { + t.Insert(name, uint64(val), true) + }) + + t.Serialize(os.Stdout) + default: + logger(fmt.Sprintf("unknown format: %s", cfg.Format)) + } + + return nil + }), + Hidden: true, + } + + cli.PopulateFlagSet(cfg, convertCmd.Flags(), vpr) + return convertCmd +} diff --git a/cmd/pyroscope/command/dbmanager.go b/cmd/pyroscope/command/dbmanager.go new file mode 100644 index 0000000000..b87de59071 --- /dev/null +++ b/cmd/pyroscope/command/dbmanager.go @@ -0,0 +1,25 @@ +package command + +import ( + "github.com/pyroscope-io/pyroscope/pkg/cli" + "github.com/pyroscope-io/pyroscope/pkg/config" + "github.com/pyroscope-io/pyroscope/pkg/dbmanager" + "github.com/spf13/cobra" +) + +func newDbManagerCmd(cfg *config.CombinedDbManager) *cobra.Command { + vpr := newViper() + dbmanagerCmd := &cobra.Command{ + Use: "dbmanager [flags] ", + Short: "tools for managing database", + RunE: createCmdRunFn(cfg, vpr, false, func(cmd *cobra.Command, args []string, logger config.LoggerFunc) error { + return dbmanager.Cli(cfg.DbManager, cfg.Server, args) + + }), + Hidden: true, + } + + cli.PopulateFlagSet(cfg.DbManager, dbmanagerCmd.Flags(), vpr) + cli.PopulateFlagSet(cfg.Server, dbmanagerCmd.Flags(), vpr, cli.WithSkip("log-level", "storage-path", "metric-export-rules")) + return dbmanagerCmd +} diff --git a/cmd/pyroscope/command/exec.go b/cmd/pyroscope/command/exec.go new file mode 100644 index 0000000000..9472cf2ffe --- /dev/null +++ b/cmd/pyroscope/command/exec.go @@ -0,0 +1,22 @@ +package command + +import ( + "github.com/pyroscope-io/pyroscope/pkg/cli" + "github.com/pyroscope-io/pyroscope/pkg/config" + "github.com/pyroscope-io/pyroscope/pkg/exec" + "github.com/spf13/cobra" +) + +func newExecCmd(cfg *config.Exec) *cobra.Command { + vpr := newViper() + execCmd := &cobra.Command{ + Use: "exec [flags] ", + Short: "Start a new process from arguments and profile it", + RunE: createCmdRunFn(cfg, vpr, true, func(cmd *cobra.Command, args []string, logger config.LoggerFunc) error { + return exec.Cli(cfg, args) + }), + } + + cli.PopulateFlagSet(cfg, execCmd.Flags(), vpr, cli.WithSkip("pid")) + return execCmd +} diff --git a/cmd/pyroscope/command/root.go b/cmd/pyroscope/command/root.go new file mode 100644 index 0000000000..dad1ce9e49 --- /dev/null +++ b/cmd/pyroscope/command/root.go @@ -0,0 +1,83 @@ +package command + +import ( + "fmt" + "os" + "runtime" + "strings" + + "github.com/pyroscope-io/pyroscope/pkg/build" + "github.com/pyroscope-io/pyroscope/pkg/config" + "github.com/sirupsen/logrus" + "github.com/spf13/cobra" +) + +func newRootCmd(cfg *config.Config) *cobra.Command { + rootCmd := &cobra.Command{ + Use: "pyroscope [flags] ", + Run: func(cmd *cobra.Command, args []string) { + if cfg.Version { + fmt.Println(gradientBanner()) + fmt.Println(build.Summary()) + fmt.Println("") + } else { + fmt.Println(gradientBanner()) + fmt.Println(DefaultUsageFunc(cmd.Flags(), cmd)) + } + }, + } + + rootCmd.SetUsageFunc(func(cmd *cobra.Command) error { + fmt.Println(gradientBanner()) + fmt.Println(DefaultUsageFunc(cmd.Flags(), cmd)) + return nil + }) + + rootCmd.SetHelpFunc(func(cmd *cobra.Command, a []string) { + fmt.Println(gradientBanner()) + fmt.Println(DefaultUsageFunc(cmd.Flags(), cmd)) + }) + + rootCmd.PersistentFlags().BoolVarP(&cfg.Version, "version", "v", false, "print pyroscope version details") + return rootCmd +} + +// Initialize adds all child commands to the root command and sets flags appropriately +func Initialize() error { + var cfg config.Config + + rootCmd := newRootCmd(&cfg) + rootCmd.SilenceErrors = true + rootCmd.AddCommand( + newAgentCmd(&cfg.Agent), + newConnectCmd(&cfg.Exec), + newConvertCmd(&cfg.Convert), + newDbManagerCmd(&config.CombinedDbManager{DbManager: &cfg.DbManager, Server: &cfg.Server}), + newExecCmd(&cfg.Exec), + newServerCmd(&cfg.Server), + newVersionCmd(), + ) + + logrus.SetReportCaller(true) + logrus.SetFormatter(&logrus.TextFormatter{ + TimestampFormat: "2006-01-02T15:04:05.000000", + FullTimestamp: true, + CallerPrettyfier: func(f *runtime.Frame) (string, string) { + filename := f.File + if len(filename) > 38 { + filename = filename[38:] + } + return "", fmt.Sprintf(" %s:%d", filename, f.Line) + }, + }) + + args := os.Args[1:] + for i, arg := range args { + if len(arg) > 2 && strings.HasPrefix(arg, "-") && !strings.HasPrefix(arg, "--") { + args[i] = fmt.Sprintf("-%s", arg) + } + } + + rootCmd.SetArgs(args) + return rootCmd.Execute() +} diff --git a/cmd/pyroscope/command/server.go b/cmd/pyroscope/command/server.go new file mode 100644 index 0000000000..2664876856 --- /dev/null +++ b/cmd/pyroscope/command/server.go @@ -0,0 +1,21 @@ +package command + +import ( + "github.com/pyroscope-io/pyroscope/pkg/cli" + "github.com/pyroscope-io/pyroscope/pkg/config" + "github.com/spf13/cobra" +) + +func newServerCmd(cfg *config.Server) *cobra.Command { + vpr := newViper() + serverCmd := &cobra.Command{ + Use: "server [flags]", + Short: "Start pyroscope server. This is the database + web-based user interface", + RunE: createCmdRunFn(cfg, vpr, false, func(cmd *cobra.Command, args []string, logger config.LoggerFunc) error { + return cli.StartServer(cfg) + }), + } + + cli.PopulateFlagSet(cfg, serverCmd.Flags(), vpr, cli.WithSkip("metric-export-rules")) + return serverCmd +} diff --git a/pkg/cli/usage.go b/cmd/pyroscope/command/usage.go similarity index 62% rename from pkg/cli/usage.go rename to cmd/pyroscope/command/usage.go index a2cfba842f..65fe8603a0 100644 --- a/pkg/cli/usage.go +++ b/cmd/pyroscope/command/usage.go @@ -1,14 +1,13 @@ -package cli +package command import ( - "flag" "fmt" "strings" "text/tabwriter" "github.com/fatih/color" - "github.com/peterbourgon/ff/v3/ffcli" - "github.com/pyroscope-io/pyroscope/pkg/util/slices" + "github.com/spf13/cobra" + "github.com/spf13/pflag" ) var ( @@ -26,50 +25,45 @@ func init() { defClr = color.New(color.FgYellow) } -// disabled these commands for now, they are not documented and confuse people -var hiddenCommands = []string{ - "convert", - "dbmanager", -} - +// TODO: Do we want to keep this or use cobra default one? Maybe banner + cobra default? Or something else? // This is mostly copied from ffcli package -func DefaultUsageFunc(sf *SortedFlags, c *ffcli.Command, deprecatedFields []string) string { +func DefaultUsageFunc(sf *pflag.FlagSet, c *cobra.Command) string { var b strings.Builder fmt.Fprintf(&b, "continuous profiling platform\n\n") headerClr.Fprintf(&b, "USAGE\n") - if c.ShortUsage != "" { - fmt.Fprintf(&b, " %s\n", c.ShortUsage) + if c.Use != "" { + fmt.Fprintf(&b, " %s\n", c.Use) } else { - fmt.Fprintf(&b, " %s\n", c.Name) + fmt.Fprintf(&b, " %s\n", c.Name()) } fmt.Fprintf(&b, "\n") - if c.LongHelp != "" { - fmt.Fprintf(&b, "%s\n\n", c.LongHelp) + if c.Long != "" { + fmt.Fprintf(&b, "%s\n\n", c.Long) } - if len(c.Subcommands) > 0 { + if c.HasSubCommands() { headerClr.Fprintf(&b, "SUBCOMMANDS\n") tw := tabwriter.NewWriter(&b, 0, 2, 2, ' ', 0) - for _, subcommand := range c.Subcommands { - if !slices.StringContains(hiddenCommands, subcommand.Name) { - fmt.Fprintf(tw, " %s\t%s\n", itemClr.Sprintf(subcommand.Name), subcommand.ShortHelp) + for _, subcommand := range c.Commands() { + if !subcommand.Hidden { + fmt.Fprintf(tw, " %s\t%s\n", itemClr.Sprintf(subcommand.Name()), subcommand.Short) } } tw.Flush() fmt.Fprintf(&b, "\n") } - if countFlags(c.FlagSet) > 0 { + if countFlags(c.Flags()) > 0 { // headerClr.Fprintf(&b, "FLAGS\n") tw := tabwriter.NewWriter(&b, 0, 2, 2, ' ', 0) fmt.Fprintf(tw, "%s\t %s@new-line@\n", headerClr.Sprintf("FLAGS"), defClr.Sprint("DEFAULT VALUES")) // TODO: it would be nice to sort by how often people would use these. // But for that we'd have to have a conversion from flag-set back to struct - sf.VisitAll(func(f *flag.Flag) { - if slices.StringContains(deprecatedFields, f.Name) { + sf.VisitAll(func(f *pflag.Flag) { + if f.Hidden { return } def := f.DefValue @@ -78,7 +72,7 @@ func DefaultUsageFunc(sf *SortedFlags, c *ffcli.Command, deprecatedFields []stri // } def = defClr.Sprint(def) // def = fmt.Sprintf("(%s)", def) - fmt.Fprintf(tw, " %s\t%s", itemClr.Sprintf("-"+f.Name), def) + fmt.Fprintf(tw, " %s\t%s", itemClr.Sprintf("--"+f.Name), def) if f.Usage != "" { fmt.Fprintf(tw, "@new-line@ ") descClr.Fprint(tw, f.Usage) @@ -90,14 +84,14 @@ func DefaultUsageFunc(sf *SortedFlags, c *ffcli.Command, deprecatedFields []stri // fmt.Fprintf(&b, "\n") } - if len(c.Subcommands) > 0 { + if c.HasSubCommands() { b.WriteString("Run 'pyroscope SUBCOMMAND --help' for more information on a subcommand.\n") } return strings.ReplaceAll(b.String(), "@new-line@", "\n") } -func countFlags(fs *flag.FlagSet) (n int) { - fs.VisitAll(func(*flag.Flag) { n++ }) +func countFlags(fs *pflag.FlagSet) (n int) { + fs.VisitAll(func(*pflag.Flag) { n++ }) return n } diff --git a/cmd/pyroscope/command/version.go b/cmd/pyroscope/command/version.go new file mode 100644 index 0000000000..4cfae3174f --- /dev/null +++ b/cmd/pyroscope/command/version.go @@ -0,0 +1,22 @@ +package command + +import ( + "fmt" + + "github.com/pyroscope-io/pyroscope/pkg/build" + "github.com/spf13/cobra" +) + +func newVersionCmd() *cobra.Command { + versionCmd := &cobra.Command{ + Use: "version", + Short: "Print pyroscope version details", + Run: func(cmd *cobra.Command, args []string) { + fmt.Println(gradientBanner()) + fmt.Println(build.Summary()) + fmt.Println("") + }, + } + + return versionCmd +} diff --git a/cmd/pyroscope/main.go b/cmd/pyroscope/main.go index 4339db9026..dac9699e9b 100644 --- a/cmd/pyroscope/main.go +++ b/cmd/pyroscope/main.go @@ -2,13 +2,11 @@ package main import ( "github.com/fatih/color" - - "github.com/pyroscope-io/pyroscope/pkg/cli" - "github.com/pyroscope-io/pyroscope/pkg/config" + "github.com/pyroscope-io/pyroscope/cmd/pyroscope/command" ) func main() { - if err := cli.Start(new(config.Config)); err != nil { + if err := command.Initialize(); err != nil { fatalf("%s %v\n\n", color.RedString("Error:"), err) } } diff --git a/go.mod b/go.mod index 0ae7071987..c711ab60f4 100644 --- a/go.mod +++ b/go.mod @@ -12,16 +12,17 @@ require ( github.com/creack/pty v1.1.11 // indirect github.com/davecgh/go-spew v1.1.1 github.com/dgraph-io/badger/v2 v2.2007.2 + github.com/dgraph-io/ristretto v0.1.0 // indirect github.com/fatih/color v1.10.0 github.com/felixge/fgprof v0.9.1 github.com/go-ole/go-ole v1.2.5 // indirect github.com/golang-jwt/jwt v3.2.1+incompatible github.com/golang/protobuf v1.5.2 + github.com/golang/snappy v0.0.3 // indirect github.com/google/go-jsonnet v0.17.0 - github.com/google/pprof v0.0.0-20200708004538-1a94d8640e99 + github.com/google/pprof v0.0.0-20210226084205-cbba55b83ad5 github.com/google/uuid v1.1.2 - github.com/iancoleman/strcase v0.1.2 - github.com/ianlancetaylor/demangle v0.0.0-20200715173712-053cf528c12f // indirect + github.com/iancoleman/strcase v0.2.0 github.com/imdario/mergo v0.3.11 // indirect github.com/josephspurrier/goversioninfo v1.2.0 github.com/jsonnet-bundler/jsonnet-bundler v0.4.0 @@ -30,14 +31,15 @@ require ( github.com/kr/pretty v0.2.0 // indirect github.com/kyoh86/richgo v0.3.3 github.com/kyoh86/xdg v1.2.0 // indirect + github.com/markbates/pkger v0.17.1 github.com/mattn/go-runewidth v0.0.10 // indirect github.com/mattn/goreman v0.3.5 github.com/mgechev/revive v1.0.3 github.com/mitchellh/go-ps v1.0.0 + github.com/mitchellh/mapstructure v1.4.1 github.com/morikuni/aec v1.0.0 // indirect github.com/onsi/ginkgo v1.16.2 github.com/onsi/gomega v1.12.0 - github.com/pelletier/go-toml v1.8.1 // indirect github.com/peterbourgon/ff/v3 v3.0.0 github.com/prometheus/client_golang v1.11.0 github.com/prometheus/common v0.29.0 @@ -46,16 +48,18 @@ require ( github.com/rivo/uniseg v0.2.0 // indirect github.com/shirou/gopsutil v3.21.4+incompatible github.com/sirupsen/logrus v1.7.0 - github.com/slok/go-http-metrics v0.9.0 // indirect - github.com/spf13/cobra v0.0.5 + github.com/slok/go-http-metrics v0.9.0 + github.com/spf13/cobra v1.2.1 + github.com/spf13/pflag v1.0.5 + github.com/spf13/viper v1.8.1 github.com/tklauser/go-sysconf v0.3.6 // indirect github.com/twmb/murmur3 v1.1.5 github.com/valyala/bytebufferpool v1.0.0 github.com/wacul/ptr v1.0.0 // indirect golang.org/x/oauth2 v0.0.0-20210514164344-f6687ab2804c - golang.org/x/sync v0.0.0-20201207232520-09787c993a3a + golang.org/x/sync v0.0.0-20210220032951-036812b2e83c golang.org/x/sys v0.0.0-20210616094352-59db8d763f22 - golang.org/x/tools v0.1.0 + golang.org/x/tools v0.1.2 google.golang.org/protobuf v1.26.0 gopkg.in/yaml.v2 v2.4.0 honnef.co/go/tools v0.0.1-2020.1.6 diff --git a/go.sum b/go.sum index e239216764..35bad10356 100644 --- a/go.sum +++ b/go.sum @@ -13,6 +13,11 @@ cloud.google.com/go v0.56.0/go.mod h1:jr7tqZxxKOVYizybht9+26Z/gUq7tiRzu+ACVAMbKV 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.78.0/go.mod h1:QjdrLG0uq+YwhjoVOLsS1t7TW8fs36kLs4XO5R5ECHg= +cloud.google.com/go v0.79.0/go.mod h1:3bzgcEeQlzbuEAYu4mrWhKqWjmpprinYgKJLgKHnbb8= +cloud.google.com/go v0.81.0/go.mod h1:mk/AM35KwGk/Nm2YSeZbxXdrNK3KZOYHmLkOqC2V6E0= 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= @@ -21,6 +26,7 @@ cloud.google.com/go/bigquery v1.7.0/go.mod h1://okPTzCYNXSlb24MZs83e2Do+h+VXtc4g 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/datastore v1.1.0/go.mod h1:umbIZjpQpHh4hmRpGhH4tLFup+FVzqBi1b3c64qFpCk= +cloud.google.com/go/firestore v1.1.0/go.mod h1:ulACoGHTpvq5r8rxGJ4ddJZBZqakUQqClKRT5SZwBmk= cloud.google.com/go/pubsub v1.0.1/go.mod h1:R0Gpsv3s54REJCy4fxDixWD93lHJMoZTyQ2kNxGRt3I= cloud.google.com/go/pubsub v1.1.0/go.mod h1:EwwdRX2sKPjnvnqCa270oGRyludottCI76h+R3AArQw= cloud.google.com/go/pubsub v1.2.0/go.mod h1:jhfEVHT8odbXTkndysNHCcx0awwzvfOlguIAii9o8iA= @@ -54,13 +60,19 @@ github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRF github.com/alecthomas/units v0.0.0-20190717042225-c3de453c63f4/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= github.com/alecthomas/units v0.0.0-20190924025748-f65c72e2690d h1:UQZhZ2O0vMHr2cI+DC1Mbh0TJxzA3RcLoMsFw+aXw7E= github.com/alecthomas/units v0.0.0-20190924025748-f65c72e2690d/go.mod h1:rBZYJk541a8SKzHPHnH3zbiI+7dagKZ0cgpgrD7Fyho= +github.com/antihax/optional v1.0.0/go.mod h1:uupD/76wgC+ih3iEmQUL+0Ugr19nfwCT1kdvxnR2qWY= +github.com/armon/circbuf v0.0.0-20150827004946-bbbad097214e/go.mod h1:3U/XgcO3hCbHZ8TKRvWD2dDTCfh9M9ya+I9JpbB7O8o= github.com/armon/consul-api v0.0.0-20180202201655-eb2c6b5be1b6/go.mod h1:grANhF5doyWs3UAsr3K4I6qtAmlQcZDesFNEHPZAzj8= +github.com/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/aybabtme/rgbterm v0.0.0-20170906152045-cc83f3b3ce59 h1:WWB576BN5zNSZc/M9d/10pqEx5VHNhaQ/yOVAkmj5Yo= github.com/aybabtme/rgbterm v0.0.0-20170906152045-cc83f3b3ce59/go.mod h1:q/89r3U2H7sSsE2t6Kca0lfwTK8JdoNGS/yzM/4iH5I= 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/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM= github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw= +github.com/bgentry/speakeasy v0.1.0/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kBD4zp0CCIs= +github.com/bketelsen/crypt v0.0.4/go.mod h1:aI6NrJ0pMGgvZKL1iVgXLnfIFJtfV+bKCoqOes/6LfM= github.com/blang/semver v3.5.1+incompatible h1:cQNTCjp13qL8KC3Nbxr/y2Bqb63oX6wdnnjpJbkM4JQ= github.com/blang/semver v3.5.1+incompatible/go.mod h1:kRBLl5iJ+tD4TcOOxsy/0fnwebNt5EWlYSAyrTnjyyk= github.com/campoy/embedmd v1.0.0/go.mod h1:oxyr9RCiSXg0M3VJ3ks0UGfp98BpSSGr0kpiX3MzVl8= @@ -82,12 +94,17 @@ github.com/clarkduvall/hyperloglog v0.0.0-20171127014514-a0107a5d8004 h1:mK6JroY github.com/clarkduvall/hyperloglog v0.0.0-20171127014514-a0107a5d8004/go.mod h1:drodPoQNro6QBO6TJ/MpMZbz8Bn2eSDtRN6jpG4VGw8= 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/coreos/etcd v3.3.10+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE= github.com/coreos/go-etcd v2.0.0+incompatible/go.mod h1:Jez6KQU2B/sWsbdaef3ED8NzMklzPG4d5KIOhIy30Tk= github.com/coreos/go-semver v0.2.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk= +github.com/coreos/go-semver v0.3.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk= +github.com/coreos/go-systemd/v22 v22.3.2/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc= github.com/cosmtrek/air v1.12.2 h1:YBO3ThUrGNO95zoJDUQeleP3paP7ZkutHkRX3y6HTd4= github.com/cosmtrek/air v1.12.2/go.mod h1:5EsgUqrBIHlW2ghNoevwPBEG1FQvF5XNulikjPte538= github.com/cpuguy83/go-md2man v1.0.10/go.mod h1:SmD6nW6nTyfqj6ABTjUi3V3JVMnlJmwcJI5acqYI6dE= +github.com/cpuguy83/go-md2man/v2 v2.0.0/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU= github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= github.com/creack/pty v1.1.11 h1:07n33Z8lZxZ2qwegKbObQohDhXDQxiMMz1NOUGYlesw= github.com/creack/pty v1.1.11/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= @@ -99,9 +116,10 @@ github.com/deadcheat/goblet v1.3.1/go.mod h1:IrMNyAwyrVgB30HsND2WgleTUM4wHTS9m40 github.com/deadcheat/gonch v0.0.0-20180528124129-c2ff7a019863/go.mod h1:/5mH3gAuXUxGN3maOBAxBfB8RXvP9tBIX5fx2x1k0V0= github.com/dgraph-io/badger/v2 v2.2007.2 h1:EjjK0KqwaFMlPin1ajhP943VPENHJdEz1KLIegjaI3k= github.com/dgraph-io/badger/v2 v2.2007.2/go.mod h1:26P/7fbL4kUZVEVKLAKXkBXKOydDmM2p1e+NhhnBCAE= -github.com/dgraph-io/ristretto v0.0.3-0.20200630154024-f66de99634de h1:t0UHb5vdojIDUqktM6+xJAfScFBsVpXZmqC9dsgJmeA= github.com/dgraph-io/ristretto v0.0.3-0.20200630154024-f66de99634de/go.mod h1:KPxhHT9ZxKefz+PCeOGsrHpl1qZ7i70dGTu2u+Ahh6E= github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ= +github.com/dgraph-io/ristretto v0.1.0 h1:Jv3CGQHp9OjuMBSne1485aDpUkTKEcUqF+jm/LuerPI= +github.com/dgraph-io/ristretto v0.1.0/go.mod h1:fux0lOrBhrVCJd3lcTHsIJhq1T2rokOu6v9Vcb3Q9ug= github.com/dgryski/go-farm v0.0.0-20190423205320-6a90982ecee2 h1:tdlZCpZ/P9DhczCTSixgIKmwPv6+wP5DGjqLYw5SUiA= github.com/dgryski/go-farm v0.0.0-20190423205320-6a90982ecee2/go.mod h1:SqUrOPUnsFjfmXRMNPybcSiG0BgUW2AuFH8PAnS2iTw= github.com/dustin/go-humanize v1.0.0 h1:VSnTsYCnlFHaM2/igO1h6X3HA71jcobQuxemgkq4zYo= @@ -110,6 +128,9 @@ github.com/emicklei/go-restful v2.14.2+incompatible/go.mod h1:otzb+WCGbkyDHkqmQm 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/go-control-plane v0.9.9-0.20210217033140-668b12f5399d/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk= github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4= github.com/fatih/color v1.9.0/go.mod h1:eQcE1qtQxscV5RaZvpXrrb8Drkc3/DdQ+uUYCNjL+zU= @@ -122,6 +143,7 @@ github.com/felixge/fgprof v0.9.1/go.mod h1:7/HK6JFtFaARhIljgP2IV8rJLIoHDoOYoUphs 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/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= github.com/gin-contrib/sse v0.1.0/go.mod h1:RHrZQHXnP2xjPF+u1gW/2HnVO7nvIa9PG3Gm+fLHvGI= github.com/gin-gonic/gin v1.6.3/go.mod h1:75u5sXoLsGZoRN5Sgbi1eraJ4GU3++wFwWzhwvtwp4M= github.com/go-chi/chi v4.1.2+incompatible/go.mod h1:eB3wogJHnLi3x/kFX2A+IbTBlXxmMeXJVKy9tTv1XzQ= @@ -143,9 +165,14 @@ github.com/go-playground/validator/v10 v10.2.0/go.mod h1:uOYAAleCW8F/7oMFd6aG0GO github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0 h1:p104kn46Q8WdvHunIJ9dAyjPVtrBPhSr3KT2yUst43I= github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0/go.mod h1:fyg7847qk6SyHyPtNmDHnmrv/HOrqktSC+C9fM+CJOE= +github.com/gobuffalo/here v0.6.0 h1:hYrd0a6gDmWxBM4TnrGw8mQg24iSVoIkHEk7FodQcBI= +github.com/gobuffalo/here v0.6.0/go.mod h1:wAG085dHOYqUpf+Ap+WOdrPTp5IYcDAs/x7PLa8Y5fM= +github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= +github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= github.com/golang-jwt/jwt v3.2.1+incompatible h1:73Z+4BJcrTC+KczS6WvTPvRGOp1WmfEP4Q1lOd9Z/+c= github.com/golang-jwt/jwt v3.2.1+incompatible/go.mod h1:8pz2t5EyA70fFQQSrl6XZXzqecmYZeUEB8OUGHkxJ+I= +github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b h1:VKtxabqXZkF25pY9ekfRL6a582T4P37/31XEstQ5p58= github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= github.com/golang/groupcache v0.0.0-20191027212112-611e8accdfc9/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= @@ -158,6 +185,7 @@ github.com/golang/mock v1.4.0/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt 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/mock v1.5.0/go.mod h1:CWnOUgYIOo4TcNZ0wHX3YZCqsaM1I1Jvs6v3mP3KVu8= 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= @@ -173,10 +201,12 @@ github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QD 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/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= +github.com/golang/protobuf v1.5.1/go.mod h1:DopwsBzvsk0Fs44TXzsVbJyPhcCPeIwnvohx4u74HPM= github.com/golang/protobuf v1.5.2 h1:ROPKBNFfQgOUMifHyP+KYbvpjbdoFNs+aK7DXlji0Tw= github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= -github.com/golang/snappy v0.0.1 h1:Qgr9rKW7uDUkrbSmQeiDsGa8SjGyCOGtuasMWwvp2P4= github.com/golang/snappy v0.0.1/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= +github.com/golang/snappy v0.0.3 h1:fHPg5GQYlCeLIPB9BZqMVR5nR9A+IM5zcgeTdjMYmLA= +github.com/golang/snappy v0.0.3/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= @@ -186,6 +216,8 @@ github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/ 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.3/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.5 h1:Khx7svrCpmxxtHBq5j2mp/xVjsi8hQMfNLvJFAlrGgU= github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= @@ -194,6 +226,7 @@ github.com/google/go-jsonnet v0.17.0/go.mod h1:sOcuej3UW1vpPTZOr8L7RQimqai1a57bt github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs= github.com/google/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= @@ -201,23 +234,48 @@ github.com/google/pprof v0.0.0-20200212024743-f11f1df84d12/go.mod h1:ZgVRPoUq/hf 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-20200615235658-03e1cf38a040/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= -github.com/google/pprof v0.0.0-20200708004538-1a94d8640e99 h1:Ak8CrdlwwXwAZxzS66vgPt4U8yUZX7JwLvVR58FN5jM= 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-20210122040257-d980be63207e/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= +github.com/google/pprof v0.0.0-20210226084205-cbba55b83ad5 h1:zIaiqGYDQwa4HVx5wGRTXbx38Pqxjemn4BP98wpzpXo= +github.com/google/pprof v0.0.0-20210226084205-cbba55b83ad5/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI= github.com/google/uuid v1.1.2 h1:EVhdT+1Kseyi1/pUmXKaFxYsDNy9RQYkMWRH68J/W7Y= github.com/google/uuid v1.1.2/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/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/mux v1.8.0/go.mod h1:DVbg23sWSpFRCP0SfiEN6jmj59UnW/n46BH5rLB71So= +github.com/grpc-ecosystem/grpc-gateway v1.16.0/go.mod h1:BDjrQk3hbvj6Nolgz8mAMFbcEtjT1g+wF4CSlocrBnw= +github.com/hashicorp/consul/api v1.1.0/go.mod h1:VmuI/Lkw1nC05EYQWNKwWGbkg+FbDBtguAZLlVdkD9Q= +github.com/hashicorp/consul/sdk v0.1.1/go.mod h1:VKf9jXwCTEY1QZP2MOLRhb5i/I/ssyNV1vwHyQBF0x8= +github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= +github.com/hashicorp/go-cleanhttp v0.5.1/go.mod h1:JpRdi6/HCYpAwUzNwuwqhbovhLtngrth3wmdIIUrZ80= +github.com/hashicorp/go-immutable-radix v1.0.0/go.mod h1:0y9vanUI8NX6FsYoO3zeMjhV/C5i9g4Q3DwcSNZ4P60= +github.com/hashicorp/go-msgpack v0.5.3/go.mod h1:ahLV/dePpqEmjfWmKiqvPkv/twdG7iPBM1vqhUKIvfM= +github.com/hashicorp/go-multierror v1.0.0/go.mod h1:dHtQlpGsu+cZNNAkkCN/P3hoUDHhCYQXV3UM06sGGrk= +github.com/hashicorp/go-rootcerts v1.0.0/go.mod h1:K6zTfqpRlCUIjkwsN4Z+hiSfzSTQa6eBIzfwKfwNnHU= +github.com/hashicorp/go-sockaddr v1.0.0/go.mod h1:7Xibr9yA9JjQq1JpNB2Vw7kxv8xerXegt+ozgdvDeDU= +github.com/hashicorp/go-syslog v1.0.0/go.mod h1:qPfqrKkXGihmCqbJM2mZgkZGvKG1dFdvsLplgctolz4= +github.com/hashicorp/go-uuid v1.0.0/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= +github.com/hashicorp/go-uuid v1.0.1/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= +github.com/hashicorp/go.net v0.0.1/go.mod h1:hjKkEWcCURg++eb33jQU7oqQcI9XDCnUzHA0oac0k90= github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= +github.com/hashicorp/hcl v1.0.0 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/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= -github.com/iancoleman/strcase v0.1.2 h1:gnomlvw9tnV3ITTAxzKSgTF+8kFWcU/f+TgttpXGz1U= -github.com/iancoleman/strcase v0.1.2/go.mod h1:SK73tn/9oHe+/Y0h39VT4UCxmurVJkR5NA7kMEAOgSE= +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-20200715173712-053cf528c12f h1:nTaA/z8mev5oJv1dNSbu8Pwvu5CJyBZKwDvHpr9FZ4I= -github.com/ianlancetaylor/demangle v0.0.0-20200715173712-053cf528c12f/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= +github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639 h1:mV02weKRL81bEnm8A0HT1/CAelMQDBuQIfLw8n+d6xI= +github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= github.com/imdario/mergo v0.3.8/go.mod h1:2EnlNZ0deacrJVfApfmtdGgDfMuh/nq6Ok1EcJh5FfA= github.com/imdario/mergo v0.3.11 h1:3tnifQM4i+fbajXKBHXWEH+KvNHqojZ778UH75j3bGA= github.com/imdario/mergo v0.3.11/go.mod h1:jmQim1M+e3UYxmgPu/WyfjB3N3VflVyUjjjwH0dnCYA= @@ -237,16 +295,20 @@ github.com/jsonnet-bundler/jsonnet-bundler v0.4.0 h1:4BKZ6LDqPc2wJDmaKnmYD/vDjUp github.com/jsonnet-bundler/jsonnet-bundler v0.4.0/go.mod h1:/by7P/OoohkI3q4CgSFqcoFsVY+IaNbzOVDknEsKDeU= github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU= github.com/jstemmer/go-junit-report v0.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/XSXhF0NWZEnDohbsk= +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/julienschmidt/httprouter v1.3.0/go.mod h1:JR6WtHb+2LUe8TCKY3cZOxFyyO8IZAc4RVcycCCAKdM= github.com/justinas/alice v1.2.0/go.mod h1:fN5HRH/reO/zrUflLfTN43t3vXvKzvZIENsNEe7i7qA= github.com/kardianos/service v1.2.0 h1:bGuZ/epo3vrt8IPC7mnKQolqFeYJb7Cs8Rk4PSOBB/g= github.com/kardianos/service v1.2.0/go.mod h1:CIMRFEJVL+0DS1a3Nx06NaMn4Dz63Ng6O7dl0qH0zVM= +github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8= github.com/kisielk/godepgraph v0.0.0-20190626013829-57a7e4a651a9 h1:ZkWH0x1yafBo+Y2WdGGdszlJrMreMXWl7/dqpEkwsIk= github.com/kisielk/godepgraph v0.0.0-20190626013829-57a7e4a651a9/go.mod h1:Gb5YEgxqiSSVrXKWQxDcKoCM94NO5QAwOwTaVmIUAMI= github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= github.com/konsorten/go-windows-terminal-sequences v1.0.3/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= +github.com/kr/fs v0.1.0/go.mod h1:FFnZGqtBN9Gxj7eW1uZ42v5BccTP0vu6NEaFoC2HwRg= github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc= github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= github.com/kr/pretty v0.2.0 h1:s5hAObm+yFO5uHYt5dYjxi2rXrsnmRpJx4OYvIWUaQs= @@ -263,6 +325,10 @@ github.com/labstack/echo/v4 v4.1.17/go.mod h1:Tn2yRQL/UclUalpb5rPdXDevbkJ+lp/2sv github.com/labstack/gommon v0.3.0/go.mod h1:MULnywXg0yavhxWKc+lOruYdAhDwPK9wf0OL7NoOu+k= github.com/leodido/go-urn v1.2.0/go.mod h1:+8+nEpDfqqsY+g338gtMEUOtuK+4dEMhiQEgxpxOKII= github.com/magiconair/properties v1.8.0/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ= +github.com/magiconair/properties v1.8.5 h1:b6kJs+EmPFMYGkow9GiUyCyOvIwYetYJ3fSaWak/Gls= +github.com/magiconair/properties v1.8.5/go.mod h1:y3VJvCyxH9uVvJTWEGAELF3aiYNyPKd5NZ3oSwXrF60= +github.com/markbates/pkger v0.17.1 h1:/MKEtWqtc0mZvu9OinB9UzVN9iYCwLWuyUv4Bw+PCno= +github.com/markbates/pkger v0.17.1/go.mod h1:0JoVlrol20BSywW79rN3kdFFsE5xYM+rSCQDXbLhiuI= github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU= github.com/mattn/go-colorable v0.1.2/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE= github.com/mattn/go-colorable v0.1.4/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE= @@ -270,6 +336,7 @@ github.com/mattn/go-colorable v0.1.7/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope github.com/mattn/go-colorable v0.1.8 h1:c1ghPdyEDarC70ftn0y+A/Ee++9zz8ljHG1b13eJ0s8= github.com/mattn/go-colorable v0.1.8/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc= github.com/mattn/go-isatty v0.0.0-20170925054904-a5cdd64afdee/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4= +github.com/mattn/go-isatty v0.0.3/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4= github.com/mattn/go-isatty v0.0.6/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s= github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s= github.com/mattn/go-isatty v0.0.9/go.mod h1:YNRxwqDuOph6SZLI9vUUz6OYw3QyUt7WiY2yME+cCiQ= @@ -287,11 +354,20 @@ github.com/matttproud/golang_protobuf_extensions v1.0.1 h1:4hp9jkHxhMHkqkrB3Ix0j github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0= github.com/mgechev/dots v0.0.0-20190921121421-c36f7dcfbb81 h1:QASJXOGm2RZ5Ardbc86qNFvby9AqkLDibfChMtAg5QM= github.com/mgechev/dots v0.0.0-20190921121421-c36f7dcfbb81/go.mod h1:KQ7+USdGKfpPjXk4Ga+5XxQM4Lm4e3gAogrreFAYpOg= +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-ps v1.0.0 h1:i6ampVEEF4wQFF+bkYfwYgY+F/uYJDktmvLPf7qIgjc= github.com/mitchellh/go-ps v1.0.0/go.mod h1:J4lOc8z8yJs6vUwklHw2XEIiT4z4C40KtWVN3nvg8Pg= +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.4.1 h1:CpVNEelQCZBooIPDn+AR3NpivK/TIKU8bDxdASFVQag= +github.com/mitchellh/mapstructure v1.4.1/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= @@ -314,18 +390,21 @@ github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7J github.com/onsi/gomega v1.10.1/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1ybHNo= github.com/onsi/gomega v1.12.0 h1:p4oGGk2M2UJc0wWN4lHFvIB71lxsh0T/UiKCCgFADY8= github.com/onsi/gomega v1.12.0/go.mod h1:lRk9szgn8TxENtWd0Tp4c3wjlRfMTMH27I+3Je41yGY= +github.com/pascaldekloe/goe v0.0.0-20180627143212-57f6aae5913c/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc= github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic= github.com/pelletier/go-toml v1.6.0/go.mod h1:5N711Q9dKgbdkxHL+MEfF31hpT7l0S0s/t2kKREewys= -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/pelletier/go-toml v1.9.3 h1:zeC5b1GviRUyKYd6OJPvBU/mcVDVoL1OhT17FCt5dSQ= +github.com/pelletier/go-toml v1.9.3/go.mod h1:u1nR/EPcESfeI/szUZKdtJ0xRNbUoANCkoOuaOx1Y+c= github.com/peterbourgon/ff/v3 v3.0.0 h1:eQzEmNahuOjQXfuegsKQTSTDbf4dNvr/eNLrmJhiH7M= github.com/peterbourgon/ff/v3 v3.0.0/go.mod h1:UILIFjRH5a/ar8TjXYLTkIvSvekZqPm5Eb/qbGk6CT0= 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/sftp v1.10.1/go.mod h1:lYOWFsE0bwd1+KfKJaKeuokY15vzFx25BLbzYYoAxZI= 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 v1.0.0/go.mod h1:db9x61etRT2tGnBNRi70OPL5FsnadC4Ky3P0J6CfImo= github.com/prometheus/client_golang v1.2.1/go.mod h1:XMU6Z2MjaRKVu/dC1qupJI9SiNkDYzz3xecMgSW/F+U= @@ -360,12 +439,17 @@ github.com/pyroscope-io/revive v1.0.6-0.20210330033039-4a71146f9dc1/go.mod h1:tS 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 v1.2.0/go.mod h1:jVj6XXZzXRy/MSR5jhDC/2q6DgLz+nrA6LYCDYWNEvQ= github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= github.com/russross/blackfriday v1.5.2/go.mod h1:JO/DiYxRf+HjHt06OyowR9PTA263kcR/rfWxYHBV53g= +github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= +github.com/ryanuber/columnize v0.0.0-20160712163229-9b3edd62028f/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts= +github.com/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/shirou/gopsutil v3.21.4+incompatible h1:fuHcTm5mX+wzo542cmYcV9RTGQLbnHLI5SyQ5ryTVck= github.com/shirou/gopsutil v3.21.4+incompatible/go.mod h1:5b4v6he4MtMOwMlS0TUMTu2PcXUg8+E1lC7eC3UO/RA= +github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc= github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo= github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE= github.com/sirupsen/logrus v1.6.0/go.mod h1:7uNnSEd1DgxDLC74fIahvMZmmYsHGZGEOFrfsX/uA88= @@ -373,26 +457,44 @@ github.com/sirupsen/logrus v1.7.0 h1:ShrD1U9pZB12TX0cVy0DtePoCH97K8EtX+mg7ZARUtM github.com/sirupsen/logrus v1.7.0/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0= github.com/slok/go-http-metrics v0.9.0 h1:o7A7j2DHs7Bnz8aGMotQLele28vf/Cl+O9cwJ6HyGk4= github.com/slok/go-http-metrics v0.9.0/go.mod h1:VCio4Xl8m11JM/0Sl9265RdKyiMypzMo3w1M8xcZGtk= +github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d h1:zE9ykElWQ6/NYmHa3jpm/yHnI4xSofP+UP6SpjHcSeM= +github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc= +github.com/smartystreets/goconvey v1.6.4 h1:fv0U8FUIMPNf1L9lnHLvLhgicrIVChEkdzIKYqbNC9s= +github.com/smartystreets/goconvey v1.6.4/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA= github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA= github.com/spaolacci/murmur3 v1.1.0 h1:7c1g84S4BPRrfL5Xrdp6fOJ206sU9y293DDHaoy0bLI= github.com/spaolacci/murmur3 v1.1.0/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA= github.com/spf13/afero v1.1.2/go.mod h1:j4pytiNVoe2o6bmDsKpLACNPDBIoEAkihy7loJ1B0CQ= +github.com/spf13/afero v1.6.0 h1:xoax2sJ2DT8S8xA2paPFjDCScCNeWsg75VG0DLRreiY= +github.com/spf13/afero v1.6.0/go.mod h1:Ai8FlHk4v/PARR026UzYexafAt9roJ7LcLMAmO6Z93I= github.com/spf13/cast v1.3.0/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE= -github.com/spf13/cobra v0.0.5 h1:f0B+LkLX6DtmRH1isoNA9VTtNUK9K8xYd28JNNfOv/s= +github.com/spf13/cast v1.3.1 h1:nFm6S0SMdyzrzcmThSipiEubIDy8WEXKNZ0UOgiRpng= +github.com/spf13/cast v1.3.1/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE= github.com/spf13/cobra v0.0.5/go.mod h1:3K3wKZymM7VvHMDS9+Akkh4K60UwM26emMESw8tLCHU= +github.com/spf13/cobra v1.2.1 h1:+KmjbUw1hriSNMF55oPrkZcb27aECyrj8V2ytv7kWDw= +github.com/spf13/cobra v1.2.1/go.mod h1:ExllRjgxM/piMAM+3tAZvg8fsklGAf3tPfi+i8t68Nk= github.com/spf13/jwalterweatherman v1.0.0/go.mod h1:cQK4TGJAtQXfYWX+Ddv3mKDzgVb68N+wFjFa4jdeBTo= -github.com/spf13/pflag v1.0.3 h1:zPAT6CGy6wXeQ7NtTnaTerfKOsV6V6F8agHXFiazDkg= +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.3.2/go.mod h1:ZiWeW+zYFKm7srdB9IoDzzZXaJaI5eL9QjNiN/DMA2s= +github.com/spf13/viper v1.8.1 h1:Kq1fyeebqsBfbjZj4EL7gj2IO0mMaiyjYUWcUsl2O44= +github.com/spf13/viper v1.8.1/go.mod h1:o0Pch8wJ9BVSWGQMbra6iw0oQ5oktSIBaujf1rJH9Ns= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/objx v0.1.1 h1:2vfRuCMp5sSVIDSqO8oNnWJq7mPa6KVP3iPIwFBuy8A= github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/testify v1.1.4/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= 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 h1:hDPOHmpOpP40lSULcqw7IrRb/u7w6RpDC9399XyoNd0= github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY= +github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/subosito/gotenv v1.2.0 h1:Slr1R9HxAlEKefgq5jn9U+DnETlIUa6HfgEzj0g5d7s= +github.com/subosito/gotenv v1.2.0/go.mod h1:N0PQaV/YGNqwC0u51sEeR/aUtSLEXKX9iv69rRypqCw= github.com/tklauser/go-sysconf v0.3.6 h1:oc1sJWvKkmvIxhDHeKWvZS4f6AW+YcoguSfRF2/Hmo4= github.com/tklauser/go-sysconf v0.3.6/go.mod h1:MkWzOF4RMCshBAMXuhXJs64Rte09mITnppBXY/rYEFI= github.com/tklauser/numcpus v0.2.2 h1:oyhllyrScuYI6g+h/zUvNXNp1wy7x8qQy3t/piefldA= @@ -416,17 +518,29 @@ github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9de 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.3.5 h1:dPmz1Snjq0kmkz159iL7S6WzdahUTHnHB5M56WFVifs= +github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= +go.etcd.io/etcd/api/v3 v3.5.0/go.mod h1:cbVKeC6lCfl7j/8jBhAK6aIYO9XOjdptoxU/nLQcPvs= +go.etcd.io/etcd/client/pkg/v3 v3.5.0/go.mod h1:IJHfcCEKxYu1Os13ZdwCwIUTUVGYTSAM3YSwc9/Ac1g= +go.etcd.io/etcd/client/v2 v2.305.0/go.mod h1:h9puh54ZTgAKtEbut2oe9P4L/oqKCVB6xsXlzd7alYQ= go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU= go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8= go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= 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= +go.opencensus.io v0.23.0/go.mod h1:XItmlyltB5F7CS4xOC1DcqMoFqwtC6OG2xF7mCv7P7E= +go.uber.org/atomic v1.7.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc= +go.uber.org/multierr v1.6.0/go.mod h1:cdWPpRnG4AhwMwsgIHip0KRBQjJy5kYEpYjJxpXp9iU= +go.uber.org/zap v1.17.0/go.mod h1:MXVU+bhUf/A7Xi2HNOnopQOrmycQ5Ih87HtOu4q5SSo= goji.io v2.0.2+incompatible/go.mod h1:sbqFwrtqZACxLBTQcdgVjFh54yGVCvwq8+w49MVMMIk= golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= +golang.org/x/crypto v0.0.0-20181029021203-45a5f77698d3/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20181203042331-505ab145d0a9/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20190820162420-60c769a6c586/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-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-20200820211705-5c72a883971a/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= @@ -452,6 +566,8 @@ golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHl 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/lint v0.0.0-20210508222113-6edffad5e616/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= @@ -459,13 +575,18 @@ 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 h1:RM4zey1++hCTbCVQfnWeKs9/IEsaBLA8vTkd0WVtmH4= 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.4.2 h1:Gz96sIWK3OalVv/I/qNygP42zyoKp3xptRVCWRFEBvo= +golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/net v0.0.0-20180404174746-b3c676e531a6/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20181023162649-9b4f9f5ad519/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20181201002055-351d144fa1fc/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-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= @@ -493,6 +614,13 @@ golang.org/x/net v0.0.0-20200625001655-4c5254603344/go.mod h1:/O7V0waA8r7cgGh81R 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-20201110031124-69a78807bb2b/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-20210119194325-5f4716e94777/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= +golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= +golang.org/x/net v0.0.0-20210316092652-d523dce5a7f4/go.mod h1:RBQZq4jEuRlivfhVLdyRGr576XBO4/greRjx4P4O3yc= +golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM= golang.org/x/net v0.0.0-20210428140749-89ef3d95e781/go.mod h1:OJAsFXCWl8Ukc7SiCT/9KSuxbyM7479/AVlXFRxuMCk= golang.org/x/net v0.0.0-20210525063256-abc453219eb5 h1:wjuX4b5yYQnEQHzd+CBcrcC6OVR2J1CN6mUy0oSxIPo= golang.org/x/net v0.0.0-20210525063256-abc453219eb5/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= @@ -501,6 +629,13 @@ golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4Iltr 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/oauth2 v0.0.0-20210220000619-9bb904979d93/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= +golang.org/x/oauth2 v0.0.0-20210313182246-cd4f82c27b84/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= +golang.org/x/oauth2 v0.0.0-20210402161424-2e8d93401602/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= golang.org/x/oauth2 v0.0.0-20210514164344-f6687ab2804c h1:pkQiBZBvdos9qq4wBAHqlzuZHEXo07pqV06ef90u1WI= golang.org/x/oauth2 v0.0.0-20210514164344-f6687ab2804c/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= @@ -512,12 +647,15 @@ golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJ 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 h1:DcqTD9SDLc+1P/r1EmRBwnVsrOwW+kk2vWf9n+1sGhs= golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20210220032951-036812b2e83c h1:5KslGYwFpkhGh+Q16bwMP3cOontH8FOep7tGV86Y7SQ= +golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sys v0.0.0-20170927054621-314a259e304f/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20180823144017-11551d06cbcc/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20181026203630-95b1ffbd15a5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20181205085412-a5c9d58dba9a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= @@ -563,14 +701,24 @@ golang.org/x/sys v0.0.0-20200615200032-f1bc736245b1/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20200625212154-ddb9806d33ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200803210538-64077c9b5642/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200826173525-f9321e4c35a6/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-20201015000850-e3ed0017c211/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-20210112080510-489259a85091/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-20210220050731-9a76102bfb43/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210305230114-8fe3ee5dd75b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210315160823-c6e025ad8005/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210316164454-77fc1eacc6aa/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210320140829-1e4c9ba3b0c4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210403161142-5e06dd20ab57/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210603081109-ebe580a85c40/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210616094352-59db8d763f22 h1:RqytpXGR1iVNX7psjB3ff8y7sNFinVFvkx1c8SjBkio= golang.org/x/sys v0.0.0-20210616094352-59db8d763f22/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= @@ -580,6 +728,8 @@ 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.5/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.6 h1:aRYxNxv6iGQlyVaZmk6ZgYEDa+Jg18DxebPSrd6bg1M= golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= @@ -591,6 +741,7 @@ golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3 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= @@ -600,6 +751,7 @@ golang.org/x/tools v0.0.0-20190628153133-6cdbf07be9d0/go.mod h1:/rFqwRUd4F7ZHNgw 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= @@ -623,12 +775,20 @@ golang.org/x/tools v0.0.0-20200501065659-ab2804fb9c9d/go.mod h1:EkVYQZoAsY45+roY 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-20200619180055-7c47624df98f/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-20201224043029-2b0845dc783e/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= -golang.org/x/tools v0.1.0 h1:po9/4sTYwZU9lPhi1tOrb4hCv3qrhiQ77LZfGa2OjwY= +golang.org/x/tools v0.0.0-20210105154028-b0ab187a4818/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= +golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= golang.org/x/tools v0.1.0/go.mod h1:xkSsbof2nBLbhDlRMhhhyNLN/zl3eTqcnHD5viDpcZ0= +golang.org/x/tools v0.1.2 h1:kRBLX7v7Af8W7Gdbbc908OJcdgtK8bOz9Uaj8/F1ACA= +golang.org/x/tools v0.1.2/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= 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= @@ -650,13 +810,20 @@ google.golang.org/api v0.24.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0M 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/api v0.41.0/go.mod h1:RkxM5lITDfTzmyKFPt+wGrCJbVfniCr2ool8kTBzRTU= +google.golang.org/api v0.43.0/go.mod h1:nQsDGjRXMo4lvh5hP0TKqF244gqhGcr/YSIykhUk/94= +google.golang.org/api v0.44.0/go.mod h1:EBOGZqzyhtvMDoxwS97ctnh0zUmYY6CxqXsc1AvkYD8= 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 h1:lMO5rYAqUxkmaj76jAkRUvt5JZgFymx/+Q5Mzfivuhc= google.golang.org/appengine v1.6.6/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= +google.golang.org/appengine v1.6.7 h1:FZR1q0exgwxzPzp/aF+VccGrSfxfPpkBqjIIEq3ru6c= +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= @@ -680,12 +847,24 @@ google.golang.org/genproto v0.0.0-20200312145019-da6875a35672/go.mod h1:55QSHmfG 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-20200513103714-09dca8ec2884/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-20210222152913-aa3ee6e6a81c/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20210303154014-9728d6b83eeb/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20210310155132-4ce2db91004e/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20210319143718-93e7006c17a6/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20210402141018-6c239bbf2bb1/go.mod h1:9lPAdzaEmUacj36I+k7YKbEc5CXzPIeORRgDAUOu28A= +google.golang.org/genproto v0.0.0-20210602131652-f16073e35f0c/go.mod h1:UODoCrxHCcBojKKwX1terBiRUaqAsFqJiF615XL43r0= 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= @@ -698,6 +877,14 @@ google.golang.org/grpc v1.28.0/go.mod h1:rpkK4SK4GF4Ach/+MFLZUBavHOvF2JJB5uozKKa 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.1/go.mod h1:fr5YgcSWrqhRRxogOsw7RzIpsmvOZ6IcH4kBYTpR3n0= +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/grpc v1.36.0/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU= +google.golang.org/grpc v1.36.1/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU= +google.golang.org/grpc v1.38.0/go.mod h1:NREThFqKR1f3iQ6oBuvc5LadQuXVGo9rkm5ZGrQdJfM= 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= @@ -719,19 +906,24 @@ gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 h1:YR8cESwS4TdDjEe65xsg0ogR gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys= +gopkg.in/ini.v1 v1.62.0 h1:duBzk771uxoUuOlyRLkHsygud9+5lrlGjdFBb4mSKDU= +gopkg.in/ini.v1 v1.62.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ= gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw= gopkg.in/yaml.v2 v2.0.0-20170812160011-eb3733d160e7/go.mod h1:JAlM8MvJe8wmxCU4Bli9HhUf9+ttbYbLASfIpnQbh74= gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.2.3/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.5/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.2.7/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.3.0/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 h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b h1:h8qDotaEPuJATrMmW04NCwg7v22aHH28wwpauUhK9Oo= +gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/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= diff --git a/pkg/cli/agent_unix.go b/pkg/cli/agent_unix.go index 2a87fe0ff1..2db2a38f4a 100644 --- a/pkg/cli/agent_unix.go +++ b/pkg/cli/agent_unix.go @@ -8,6 +8,6 @@ import ( "github.com/pyroscope-io/pyroscope/pkg/config" ) -func startAgent(_ *config.Agent) error { +func StartAgent(_ *config.Agent) error { return fmt.Errorf("agent mode is supported only on Windows") } diff --git a/pkg/cli/agent_windows.go b/pkg/cli/agent_windows.go index 269bf36d3a..16ab819bfe 100644 --- a/pkg/cli/agent_windows.go +++ b/pkg/cli/agent_windows.go @@ -7,15 +7,15 @@ import ( "github.com/pyroscope-io/pyroscope/pkg/config" ) -func startAgent(config *config.Agent) error { +func StartAgent(config *config.Agent) error { logger, err := createLogger(config) if err != nil { return fmt.Errorf("could not create logger: %w", err) } - logger.Info("starting pyroscope agent") if err = loadAgentConfig(config); err != nil { return fmt.Errorf("could not load targets: %w", err) } + logger.Info("starting pyroscope agent") agent, err := newAgentService(logger, config) if err != nil { return fmt.Errorf("could not initialize agent: %w", err) diff --git a/pkg/cli/cli.go b/pkg/cli/cli.go deleted file mode 100644 index 2d68a697f6..0000000000 --- a/pkg/cli/cli.go +++ /dev/null @@ -1,207 +0,0 @@ -package cli - -import ( - "context" - "flag" - "fmt" - "os" - goexec "os/exec" - "runtime" - - "github.com/peterbourgon/ff/v3" - "github.com/peterbourgon/ff/v3/ffcli" - "github.com/sirupsen/logrus" - - "github.com/pyroscope-io/pyroscope/pkg/build" - "github.com/pyroscope-io/pyroscope/pkg/config" - "github.com/pyroscope-io/pyroscope/pkg/convert" - "github.com/pyroscope-io/pyroscope/pkg/dbmanager" - "github.com/pyroscope-io/pyroscope/pkg/exec" -) - -func generateRootCmd(cfg *config.Config) *ffcli.Command { - // init the log formatter for logrus - logrus.SetReportCaller(true) - logrus.SetFormatter(&logrus.TextFormatter{ - TimestampFormat: "2006-01-02T15:04:05.000000", - FullTimestamp: true, - CallerPrettyfier: func(f *runtime.Frame) (string, string) { - filename := f.File - if len(filename) > 38 { - filename = filename[38:] - } - return "", fmt.Sprintf(" %s:%d", filename, f.Line) - }, - }) - - var ( - serverFlagSet = flag.NewFlagSet("pyroscope server", flag.ExitOnError) - agentFlagSet = flag.NewFlagSet("pyroscope agent", flag.ExitOnError) - convertFlagSet = flag.NewFlagSet("pyroscope convert", flag.ExitOnError) - execFlagSet = flag.NewFlagSet("pyroscope exec", flag.ExitOnError) - connectFlagSet = flag.NewFlagSet("pyroscope connect", flag.ExitOnError) - dbmanagerFlagSet = flag.NewFlagSet("pyroscope dbmanager", flag.ExitOnError) - rootFlagSet = flag.NewFlagSet("pyroscope", flag.ExitOnError) - ) - - serverSortedFlags := PopulateFlagSet(&cfg.Server, serverFlagSet, WithSkip("metric-export-rules")) - agentSortedFlags := PopulateFlagSet(&cfg.Agent, agentFlagSet, WithSkip("targets")) - convertSortedFlags := PopulateFlagSet(&cfg.Convert, convertFlagSet) - execSortedFlags := PopulateFlagSet(&cfg.Exec, execFlagSet, WithSkip("pid")) - connectSortedFlags := PopulateFlagSet(&cfg.Exec, connectFlagSet, WithSkip("group-name", "user-name", "no-root-drop")) - dbmanagerSortedFlags := PopulateFlagSet(&cfg.DbManager, dbmanagerFlagSet) - rootSortedFlags := PopulateFlagSet(cfg, rootFlagSet) - - options := []ff.Option{ - ff.WithConfigFileParser(parser), - ff.WithEnvVarPrefix("PYROSCOPE"), - ff.WithAllowMissingConfigFile(true), - ff.WithConfigFileFlag("config"), - } - - serverCmd := &ffcli.Command{ - UsageFunc: serverSortedFlags.printUsage, - Options: append(options, ff.WithIgnoreUndefined(true)), - Name: "server", - ShortUsage: "pyroscope server [flags]", - ShortHelp: "starts pyroscope server. This is the database + web-based user interface", - FlagSet: serverFlagSet, - } - - agentCmd := &ffcli.Command{ - UsageFunc: agentSortedFlags.printUsage, - Options: append(options, ff.WithIgnoreUndefined(true)), - Name: "agent", - ShortUsage: "pyroscope agent [flags]", - ShortHelp: "starts pyroscope agent.", - FlagSet: agentFlagSet, - } - - convertCmd := &ffcli.Command{ - UsageFunc: convertSortedFlags.printUsage, - Options: options, - Name: "convert", - ShortUsage: "pyroscope convert [flags] ", - ShortHelp: "converts between different profiling formats", - FlagSet: convertFlagSet, - } - - execCmd := &ffcli.Command{ - UsageFunc: execSortedFlags.printUsage, - Options: options, - Name: "exec", - ShortUsage: "pyroscope exec [flags] ", - ShortHelp: "starts a new process from and profiles it", - FlagSet: execFlagSet, - } - - connectCmd := &ffcli.Command{ - UsageFunc: connectSortedFlags.printUsage, - Options: options, - Name: "connect", - ShortUsage: "pyroscope connect [flags]", - ShortHelp: "connects to an existing process and profiles it", - FlagSet: connectFlagSet, - } - - dbmanagerCmd := &ffcli.Command{ - UsageFunc: dbmanagerSortedFlags.printUsage, - Options: options, - Name: "dbmanager", - ShortUsage: "pyroscope dbmanager [flags] ", - ShortHelp: "tools for managing database", - FlagSet: dbmanagerFlagSet, - } - - serverCmd.Exec = func(ctx context.Context, args []string) error { - return startServer(&cfg.Server) - } - - agentCmd.Exec = func(ctx context.Context, args []string) error { - return startAgent(&cfg.Agent) - } - - convertCmd.Exec = func(ctx context.Context, args []string) error { - logrus.SetOutput(os.Stderr) - logger := func(s string) { - logrus.Fatal(s) - } - return convert.Cli(&cfg.Convert, logger, args) - } - - execCmd.Exec = func(_ context.Context, args []string) error { - if cfg.Exec.NoLogging { - logrus.SetLevel(logrus.PanicLevel) - } else if l, err := logrus.ParseLevel(cfg.Exec.LogLevel); err == nil { - logrus.SetLevel(l) - } - if len(args) == 0 || args[0] == "help" { - fmt.Println(gradientBanner()) - fmt.Println(DefaultUsageFunc(execSortedFlags, execCmd, []string{})) - return nil - } - err := exec.Cli(&cfg.Exec, args) - // Normally, if the program ran, the call should return ExitError and - // the exit code must be preserved. Otherwise, the error originates from - // pyroscope and will be printed. - if e, ok := err.(*goexec.ExitError); ok { - os.Exit(e.ExitCode()) - } - - return err - } - - connectCmd.Exec = func(ctx context.Context, args []string) error { - if cfg.Exec.NoLogging { - logrus.SetLevel(logrus.PanicLevel) - } else if l, err := logrus.ParseLevel(cfg.Exec.LogLevel); err == nil { - logrus.SetLevel(l) - } - if len(args) > 0 && args[0] == "help" { - fmt.Println(gradientBanner()) - fmt.Println(DefaultUsageFunc(connectSortedFlags, connectCmd, []string{})) - return nil - } - return exec.Cli(&cfg.Exec, args) - } - - dbmanagerCmd.Exec = func(ctx context.Context, args []string) error { - if l, err := logrus.ParseLevel(cfg.DbManager.LogLevel); err == nil { - logrus.SetLevel(l) - } - return dbmanager.Cli(&cfg.DbManager, &cfg.Server, args) - } - - rootCmd := &ffcli.Command{ - UsageFunc: rootSortedFlags.printUsage, - Options: options, - ShortUsage: "pyroscope [flags] ", - FlagSet: rootFlagSet, - Subcommands: []*ffcli.Command{ - convertCmd, - serverCmd, - agentCmd, - execCmd, - connectCmd, - dbmanagerCmd, - }, - } - - rootCmd.Exec = func(ctx context.Context, args []string) error { - if cfg.Version || len(args) > 0 && args[0] == "version" { - fmt.Println(gradientBanner()) - fmt.Println(build.Summary()) - fmt.Println("") - } else { - fmt.Println(gradientBanner()) - fmt.Println(DefaultUsageFunc(rootSortedFlags, rootCmd, []string{})) - } - return nil - } - - return rootCmd -} - -func Start(cfg *config.Config) error { - return generateRootCmd(cfg).ParseAndRun(context.Background(), os.Args[1:]) -} diff --git a/pkg/cli/flags.go b/pkg/cli/flags.go index 20d9243906..2d242973e3 100644 --- a/pkg/cli/flags.go +++ b/pkg/cli/flags.go @@ -1,7 +1,6 @@ package cli import ( - "flag" "fmt" "reflect" "strconv" @@ -9,12 +8,13 @@ import ( "time" "github.com/iancoleman/strcase" - "github.com/sirupsen/logrus" - "github.com/pyroscope-io/pyroscope/pkg/agent/spy" "github.com/pyroscope-io/pyroscope/pkg/util/bytesize" "github.com/pyroscope-io/pyroscope/pkg/util/duration" "github.com/pyroscope-io/pyroscope/pkg/util/slices" + "github.com/sirupsen/logrus" + "github.com/spf13/pflag" + "github.com/spf13/viper" ) const timeFormat = "2006-01-02T15:04:05Z0700" @@ -33,6 +33,11 @@ func (i *arrayFlags) Set(value string) error { return nil } +func (i *arrayFlags) Type() string { + t := reflect.TypeOf([]string{}) + return t.String() +} + type timeFlag time.Time func (tf *timeFlag) String() string { @@ -58,6 +63,12 @@ func (tf *timeFlag) Set(value string) error { return nil } +func (tf *timeFlag) Type() string { + v := time.Time(*tf) + t := reflect.TypeOf(v) + return t.String() +} + type mapFlags map[string]string func (m mapFlags) String() string { @@ -84,6 +95,11 @@ func (m *mapFlags) Set(s string) error { return nil } +func (m *mapFlags) Type() string { + t := reflect.TypeOf(map[string]string{}) + return t.String() +} + type options struct { replacements map[string]string skip []string @@ -131,10 +147,39 @@ func (df *durFlag) Set(value string) error { return nil } -func PopulateFlagSet(obj interface{}, flagSet *flag.FlagSet, opts ...FlagOption) *SortedFlags { +func (df *durFlag) Type() string { + v := time.Duration(*df) + t := reflect.TypeOf(v) + return t.String() +} + +type byteSizeFlag bytesize.ByteSize + +func (bs *byteSizeFlag) String() string { + v := bytesize.ByteSize(*bs) + return v.String() +} + +func (bs *byteSizeFlag) Set(value string) error { + d, err := bytesize.Parse(value) + if err != nil { + return err + } + + *bs = byteSizeFlag(d) + + return nil +} + +func (bs *byteSizeFlag) Type() string { + v := bytesize.ByteSize(*bs) + t := reflect.TypeOf(v) + return t.String() +} + +func PopulateFlagSet(obj interface{}, flagSet *pflag.FlagSet, vpr *viper.Viper, opts ...FlagOption) *pflag.FlagSet { v := reflect.ValueOf(obj).Elem() t := reflect.TypeOf(v.Interface()) - num := t.NumField() o := &options{ replacements: map[string]string{ @@ -148,11 +193,17 @@ func PopulateFlagSet(obj interface{}, flagSet *flag.FlagSet, opts ...FlagOption) option(o) } - deprecatedFields := []string{} + visitFields(flagSet, vpr, "", t, v, o) + + return flagSet +} +func visitFields(flagSet *pflag.FlagSet, vpr *viper.Viper, prefix string, t reflect.Type, v reflect.Value, o *options) { + num := t.NumField() for i := 0; i < num; i++ { field := t.Field(i) fieldV := v.Field(i) + if !(fieldV.IsValid() && fieldV.CanSet()) { continue } @@ -165,15 +216,12 @@ func PopulateFlagSet(obj interface{}, flagSet *flag.FlagSet, opts ...FlagOption) if nameVal == "" { nameVal = strcase.ToKebab(field.Name) } - if skipVal == "true" || slices.StringContains(o.skip, nameVal) { - continue + if prefix != "" { + nameVal = prefix + "." + nameVal } - if deprecatedVal == "true" { - deprecatedFields = append(deprecatedFields, nameVal) - if o.skipDeprecated { - continue - } + if skipVal == "true" || slices.StringContains(o.skip, nameVal) { + continue } for old, n := range o.replacements { @@ -190,23 +238,31 @@ func PopulateFlagSet(obj interface{}, flagSet *flag.FlagSet, opts ...FlagOption) val := fieldV.Addr().Interface().(*[]string) val2 := (*arrayFlags)(val) flagSet.Var(val2, nameVal, descVal) + // setting empty defaults to allow vpr.Unmarshal to recognize this field + vpr.SetDefault(nameVal, []string{}) case reflect.TypeOf(map[string]string{}): val := fieldV.Addr().Interface().(*map[string]string) val2 := (*mapFlags)(val) flagSet.Var(val2, nameVal, descVal) + // setting empty defaults to allow vpr.Unmarshal to recognize this field + vpr.SetDefault(nameVal, map[string]string{}) case reflect.TypeOf(""): val := fieldV.Addr().Interface().(*string) for old, n := range o.replacements { defaultValStr = strings.ReplaceAll(defaultValStr, old, n) } flagSet.StringVar(val, nameVal, defaultValStr, descVal) + vpr.SetDefault(nameVal, defaultValStr) case reflect.TypeOf(true): val := fieldV.Addr().Interface().(*bool) flagSet.BoolVar(val, nameVal, defaultValStr == "true", descVal) + vpr.SetDefault(nameVal, defaultValStr == "true") case reflect.TypeOf(time.Time{}): valTime := fieldV.Addr().Interface().(*time.Time) val := (*timeFlag)(valTime) flagSet.Var(val, nameVal, descVal) + // setting empty defaults to allow vpr.Unmarshal to recognize this field + vpr.SetDefault(nameVal, time.Time{}) case reflect.TypeOf(time.Second): valDur := fieldV.Addr().Interface().(*time.Duration) val := (*durFlag)(valDur) @@ -222,8 +278,10 @@ func PopulateFlagSet(obj interface{}, flagSet *flag.FlagSet, opts ...FlagOption) *val = (durFlag)(defaultVal) flagSet.Var(val, nameVal, descVal) + vpr.SetDefault(nameVal, defaultVal) case reflect.TypeOf(bytesize.Byte): - val := fieldV.Addr().Interface().(*bytesize.ByteSize) + valByteSize := fieldV.Addr().Interface().(*bytesize.ByteSize) + val := (*byteSizeFlag)(valByteSize) var defaultVal bytesize.ByteSize if defaultValStr != "" { var err error @@ -232,8 +290,10 @@ func PopulateFlagSet(obj interface{}, flagSet *flag.FlagSet, opts ...FlagOption) logrus.Fatalf("invalid default value: %q (%s)", defaultValStr, nameVal) } } - *val = defaultVal + + *val = (byteSizeFlag)(defaultVal) flagSet.Var(val, nameVal, descVal) + vpr.SetDefault(nameVal, defaultVal) case reflect.TypeOf(1): val := fieldV.Addr().Interface().(*int) var defaultVal int @@ -247,6 +307,7 @@ func PopulateFlagSet(obj interface{}, flagSet *flag.FlagSet, opts ...FlagOption) } } flagSet.IntVar(val, nameVal, defaultVal, descVal) + vpr.SetDefault(nameVal, defaultVal) case reflect.TypeOf(1.00): val := fieldV.Addr().Interface().(*float64) var defaultVal float64 @@ -260,6 +321,7 @@ func PopulateFlagSet(obj interface{}, flagSet *flag.FlagSet, opts ...FlagOption) } } flagSet.Float64Var(val, nameVal, defaultVal, descVal) + vpr.SetDefault(nameVal, defaultVal) case reflect.TypeOf(uint64(1)): val := fieldV.Addr().Interface().(*uint64) var defaultVal uint64 @@ -273,6 +335,7 @@ func PopulateFlagSet(obj interface{}, flagSet *flag.FlagSet, opts ...FlagOption) } } flagSet.Uint64Var(val, nameVal, defaultVal, descVal) + vpr.SetDefault(nameVal, defaultVal) case reflect.TypeOf(uint(1)): val := fieldV.Addr().Interface().(*uint) var defaultVal uint @@ -286,12 +349,22 @@ func PopulateFlagSet(obj interface{}, flagSet *flag.FlagSet, opts ...FlagOption) defaultVal = uint(out) } flagSet.UintVar(val, nameVal, defaultVal, descVal) + vpr.SetDefault(nameVal, defaultVal) default: + if field.Type.Kind() == reflect.Struct { + visitFields(flagSet, vpr, nameVal, field.Type, fieldV, o) + continue + } + // A stub for unknown types. This is required for generated configs and // documentation (when a parameter can not be set via flag but present // in the configuration). Empty value is shown as '{}'. flagSet.Var(new(mapFlags), nameVal, descVal) } + + if deprecatedVal == "true" { + // TODO: We could specify which flag to use instead but would add code complexity + flagSet.MarkDeprecated(nameVal, "repalce this flag as it will be removed in future versions") + } } - return NewSortedFlags(obj, flagSet, deprecatedFields) } diff --git a/pkg/cli/flags_test.go b/pkg/cli/flags_test.go index 1c8b960308..9d51fb6621 100644 --- a/pkg/cli/flags_test.go +++ b/pkg/cli/flags_test.go @@ -1,56 +1,87 @@ package cli import ( - "context" - "flag" + "bytes" + "fmt" + "os" + "reflect" "time" + "github.com/mitchellh/mapstructure" . "github.com/onsi/ginkgo" . "github.com/onsi/gomega" - "github.com/peterbourgon/ff/v3" - "github.com/peterbourgon/ff/v3/ffcli" + "github.com/spf13/cobra" + "github.com/spf13/viper" "github.com/pyroscope-io/pyroscope/pkg/config" "github.com/pyroscope-io/pyroscope/pkg/util/bytesize" ) type FlagsStruct struct { - Config string - Foo string - Foos []string - Bar int - Baz time.Duration - FooBar string - FooFoo float64 - FooBytes bytesize.ByteSize + Config string `mapstructure:"config"` + Foo string `mapstructure:"foo"` + Foos []string `mapstructure:"foos"` + Bar int `mapstructure:"bar"` + Baz time.Duration `mapstructure:"baz"` + FooBar string `mapstructure:"foo-bar"` + FooFoo float64 `mapstructure:"foo-foo"` + FooBytes bytesize.ByteSize `mapstructure:"foo-bytes"` + FooDur time.Duration `mapstructure:"foo-dur"` +} + +func viperUnmarshalWithBytesHook(vpr *viper.Viper, cfg interface{}) error { + return vpr.Unmarshal(cfg, viper.DecodeHook( + mapstructure.ComposeDecodeHookFunc( + // Function to add a special type for «env. mode» + func(f reflect.Type, t reflect.Type, data interface{}) (interface{}, error) { + if t != reflect.TypeOf(bytesize.Byte) { + return data, nil + } + + stringData, ok := data.(string) + if !ok { + return data, nil + } + + return bytesize.Parse(stringData) + }, + // Function to support net.IP + mapstructure.StringToIPHookFunc(), + // Appended by the two default functions + mapstructure.StringToTimeDurationHookFunc(), + mapstructure.StringToSliceHookFunc(","), + ), + )) } var _ = Describe("flags", func() { Context("PopulateFlagSet", func() { Context("without config file", func() { It("correctly sets all types of arguments", func() { - exampleFlagSet := flag.NewFlagSet("example flag set", flag.ExitOnError) - cfg := FlagsStruct{} - PopulateFlagSet(&cfg, exampleFlagSet) - - exampleCommand := &ffcli.Command{ - FlagSet: exampleFlagSet, - Exec: func(_ context.Context, args []string) error { + vpr := viper.New() + exampleCommand := &cobra.Command{ + RunE: func(cmd *cobra.Command, args []string) error { return nil }, } - err := exampleCommand.ParseAndRun(context.Background(), []string{ - "-foo", "test-val-1", - "-foos", "test-val-2", - "-foos", "test-val-3", - "-bar", "123", - "-baz", "10h", - "-foo-bar", "test-val-4", - "-foo-foo", "10.23", - "-foo-bytes", "100MB", + cfg := FlagsStruct{} + PopulateFlagSet(&cfg, exampleCommand.Flags(), vpr) + + b := bytes.NewBufferString("") + exampleCommand.SetOut(b) + exampleCommand.SetArgs([]string{ + fmt.Sprintf("--foo=%s", "test-val-1"), + fmt.Sprintf("--foos=%s", "test-val-2"), + fmt.Sprintf("--foos=%s", "test-val-3"), + fmt.Sprintf("--bar=%s", "123"), + fmt.Sprintf("--baz=%s", "10h"), + fmt.Sprintf("--foo-bar=%s", "test-val-4"), + fmt.Sprintf("--foo-foo=%s", "10.23"), + fmt.Sprintf("--foo-bytes=%s", "100MB"), }) + err := exampleCommand.Execute() Expect(err).ToNot(HaveOccurred()) Expect(cfg.Foo).To(Equal("test-val-1")) Expect(cfg.Foos).To(Equal([]string{"test-val-2", "test-val-3"})) @@ -64,25 +95,38 @@ var _ = Describe("flags", func() { Context("with config file", func() { It("correctly sets all types of arguments", func() { - exampleFlagSet := flag.NewFlagSet("example flag set", flag.ExitOnError) cfg := FlagsStruct{} - PopulateFlagSet(&cfg, exampleFlagSet) + vpr := viper.New() + exampleCommand := &cobra.Command{ + RunE: func(cmd *cobra.Command, args []string) error { + if cfg.Config != "" { + // Use config file from the flag. + vpr.SetConfigFile(cfg.Config) + + // If a config file is found, read it in. + if err := vpr.ReadInConfig(); err == nil { + fmt.Fprintln(os.Stderr, "Using config file:", vpr.ConfigFileUsed()) + } + + if err := viperUnmarshalWithBytesHook(vpr, &cfg); err != nil { + fmt.Fprintln(os.Stderr, "Unable to unmarshal:", err) + } + + fmt.Printf("configuration is %+v \n", cfg) + } - exampleCommand := &ffcli.Command{ - FlagSet: exampleFlagSet, - Options: []ff.Option{ - ff.WithConfigFileParser(parser), - ff.WithConfigFileFlag("config"), - }, - Exec: func(_ context.Context, args []string) error { return nil }, } - err := exampleCommand.ParseAndRun(context.Background(), []string{ - "-config", "testdata/example.yml", - }) + PopulateFlagSet(&cfg, exampleCommand.Flags(), vpr) + vpr.BindPFlags(exampleCommand.Flags()) + + b := bytes.NewBufferString("") + exampleCommand.SetOut(b) + exampleCommand.SetArgs([]string{fmt.Sprintf("--config=%s", "testdata/example.yml")}) + err := exampleCommand.Execute() Expect(err).ToNot(HaveOccurred()) Expect(cfg.Foo).To(Equal("test-val-1")) Expect(cfg.Foos).To(Equal([]string{"test-val-2", "test-val-3"})) @@ -91,213 +135,148 @@ var _ = Describe("flags", func() { Expect(cfg.FooBar).To(Equal("test-val-4")) Expect(cfg.FooFoo).To(Equal(10.23)) Expect(cfg.FooBytes).To(Equal(100 * bytesize.MB)) + Expect(cfg.FooDur).To(Equal(5*time.Minute + 23*time.Second)) }) It("arguments take precedence", func() { - exampleFlagSet := flag.NewFlagSet("example flag set", flag.ExitOnError) cfg := FlagsStruct{} - PopulateFlagSet(&cfg, exampleFlagSet) + vpr := viper.New() + exampleCommand := &cobra.Command{ + RunE: func(cmd *cobra.Command, args []string) error { + if cfg.Config != "" { + // Use config file from the flag. + vpr.SetConfigFile(cfg.Config) + + // If a config file is found, read it in. + if err := vpr.ReadInConfig(); err == nil { + fmt.Fprintln(os.Stderr, "Using config file:", vpr.ConfigFileUsed()) + } + + if err := viperUnmarshalWithBytesHook(vpr, &cfg); err != nil { + fmt.Fprintln(os.Stderr, "Unable to unmarshal:", err) + } + + fmt.Printf("configuration is %+v \n", cfg) + } - exampleCommand := &ffcli.Command{ - FlagSet: exampleFlagSet, - Options: []ff.Option{ - ff.WithConfigFileParser(parser), - ff.WithConfigFileFlag("config"), - }, - Exec: func(_ context.Context, args []string) error { return nil }, } - err := exampleCommand.ParseAndRun(context.Background(), []string{ - "-config", "testdata/example.yml", - "-foo", "test-val-4", + PopulateFlagSet(&cfg, exampleCommand.Flags(), vpr) + vpr.BindPFlags(exampleCommand.Flags()) + + b := bytes.NewBufferString("") + exampleCommand.SetOut(b) + exampleCommand.SetArgs([]string{ + fmt.Sprintf("--config=%s", "testdata/example.yml"), + fmt.Sprintf("--foo=%s", "test-val-4"), + fmt.Sprintf("--foo-dur=%s", "3h"), }) + err := exampleCommand.Execute() Expect(err).ToNot(HaveOccurred()) Expect(cfg.Foo).To(Equal("test-val-4")) + Expect(cfg.FooDur).To(Equal(3 * time.Hour)) }) - It("server configuration", func() { - exampleFlagSet := flag.NewFlagSet("example flag set", flag.ExitOnError) var cfg config.Server - PopulateFlagSet(&cfg, exampleFlagSet) - - exampleCommand := &ffcli.Command{ - FlagSet: exampleFlagSet, - Options: []ff.Option{ - ff.WithIgnoreUndefined(true), - ff.WithConfigFileParser(parser), - ff.WithConfigFileFlag("config"), - }, - Exec: func(_ context.Context, args []string) error { - return nil - }, - } + vpr := viper.New() + exampleCommand := &cobra.Command{ + RunE: func(cmd *cobra.Command, args []string) error { + if cfg.Config != "" { + // Use config file from the flag. + vpr.SetConfigFile(cfg.Config) - err := exampleCommand.ParseAndRun(context.Background(), []string{ - "-config", "testdata/server.yml", - }) + // If a config file is found, read it in. + if err := vpr.ReadInConfig(); err == nil { + fmt.Fprintln(os.Stderr, "Using config file:", vpr.ConfigFileUsed()) + } - Expect(err).ToNot(HaveOccurred()) - Expect(cfg).To(Equal(config.Server{ - AnalyticsOptOut: false, - Config: "testdata/server.yml", - LogLevel: "info", - BadgerLogLevel: "error", - StoragePath: "/var/lib/pyroscope", - APIBindAddr: ":4040", - BaseURL: "", - CacheEvictThreshold: 0.25, - CacheEvictVolume: 0.33, - BadgerNoTruncate: false, - DisablePprofEndpoint: false, - MaxNodesSerialization: 2048, - MaxNodesRender: 8192, - HideApplications: nil, - Retention: 0, - SampleRate: 0, - OutOfSpaceThreshold: 0, - CacheDimensionSize: 0, - CacheDictionarySize: 0, - CacheSegmentSize: 0, - CacheTreeSize: 0, - GoogleEnabled: false, - GoogleClientID: "", - GoogleClientSecret: "", - GoogleRedirectURL: "", - GoogleAuthURL: "https://accounts.google.com/o/oauth2/auth", - GoogleTokenURL: "https://accounts.google.com/o/oauth2/token", - GitlabEnabled: false, - GitlabApplicationID: "", - GitlabClientSecret: "", - GitlabRedirectURL: "", - GitlabAuthURL: "https://gitlab.com/oauth/authorize", - GitlabTokenURL: "https://gitlab.com/oauth/token", - GitlabAPIURL: "https://gitlab.com/api/v4/user", - GithubEnabled: false, - GithubClientID: "", - GithubClientSecret: "", - GithubRedirectURL: "", - GithubAuthURL: "https://github.com/login/oauth/authorize", - GithubTokenURL: "https://github.com/login/oauth/access_token", - JWTSecret: "", - LoginMaximumLifetimeDays: 0, - MetricExportRules: nil, - })) + if err := viperUnmarshalWithBytesHook(vpr, &cfg); err != nil { + fmt.Fprintln(os.Stderr, "Unable to unmarshal:", err) + } - Expect(loadServerConfig(&cfg)).ToNot(HaveOccurred()) - Expect(cfg.MetricExportRules).To(Equal(config.MetricExportRules{ - "my_metric_name": { - Expr: `app.name{foo=~"bar"}`, - Node: "a;b;c", - }, - })) - }) + fmt.Printf("configuration is %+v \n", cfg) + } - It("agent configuration", func() { - exampleFlagSet := flag.NewFlagSet("example flag set", flag.ExitOnError) - var cfg config.Agent - PopulateFlagSet(&cfg, exampleFlagSet) - - exampleCommand := &ffcli.Command{ - FlagSet: exampleFlagSet, - Options: []ff.Option{ - ff.WithIgnoreUndefined(true), - ff.WithConfigFileParser(parser), - ff.WithConfigFileFlag("config"), - }, - Exec: func(_ context.Context, args []string) error { return nil }, } - err := exampleCommand.ParseAndRun(context.Background(), []string{ - "-config", "testdata/agent.yml", - "-tag", "baz=zzz", - }) + PopulateFlagSet(&cfg, exampleCommand.Flags(), vpr) + vpr.BindPFlags(exampleCommand.Flags()) + + b := bytes.NewBufferString("") + exampleCommand.SetOut(b) + exampleCommand.SetArgs([]string{fmt.Sprintf("--config=%s", "testdata/server.yml")}) + err := exampleCommand.Execute() Expect(err).ToNot(HaveOccurred()) - Expect(cfg).To(Equal(config.Agent{ - Config: "testdata/agent.yml", - LogLevel: "debug", - NoLogging: false, - ServerAddress: "http://localhost:4040", - AuthToken: "", - UpstreamThreads: 4, - UpstreamRequestTimeout: 10 * time.Second, - Tags: map[string]string{ - "baz": "zzz", + Expect(cfg).To(Equal(config.Server{ + AnalyticsOptOut: false, + Config: "testdata/server.yml", + LogLevel: "info", + BadgerLogLevel: "error", + StoragePath: "/var/lib/pyroscope", + APIBindAddr: ":4040", + BaseURL: "", + CacheEvictThreshold: 0.25, + CacheEvictVolume: 0.33, + BadgerNoTruncate: false, + DisablePprofEndpoint: false, + MaxNodesSerialization: 2048, + MaxNodesRender: 8192, + HideApplications: []string{}, + Retention: 0, + SampleRate: 0, + OutOfSpaceThreshold: 0, + CacheDimensionSize: 0, + CacheDictionarySize: 0, + CacheSegmentSize: 0, + CacheTreeSize: 0, + Auth: config.Auth{ + Google: config.GoogleOauth{ + Enabled: false, + ClientID: "", + ClientSecret: "", + RedirectURL: "", + AuthURL: "https://accounts.google.com/o/oauth2/auth", + TokenURL: "https://accounts.google.com/o/oauth2/token", + AllowedDomains: []string{}, + }, + Gitlab: config.GitlabOauth{ + Enabled: false, + ClientID: "", + ClientSecret: "", + RedirectURL: "", + AuthURL: "https://gitlab.com/oauth/authorize", + TokenURL: "https://gitlab.com/oauth/token", + APIURL: "https://gitlab.com/api/v4", + AllowedGroups: []string{}, + }, + Github: config.GithubOauth{ + Enabled: false, + ClientID: "", + ClientSecret: "", + RedirectURL: "", + AuthURL: "https://github.com/login/oauth/authorize", + TokenURL: "https://github.com/login/oauth/access_token", + AllowedOrganizations: []string{}, + }, + JWTSecret: "", + LoginMaximumLifetimeDays: 0, }, - })) - Expect(loadAgentConfig(&cfg)).ToNot(HaveOccurred()) - Expect(cfg).To(Equal(config.Agent{ - Config: "testdata/agent.yml", - LogLevel: "debug", - NoLogging: false, - ServerAddress: "http://localhost:4040", - AuthToken: "", - UpstreamThreads: 4, - UpstreamRequestTimeout: 10 * time.Second, - Tags: map[string]string{ - "foo": "bar", - "baz": "zzz", - }, - Targets: []config.Target{ - { - ServiceName: "foo", - SpyName: "debugspy", - ApplicationName: "foo.app", - SampleRate: 0, - DetectSubprocesses: false, - PyspyBlocking: false, - RbspyBlocking: false, - Tags: map[string]string{ - "foo": "bar", - "baz": "zzz", - }, + MetricExportRules: config.MetricExportRules{ + "my_metric_name": { + Expr: `app.name{foo=~"bar"}`, + Node: "a;b;c", }, }, })) - }) - - It("parses tag flags in exec", func() { - exampleFlagSet := flag.NewFlagSet("example flag set", flag.ExitOnError) - var cfg config.Exec - PopulateFlagSet(&cfg, exampleFlagSet) - - exampleCommand := &ffcli.Command{ - FlagSet: exampleFlagSet, - Options: []ff.Option{ - ff.WithIgnoreUndefined(true), - ff.WithConfigFileParser(parser), - ff.WithConfigFileFlag("config"), - }, - Exec: func(_ context.Context, args []string) error { - return nil - }, - } - err := exampleCommand.ParseAndRun(context.Background(), []string{ - "-tag", "foo=bar", - "-tag", "baz=qux", - }) - - Expect(err).ToNot(HaveOccurred()) - Expect(cfg).To(Equal(config.Exec{ - SpyName: "auto", - SampleRate: 100, - DetectSubprocesses: true, - LogLevel: "info", - ServerAddress: "http://localhost:4040", - UpstreamThreads: 4, - UpstreamRequestTimeout: 10 * time.Second, - Tags: map[string]string{ - "foo": "bar", - "baz": "qux", - }, - })) + Expect(loadServerConfig(&cfg)).ToNot(HaveOccurred()) }) }) }) diff --git a/pkg/cli/root_cmd_test.go b/pkg/cli/root_cmd_test.go deleted file mode 100644 index 944456498e..0000000000 --- a/pkg/cli/root_cmd_test.go +++ /dev/null @@ -1,19 +0,0 @@ -package cli - -import ( - . "github.com/onsi/ginkgo" - . "github.com/onsi/gomega" - "github.com/pyroscope-io/pyroscope/pkg/config" - "github.com/pyroscope-io/pyroscope/pkg/testing" -) - -var _ = Describe("flags", func() { - Describe("generateRootCmd", func() { - testing.WithConfig(func(cfg **config.Config) { - It("returns rootCmd", func() { - rootCmd := generateRootCmd(*cfg) - Expect(rootCmd).ToNot(BeNil()) - }) - }) - }) -}) diff --git a/pkg/cli/server_unix.go b/pkg/cli/server_unix.go index 37aea62e18..cf56a84462 100644 --- a/pkg/cli/server_unix.go +++ b/pkg/cli/server_unix.go @@ -14,7 +14,7 @@ import ( "github.com/pyroscope-io/pyroscope/pkg/config" ) -func startServer(c *config.Server) error { +func StartServer(c *config.Server) error { logLevel, err := logrus.ParseLevel(c.LogLevel) if err != nil { return err @@ -29,6 +29,13 @@ func startServer(c *config.Server) error { return fmt.Errorf("could not initialize server: %w", err) } + if srv.config.Auth.JWTSecret == "" { + srv.config.Auth.JWTSecret, err = srv.storage.JWT() + if err != nil { + return err + } + } + var stopTime time.Time s := make(chan os.Signal, 1) signal.Notify(s, syscall.SIGINT, syscall.SIGTERM) diff --git a/pkg/cli/server_windows.go b/pkg/cli/server_windows.go index 152c647a2d..09717850c0 100644 --- a/pkg/cli/server_windows.go +++ b/pkg/cli/server_windows.go @@ -6,6 +6,6 @@ import ( "github.com/pyroscope-io/pyroscope/pkg/config" ) -func startServer(_ *config.Server) error { +func StartServer(_ *config.Server) error { return fmt.Errorf("server mode is not supported on Windows") } diff --git a/pkg/cli/sortedflags.go b/pkg/cli/sortedflags.go deleted file mode 100644 index 83f8382b13..0000000000 --- a/pkg/cli/sortedflags.go +++ /dev/null @@ -1,70 +0,0 @@ -package cli - -import ( - "flag" - "reflect" - "sort" - - "github.com/iancoleman/strcase" - "github.com/peterbourgon/ff/v3/ffcli" -) - -// SortedFlags is needed because by default there's no way to provide order for flags -// this is kind of an ugly workaround -type SortedFlags struct { - orderMap map[string]int - flags []*flag.Flag - deprecatedFields []string -} - -func (sf *SortedFlags) Len() int { - return len(sf.flags) -} - -func (sf *SortedFlags) Swap(i, j int) { - sf.flags[i], sf.flags[j] = sf.flags[j], sf.flags[i] -} - -func (sf *SortedFlags) Less(i, j int) bool { - return sf.orderMap[sf.flags[i].Name] < sf.orderMap[sf.flags[j].Name] -} - -func (sf *SortedFlags) VisitAll(cb func(*flag.Flag)) { - for _, v := range sf.flags { - cb(v) - } -} - -func (sf *SortedFlags) printUsage(c *ffcli.Command) string { - return gradientBanner() + "\n" + DefaultUsageFunc(sf, c, sf.deprecatedFields) -} - -func NewSortedFlags(obj interface{}, fs *flag.FlagSet, deprecatedFields []string) *SortedFlags { - v := reflect.ValueOf(obj).Elem() - t := reflect.TypeOf(v.Interface()) - num := t.NumField() - - res := SortedFlags{ - orderMap: make(map[string]int), - flags: []*flag.Flag{}, - deprecatedFields: deprecatedFields, - } - - for i := 0; i < num; i++ { - field := t.Field(i) - nameVal := field.Tag.Get("name") - if nameVal == "" { - nameVal = strcase.ToKebab(field.Name) - } - // orderVal := field.Tag.Get("order") - // order, _ := strconv.Atoi(orderVal) - order := i - res.orderMap[nameVal] = order - } - fs.VisitAll(func(f *flag.Flag) { - res.flags = append(res.flags, f) - }) - - sort.Sort(&res) - return &res -} diff --git a/pkg/cli/testdata/example.yml b/pkg/cli/testdata/example.yml index cf8f551ff7..71cd05621a 100644 --- a/pkg/cli/testdata/example.yml +++ b/pkg/cli/testdata/example.yml @@ -8,3 +8,4 @@ baz: "10h" foo-bar: "test-val-4" foo-foo: 10.23 foo-bytes: "100mb" +foo-dur: "5m23s" diff --git a/pkg/config/config.go b/pkg/config/config.go index 451e293827..189b6ec34d 100644 --- a/pkg/config/config.go +++ b/pkg/config/config.go @@ -7,33 +7,33 @@ import ( ) type Config struct { - Version bool + Version bool `mapstructure:"version"` - Agent Agent `skip:"true"` - Server Server `skip:"true"` - Convert Convert `skip:"true"` - Exec Exec `skip:"true"` - DbManager DbManager `skip:"true"` + Agent Agent `skip:"true" mapstructure:",squash"` + Server Server `skip:"true" mapstructure:",squash"` + Convert Convert `skip:"true" mapstructure:",squash"` + Exec Exec `skip:"true" mapstructure:",squash"` + DbManager DbManager `skip:"true" mapstructure:",squash"` } type Agent struct { - Config string `def:"" desc:"location of config file"` + Config string `def:"" desc:"location of config file" mapstructure:"config"` - LogFilePath string `def:"" desc:"log file path"` - LogLevel string `def:"info" desc:"log level: debug|info|warn|error"` - NoLogging bool `def:"false" desc:"disables logging from pyroscope"` + LogFilePath string `def:"" desc:"log file path" mapstructure:"log-file-path"` + LogLevel string `def:"info" desc:"log level: debug|info|warn|error" mapstructure:"log-level"` + NoLogging bool `def:"false" desc:"disables logging from pyroscope" mapstructure:"no-logging"` - ServerAddress string `def:"http://localhost:4040" desc:"address of the pyroscope server"` - AuthToken string `def:"" desc:"authorization token used to upload profiling data"` - UpstreamThreads int `def:"4" desc:"number of upload threads"` - UpstreamRequestTimeout time.Duration `def:"10s" desc:"profile upload timeout"` + ServerAddress string `def:"http://localhost:4040" desc:"address of the pyroscope server" mapstructure:"server-address"` + AuthToken string `def:"" desc:"authorization token used to upload profiling data" mapstructure:"auth-token"` + UpstreamThreads int `def:"4" desc:"number of upload threads" mapstructure:"upstream-threads"` + UpstreamRequestTimeout time.Duration `def:"10s" desc:"profile upload timeout" mapstructure:"upstream-request-timeout"` // Structs and slices are not parsed with ffcli package, // instead `loadAgentConfig` function should be used. - Targets []Target `yaml:"targets" desc:"list of targets to be profiled"` + Targets []Target `yaml:"targets" desc:"list of targets to be profiled" mapstructure:"targets"` // Note that in YAML the key is 'tags' but the flag is 'tag'. - Tags map[string]string `yaml:"tags" name:"tag" def:"" desc:"tag key value pairs"` + Tags map[string]string `yaml:"tags" name:"tag" def:"" desc:"tag key value pairs" mapstructure:"tags"` } type Target struct { @@ -54,112 +54,135 @@ type Target struct { } type Server struct { - AnalyticsOptOut bool `def:"false" desc:"disables analytics"` + AnalyticsOptOut bool `def:"false" desc:"disables analytics" mapstructure:"analytics-opt-out"` - Config string `def:"/etc/pyroscope/server.yml" desc:"location of config file"` - LogLevel string `def:"info" desc:"log level: debug|info|warn|error"` - BadgerLogLevel string `def:"error" desc:"log level: debug|info|warn|error"` + Config string `def:"/etc/pyroscope/server.yml" desc:"location of config file" mapstructure:"config"` + LogLevel string `def:"info" desc:"log level: debug|info|warn|error" mapstructure:"log-level"` + BadgerLogLevel string `def:"error" desc:"log level: debug|info|warn|error" mapstructure:"badger-log-level"` - StoragePath string `def:"/var/lib/pyroscope" desc:"directory where pyroscope stores profiling data"` - APIBindAddr string `def:":4040" desc:"port for the HTTP server used for data ingestion and web UI"` - BaseURL string `def:"" desc:"base URL for when the server is behind a reverse proxy with a different path"` + StoragePath string `def:"/var/lib/pyroscope" desc:"directory where pyroscope stores profiling data" mapstructure:"storage-path"` + APIBindAddr string `def:":4040" desc:"port for the HTTP server used for data ingestion and web UI" mapstructure:"api-bind-addr"` + BaseURL string `def:"" desc:"base URL for when the server is behind a reverse proxy with a different path" mapstructure:"base-url"` - CacheEvictThreshold float64 `def:"0.25" desc:"percentage of memory at which cache evictions start"` - CacheEvictVolume float64 `def:"0.33" desc:"percentage of cache that is evicted per eviction run"` + CacheEvictThreshold float64 `def:"0.25" desc:"percentage of memory at which cache evictions start" mapstructure:"cache-evict-threshold"` + CacheEvictVolume float64 `def:"0.33" desc:"percentage of cache that is evicted per eviction run" mapstructure:"cache-evict-volume"` // TODO: I don't think a lot of people will change these values. // I think these should just be constants. - BadgerNoTruncate bool `def:"false" desc:"indicates whether value log files should be truncated to delete corrupt data, if any"` - DisablePprofEndpoint bool `def:"false" desc:"disables /debug/pprof route"` + BadgerNoTruncate bool `def:"false" desc:"indicates whether value log files should be truncated to delete corrupt data, if any" mapstructure:"badger-no-truncate"` + DisablePprofEndpoint bool `def:"false" desc:"disables /debug/pprof route" mapstructure:"disable-pprof-endpoint"` - MaxNodesSerialization int `def:"2048" desc:"max number of nodes used when saving profiles to disk"` - MaxNodesRender int `def:"8192" desc:"max number of nodes used to display data on the frontend"` + MaxNodesSerialization int `def:"2048" desc:"max number of nodes used when saving profiles to disk" mapstructure:"max-nodes-serialization"` + MaxNodesRender int `def:"8192" desc:"max number of nodes used to display data on the frontend" mapstructure:"max-nodes-render"` // currently only used in our demo app - HideApplications []string `def:"" desc:"please don't use, this will soon be deprecated"` + HideApplications []string `def:"" desc:"please don't use, this will soon be deprecated" mapstructure:"hide-applications"` - Retention time.Duration `def:"" desc:"sets the maximum amount of time the profiling data is stored for. Data before this threshold is deleted. Disabled by default"` + Retention time.Duration `def:"" desc:"sets the maximum amount of time the profiling data is stored for. Data before this threshold is deleted. Disabled by default" mapstructure:"retention"` // Deprecated fields. They can be set (for backwards compatibility) but have no effect // TODO: we should print some warning messages when people try to use these - SampleRate uint `deprecated:"true"` - OutOfSpaceThreshold bytesize.ByteSize `deprecated:"true"` - CacheDimensionSize int `deprecated:"true"` - CacheDictionarySize int `deprecated:"true"` - CacheSegmentSize int `deprecated:"true"` - CacheTreeSize int `deprecated:"true"` - - // TODO: remove skip: true when we enable these back - GoogleEnabled bool `json:"-" deprecated:"true" def:"false" desc:"enables Google Oauth"` - GoogleClientID string `json:"-" deprecated:"true" def:"" desc:"client ID generated for Google API"` - GoogleClientSecret string `json:"-" deprecated:"true" def:"" desc:"client secret generated for Google API"` - GoogleRedirectURL string `json:"-" deprecated:"true" def:"" desc:"url that google will redirect to after logging in. Has to be in form "` - GoogleAuthURL string `json:"-" deprecated:"true" def:"https://accounts.google.com/o/oauth2/auth" desc:"auth url for Google API (usually present in credentials.json file)"` - GoogleTokenURL string `json:"-" deprecated:"true" def:"https://accounts.google.com/o/oauth2/token" desc:"token url for Google API (usually present in credentials.json file)"` - - GitlabEnabled bool `json:"-" deprecated:"true" def:"false" desc:"enables Gitlab Oauth"` - // TODO: why is this one ApplicationID and not ClientID ? - GitlabApplicationID string `json:"-" deprecated:"true" def:"" desc:"application ID generated for GitLab API"` - GitlabClientSecret string `json:"-" deprecated:"true" def:"" desc:"client secret generated for GitLab API"` - GitlabRedirectURL string `json:"-" deprecated:"true" def:"" desc:"url that gitlab will redirect to after logging in. Has to be in form "` - GitlabAuthURL string `json:"-" deprecated:"true" def:"https://gitlab.com/oauth/authorize" desc:"auth url for GitLab API (keep default for cloud, usually https://gitlab.mycompany.com/oauth/authorize for on-premise)"` - GitlabTokenURL string `json:"-" deprecated:"true" def:"https://gitlab.com/oauth/token" desc:"token url for GitLab API (keep default for cloud, usually https://gitlab.mycompany.com/oauth/token for on-premise)"` - GitlabAPIURL string `json:"-" deprecated:"true" def:"https://gitlab.com/api/v4/user" desc:"URL to gitlab API (keep default for cloud, usually https://gitlab.mycompany.com/api/v4/user for on-premise)"` - - GithubEnabled bool `json:"-" deprecated:"true" def:"false" desc:"enables Github Oauth"` - GithubClientID string `json:"-" deprecated:"true" def:"" desc:"client ID generated for Github API"` - GithubClientSecret string `json:"-" deprecated:"true" def:"" desc:"client secret generated for Github API"` - GithubRedirectURL string `json:"-" deprecated:"true" def:"" desc:"url that Github will redirect to after logging in. Has to be in form "` - GithubAuthURL string `json:"-" deprecated:"true" def:"https://github.com/login/oauth/authorize" desc:"auth url for Github API"` - GithubTokenURL string `json:"-" deprecated:"true" def:"https://github.com/login/oauth/access_token" desc:"token url for Github API"` + SampleRate uint `deprecated:"true" mapstructure:"sample-rate"` + OutOfSpaceThreshold bytesize.ByteSize `deprecated:"true" mapstructure:"out-of-space-threshold"` + CacheDimensionSize int `deprecated:"true" mapstructure:"cache-dimensions-size"` + CacheDictionarySize int `deprecated:"true" mapstructure:"cache-dictonary-size"` + CacheSegmentSize int `deprecated:"true" mapstructure:"cache-segment-size"` + CacheTreeSize int `deprecated:"true" mapstructure:"cache-tree-size"` - // TODO: can we generate these automatically if it's empty? - JWTSecret string `json:"-" deprecated:"true" def:"" desc:"secret used to secure your JWT tokens"` - LoginMaximumLifetimeDays int `json:"-" deprecated:"true" def:"0" desc:"amount of days after which user will be logged out. 0 means non-expiring."` + Auth Auth `mapstructure:"auth"` - MetricExportRules MetricExportRules `yaml:"metric-export-rules" def:"" desc:"metric export rules"` + MetricExportRules MetricExportRules `yaml:"metric-export-rules" def:"" desc:"metric export rules" mapstructure:"metric-export-rules"` } type MetricExportRules map[string]MetricExportRule type MetricExportRule struct { - Expr string `def:"" desc:"expression in FlameQL syntax to be evaluated against samples"` - Node string `def:"total" desc:"tree node filter expression. Should be either 'total' or a valid regexp"` - Labels []string `def:"" desc:"list of tags to be exported as prometheus labels"` + Expr string `def:"" desc:"expression in FlameQL syntax to be evaluated against samples" mapstructure:"expr"` + Node string `def:"total" desc:"tree node filter expression. Should be either 'total' or a valid regexp" mapstructure:"node"` + Labels []string `def:"" desc:"list of tags to be exported as prometheus labels" mapstructure:"labels"` +} + +type Auth struct { + Google GoogleOauth `mapstructure:"google"` + Gitlab GitlabOauth `mapstructure:"gitlab"` + Github GithubOauth `mapstructure:"github"` + + // TODO: can we generate these automatically if it's empty? + JWTSecret string `json:"-" deprecated:"true" def:"" desc:"secret used to secure your JWT tokens" mapstructure:"jwt-secret"` + LoginMaximumLifetimeDays int `json:"-" deprecated:"true" def:"0" desc:"amount of days after which user will be logged out. 0 means non-expiring." mapstructure:"login-maximum-lifetime-days"` +} + +// TODO: Maybe merge Oauth structs into one (would have to move def and desc tags somewhere else in code) +type GoogleOauth struct { + // TODO: remove deprecated: true when we enable these back + Enabled bool `json:"-" deprecated:"true" def:"false" desc:"enables Google Oauth" mapstructure:"enabled"` + ClientID string `json:"-" deprecated:"true" def:"" desc:"client ID generated for Google API" mapstructure:"client-id"` + ClientSecret string `json:"-" deprecated:"true" def:"" desc:"client secret generated for Google API" mapstructure:"client-secret"` + RedirectURL string `json:"-" deprecated:"true" def:"" desc:"url that google will redirect to after logging in. Has to be in form " mapstructure:"redirect-url"` + AuthURL string `json:"-" deprecated:"true" def:"https://accounts.google.com/o/oauth2/auth" desc:"auth url for Google API (usually present in credentials.json file)" mapstructure:"auth-url"` + TokenURL string `json:"-" deprecated:"true" def:"https://accounts.google.com/o/oauth2/token" desc:"token url for Google API (usually present in credentials.json file)" mapstructure:"token-url"` + AllowedDomains []string `json:"-" deprecated:"true" def:"" desc:"list of domains that are allowed to login through google" mapstructure:"allowed-domains"` +} + +type GitlabOauth struct { + Enabled bool `json:"-" deprecated:"true" def:"false" desc:"enables Gitlab Oauth" mapstructure:"enabled"` + // TODO: I changed this to ClientID to fit others, but in Gitlab docs it's Application ID so it might get someone confused? + ClientID string `json:"-" deprecated:"true" def:"" desc:"client ID generated for GitLab API" mapstructure:"client-id"` + ClientSecret string `json:"-" deprecated:"true" def:"" desc:"client secret generated for GitLab API" mapstructure:"client-secret"` + RedirectURL string `json:"-" deprecated:"true" def:"" desc:"url that gitlab will redirect to after logging in. Has to be in form " mapstructure:"redirect-url"` + AuthURL string `json:"-" deprecated:"true" def:"https://gitlab.com/oauth/authorize" desc:"auth url for GitLab API (keep default for cloud, usually https://gitlab.mycompany.com/oauth/authorize for on-premise)" mapstructure:"auth-url"` + TokenURL string `json:"-" deprecated:"true" def:"https://gitlab.com/oauth/token" desc:"token url for GitLab API (keep default for cloud, usually https://gitlab.mycompany.com/oauth/token for on-premise)" mapstructure:"token-url"` + APIURL string `json:"-" deprecated:"true" def:"https://gitlab.com/api/v4" desc:"URL to gitlab API (keep default for cloud, usually https://gitlab.mycompany.com/api/v4/user for on-premise)" mapstructure:"api-url"` + AllowedGroups []string `json:"-" deprecated:"true" def:"" desc:"list of groups (unique names of the group as listed in URL) that are allowed to login through gitlab" mapstructure:"allowed-groups"` +} + +type GithubOauth struct { + Enabled bool `json:"-" deprecated:"true" def:"false" desc:"enables Github Oauth" mapstructure:"enabled"` + ClientID string `json:"-" deprecated:"true" def:"" desc:"client ID generated for Github API" mapstructure:"client-id"` + ClientSecret string `json:"-" deprecated:"true" def:"" desc:"client secret generated for Github API" mapstructure:"client-secret"` + RedirectURL string `json:"-" deprecated:"true" def:"" desc:"url that Github will redirect to after logging in. Has to be in form " mapstructure:"redirect-url"` + AuthURL string `json:"-" deprecated:"true" def:"https://github.com/login/oauth/authorize" desc:"auth url for Github API" mapstructure:"auth-url"` + TokenURL string `json:"-" deprecated:"true" def:"https://github.com/login/oauth/access_token" desc:"token url for Github API" mapstructure:"token-url"` + AllowedOrganizations []string `json:"-" deprecated:"true" def:"" desc:"list of organizations that are allowed to login through github" mapstructure:"allowed-organizations"` } type Convert struct { - Format string `def:"tree"` + Format string `def:"tree" mapstructure:"format"` +} + +type CombinedDbManager struct { + *DbManager `mapstructure:",squash"` + *Server `mapstructure:",squash"` } type DbManager struct { - LogLevel string `def:"error" desc:"log level: debug|info|warn|error"` - StoragePath string `def:"/var/lib/pyroscope" desc:"directory where pyroscope stores profiling data"` + LogLevel string `def:"error" desc:"log level: debug|info|warn|error" mapstructure:"log-level"` + StoragePath string `def:"/var/lib/pyroscope" desc:"directory where pyroscope stores profiling data" mapstructure:"storage-path"` DstStartTime time.Time DstEndTime time.Time SrcStartTime time.Time ApplicationName string - EnableProfiling bool `def:"false" desc:"enables profiling of dbmanager"` + EnableProfiling bool `def:"false" desc:"enables profiling of dbmanager" mapstructure:"enable-profiling"` } type Exec struct { - SpyName string `def:"auto" desc:"name of the profiler you want to use. Supported ones are: "` - ApplicationName string `def:"" desc:"application name used when uploading profiling data"` - SampleRate uint `def:"100" desc:"sample rate for the profiler in Hz. 100 means reading 100 times per second"` - DetectSubprocesses bool `def:"true" desc:"makes pyroscope keep track of and profile subprocesses of the main process"` - LogLevel string `def:"info" desc:"log level: debug|info|warn|error"` - ServerAddress string `def:"http://localhost:4040" desc:"address of the pyroscope server"` - AuthToken string `def:"" desc:"authorization token used to upload profiling data"` - UpstreamThreads int `def:"4" desc:"number of upload threads"` - UpstreamRequestTimeout time.Duration `def:"10s" desc:"profile upload timeout"` - NoLogging bool `def:"false" desc:"disables logging from pyroscope"` - NoRootDrop bool `def:"false" desc:"disables permissions drop when ran under root. use this one if you want to run your command as root"` - Pid int `def:"0" desc:"PID of the process you want to profile. Pass -1 to profile the whole system (only supported by ebpfspy)"` - UserName string `def:"" desc:"starts process under specified user name"` - GroupName string `def:"" desc:"starts process under specified group name"` - PyspyBlocking bool `def:"false" desc:"enables blocking mode for pyspy"` - RbspyBlocking bool `def:"false" desc:"enables blocking mode for rbspy"` - - Tags map[string]string `name:"tag" def:"" desc:"tag in key=value form. The flag may be specified multiple times"` + SpyName string `def:"auto" desc:"name of the profiler you want to use. Supported ones are: " mapstructure:"spy-name"` + ApplicationName string `def:"" desc:"application name used when uploading profiling data" mapstructure:"application-name"` + SampleRate uint `def:"100" desc:"sample rate for the profiler in Hz. 100 means reading 100 times per second" mapstructure:"sample-rate"` + DetectSubprocesses bool `def:"true" desc:"makes pyroscope keep track of and profile subprocesses of the main process" mapstructure:"detect-subprocesses"` + LogLevel string `def:"info" desc:"log level: debug|info|warn|error" mapstructure:"log-level"` + ServerAddress string `def:"http://localhost:4040" desc:"address of the pyroscope server" mapstructure:"server-address"` + AuthToken string `def:"" desc:"authorization token used to upload profiling data" mapstructure:"auth-token"` + UpstreamThreads int `def:"4" desc:"number of upload threads" mapstructure:"upstream-threads"` + UpstreamRequestTimeout time.Duration `def:"10s" desc:"profile upload timeout" mapstructure:"upstream-request-timeout"` + NoLogging bool `def:"false" desc:"disables logging from pyroscope" mapstructure:"no-logging"` + NoRootDrop bool `def:"false" desc:"disables permissions drop when ran under root. use this one if you want to run your command as root" mapstructure:"no-root-drop"` + Pid int `def:"0" desc:"PID of the process you want to profile. Pass -1 to profile the whole system (only supported by ebpfspy)" mapstructure:"pid"` + UserName string `def:"" desc:"starts process under specified user name" mapstructure:"user-name"` + GroupName string `def:"" desc:"starts process under specified group name" mapstructure:"group-name"` + PyspyBlocking bool `def:"false" desc:"enables blocking mode for pyspy" mapstructure:"pyspy-blocking"` + RbspyBlocking bool `def:"false" desc:"enables blocking mode for rbspy" mapstructure:"rbspy-blocking"` + + Tags map[string]string `name:"tag" def:"" desc:"tag in key=value form. The flag may be specified multiple times" mapstructure:"tags"` } diff --git a/pkg/config/interface.go b/pkg/config/interface.go new file mode 100644 index 0000000000..792237c2a9 --- /dev/null +++ b/pkg/config/interface.go @@ -0,0 +1,51 @@ +package config + +import ( + "os" + + "github.com/sirupsen/logrus" +) + +type FileConfiger interface{ ConfigFilePath() string } + +func (cfg Agent) ConfigFilePath() string { + return cfg.Config +} + +func (cfg Server) ConfigFilePath() string { + return cfg.Config +} + +func (cfg CombinedDbManager) ConfigFilePath() string { + return cfg.Server.Config +} + +type LoggerFunc func(s string) +type LoggerConfiger interface{ InitializeLogging() LoggerFunc } + +func (cfg Convert) InitializeLogging() LoggerFunc { + logrus.SetOutput(os.Stderr) + logger := func(s string) { + logrus.Fatal(s) + } + + return logger +} + +func (cfg CombinedDbManager) InitializeLogging() LoggerFunc { + if l, err := logrus.ParseLevel(cfg.DbManager.LogLevel); err == nil { + logrus.SetLevel(l) + } + + return nil +} + +func (cfg Exec) InitializeLogging() LoggerFunc { + if cfg.NoLogging { + logrus.SetLevel(logrus.PanicLevel) + } else if l, err := logrus.ParseLevel(cfg.LogLevel); err == nil { + logrus.SetLevel(l) + } + + return nil +} diff --git a/pkg/convert/cli.go b/pkg/convert/cli.go deleted file mode 100644 index a9d229f09d..0000000000 --- a/pkg/convert/cli.go +++ /dev/null @@ -1,41 +0,0 @@ -package convert - -import ( - "fmt" - "io" - "os" - - "github.com/pyroscope-io/pyroscope/pkg/config" - "github.com/pyroscope-io/pyroscope/pkg/storage/tree" - "github.com/pyroscope-io/pyroscope/pkg/structs/transporttrie" -) - -func Cli(cfg *config.Convert, logger func(string), args []string) error { - var input io.Reader - if len(args) == 0 { - input = os.Stdin - } else { - logger("not implemented yet") - } - - parser := ParseGroups - switch cfg.Format { - case "tree": - t := tree.New() - parser(input, func(name []byte, val int) { - t.Insert(name, uint64(val)) - }) - - t.SerializeNoDict(4096, os.Stdout) - case "trie": - t := transporttrie.New() - parser(input, func(name []byte, val int) { - t.Insert(name, uint64(val), true) - }) - - t.Serialize(os.Stdout) - default: - logger(fmt.Sprintf("unknown format: %s", cfg.Format)) - } - return nil -} diff --git a/pkg/exec/cli.go b/pkg/exec/cli.go index 934785eaa6..055c564c36 100644 --- a/pkg/exec/cli.go +++ b/pkg/exec/cli.go @@ -77,12 +77,12 @@ func Cli(cfg *config.Exec, args []string) error { } } - logrus.Info("to disable logging from pyroscope, pass " + color.YellowString("-no-logging") + " argument to pyroscope exec") - if err := performChecks(spyName); err != nil { return err } + logrus.Info("to disable logging from pyroscope, pass " + color.YellowString("-no-logging") + " argument to pyroscope exec") + if cfg.ApplicationName == "" { logrus.Infof("we recommend specifying application name via %s flag or env variable %s", color.YellowString("-application-name"), color.YellowString("PYROSCOPE_APPLICATION_NAME")) diff --git a/pkg/server/controller.go b/pkg/server/controller.go index c895f49299..8ea110e375 100644 --- a/pkg/server/controller.go +++ b/pkg/server/controller.go @@ -8,7 +8,6 @@ import ( golog "log" "net/http" "net/http/pprof" - "net/url" "sync" "sync/atomic" "time" @@ -20,7 +19,6 @@ import ( metrics "github.com/slok/go-http-metrics/metrics/prometheus" goHttpMetricsMiddleware "github.com/slok/go-http-metrics/middleware" middlewarestd "github.com/slok/go-http-metrics/middleware/std" - "golang.org/x/oauth2" "github.com/pyroscope-io/pyroscope/pkg/config" "github.com/pyroscope-io/pyroscope/pkg/storage" @@ -142,122 +140,52 @@ func (ctrl *Controller) mux() (http.Handler, error) { return mux, nil } -type oauthInfo struct { - Config *oauth2.Config - AuthURL *url.URL - Type int -} - -func (ctrl *Controller) generateOauthInfo(oauthType int) *oauthInfo { - switch oauthType { - case oauthGoogle: - googleOauthInfo := &oauthInfo{ - Config: &oauth2.Config{ - ClientID: ctrl.config.GoogleClientID, - ClientSecret: ctrl.config.GoogleClientSecret, - Scopes: []string{"https://www.googleapis.com/auth/userinfo.email"}, - Endpoint: oauth2.Endpoint{AuthURL: ctrl.config.GoogleAuthURL, TokenURL: ctrl.config.GoogleTokenURL}, - }, - Type: oauthGoogle, - } - if ctrl.config.GoogleRedirectURL != "" { - googleOauthInfo.Config.RedirectURL = ctrl.config.GoogleRedirectURL - } - - return googleOauthInfo - case oauthGithub: - githubOauthInfo := &oauthInfo{ - Config: &oauth2.Config{ - ClientID: ctrl.config.GithubClientID, - ClientSecret: ctrl.config.GithubClientSecret, - Scopes: []string{"read:user", "user:email"}, - Endpoint: oauth2.Endpoint{AuthURL: ctrl.config.GithubAuthURL, TokenURL: ctrl.config.GithubTokenURL}, - }, - Type: oauthGithub, - } - - if ctrl.config.GithubRedirectURL != "" { - githubOauthInfo.Config.RedirectURL = ctrl.config.GithubRedirectURL - } - - return githubOauthInfo - case oauthGitlab: - gitlabOauthInfo := &oauthInfo{ - Config: &oauth2.Config{ - ClientID: ctrl.config.GitlabApplicationID, - ClientSecret: ctrl.config.GitlabClientSecret, - Scopes: []string{"read_user"}, - Endpoint: oauth2.Endpoint{AuthURL: ctrl.config.GitlabAuthURL, TokenURL: ctrl.config.GitlabTokenURL}, - }, - Type: oauthGitlab, - } - - if ctrl.config.GitlabRedirectURL != "" { - gitlabOauthInfo.Config.RedirectURL = ctrl.config.GitlabRedirectURL - } - - return gitlabOauthInfo - } - - return nil -} - func (ctrl *Controller) getAuthRoutes() ([]route, error) { authRoutes := []route{ {"/login", ctrl.loginHandler()}, {"/logout", ctrl.logoutHandler()}, } - if ctrl.config.GoogleEnabled { - authURL, err := url.Parse(ctrl.config.GoogleAuthURL) + if ctrl.config.Auth.Google.Enabled { + googleHandler, err := newGoogleHandler(ctrl.config.Auth.Google, ctrl.log) if err != nil { return nil, err } - googleOauthInfo := ctrl.generateOauthInfo(oauthGoogle) - if googleOauthInfo != nil { - googleOauthInfo.AuthURL = authURL - authRoutes = append(authRoutes, []route{ - {"/auth/google/login", ctrl.oauthLoginHandler(googleOauthInfo)}, - {"/auth/google/callback", ctrl.callbackHandler("/auth/google/redirect")}, - {"/auth/google/redirect", ctrl.callbackRedirectHandler( - "https://www.googleapis.com/oauth2/v2/userinfo", googleOauthInfo, ctrl.decodeGoogleCallbackResponse)}, - }...) - } + authRoutes = append(authRoutes, []route{ + {"/auth/google/login", ctrl.oauthLoginHandler(googleHandler)}, + {"/auth/google/callback", ctrl.callbackHandler(googleHandler.redirectRoute)}, + {"/auth/google/redirect", ctrl.callbackRedirectHandler(googleHandler)}, + }...) + } - if ctrl.config.GithubEnabled { - authURL, err := url.Parse(ctrl.config.GithubAuthURL) + if ctrl.config.Auth.Github.Enabled { + githubHandler, err := newGithubHandler(ctrl.config.Auth.Github, ctrl.log) if err != nil { return nil, err } - githubOauthInfo := ctrl.generateOauthInfo(oauthGithub) - if githubOauthInfo != nil { - githubOauthInfo.AuthURL = authURL - authRoutes = append(authRoutes, []route{ - {"/auth/github/login", ctrl.oauthLoginHandler(githubOauthInfo)}, - {"/auth/github/callback", ctrl.callbackHandler("/auth/github/redirect")}, - {"/auth/github/redirect", ctrl.callbackRedirectHandler("https://api.github.com/user", githubOauthInfo, ctrl.decodeGithubCallbackResponse)}, - }...) - } + authRoutes = append(authRoutes, []route{ + {"/auth/github/login", ctrl.oauthLoginHandler(githubHandler)}, + {"/auth/github/callback", ctrl.callbackHandler(githubHandler.redirectRoute)}, + {"/auth/github/redirect", ctrl.callbackRedirectHandler(githubHandler)}, + }...) + } - if ctrl.config.GitlabEnabled { - authURL, err := url.Parse(ctrl.config.GitlabAuthURL) + if ctrl.config.Auth.Gitlab.Enabled { + gitlabHandler, err := newGitlabHandler(ctrl.config.Auth.Gitlab, ctrl.log) if err != nil { return nil, err } - gitlabOauthInfo := ctrl.generateOauthInfo(oauthGitlab) - if gitlabOauthInfo != nil { - gitlabOauthInfo.AuthURL = authURL - authRoutes = append(authRoutes, []route{ - {"/auth/gitlab/login", ctrl.oauthLoginHandler(gitlabOauthInfo)}, - {"/auth/gitlab/callback", ctrl.callbackHandler("/auth/gitlab/redirect")}, - {"/auth/gitlab/redirect", ctrl.callbackRedirectHandler(ctrl.config.GitlabAPIURL, gitlabOauthInfo, ctrl.decodeGitLabCallbackResponse)}, - }...) - } + authRoutes = append(authRoutes, []route{ + {"/auth/gitlab/login", ctrl.oauthLoginHandler(gitlabHandler)}, + {"/auth/gitlab/callback", ctrl.callbackHandler(gitlabHandler.redirectRoute)}, + {"/auth/gitlab/redirect", ctrl.callbackRedirectHandler(gitlabHandler)}, + }...) + } return authRoutes, nil @@ -322,7 +250,7 @@ func (ctrl *Controller) trackMetrics(route string) func(next http.HandlerFunc) h } func (ctrl *Controller) isAuthRequired() bool { - return ctrl.config.GoogleEnabled || ctrl.config.GithubEnabled || ctrl.config.GitlabEnabled + return ctrl.config.Auth.Google.Enabled || ctrl.config.Auth.Github.Enabled || ctrl.config.Auth.Gitlab.Enabled } func (ctrl *Controller) authMiddleware(next http.HandlerFunc) http.HandlerFunc { @@ -346,7 +274,7 @@ func (ctrl *Controller) authMiddleware(next http.HandlerFunc) http.HandlerFunc { if token.Method.Alg() != jwt.SigningMethodHS256.Alg() { return nil, fmt.Errorf("unexpected signing method: %v", token.Header["alg"]) } - return []byte(ctrl.config.JWTSecret), nil + return []byte(ctrl.config.Auth.JWTSecret), nil }) if err != nil { diff --git a/pkg/server/handler.go b/pkg/server/handler.go index 3e12305211..9603d102e3 100644 --- a/pkg/server/handler.go +++ b/pkg/server/handler.go @@ -4,14 +4,11 @@ import ( "crypto/rand" "encoding/hex" "encoding/json" - "errors" "fmt" "io" "io/ioutil" "net/http" - "net/url" "os" - "strings" "text/template" "time" @@ -30,9 +27,9 @@ func (ctrl *Controller) loginHandler() http.HandlerFunc { return } mustExecute(tmpl, w, map[string]interface{}{ - "GoogleEnabled": ctrl.config.GoogleEnabled, - "GithubEnabled": ctrl.config.GithubEnabled, - "GitlabEnabled": ctrl.config.GitlabEnabled, + "GoogleEnabled": ctrl.config.Auth.Google.Enabled, + "GithubEnabled": ctrl.config.Auth.Github.Enabled, + "GitlabEnabled": ctrl.config.Auth.Gitlab.Enabled, "BaseURL": ctrl.config.BaseURL, }) } @@ -82,60 +79,15 @@ func generateStateToken(length int) (string, error) { return hex.EncodeToString(b), nil } -func getCallbackURL(host, configCallbackURL string, oauthType int, hasTLS bool) (string, error) { - if configCallbackURL != "" { - return configCallbackURL, nil - } - - if host == "" { - return "", errors.New("host is empty") - } - - schema := "http" - if hasTLS { - schema = "https" - } - - switch oauthType { - case oauthGoogle: - return fmt.Sprintf("%v://%v/auth/google/callback", schema, host), nil - case oauthGithub: - return fmt.Sprintf("%v://%v/auth/github/callback", schema, host), nil - case oauthGitlab: - return fmt.Sprintf("%v://%v/auth/gitlab/callback", schema, host), nil - } - - return "", errors.New("invalid oauth type provided") -} - -func (ctrl *Controller) oauthLoginHandler(info *oauthInfo) http.HandlerFunc { +func (ctrl *Controller) oauthLoginHandler(oh oauthHandler) http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { - callbackURL, err := getCallbackURL(r.Host, info.Config.RedirectURL, info.Type, r.URL.Query().Get("tls") == "true") - if err != nil { - ctrl.log.WithError(err).Error("callbackURL parsing failed") - return - } - - authURL := *info.AuthURL - parameters := url.Values{} - parameters.Add("client_id", info.Config.ClientID) - parameters.Add("scope", strings.Join(info.Config.Scopes, " ")) - parameters.Add("redirect_uri", callbackURL) - parameters.Add("response_type", "code") - - // generate state token for CSRF protection - state, err := generateStateToken(16) + authURL, err := oh.getOauthBase().buildAuthQuery(r, w) if err != nil { ctrl.log.WithError(err).Error("problem generating state token") - w.WriteHeader(http.StatusInternalServerError) return } - createCookie(w, stateCookieName, state) - parameters.Add("state", state) - authURL.RawQuery = parameters.Encode() - - http.Redirect(w, r, authURL.String(), http.StatusTemporaryRedirect) + http.Redirect(w, r, authURL, http.StatusTemporaryRedirect) } } @@ -168,69 +120,18 @@ func (ctrl *Controller) forbiddenHandler() http.HandlerFunc { } } -func (*Controller) decodeGoogleCallbackResponse(resp *http.Response) (string, error) { - type callbackResponse struct { - ID string - Email string - VerifiedEmail bool - Picture string - } - - var userProfile callbackResponse - err := json.NewDecoder(resp.Body).Decode(&userProfile) - if err != nil { - return "", err - } - - return userProfile.Email, nil -} - -func (*Controller) decodeGithubCallbackResponse(resp *http.Response) (string, error) { - type callbackResponse struct { - ID int64 - Email string - Login string - AvatarURL string - } - - var userProfile callbackResponse - err := json.NewDecoder(resp.Body).Decode(&userProfile) - if err != nil { - return "", err - } - - return userProfile.Login, nil -} - -func (*Controller) decodeGitLabCallbackResponse(resp *http.Response) (string, error) { - type callbackResponse struct { - ID int64 - Email string - Username string - AvatarURL string - } - - var userProfile callbackResponse - err := json.NewDecoder(resp.Body).Decode(&userProfile) - if err != nil { - return "", nil - } - - return userProfile.Username, nil -} - func (ctrl *Controller) newJWTToken(name string) (string, error) { claims := jwt.MapClaims{ "iat": time.Now().Unix(), "name": name, } - if ctrl.config.LoginMaximumLifetimeDays > 0 { - claims["exp"] = time.Now().Add(time.Hour * 24 * time.Duration(ctrl.config.LoginMaximumLifetimeDays)).Unix() + if ctrl.config.Auth.LoginMaximumLifetimeDays > 0 { + claims["exp"] = time.Now().Add(time.Hour * 24 * time.Duration(ctrl.config.Auth.LoginMaximumLifetimeDays)).Unix() } jwtToken := jwt.NewWithClaims(jwt.SigningMethodHS256, claims) - return jwtToken.SignedString([]byte(ctrl.config.JWTSecret)) + return jwtToken.SignedString([]byte(ctrl.config.Auth.JWTSecret)) } func (ctrl *Controller) logErrorAndRedirect(w http.ResponseWriter, r *http.Request, msg string, err error) { @@ -243,7 +144,7 @@ func (ctrl *Controller) logErrorAndRedirect(w http.ResponseWriter, r *http.Reque http.Redirect(w, r, "/forbidden", http.StatusTemporaryRedirect) } -func (ctrl *Controller) callbackRedirectHandler(getAccountInfoURL string, info *oauthInfo, decodeResponse decodeResponseFunc) http.HandlerFunc { +func (ctrl *Controller) callbackRedirectHandler(oh oauthHandler) http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { cookie, err := r.Cookie(stateCookieName) if err != nil { @@ -254,37 +155,16 @@ func (ctrl *Controller) callbackRedirectHandler(getAccountInfoURL string, info * ctrl.logErrorAndRedirect(w, r, "invalid oauth state", nil) return } - code := r.FormValue("code") - if code == "" { - ctrl.logErrorAndRedirect(w, r, "code not found", nil) - return - } - - callbackURL, err := getCallbackURL(r.Host, info.Config.RedirectURL, info.Type, r.URL.Query().Get("tls") == "true") - if err != nil { - ctrl.logErrorAndRedirect(w, r, "callbackURL parsing failed", nil) - return - } - oauthConf := *info.Config - oauthConf.RedirectURL = callbackURL - token, err := oauthConf.Exchange(r.Context(), code) - if err != nil { - ctrl.logErrorAndRedirect(w, r, "exchanging auth code for token failed", err) - return - } - client := oauthConf.Client(r.Context(), token) - resp, err := client.Get(getAccountInfoURL) + client, err := oh.getOauthBase().generateOauthClient(r) if err != nil { - ctrl.logErrorAndRedirect(w, r, "failed to get oauth user info", err) + ctrl.logErrorAndRedirect(w, r, "failed to generate oauth client", err) return } - defer resp.Body.Close() - name, err := decodeResponse(resp) + name, err := oh.userAuth(client) if err != nil { - ctrl.logErrorAndRedirect(w, r, "decoding response body failed", err) - return + ctrl.logErrorAndRedirect(w, r, "failed to get user auth info", err) } tk, err := ctrl.newJWTToken(name) diff --git a/pkg/server/oauth_base.go b/pkg/server/oauth_base.go new file mode 100644 index 0000000000..ea038df9f2 --- /dev/null +++ b/pkg/server/oauth_base.go @@ -0,0 +1,111 @@ +package server + +import ( + "errors" + "fmt" + "net/http" + "net/url" + "regexp" + "strings" + + "github.com/sirupsen/logrus" + "golang.org/x/oauth2" +) + +var errForbidden = errors.New("access forbidden") + +type oauthHandler interface { + userAuth(client *http.Client) (string, error) + getOauthBase() oauthBase +} + +type oauthBase struct { + config *oauth2.Config + authURL *url.URL + apiURL string + log *logrus.Logger + callbackRoute string + redirectRoute string +} + +func (o oauthBase) getCallbackURL(host, configCallbackURL string, hasTLS bool) (string, error) { + if configCallbackURL != "" { + return configCallbackURL, nil + } + + if host == "" { + return "", errors.New("host is empty") + } + + schema := "http" + if hasTLS { + schema = "https" + } + + return fmt.Sprintf("%v://%v/%v", schema, host, o.callbackRoute), nil +} + +func (o oauthBase) buildAuthQuery(r *http.Request, w http.ResponseWriter) (string, error) { + callbackURL, err := o.getCallbackURL(r.Host, o.config.RedirectURL, r.URL.Query().Get("tls") == "true") + if err != nil { + w.WriteHeader(http.StatusBadRequest) + return "", fmt.Errorf("callbackURL parsing failed: %w", err) + } + + authURL := *o.authURL + parameters := url.Values{} + parameters.Add("client_id", o.config.ClientID) + parameters.Add("scope", strings.Join(o.config.Scopes, " ")) + parameters.Add("redirect_uri", callbackURL) + parameters.Add("response_type", "code") + + // generate state token for CSRF protection + state, err := generateStateToken(16) + if err != nil { + w.WriteHeader(http.StatusInternalServerError) + return "", fmt.Errorf("problem generating state token: %w", err) + } + + createCookie(w, stateCookieName, state) + parameters.Add("state", state) + authURL.RawQuery = parameters.Encode() + + return authURL.String(), nil +} + +func (o oauthBase) generateOauthClient(r *http.Request) (*http.Client, error) { + code := r.FormValue("code") + if code == "" { + return nil, errors.New("code not found") + } + + callbackURL, err := o.getCallbackURL(r.Host, o.config.RedirectURL, r.URL.Query().Get("tls") == "true") + if err != nil { + return nil, fmt.Errorf("callbackURL parsing failed: %w", err) + } + oauthConf := *o.config + oauthConf.RedirectURL = callbackURL + token, err := oauthConf.Exchange(r.Context(), code) + if err != nil { + return nil, fmt.Errorf("exchanging auth code for token failed: %w", err) + } + + return oauthConf.Client(r.Context(), token), err +} + +func hasMoreLinkResults(headers http.Header) (string, bool) { + value, exists := headers["Link"] + if !exists { + return "", false + } + + pattern := regexp.MustCompile(`<([^>]+)>; rel="next"`) + matches := pattern.FindStringSubmatch(value[0]) + if matches == nil { + return "", false + } + + next := matches[1] + + return next, true +} diff --git a/pkg/server/oauth_github.go b/pkg/server/oauth_github.go new file mode 100644 index 0000000000..0cadedfd90 --- /dev/null +++ b/pkg/server/oauth_github.go @@ -0,0 +1,124 @@ +package server + +import ( + "encoding/json" + "fmt" + "net/http" + "net/url" + + "github.com/pyroscope-io/pyroscope/pkg/config" + "github.com/sirupsen/logrus" + "golang.org/x/oauth2" +) + +type oauthHanlderGithub struct { + oauthBase + allowedOrganizations []string +} + +func newGithubHandler(cfg config.GithubOauth, log *logrus.Logger) (*oauthHanlderGithub, error) { + authURL, err := url.Parse(cfg.AuthURL) + if err != nil { + return nil, err + } + + h := &oauthHanlderGithub{ + oauthBase: oauthBase{ + config: &oauth2.Config{ + ClientID: cfg.ClientID, + ClientSecret: cfg.ClientSecret, + Scopes: []string{"read:user", "user:email", "read:org"}, + Endpoint: oauth2.Endpoint{AuthURL: cfg.AuthURL, TokenURL: cfg.TokenURL}, + }, + authURL: authURL, + log: log, + callbackRoute: "auth/github/callback", + redirectRoute: "/auth/github/redirect", + apiURL: "https://api.github.com", + }, + allowedOrganizations: cfg.AllowedOrganizations, + } + + if cfg.RedirectURL != "" { + h.config.RedirectURL = cfg.RedirectURL + } + + return h, nil +} + +type githubOrganizations struct { + Login string +} + +func (o oauthHanlderGithub) userAuth(client *http.Client) (string, error) { + type userProfileResponse struct { + ID int64 + Email string + Login string + AvatarURL string + } + + resp, err := client.Get(o.apiURL + "/user") + if err != nil { + return "", fmt.Errorf("failed to get oauth user info: %w", err) + } + defer resp.Body.Close() + + var userProfile userProfileResponse + err = json.NewDecoder(resp.Body).Decode(&userProfile) + if err != nil { + return "", fmt.Errorf("failed to decode user profile response: %w", err) + } + + if len(o.allowedOrganizations) == 0 { + return userProfile.Login, nil + } + + organizations, err := o.fetchOrganizations(client) + if err != nil { + return "", fmt.Errorf("failed to get organizations: %w", err) + } + + for _, allowed := range o.allowedOrganizations { + for _, member := range organizations { + if member.Login == allowed { + return userProfile.Login, nil + } + } + } + + return "", errForbidden +} + +func (o oauthHanlderGithub) fetchOrganizations(client *http.Client) ([]githubOrganizations, error) { + url := o.apiURL + "/user/orgs" + more := true + organizations := make([]githubOrganizations, 0) + + for more { + resp, err := client.Get(url) + if err != nil { + return nil, err + } + defer resp.Body.Close() + + var orgs []githubOrganizations + err = json.NewDecoder(resp.Body).Decode(&orgs) + if err != nil { + return nil, err + } + + organizations = append(organizations, orgs...) + + url, more = hasMoreLinkResults(resp.Header) + if err != nil { + return nil, err + } + } + + return organizations, nil +} + +func (o oauthHanlderGithub) getOauthBase() oauthBase { + return o.oauthBase +} diff --git a/pkg/server/oauth_gitlab.go b/pkg/server/oauth_gitlab.go new file mode 100644 index 0000000000..ff795b1ea8 --- /dev/null +++ b/pkg/server/oauth_gitlab.go @@ -0,0 +1,124 @@ +package server + +import ( + "encoding/json" + "fmt" + "net/http" + "net/url" + + "github.com/pyroscope-io/pyroscope/pkg/config" + "github.com/sirupsen/logrus" + "golang.org/x/oauth2" +) + +type oauthHanlderGitlab struct { + oauthBase + allowedGroups []string +} + +func newGitlabHandler(cfg config.GitlabOauth, log *logrus.Logger) (*oauthHanlderGitlab, error) { + authURL, err := url.Parse(cfg.AuthURL) + if err != nil { + return nil, err + } + + h := &oauthHanlderGitlab{ + oauthBase: oauthBase{ + config: &oauth2.Config{ + ClientID: cfg.ClientID, + ClientSecret: cfg.ClientSecret, + Scopes: []string{"read_api"}, + Endpoint: oauth2.Endpoint{AuthURL: cfg.AuthURL, TokenURL: cfg.TokenURL}, + }, + authURL: authURL, + log: log, + callbackRoute: "auth/gitlab/callback", + redirectRoute: "/auth/gitlab/redirect", + apiURL: cfg.APIURL, + }, + allowedGroups: cfg.AllowedGroups, + } + + if cfg.RedirectURL != "" { + h.config.RedirectURL = cfg.RedirectURL + } + + return h, nil +} + +type gitlabGroups struct { + Path string +} + +func (o oauthHanlderGitlab) userAuth(client *http.Client) (string, error) { + type userProfileResponse struct { + ID int64 + Email string + Username string + AvatarURL string + } + + resp, err := client.Get(o.oauthBase.apiURL + "/user") + if err != nil { + return "", fmt.Errorf("failed to get oauth user info: %w", err) + } + defer resp.Body.Close() + + var userProfile userProfileResponse + err = json.NewDecoder(resp.Body).Decode(&userProfile) + if err != nil { + return "", fmt.Errorf("failed to decode user profile response: %w", err) + } + + if len(o.allowedGroups) == 0 { + return userProfile.Username, nil + } + + groups, err := o.fetchGroups(client) + if err != nil { + return "", fmt.Errorf("failed to get groups: %w", err) + } + + for _, allowed := range o.allowedGroups { + for _, member := range groups { + if member.Path == allowed { + return userProfile.Username, nil + } + } + } + + return "", errForbidden +} + +func (o oauthHanlderGitlab) fetchGroups(client *http.Client) ([]gitlabGroups, error) { + url := o.apiURL + "/groups" + more := true + groups := make([]gitlabGroups, 0) + + for more { + resp, err := client.Get(url) + if err != nil { + return nil, err + } + defer resp.Body.Close() + + var grp []gitlabGroups + err = json.NewDecoder(resp.Body).Decode(&grp) + if err != nil { + return nil, err + } + + groups = append(groups, grp...) + + url, more = hasMoreLinkResults(resp.Header) + if err != nil { + return nil, err + } + } + + return groups, nil +} + +func (o oauthHanlderGitlab) getOauthBase() oauthBase { + return o.oauthBase +} diff --git a/pkg/server/oauth_google.go b/pkg/server/oauth_google.go new file mode 100644 index 0000000000..ee752201f6 --- /dev/null +++ b/pkg/server/oauth_google.go @@ -0,0 +1,94 @@ +package server + +import ( + "encoding/json" + "errors" + "fmt" + "net/http" + "net/url" + "strings" + + "github.com/pyroscope-io/pyroscope/pkg/config" + "github.com/sirupsen/logrus" + "golang.org/x/oauth2" +) + +type oauthHanlderGoogle struct { + oauthBase + allowedDomains []string +} + +func newGoogleHandler(cfg config.GoogleOauth, log *logrus.Logger) (*oauthHanlderGoogle, error) { + authURL, err := url.Parse(cfg.AuthURL) + if err != nil { + return nil, err + } + + h := &oauthHanlderGoogle{ + oauthBase: oauthBase{ + config: &oauth2.Config{ + ClientID: cfg.ClientID, + ClientSecret: cfg.ClientSecret, + Scopes: []string{"https://www.googleapis.com/auth/userinfo.email"}, + Endpoint: oauth2.Endpoint{AuthURL: cfg.AuthURL, TokenURL: cfg.TokenURL}, + }, + authURL: authURL, + log: log, + callbackRoute: "auth/google/callback", + redirectRoute: "/auth/google/redirect", + apiURL: "https://www.googleapis.com/oauth2/v2", + }, + allowedDomains: cfg.AllowedDomains, + } + + if cfg.RedirectURL != "" { + h.config.RedirectURL = cfg.RedirectURL + } + + return h, nil +} + +func (o oauthHanlderGoogle) userAuth(client *http.Client) (string, error) { + type userProfileResponse struct { + ID string + Email string + VerifiedEmail bool + Picture string + } + + resp, err := client.Get(o.apiURL + "/userinfo") + if err != nil { + return "", fmt.Errorf("failed to get oauth user info: %w", err) + } + defer resp.Body.Close() + + var userProfile userProfileResponse + err = json.NewDecoder(resp.Body).Decode(&userProfile) + if err != nil { + return "", fmt.Errorf("failed to decode user profile response: %w", err) + } + + if userProfile.Email == "" { + return "", errors.New("user email is empty") + } + + if len(o.allowedDomains) == 0 || (len(o.allowedDomains) > 0 && isAllowedDomain(o.allowedDomains, userProfile.Email)) { + return userProfile.Email, nil + } + + return "", errForbidden +} + +func isAllowedDomain(allowedDomains []string, email string) bool { + for _, domain := range allowedDomains { + if strings.HasSuffix(email, fmt.Sprintf("@%s", domain)) { + return true + } + } + + return false +} + +func (o oauthHanlderGoogle) getOauthBase() oauthBase { + return o.oauthBase +} diff --git a/pkg/storage/jwt.go b/pkg/storage/jwt.go new file mode 100644 index 0000000000..20a027c0a2 --- /dev/null +++ b/pkg/storage/jwt.go @@ -0,0 +1,68 @@ +package storage + +import ( + "crypto/rand" + "math/big" + + "github.com/dgraph-io/badger/v2" +) + +const ( + jwtLenght = 32 + jwtSecret = "jwtSecret" +) + +func (s *Storage) JWT() (string, error) { + var secret []byte + err := s.db.View(func(txn *badger.Txn) error { + item, err := txn.Get([]byte(jwtSecret)) + if err != nil { + if err == badger.ErrKeyNotFound { + return nil + } + return err + } + + err = item.Value(func(val []byte) error { + secret = append([]byte{}, val...) + return nil + }) + if err != nil { + return err + } + return nil + }) + if err != nil { + return "", err + } + + if secret == nil { + generatedJWT, err := newJWTSecret() + if err != nil { + return "", err + } + secret = []byte(generatedJWT) + err = s.db.Update(func(txn *badger.Txn) error { + return txn.SetEntry(badger.NewEntry([]byte(jwtSecret), secret)) + }) + if err != nil { + return "", err + } + } + + return string(secret), nil +} + +func newJWTSecret() (string, error) { + const letters = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz-" + ret := make([]byte, jwtLenght) + for i := 0; i < jwtLenght; i++ { + num, err := rand.Int(rand.Reader, big.NewInt(int64(len(letters)))) + if err != nil { + return "", err + } + ret[i] = letters[num.Int64()] + } + + return string(ret), nil +} diff --git a/pyroscope.go b/pyroscope.go new file mode 100644 index 0000000000..150292eac6 --- /dev/null +++ b/pyroscope.go @@ -0,0 +1,4 @@ +package pyroscope + +// this file is only here because otherwise pkger fails. +// See: https://github.com/markbates/pkger/issues/132 diff --git a/scripts/generate-sample-config/main.go b/scripts/generate-sample-config/main.go index 3d0a5cd5e1..4c38fc6bd8 100644 --- a/scripts/generate-sample-config/main.go +++ b/scripts/generate-sample-config/main.go @@ -15,6 +15,8 @@ import ( "unicode" "github.com/sirupsen/logrus" + "github.com/spf13/pflag" + "github.com/spf13/viper" "github.com/pyroscope-io/pyroscope/pkg/cli" "github.com/pyroscope-io/pyroscope/pkg/config" @@ -82,7 +84,13 @@ func processFile(path string) error { } func writeConfigDocs(w io.Writer, subcommand, format string) { - flagSet := flag.NewFlagSet("pyroscope "+subcommand, flag.ExitOnError) + flagSet := pflag.NewFlagSet("pyroscope "+subcommand, pflag.ExitOnError) + + v := viper.New() + v.SetEnvPrefix("PYROSCOPE") + v.AutomaticEnv() + v.SetEnvKeyReplacer(strings.NewReplacer("-", "_", ".", "_")) + opts := []cli.FlagOption{ cli.WithReplacement("", "pyspy, rbspy, phpspy, dotnetspy, ebpfspy"), cli.WithSkipDeprecated(true), @@ -94,45 +102,44 @@ func writeConfigDocs(w io.Writer, subcommand, format string) { val = new(config.Agent) // Skip `targets` only from CLI reference. if format == "md" { - cli.PopulateFlagSet(val, flagSet, append(opts, cli.WithSkip("targets"))...) + cli.PopulateFlagSet(val, flagSet, v, append(opts, cli.WithSkip("targets"))...) } else { - cli.PopulateFlagSet(val, flagSet, opts...) + cli.PopulateFlagSet(val, flagSet, v, opts...) } case "server": val = new(config.Server) // Skip `metric-export-rules` only from CLI reference. if format == "md" { - cli.PopulateFlagSet(val, flagSet, append(opts, cli.WithSkip("metric-export-rules"))...) + cli.PopulateFlagSet(val, flagSet, v, append(opts, cli.WithSkip("metric-export-rules"))...) } else { - cli.PopulateFlagSet(val, flagSet, opts...) + cli.PopulateFlagSet(val, flagSet, v, opts...) } case "convert": val = new(config.Convert) - cli.PopulateFlagSet(val, flagSet, opts...) + cli.PopulateFlagSet(val, flagSet, v, opts...) case "exec": val = new(config.Exec) - cli.PopulateFlagSet(val, flagSet, append(opts, cli.WithSkip("pid"))...) + cli.PopulateFlagSet(val, flagSet, v, append(opts, cli.WithSkip("pid"))...) case "connect": val = new(config.Exec) - cli.PopulateFlagSet(val, flagSet, append(opts, cli.WithSkip("group-name", "user-name", "no-root-drop"))...) + cli.PopulateFlagSet(val, flagSet, v, append(opts, cli.WithSkip("group-name", "user-name", "no-root-drop"))...) case "target": val = new(config.Target) - cli.PopulateFlagSet(val, flagSet, append(opts, cli.WithSkip("tags"))...) + cli.PopulateFlagSet(val, flagSet, v, append(opts, cli.WithSkip("tags"))...) case "metric-export-rule": val = new(config.MetricExportRule) - cli.PopulateFlagSet(val, flagSet, opts...) + cli.PopulateFlagSet(val, flagSet, v, opts...) default: log.Fatalf("Unknown subcommand %q", subcommand) } _, _ = fmt.Fprintf(w, "\n", subcommand, format) - sf := cli.NewSortedFlags(val, flagSet, nil) switch format { case "yaml": - writeYaml(w, sf) + writeYaml(w, flagSet) case "md": - writeMarkdown(w, sf) + writeMarkdown(w, flagSet) default: logrus.Fatalf("Unknown format %q", format) } @@ -140,9 +147,9 @@ func writeConfigDocs(w io.Writer, subcommand, format string) { _, _ = fmt.Fprintf(w, "") } -func writeYaml(w io.Writer, sf *cli.SortedFlags) { +func writeYaml(w io.Writer, flagSet *pflag.FlagSet) { _, _ = fmt.Fprintf(w, "```yaml\n---\n") - sf.VisitAll(func(f *flag.Flag) { + flagSet.VisitAll(func(f *pflag.Flag) { if f.Name == "config" { return } @@ -158,10 +165,10 @@ func writeYaml(w io.Writer, sf *cli.SortedFlags) { _, _ = fmt.Fprintf(w, "```\n") } -func writeMarkdown(w io.Writer, sf *cli.SortedFlags) { +func writeMarkdown(w io.Writer, flagSet *pflag.FlagSet) { _, _ = fmt.Fprintf(w, "| %s | %s | %s |\n", "Name", "Default Value", "Usage") _, _ = fmt.Fprintf(w, "| %s | %s | %s |\n", ":-", ":-", ":-") - sf.VisitAll(func(f *flag.Flag) { + flagSet.VisitAll(func(f *pflag.Flag) { if f.Name == "config" { return }