modifies the command line interface handling / timer work
Some checks reported errors
continuous-integration/drone/push Build was killed
continuous-integration/drone Build was killed

This commit is contained in:
Marvin Preuss 2021-08-09 10:49:04 +02:00
parent d5a7d1d9a4
commit 63a16a3253
20 changed files with 983 additions and 55 deletions

View File

@ -5,11 +5,13 @@ import (
"fmt" "fmt"
"os" "os"
"strings" "strings"
"time"
"github.com/fsnotify/fsnotify" "github.com/fsnotify/fsnotify"
"github.com/rs/zerolog" "github.com/rs/zerolog"
"github.com/rs/zerolog/log" "github.com/rs/zerolog/log"
"github.com/spf13/cobra" "github.com/spf13/cobra"
"github.com/spf13/pflag"
"github.com/spf13/viper" "github.com/spf13/viper"
"go.xsfx.dev/schnutibox/internal/config" "go.xsfx.dev/schnutibox/internal/config"
"go.xsfx.dev/schnutibox/pkg/prepare" "go.xsfx.dev/schnutibox/pkg/prepare"
@ -67,16 +69,23 @@ func init() {
// Timer. // Timer.
rootCmd.AddCommand(timerCmd) rootCmd.AddCommand(timerCmd)
timerCmd.Flags().String("hostname", "localhost", "Hostname of schnutibox")
timerCmd.Flags().Int("port", 6600, "Port of schnutibox")
timerCmd.Flags().DurationP("duration", "d", time.Minute, "Duration until the timer stops the playback")
if err := timerCmd.MarkFlagRequired("duration"); err != nil {
log.Fatal().Err(err).Msg("missing flag")
}
// Defaults. // Defaults.
viper.SetDefault("box.hostname", "localhost") viper.SetDefault("box.hostname", "localhost")
viper.SetDefault("box.port", 9999) viper.SetDefault("box.port", 9999)
viper.SetDefault("box.grpc", 9998)
viper.SetDefault("mpd.hostname", "localhost") viper.SetDefault("mpd.hostname", "localhost")
viper.SetDefault("mpd.port", 6600) viper.SetDefault("mpd.port", 6600)
viper.SetDefault("reader.dev", "/dev/hidraw0") viper.SetDefault("reader.dev", "/dev/hidraw0")
viper.SetDefault("reader.ignore", false) viper.SetDefault("reader.ignore", false)
viper.SetDefault("pprof", false) viper.SetDefault("debug.pprof", false)
viper.SetDefault("timer.duration", time.Minute)
// Environment handling. // Environment handling.
viper.SetEnvPrefix("SCHNUTIBOX") viper.SetEnvPrefix("SCHNUTIBOX")
@ -84,16 +93,17 @@ func init() {
viper.AutomaticEnv() viper.AutomaticEnv()
// Flags. // Flags.
if err := viper.BindPFlag("pprof", rootCmd.PersistentFlags().Lookup("pprof")); err != nil { for k, v := range map[string]*pflag.Flag{
"debug.pprof": rootCmd.PersistentFlags().Lookup("pprof"),
"reader.dev": prepareCmd.Flags().Lookup("rfid-reader"),
"reader.ignore": runCmd.Flags().Lookup("ignore-reader"),
"box.hostname": timerCmd.Flags().Lookup("hostname"),
"box.port": timerCmd.Flags().Lookup("port"),
"timer.duration": timerCmd.Flags().Lookup("duration"),
} {
if err := viper.BindPFlag(k, v); err != nil {
log.Fatal().Err(err).Msg("could not bind flag") log.Fatal().Err(err).Msg("could not bind flag")
} }
if err := viper.BindPFlag("reader.dev", prepareCmd.Flags().Lookup("rfid-reader")); err != nil {
log.Fatal().Err(err).Msg("could not bind flag")
}
if err := viper.BindPFlag("reader.ignore", runCmd.Flags().Lookup("ignore-reader")); err != nil {
log.Fatal().Err(err).Msg("could not bind flag")
} }
} }
@ -103,18 +113,25 @@ func initConfig(fatal bool) {
logger := log.With().Str("config", cfgFile).Logger() logger := log.With().Str("config", cfgFile).Logger()
// Parse config file. // Parse config file.
if cfgFile != "" { if cfgFile == "" && fatal {
viper.SetConfigFile(cfgFile)
parseConfig(logger, fatal)
} else {
logger.Fatal().Msg("missing config file") logger.Fatal().Msg("missing config file")
} else {
logger.Warn().Msg("missing config file")
} }
// Dont mind if there is no config file... viper also should populate
// flags and environment variables.
viper.SetConfigFile(cfgFile)
parseConfig(logger, fatal)
// Configfile changes watch only enabled if there is a config file.
if cfgFile != "" {
viper.WatchConfig() viper.WatchConfig()
viper.OnConfigChange(func(e fsnotify.Event) { viper.OnConfigChange(func(e fsnotify.Event) {
logger.Info().Msg("config file changed") logger.Info().Msg("config file changed")
parseConfig(logger, false) parseConfig(logger, false)
}) })
}
} }
// parseConfig parses the config and does some tests if required fields are there. // parseConfig parses the config and does some tests if required fields are there.
@ -126,8 +143,6 @@ func parseConfig(logger zerolog.Logger, fatal bool) {
} }
logger.Error().Err(err).Msg("error loading config file") logger.Error().Err(err).Msg("error loading config file")
return
} }
if err := viper.Unmarshal(&config.Cfg); err != nil { if err := viper.Unmarshal(&config.Cfg); err != nil {
@ -136,10 +151,11 @@ func parseConfig(logger zerolog.Logger, fatal bool) {
} }
logger.Error().Err(err).Msg("could not unmarshal config") logger.Error().Err(err).Msg("could not unmarshal config")
return
} }
// Disabling require check if no config is set.
// Not sure about this!
if cfgFile != "" {
if err := config.Cfg.Require(); err != nil { if err := config.Cfg.Require(); err != nil {
if fatal { if fatal {
logger.Fatal().Err(err).Msg("missing config parts") logger.Fatal().Err(err).Msg("missing config parts")
@ -147,6 +163,11 @@ func parseConfig(logger zerolog.Logger, fatal bool) {
logger.Error().Err(err).Msg("missing config parts") logger.Error().Err(err).Msg("missing config parts")
return
}
} else {
logger.Warn().Msg("doesnt do a config requirement check")
return return
} }
} }

View File

@ -1,6 +1,10 @@
package cmd package cmd
import ( import (
"os"
"github.com/rs/zerolog"
"github.com/rs/zerolog/log"
"github.com/spf13/cobra" "github.com/spf13/cobra"
"go.xsfx.dev/schnutibox/pkg/timer" "go.xsfx.dev/schnutibox/pkg/timer"
) )
@ -10,4 +14,8 @@ var timerCmd = &cobra.Command{
Use: "timer", Use: "timer",
Short: "Handling timer", Short: "Handling timer",
Run: timer.Run, Run: timer.Run,
PreRun: func(cmd *cobra.Command, args []string) {
log.Logger = log.Output(zerolog.ConsoleWriter{Out: os.Stderr})
initConfig(false)
},
} }

3
go.mod
View File

@ -8,14 +8,17 @@ require (
github.com/containerd/continuity v0.0.0-20210208174643-50096c924a4e // indirect github.com/containerd/continuity v0.0.0-20210208174643-50096c924a4e // indirect
github.com/fhs/gompd/v2 v2.2.0 github.com/fhs/gompd/v2 v2.2.0
github.com/fsnotify/fsnotify v1.4.7 github.com/fsnotify/fsnotify v1.4.7
github.com/golang/protobuf v1.5.2
github.com/grpc-ecosystem/grpc-gateway/v2 v2.4.0 github.com/grpc-ecosystem/grpc-gateway/v2 v2.4.0
github.com/helloyi/go-sshclient v1.0.0 github.com/helloyi/go-sshclient v1.0.0
github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51 // indirect github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51 // indirect
github.com/moby/term v0.0.0-20201216013528-df9cb8a40635 // indirect github.com/moby/term v0.0.0-20201216013528-df9cb8a40635 // indirect
github.com/ory/dockertest/v3 v3.6.3 github.com/ory/dockertest/v3 v3.6.3
github.com/philip-bui/grpc-zerolog v1.0.1
github.com/prometheus/client_golang v0.9.3 github.com/prometheus/client_golang v0.9.3
github.com/rs/zerolog v1.22.0 github.com/rs/zerolog v1.22.0
github.com/spf13/cobra v1.1.3 github.com/spf13/cobra v1.1.3
github.com/spf13/pflag v1.0.5
github.com/spf13/viper v1.7.0 github.com/spf13/viper v1.7.0
github.com/tmc/scp v0.0.0-20170824174625-f7b48647feef github.com/tmc/scp v0.0.0-20170824174625-f7b48647feef
go.xsfx.dev/don v1.0.0 go.xsfx.dev/don v1.0.0

3
go.sum
View File

@ -274,6 +274,8 @@ github.com/ory/dockertest/v3 v3.6.3/go.mod h1:EFLcVUOl8qCwp9NyDAcCDtq/QviLtYswW/
github.com/pascaldekloe/goe v0.0.0-20180627143212-57f6aae5913c/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc= github.com/pascaldekloe/goe v0.0.0-20180627143212-57f6aae5913c/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc=
github.com/pelletier/go-toml v1.2.0 h1:T5zMGML61Wp+FlcbWjRDT7yAxhJNAiPPLOFECq181zc= github.com/pelletier/go-toml v1.2.0 h1:T5zMGML61Wp+FlcbWjRDT7yAxhJNAiPPLOFECq181zc=
github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic= github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic=
github.com/philip-bui/grpc-zerolog v1.0.1 h1:EMacvLRUd2O1K0eWod27ZP5CY1iTNkhBDLSN+Q4JEvA=
github.com/philip-bui/grpc-zerolog v1.0.1/go.mod h1:qXbiq/2X4ZUMMshsqlWyTHOcw7ns+GZmlqZZN05ZHcQ=
github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= 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.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
@ -336,6 +338,7 @@ github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An
github.com/spf13/viper v1.7.0 h1:xVKxvI7ouOI5I+U9s2eeiUfMaWBVoXA3AWskkrqK0VM= github.com/spf13/viper v1.7.0 h1:xVKxvI7ouOI5I+U9s2eeiUfMaWBVoXA3AWskkrqK0VM=
github.com/spf13/viper v1.7.0/go.mod h1:8WkrPz2fc9jxqZNCJI/76HCieCp4Q8HaLFoCha5qpdg= github.com/spf13/viper v1.7.0/go.mod h1:8WkrPz2fc9jxqZNCJI/76HCieCp4Q8HaLFoCha5qpdg=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.1.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/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/testify v1.2.2/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.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=

View File

@ -11,6 +11,10 @@ import (
var Cfg Config var Cfg Config
type Config struct { type Config struct {
Debug struct {
PPROF bool `mapstructure:"PPROF"`
}
// Reader is used to configure the RFID Reader. // Reader is used to configure the RFID Reader.
Reader struct { Reader struct {
Dev string `mapstructure:"Dev"` Dev string `mapstructure:"Dev"`

View File

@ -0,0 +1,18 @@
package grpcclient
import (
"fmt"
"google.golang.org/grpc"
)
func Conn(hostname string, port int) (*grpc.ClientConn, error) {
var conn *grpc.ClientConn
conn, err := grpc.Dial(fmt.Sprintf("%s:%d", hostname, port), grpc.WithInsecure())
if err != nil {
return nil, fmt.Errorf("could not connect: %w", err)
}
return conn, nil
}

View File

@ -1,45 +1,77 @@
package timer package timer
import ( import (
"context"
"github.com/golang/protobuf/ptypes/duration" "github.com/golang/protobuf/ptypes/duration"
"github.com/rs/zerolog/log" "github.com/rs/zerolog/log"
"github.com/spf13/cobra" "github.com/spf13/cobra"
"github.com/spf13/viper"
"go.xsfx.dev/schnutibox/internal/config"
"go.xsfx.dev/schnutibox/internal/grpcclient"
api "go.xsfx.dev/schnutibox/pkg/api/v1" api "go.xsfx.dev/schnutibox/pkg/api/v1"
"go.xsfx.dev/schnutibox/pkg/mpc"
"google.golang.org/protobuf/types/known/durationpb"
) )
// nolint:gochecknoglobals // nolint:gochecknoglobals
var T = &api.Timer{} var T = &Timer{}
func Timer() { type Timer struct {
if T.Duration != nil { Req *api.Timer
}
func (t *Timer) Handle() {
if t.Req != nil {
// Initialize the current object. // Initialize the current object.
if T.Current == nil { if t.Req.Current == nil {
T.Current = &duration.Duration{} t.Req.Current = &duration.Duration{}
T.Current.Seconds = T.Duration.Seconds t.Req.Current.Seconds = t.Req.Duration.Seconds
} }
switch { switch {
// There is some timing going on. // There is some timing going on.
case T.Duration.Seconds != 0 && T.Current.Seconds != 0: case t.Req.Duration.Seconds != 0 && t.Req.Current.Seconds != 0:
log.Debug(). log.Debug().
Int64("current", T.Current.Seconds). Int64("current", t.Req.Current.Seconds).
Int64("duration", T.Duration.Seconds). Int64("duration", t.Req.Duration.Seconds).
Msg("timer is running") Msg("timer is running")
if T.Current.Seconds > 0 { if t.Req.Current.Seconds > 0 {
T.Current.Seconds -= 1 t.Req.Current.Seconds -= 1
return return
} }
// No timer is running... so setting the duration to 0. // No timer is running... so setting the duration to 0.
// TODO: Needs to do something actually! case t.Req.Current.Seconds == 0 && t.Req.Duration.Seconds != 0:
case T.Current.Seconds == 0 && T.Duration.Seconds != 0:
log.Debug().Msg("stoping timer") log.Debug().Msg("stoping timer")
T.Duration.Seconds = 0 if err := mpc.Stop(log.Logger); err != nil {
log.Error().Err(err).Msg("could not stop")
}
t.Req.Duration.Seconds = 0
} }
} }
} }
func Run(cmd *cobra.Command, args []string) {} // Run is the command line interface for triggering the timer.
func Run(cmd *cobra.Command, args []string) {
conn, err := grpcclient.Conn(config.Cfg.Box.Hostname, config.Cfg.Box.Port)
if err != nil {
log.Fatal().Err(err).Msg("could not connect")
}
defer conn.Close()
c := api.NewTimerServiceClient(conn)
d := durationpb.New(viper.GetDuration("timer.duration"))
_, err = c.Create(context.Background(), &api.Timer{Duration: d})
if err != nil {
log.Fatal().Err(err).Msg("could not create timer")
}
log.Info().Msg("added timer")
}

View File

@ -23,19 +23,20 @@ func Run() {
<-ticker.C <-ticker.C
// Timer. // Timer.
go timer.Timer() go timer.T.Handle()
// Metrics. // Metrics.
go func() { go func() {
m, err := mpc.Conn() m, err := mpc.Conn()
if err != nil { if err != nil {
// log.Error().Err(err).Msg("could not connect") log.Error().Err(err).Msg("could not connect")
return return
} }
uris, err := mpc.PlaylistURIS() uris, err := mpc.PlaylistURIS()
if err != nil { if err != nil {
// log.Error().Err(err).Msg("could not get playlist uris") log.Error().Err(err).Msg("could not get playlist uris")
metrics.BoxErrors.Inc() metrics.BoxErrors.Inc()
return return
@ -44,7 +45,7 @@ func Run() {
// Gettings MPD state. // Gettings MPD state.
s, err := m.Status() s, err := m.Status()
if err != nil { if err != nil {
// log.Error().Err(err).Msg("could not get status") log.Error().Err(err).Msg("could not get status")
metrics.BoxErrors.Inc() metrics.BoxErrors.Inc()
return return

View File

@ -9,10 +9,10 @@ import (
"strings" "strings"
"github.com/grpc-ecosystem/grpc-gateway/v2/runtime" "github.com/grpc-ecosystem/grpc-gateway/v2/runtime"
zerolog "github.com/philip-bui/grpc-zerolog"
"github.com/prometheus/client_golang/prometheus/promhttp" "github.com/prometheus/client_golang/prometheus/promhttp"
"github.com/rs/zerolog/log" "github.com/rs/zerolog/log"
"github.com/spf13/cobra" "github.com/spf13/cobra"
"github.com/spf13/viper"
"go.xsfx.dev/logginghandler" "go.xsfx.dev/logginghandler"
assets "go.xsfx.dev/schnutibox/assets/web" assets "go.xsfx.dev/schnutibox/assets/web"
"go.xsfx.dev/schnutibox/internal/config" "go.xsfx.dev/schnutibox/internal/config"
@ -81,14 +81,14 @@ func (i identifyServer) Identify(ctx context.Context, in *api.IdentifyRequest) (
type timerServer struct{} type timerServer struct{}
func (t timerServer) Create(ctx context.Context, req *api.Timer) (*api.Timer, error) { func (t timerServer) Create(ctx context.Context, req *api.Timer) (*api.Timer, error) {
timer.T = req timer.T.Req = req
return timer.T, nil return timer.T.Req, nil
} }
// Get just returns the status of the timer. // Get just returns the status of the timer.
func (t timerServer) Get(ctx context.Context, req *api.TimerEmpty) (*api.Timer, error) { func (t timerServer) Get(ctx context.Context, req *api.TimerEmpty) (*api.Timer, error) {
return timer.T, nil return timer.T.Req, nil
} }
func gw(s *grpc.Server, conn string) *runtime.ServeMux { func gw(s *grpc.Server, conn string) *runtime.ServeMux {
@ -114,7 +114,9 @@ func Run(command *cobra.Command, args []string) {
lh := fmt.Sprintf("%s:%d", config.Cfg.Box.Hostname, config.Cfg.Box.Port) lh := fmt.Sprintf("%s:%d", config.Cfg.Box.Hostname, config.Cfg.Box.Port)
// Create grpc server. // Create grpc server.
grpcServer := grpc.NewServer() grpcServer := grpc.NewServer(
zerolog.UnaryInterceptor(),
)
// Define http handlers. // Define http handlers.
mux := http.NewServeMux() mux := http.NewServeMux()
@ -138,7 +140,7 @@ func Run(command *cobra.Command, args []string) {
mux.Handle("/api/", http.StripPrefix("/api", gw(grpcServer, lh))) mux.Handle("/api/", http.StripPrefix("/api", gw(grpcServer, lh)))
// PPROF. // PPROF.
if viper.GetBool("pprof") { if config.Cfg.Debug.PPROF {
mux.HandleFunc("/debug/pprof/", pprof.Index) mux.HandleFunc("/debug/pprof/", pprof.Index)
} }

16
vendor/github.com/philip-bui/grpc-zerolog/.gitignore generated vendored Normal file
View File

@ -0,0 +1,16 @@
# Binaries for programs and plugins
*.exe
*.exe~
*.dll
*.so
*.dylib
# Test binary, build with `go test -c`
*.test
# Output of the go coverage tool, specifically when used with LiteIDE
*.out
/vendor
grpc-zerolog
coverage.txt

14
vendor/github.com/philip-bui/grpc-zerolog/.travis.yml generated vendored Normal file
View File

@ -0,0 +1,14 @@
language: go
go:
- tip
before_install:
- curl https://raw.githubusercontent.com/golang/dep/master/install.sh | sh
- dep ensure
script:
- go test -race -coverprofile=coverage.txt -covermode=atomic
after_success:
- bash <(curl -s https://codecov.io/bash)

259
vendor/github.com/philip-bui/grpc-zerolog/Gopkg.lock generated vendored Normal file
View File

@ -0,0 +1,259 @@
# This file is autogenerated, do not edit; changes may be undone by the next 'dep ensure'.
[[projects]]
digest = "1:a2c1d0e43bd3baaa071d1b9ed72c27d78169b2b269f71c105ac4ba34b1be4a39"
name = "github.com/davecgh/go-spew"
packages = ["spew"]
pruneopts = "UT"
revision = "346938d642f2ec3594ed81d874461961cd0faa76"
version = "v1.1.0"
[[projects]]
digest = "1:4ba8096f8ea6ea9f7aefb98e189e0128324883f0b4e839800ce4b37c90230c5c"
name = "github.com/golang/protobuf"
packages = [
"jsonpb",
"proto",
"ptypes",
"ptypes/any",
"ptypes/duration",
"ptypes/struct",
"ptypes/timestamp",
]
pruneopts = "UT"
revision = "d04d7b157bb510b1e0c10132224b616ac0e26b17"
version = "v1.4.2"
[[projects]]
digest = "1:0028cb19b2e4c3112225cd871870f2d9cf49b9b4276531f03438a88e94be86fe"
name = "github.com/pmezard/go-difflib"
packages = ["difflib"]
pruneopts = "UT"
revision = "792786c7400a136282c1664665ae0a8db921c6c2"
version = "v1.0.0"
[[projects]]
digest = "1:342baf1b227dbc8829a60dc06cd7a5e70d3de350b501e2184ec8f19fe4418d4a"
name = "github.com/rs/zerolog"
packages = [
".",
"internal/cbor",
"internal/json",
"log",
]
pruneopts = "UT"
revision = "7825d863376faee2723fc99c061c538bd80812c8"
version = "v1.19.0"
[[projects]]
digest = "1:ac83cf90d08b63ad5f7e020ef480d319ae890c208f8524622a2f3136e2686b02"
name = "github.com/stretchr/objx"
packages = ["."]
pruneopts = "UT"
revision = "477a77ecc69700c7cdeb1fa9e129548e1c1c393c"
version = "v0.1.1"
[[projects]]
digest = "1:5201127841a78d84d0ca68a2e564c08e3882c0fb9321a75997ce87926e0d63ea"
name = "github.com/stretchr/testify"
packages = [
"assert",
"mock",
"require",
"suite",
]
pruneopts = "UT"
revision = "f654a9112bbeac49ca2cd45bfbe11533c4666cf8"
version = "v1.6.1"
[[projects]]
branch = "master"
digest = "1:dc1b5a4f3a84b5c8503036630dd0f245de44d9c22414ddff0e5e473849139fd3"
name = "golang.org/x/net"
packages = [
"context",
"http/httpguts",
"http2",
"http2/hpack",
"idna",
"internal/timeseries",
"trace",
]
pruneopts = "UT"
revision = "ed29d75add3d7c4bf7ca65aac0c6df3d1420216f"
[[projects]]
digest = "1:a2ab62866c75542dd18d2b069fec854577a20211d7c0ea6ae746072a1dccdd18"
name = "golang.org/x/text"
packages = [
"collate",
"collate/build",
"internal/colltab",
"internal/gen",
"internal/tag",
"internal/triegen",
"internal/ucd",
"language",
"secure/bidirule",
"transform",
"unicode/bidi",
"unicode/cldr",
"unicode/norm",
"unicode/rangetable",
]
pruneopts = "UT"
revision = "f21a4dfb5e38f5895301dc265a8def02365cc3d0"
version = "v0.3.0"
[[projects]]
branch = "master"
digest = "1:601e63e7d4577f907118bec825902505291918859d223bce015539e79f1160e3"
name = "google.golang.org/genproto"
packages = ["googleapis/rpc/status"]
pruneopts = "UT"
revision = "ff3583edef7de132f219f0efc00e097cabcc0ec0"
[[projects]]
digest = "1:2dab32a43451e320e49608ff4542fdfc653c95dcc35d0065ec9c6c3dd540ed74"
name = "google.golang.org/grpc"
packages = [
".",
"balancer",
"balancer/base",
"balancer/roundrobin",
"codes",
"connectivity",
"credentials",
"encoding",
"encoding/proto",
"grpclog",
"internal",
"internal/backoff",
"internal/channelz",
"internal/grpcrand",
"keepalive",
"metadata",
"naming",
"peer",
"resolver",
"resolver/dns",
"resolver/passthrough",
"stats",
"status",
"tap",
"transport",
]
pruneopts = "UT"
revision = "168a6198bcb0ef175f7dacec0b8691fc141dc9b8"
version = "v1.13.0"
[[projects]]
digest = "1:6a351cb031761dcd668e5aca9a10bd6d1675aa5427ac1483a61c3a49b2d64962"
name = "google.golang.org/protobuf"
packages = [
"encoding/protojson",
"encoding/prototext",
"encoding/protowire",
"internal/descfmt",
"internal/descopts",
"internal/detrand",
"internal/encoding/defval",
"internal/encoding/json",
"internal/encoding/messageset",
"internal/encoding/tag",
"internal/encoding/text",
"internal/errors",
"internal/fieldsort",
"internal/filedesc",
"internal/filetype",
"internal/flags",
"internal/genid",
"internal/impl",
"internal/mapsort",
"internal/pragma",
"internal/set",
"internal/strs",
"internal/version",
"proto",
"reflect/protoreflect",
"reflect/protoregistry",
"runtime/protoiface",
"runtime/protoimpl",
"types/known/anypb",
"types/known/durationpb",
"types/known/structpb",
"types/known/timestamppb",
]
pruneopts = "UT"
revision = "3f7a61f89bb6813f89d981d1870ed68da0b3c3f1"
version = "v1.25.0"
[[projects]]
branch = "v3"
digest = "1:229cb0f6192914f518cc1241ede6d6f1f458b31debfa18bf3a5c9e4f7b01e24b"
name = "gopkg.in/yaml.v3"
packages = ["."]
pruneopts = "UT"
revision = "eeeca48fe7764f320e4870d231902bf9c1be2c08"
[solve-meta]
analyzer-name = "dep"
analyzer-version = 1
input-imports = [
"github.com/davecgh/go-spew/spew",
"github.com/golang/protobuf/jsonpb",
"github.com/golang/protobuf/proto",
"github.com/golang/protobuf/ptypes",
"github.com/golang/protobuf/ptypes/any",
"github.com/golang/protobuf/ptypes/duration",
"github.com/golang/protobuf/ptypes/struct",
"github.com/golang/protobuf/ptypes/timestamp",
"github.com/pmezard/go-difflib/difflib",
"github.com/rs/zerolog",
"github.com/rs/zerolog/log",
"github.com/stretchr/objx",
"github.com/stretchr/testify/assert",
"github.com/stretchr/testify/mock",
"github.com/stretchr/testify/require",
"github.com/stretchr/testify/suite",
"golang.org/x/net/context",
"golang.org/x/net/http/httpguts",
"golang.org/x/net/http2",
"golang.org/x/net/http2/hpack",
"golang.org/x/net/idna",
"golang.org/x/net/trace",
"golang.org/x/text/collate",
"golang.org/x/text/collate/build",
"golang.org/x/text/language",
"golang.org/x/text/secure/bidirule",
"golang.org/x/text/transform",
"golang.org/x/text/unicode/bidi",
"golang.org/x/text/unicode/cldr",
"golang.org/x/text/unicode/norm",
"golang.org/x/text/unicode/rangetable",
"google.golang.org/genproto/googleapis/rpc/status",
"google.golang.org/grpc",
"google.golang.org/grpc/balancer",
"google.golang.org/grpc/balancer/base",
"google.golang.org/grpc/balancer/roundrobin",
"google.golang.org/grpc/codes",
"google.golang.org/grpc/connectivity",
"google.golang.org/grpc/credentials",
"google.golang.org/grpc/encoding",
"google.golang.org/grpc/encoding/proto",
"google.golang.org/grpc/grpclog",
"google.golang.org/grpc/keepalive",
"google.golang.org/grpc/metadata",
"google.golang.org/grpc/naming",
"google.golang.org/grpc/peer",
"google.golang.org/grpc/resolver",
"google.golang.org/grpc/resolver/dns",
"google.golang.org/grpc/resolver/passthrough",
"google.golang.org/grpc/stats",
"google.golang.org/grpc/status",
"google.golang.org/grpc/tap",
"google.golang.org/grpc/transport",
]
solver-name = "gps-cdcl"
solver-version = 1

46
vendor/github.com/philip-bui/grpc-zerolog/Gopkg.toml generated vendored Normal file
View File

@ -0,0 +1,46 @@
# Gopkg.toml example
#
# Refer to https://github.com/golang/dep/blob/master/docs/Gopkg.toml.md
# for detailed Gopkg.toml documentation.
#
# required = ["github.com/user/thing/cmd/thing"]
# ignored = ["github.com/user/project/pkgX", "bitbucket.org/user/project/pkgA/pkgY"]
#
# [[constraint]]
# name = "github.com/user/project"
# version = "1.0.0"
#
# [[constraint]]
# name = "github.com/user/project2"
# branch = "dev"
# source = "github.com/myfork/project2"
#
# [[override]]
# name = "github.com/x/y"
# version = "2.4.0"
#
# [prune]
# non-go = false
# go-tests = true
# unused-packages = true
[[constraint]]
name = "github.com/golang/protobuf"
version = "1.1.0"
[[constraint]]
name = "github.com/rs/zerolog"
version = "1.8.0"
[[constraint]]
name = "google.golang.org/grpc"
version = "1.13.0"
[prune]
go-tests = true
unused-packages = true
[[constraint]]
name = "github.com/stretchr/testify"
version = "1.2.2"

21
vendor/github.com/philip-bui/grpc-zerolog/LICENSE generated vendored Normal file
View File

@ -0,0 +1,21 @@
MIT License
Copyright (c) 2019 Philip Bui
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

17
vendor/github.com/philip-bui/grpc-zerolog/Makefile generated vendored Normal file
View File

@ -0,0 +1,17 @@
PORT?=8000
PACKAGE:=github.com/philip-bui/grpc-zerolog
COVERAGE:=coverage.txt
proto:
protoc -I protos/ protos/*.proto --go_out=plugins=grpc:protos
godoc:
echo "localhost:${PORT}/pkg/${PACKAGE}"
godoc -http=:${PORT}
.PHONY: test
test:
go test -race -coverprofile=${COVERAGE} -covermode=atomic
coverage:
go tool cover -html=${COVERAGE}

49
vendor/github.com/philip-bui/grpc-zerolog/README.md generated vendored Normal file
View File

@ -0,0 +1,49 @@
# gRPC Zerolog
[![BuildStatus Widget]][BuildStatus Result]
[![CodeCov Widget]][CodeCov Result]
[![GoReport Widget]][GoReport Status]
[![GoDoc Widget]][GoDoc]
[BuildStatus Result]: https://travis-ci.org/philip-bui/grpc-zerolog
[BuildStatus Widget]: https://travis-ci.org/philip-bui/grpc-zerolog.svg?branch=master
[CodeCov Result]: https://codecov.io/gh/philip-bui/grpc-zerolog
[CodeCov Widget]: https://codecov.io/gh/philip-bui/grpc-zerolog/branch/master/graph/badge.svg
[GoReport Status]: https://goreportcard.com/report/github.com/philip-bui/grpc-zerolog
[GoReport Widget]: https://goreportcard.com/badge/github.com/philip-bui/grpc-zerolog
[GoDoc]: https://godoc.org/github.com/philip-bui/grpc-zerolog
[GoDoc Widget]: https://godoc.org/github.com/philip-bui/grpc-zerolog?status.svg
Implementation of gRPC Logging Middleware, integrating [Zerolog](https://github.com/rs/zerolog) as a gRPC [Interceptor](https://github.com/grpc-ecosystem/go-grpc-middleware) to log the following fields:
- Request Protobufs as JSON.
- Response Protobufs as JSON, or Errors.
- Status Code, Duration, Timestamp, Service Name, Service Method, IP, Metadata Fields and User Agent.
## Usage
```go
import (
"github.com/philip-bui/grpc-zerolog"
)
func main() {
// With global Zerolog logger.
grpc.NewServer(
zerolog.UnaryInterceptor(),
)
// With custom Zerolog instance.
log := zerolog.New(os.Stdout)
grpc.NewServer(
zerolog.UnaryInterceptorWithLogger(&log),
)
}
```
## License
gRPC Zerolog is available under the MIT license. [See LICENSE](https://github.com/philip-bui/grpc-zerolog/blob/master/LICENSE) for details.

View File

@ -0,0 +1,66 @@
package zerolog
import (
"context"
"time"
"github.com/rs/zerolog"
"github.com/rs/zerolog/log"
"google.golang.org/grpc"
)
// UnaryInterceptor is a gRPC Server Option that uses NewUnaryServerInterceptor() to log gRPC Requests.
func UnaryInterceptor() grpc.ServerOption {
return grpc.UnaryInterceptor(NewUnaryServerInterceptor())
}
func UnaryInterceptorWithLogger(log *zerolog.Logger) grpc.ServerOption {
return grpc.UnaryInterceptor(NewUnaryServerInterceptorWithLogger(log))
}
// NewUnaryServerInterceptor that logs gRPC Requests using Zerolog.
// {
// ServiceField: "ExampleService",
// MethodField: "ExampleMethod",
// DurationField: 1.00
//
// IpField: "127.0.0.1",
//
// MetadataField: {},
//
// UserAgentField: "ExampleClientUserAgent",
// ReqField: {}, // JSON representation of Request Protobuf
//
// Err: "An unexpected error occurred",
// CodeField: "Unknown",
// MsgField: "Error message returned from the server",
// DetailsField: [Errors],
//
// RespField: {}, // JSON representation of Response Protobuf
//
// ZerologMessageField: "UnaryMessageDefault",
// }
func NewUnaryServerInterceptor() grpc.UnaryServerInterceptor {
return NewUnaryServerInterceptorWithLogger(&log.Logger)
}
func NewUnaryServerInterceptorWithLogger(log *zerolog.Logger) grpc.UnaryServerInterceptor {
return func(ctx context.Context, req interface{}, info *grpc.UnaryServerInfo, handler grpc.UnaryHandler) (interface{}, error) {
now := time.Now()
resp, err := handler(ctx, req)
if log.Error().Enabled() {
if err != nil {
logger := log.Error()
LogIncomingCall(ctx, logger, info.FullMethod, now, req)
LogStatusError(logger, err)
logger.Msg(UnaryMessageDefault)
} else if log.Info().Enabled() {
logger := log.Info()
LogIncomingCall(ctx, logger, info.FullMethod, now, req)
LogResponse(logger, resp)
logger.Msg(UnaryMessageDefault)
}
}
return resp, err
}
}

224
vendor/github.com/philip-bui/grpc-zerolog/util.go generated vendored Normal file
View File

@ -0,0 +1,224 @@
package zerolog
import (
"bytes"
"context"
"path"
"strings"
"time"
"github.com/golang/protobuf/jsonpb"
"github.com/golang/protobuf/proto"
"github.com/rs/zerolog"
"google.golang.org/grpc/metadata"
"google.golang.org/grpc/peer"
"google.golang.org/grpc/status"
)
var (
// Marshaller of Protobuf to JSON
Marshaller = &jsonpb.Marshaler{}
// TimestampLog call start.
TimestampLog = true
// ServiceField key.
ServiceField = "service"
// ServiceLog gRPC service name.
ServiceLog = true
// MethodField key.
MethodField = "method"
// MethodLog gRPC method name.
MethodLog = true
// DurationField key.
DurationField = "dur"
// DurationLog gRPC call duration.
DurationLog = true
// IPField key.
IPField = "ip"
// IPLog gRPC client IP.
IPLog = true
// MetadataField key.
MetadataField = "md"
// MetadataLog gRPC call metadata.
MetadataLog = true
// UserAgentField key.
UserAgentField = "ua"
// UserAgentLog gRPC client User Agent.
UserAgentLog = true
// ReqField key.
ReqField = "req"
// ReqLog gRPC request body.
ReqLog = true
// RespField key.
RespField = "resp"
// RespLog gRPC response body.
RespLog = true
// MaxSize to log gRPC bodies.
MaxSize = 2048000
// CodeField gRPC status code response.
CodeField = "code"
// MsgField gRPC response message.
MsgField = "msg"
// DetailsField gRPC response errors.
DetailsField = "details"
// UnaryMessageDefault of logging messages from unary.
UnaryMessageDefault = "unary"
)
// LogIncomingCall of gRPC method.
// {
// ServiceField: ExampleService,
// MethodField: ExampleMethod,
// DurationField: 1.00,
// }
func LogIncomingCall(ctx context.Context, logger *zerolog.Event, method string, t time.Time, req interface{}) {
LogTimestamp(logger, t)
LogService(logger, method)
LogMethod(logger, method)
LogDuration(logger, t)
LogRequest(logger, req)
LogIncomingMetadata(ctx, logger)
}
// LogTimestamp of call.
// {
// TimestampField: Timestamp,
// }
func LogTimestamp(logger *zerolog.Event, t time.Time) {
if TimestampLog {
*logger = *logger.Time(zerolog.TimestampFieldName, t)
}
}
// LogService of gRPC name.
// {
// ServiceField: gRPCServiceName,
// }
func LogService(logger *zerolog.Event, method string) {
if ServiceLog {
*logger = *logger.Str(ServiceField, path.Dir(method)[1:])
}
}
// LogMethod of gRPC call.
// {
// MethodField: gRPCMethodName,
// }
func LogMethod(logger *zerolog.Event, method string) {
if MethodLog {
*logger = *logger.Str(MethodField, path.Base(method))
}
}
// LogDuration in seconds of gRPC call.
// {
// DurationField: Timestamp,
// }
func LogDuration(logger *zerolog.Event, t time.Time) {
if DurationLog {
*logger = *logger.Dur(DurationField, time.Since(t))
}
}
// LogIP address of gRPC client, if assigned.
// {
// IpField: 127.0.0.1
// }
func LogIP(ctx context.Context, logger *zerolog.Event) {
if IPLog {
if p, ok := peer.FromContext(ctx); ok {
*logger = *logger.Str(IPField, p.Addr.String())
}
}
}
// LogRequest in JSON of gRPC Call, given Request is smaller than MaxSize (Default=2MB).
// {
// ReqField: {}
// }
func LogRequest(e *zerolog.Event, req interface{}) {
if ReqLog {
if b := GetRawJSON(req); b != nil {
*e = *e.RawJSON(ReqField, b.Bytes())
}
}
}
// LogResponse in JSON of gRPC Call, given Response is smaller than MaxSize (Default=2MB).
// {
// RespField: {}
// }
func LogResponse(e *zerolog.Event, resp interface{}) {
if RespLog {
if b := GetRawJSON(resp); b != nil {
*e = *e.RawJSON(RespField, b.Bytes())
}
}
}
// GetRawJSON converts a Protobuf message to JSON bytes if less than MaxSize.
func GetRawJSON(i interface{}) *bytes.Buffer {
if pb, ok := i.(proto.Message); ok {
b := &bytes.Buffer{}
if err := Marshaller.Marshal(b, pb); err == nil && b.Len() < MaxSize {
return b
}
}
return nil
}
// LogIncomingMetadata or UserAgent field of incoming gRPC Request, if assigned.
// {
// MetadataField: {
// MetadataKey1: MetadataValue1,
// }
// }
//
// {
// UserAgentField: "Client-assigned User-Agent",
// }
func LogIncomingMetadata(ctx context.Context, e *zerolog.Event) {
if md, ok := metadata.FromIncomingContext(ctx); ok {
if MetadataLog {
*e = *e.Dict(MetadataField, LogMetadata(&md))
return
} else if UserAgentLog {
LogUserAgent(e, &md)
}
}
}
// LogMetadata of gRPC Request
// {
// MetadataField: {
// MetadataKey1: MetadataValue1,
// }
// }
func LogMetadata(md *metadata.MD) *zerolog.Event {
dict := zerolog.Dict()
for i := range *md {
dict = dict.Str(i, strings.Join(md.Get(i), ","))
}
return dict
}
// LogUserAgent of gRPC Client, if assigned.
// {
// UserAgentField: "Client-assigned User-Agent",
// }
func LogUserAgent(logger *zerolog.Event, md *metadata.MD) {
if ua := strings.Join(md.Get("user-agent"), ""); ua != "" {
*logger = *logger.Str(UserAgentField, ua)
}
}
// LogStatusError of gRPC Error Response.
// {
// Err: "An unexpected error occurred",
// CodeField: "Unknown",
// MsgField: "Error message returned from the server",
// DetailsField: [Errors],
// }
func LogStatusError(logger *zerolog.Event, err error) {
statusErr := status.Convert(err)
*logger = *logger.Err(err).Str(CodeField, statusErr.Code().String()).Str(MsgField, statusErr.Message()).Interface(DetailsField, statusErr.Details())
}

119
vendor/github.com/philip-bui/grpc-zerolog/zerolog.go generated vendored Normal file
View File

@ -0,0 +1,119 @@
package zerolog
import (
"fmt"
"github.com/rs/zerolog"
"google.golang.org/grpc/grpclog"
)
// GrpcLogSetNewZeroLogger sets grpclog to a new GrpcZeroLogger.
func GrpcLogSetNewZeroLogger() {
grpclog.SetLoggerV2(NewGrpcZeroLogger(zerolog.Logger{}))
}
// GrpcLogSetZeroLogger sets grpclog to a GrpcZeroLogger.
func GrpcLogSetZeroLogger(logger GrpcZeroLogger) {
grpclog.SetLoggerV2(logger)
}
// GrpcZeroLogger transforms grpc log calls to Zerolog logger.
type GrpcZeroLogger struct {
log zerolog.Logger
}
// NewGrpcZeroLogger creates a new GrpcZeroLogger
func NewGrpcZeroLogger(logger zerolog.Logger) GrpcZeroLogger {
return GrpcZeroLogger{log: logger}
}
// Fatal fatals arguments.
func (l GrpcZeroLogger) Fatal(args ...interface{}) {
l.log.Fatal().Msg(fmt.Sprint(args...))
}
// Fatalf fatals formatted string with arguments.
func (l GrpcZeroLogger) Fatalf(format string, args ...interface{}) {
l.log.Fatal().Msgf(format, args...)
}
// Fatalln fatals and new line.
func (l GrpcZeroLogger) Fatalln(args ...interface{}) {
l.Fatal(args...)
}
// Error errors arguments.
func (l GrpcZeroLogger) Error(args ...interface{}) {
l.log.Error().Msg(fmt.Sprint(args...))
}
// Errorf errors formatted string with arguments.
func (l GrpcZeroLogger) Errorf(format string, args ...interface{}) {
l.log.Error().Msgf(format, args...)
}
// Errorln errors and new line.
func (l GrpcZeroLogger) Errorln(args ...interface{}) {
l.Error(args...)
}
// Info infos arguments.
func (l GrpcZeroLogger) Info(args ...interface{}) {
l.log.Info().Msg(fmt.Sprint(args...))
}
// Infof infos formatted string with arguments.
func (l GrpcZeroLogger) Infof(format string, args ...interface{}) {
l.log.Info().Msgf(format, args...)
}
// Infoln infos and new line.
func (l GrpcZeroLogger) Infoln(args ...interface{}) {
l.Info(args...)
}
// Warning warns arguments.
func (l GrpcZeroLogger) Warning(args ...interface{}) {
l.log.Warn().Msg(fmt.Sprint(args...))
}
// Warningf warns formatted string with arguments.
func (l GrpcZeroLogger) Warningf(format string, args ...interface{}) {
l.log.Warn().Msgf(format, args...)
}
// Warningln warns and new line.
func (l GrpcZeroLogger) Warningln(args ...interface{}) {
l.Warning(args...)
}
// Print logs arguments.
func (l GrpcZeroLogger) Print(args ...interface{}) {
l.Info(args...)
}
// Printf logs formatted string with arguments.
func (l GrpcZeroLogger) Printf(format string, args ...interface{}) {
l.Infof(format, args...)
}
// Println logs with new line.
func (l GrpcZeroLogger) Println(args ...interface{}) {
l.Infoln(args...)
}
// V determines Verbosity Level.
func (l GrpcZeroLogger) V(level int) bool {
switch level {
case 0:
return zerolog.InfoLevel <= zerolog.GlobalLevel()
case 1:
return zerolog.WarnLevel <= zerolog.GlobalLevel()
case 2:
return zerolog.ErrorLevel <= zerolog.GlobalLevel()
case 3:
return zerolog.FatalLevel <= zerolog.GlobalLevel()
default:
panic("unhandled gRPC logger level")
}
}

5
vendor/modules.txt vendored
View File

@ -156,6 +156,7 @@ github.com/golang/glog
# github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e # github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e
github.com/golang/groupcache/lru github.com/golang/groupcache/lru
# github.com/golang/protobuf v1.5.2 # github.com/golang/protobuf v1.5.2
## explicit
github.com/golang/protobuf/descriptor github.com/golang/protobuf/descriptor
github.com/golang/protobuf/jsonpb github.com/golang/protobuf/jsonpb
github.com/golang/protobuf/proto github.com/golang/protobuf/proto
@ -268,6 +269,9 @@ github.com/ory/dockertest/v3/docker/types/strslice
github.com/ory/dockertest/v3/docker/types/versions github.com/ory/dockertest/v3/docker/types/versions
# github.com/pelletier/go-toml v1.2.0 # github.com/pelletier/go-toml v1.2.0
github.com/pelletier/go-toml github.com/pelletier/go-toml
# github.com/philip-bui/grpc-zerolog v1.0.1
## explicit
github.com/philip-bui/grpc-zerolog
# github.com/pkg/errors v0.9.1 # github.com/pkg/errors v0.9.1
github.com/pkg/errors github.com/pkg/errors
# github.com/pkg/profile v1.5.0 # github.com/pkg/profile v1.5.0
@ -306,6 +310,7 @@ github.com/spf13/cobra
# github.com/spf13/jwalterweatherman v1.0.0 # github.com/spf13/jwalterweatherman v1.0.0
github.com/spf13/jwalterweatherman github.com/spf13/jwalterweatherman
# github.com/spf13/pflag v1.0.5 # github.com/spf13/pflag v1.0.5
## explicit
github.com/spf13/pflag github.com/spf13/pflag
# github.com/spf13/viper v1.7.0 # github.com/spf13/viper v1.7.0
## explicit ## explicit