diff --git a/README.md b/README.md index 5996d70..b876509 100644 --- a/README.md +++ b/README.md @@ -1,34 +1,6 @@ -# Yeet +# Yeet ![GitHub Workflow Status](https://img.shields.io/github/workflow/status/vilsol/yeet/build) ![GitHub go.mod Go version](https://img.shields.io/github/go-mod/go-version/vilsol/yeet) ![GitHub tag (latest by date)](https://img.shields.io/github/v/tag/vilsol/yeet) -![GitHub Workflow Status](https://img.shields.io/github/workflow/status/vilsol/yeet/build) -![GitHub go.mod Go version](https://img.shields.io/github/go-mod/go-version/vilsol/yeet) -![GitHub tag (latest by date)](https://img.shields.io/github/v/tag/vilsol/yeet) - -``` -Usage: - yeet [command] - -Available Commands: - completion generate the autocompletion script for the specified shell - help Help about any command - serve Run the webserver - -Flags: - --colors Force output with colors - --expiry Use cache expiry - --expiry-interval duration Interval between cache GC's (default 10m0s) - --expiry-time duration Lifetime of a cache entry (default 1h0m0s) - -h, --help help for yeet - --host string Hostname to bind the webserver - --index-file string The directory default index file (default "index.html") - --log string The log level to output (default "info") - --paths strings Paths to serve on the webserver (default [./www]) - --port int Port to run the webserver on (default 8080) - --warmup Load all files into memory on startup - --watch Watch filesystem for changes - -Use "yeet [command] --help" for more information about a command. -``` +CLI Usage: [Docs](./docs/yeet.md) ## Docker @@ -63,16 +35,3 @@ BenchmarkServerGet2ReqPerConn10KClientsExpiry 11266317 10 BenchmarkServerGet10ReqPerConn10KClientsExpiry 15184057 776 ns/op 0 B/op 0 allocs/op BenchmarkServerGet100ReqPerConn10KClientsExpiry 16339011 714 ns/op 0 B/op 0 allocs/op ``` - -### With file watching - -``` -BenchmarkServerGet1ReqPerConnWatch 8282121 1430 ns/op 0 B/op 0 allocs/op -BenchmarkServerGet2ReqPerConnWatch 11046026 1080 ns/op 0 B/op 0 allocs/op -BenchmarkServerGet10ReqPerConnWatch 15087688 791 ns/op 0 B/op 0 allocs/op -BenchmarkServerGet10KReqPerConnWatch 16619936 733 ns/op 0 B/op 0 allocs/op -BenchmarkServerGet1ReqPerConn10KClientsWatch 8312827 1460 ns/op 0 B/op 0 allocs/op -BenchmarkServerGet2ReqPerConn10KClientsWatch 10938738 1097 ns/op 0 B/op 0 allocs/op -BenchmarkServerGet10ReqPerConn10KClientsWatch 14515534 811 ns/op 0 B/op 0 allocs/op -BenchmarkServerGet100ReqPerConn10KClientsWatch 16010731 717 ns/op 0 B/op 0 allocs/op -``` \ No newline at end of file diff --git a/cache/cache.go b/cache/cache.go index c3fb4cd..15977ba 100644 --- a/cache/cache.go +++ b/cache/cache.go @@ -1,6 +1,10 @@ package cache -import "time" +import ( + "github.com/Vilsol/yeet/source" + "io" + "time" +) type CachedInstance struct { RelativePath string @@ -10,7 +14,20 @@ type CachedInstance struct { LoadTime time.Time } +type commonInstance struct { + Instance *CachedInstance + Get func(instance *commonInstance) (string, io.Reader, int) +} + +type KeyValue struct { + Key string + Value *commonInstance +} + type Cache interface { Index() (int64, error) - Get(path []byte) (string, []byte) + Get(path []byte, host []byte) (string, io.Reader, int) + Source() source.Source + Iter() <-chan KeyValue + Store(path []byte, host []byte, instance *commonInstance) } diff --git a/cache/hashmap.go b/cache/hashmap.go new file mode 100644 index 0000000..e31ce66 --- /dev/null +++ b/cache/hashmap.go @@ -0,0 +1,122 @@ +package cache + +import ( + "github.com/Vilsol/yeet/source" + "github.com/cornelk/hashmap" + "github.com/rs/zerolog/log" + "github.com/spf13/viper" + "io" +) + +var _ Cache = (*HashMapCache)(nil) + +type HashMapCache struct { + data *hashmap.HashMap + source source.Source + hosts bool +} + +func NewHashMapCache(source source.Source, hosts bool) (*HashMapCache, error) { + data := &hashmap.HashMap{} + + c := &HashMapCache{ + data: data, + source: source, + hosts: hosts, + } + + if viper.GetBool("expiry") { + expiry(c) + } + + return c, nil +} + +func (c *HashMapCache) Index() (int64, error) { + totalCount, err := indexBase(c) + + if viper.GetBool("watch") { + events, err := c.source.Watch() + if err != nil { + return 0, err + } + + go func(c *HashMapCache, events <-chan source.WatchEvent) { + for event := range events { + switch event.Op { + case source.WatchRename: + fallthrough + case source.WatchDelete: + c.data.Del(event.CleanPath) + log.Trace().Msgf("Removed from cache: %s", event.CleanPath) + case source.WatchCreate: + instance := &commonInstance{ + Instance: &CachedInstance{ + RelativePath: event.CleanPath, + AbsolutePath: event.AbsPath, + }, + Get: load(c), + } + + c.data.Set(event.CleanPath, instance) + + if viper.GetBool("warmup") { + instance.Get(instance) + } + + log.Trace().Msgf("Added to cache: %s", event.CleanPath) + case source.WatchModify: + instance, ok := c.data.GetStringKey(event.CleanPath) + if ok { + instance.(*commonInstance).Get = load(c) + instance.(*commonInstance).Instance.Data = nil + instance.(*commonInstance).Instance.ContentType = "" + log.Trace().Msgf("Evicted from cache: %s", event.CleanPath) + } + } + } + }(c, events) + } + + return totalCount, err +} + +func (c *HashMapCache) Store(path []byte, host []byte, instance *commonInstance) { + if !c.hosts { + c.data.Set(path, instance) + } else { + c.data.Set(append(host, path...), instance) + } +} + +func (c *HashMapCache) Get(path []byte, host []byte) (string, io.Reader, int) { + if !c.hosts { + if instance, ok := c.data.Get(path); ok { + return instance.(*commonInstance).Get(instance.(*commonInstance)) + } + } else { + if instance, ok := c.data.Get(append(host, path...)); ok { + return instance.(*commonInstance).Get(instance.(*commonInstance)) + } + } + + return "", nil, 0 +} + +func (c *HashMapCache) Source() source.Source { + return c.source +} + +func (c *HashMapCache) Iter() <-chan KeyValue { + ch := make(chan KeyValue) + go func(c *HashMapCache, ch chan KeyValue) { + for keyVal := range c.data.Iter() { + ch <- KeyValue{ + Key: keyVal.Key.(string), + Value: keyVal.Value.(*commonInstance), + } + } + close(ch) + }(c, ch) + return ch +} diff --git a/cache/readonly.go b/cache/readonly.go deleted file mode 100644 index c6c4611..0000000 --- a/cache/readonly.go +++ /dev/null @@ -1,94 +0,0 @@ -package cache - -import ( - "github.com/pkg/errors" - log "github.com/sirupsen/logrus" - "github.com/spf13/viper" - "time" -) - -type readOnlyInstance struct { - Instance *CachedInstance - Get func(instance *readOnlyInstance) (string, []byte) -} - -type readOnlyCache struct { - Cache - data map[string]*readOnlyInstance -} - -func NewReadOnlyCache() (*readOnlyCache, error) { - if viper.GetBool("watch") { - return nil, errors.New("read only cache doesn't support file watching") - } - - c := &readOnlyCache{ - data: make(map[string]*readOnlyInstance), - } - - if viper.GetBool("expiry") { - go func(c *readOnlyCache) { - ticker := time.NewTicker(viper.GetDuration("expiry.interval")) - defer ticker.Stop() - for range ticker.C { - cleanBefore := time.Now().Add(viper.GetDuration("expiry.time") * -1) - for key, instance := range c.data { - if instance.Instance.Data != nil && instance.Instance.LoadTime.Before(cleanBefore) { - instance.Get = loadReadOnly - instance.Instance.Data = nil - instance.Instance.ContentType = "" - log.Tracef("Evicted from cache: %s", key) - } - } - } - }(c) - } - - return c, nil -} - -func (c *readOnlyCache) Index() (int64, error) { - return index(func(absolutePath string, cleanedPath string) int64 { - instance := &readOnlyInstance{ - Instance: &CachedInstance{ - RelativePath: cleanedPath, - AbsolutePath: absolutePath, - }, - Get: loadReadOnly, - } - - c.data[cleanedPath] = instance - - if viper.GetBool("warmup") { - if viper.GetBool("expiry") { - panic("expiry not supported if warmup is enabled") - } - - instance.Get(instance) - return int64(len(instance.Instance.Data)) - } - - return 0 - }) -} - -func (c *readOnlyCache) Get(path []byte) (string, []byte) { - if instance, ok := c.data[string(path)]; ok { - return instance.Get(instance) - } - - return "", nil -} - -func loadReadOnly(instance *readOnlyInstance) (string, []byte) { - fileType, data := load(instance.Instance) - - instance.Instance.LoadTime = time.Now() - instance.Instance.Data = data - instance.Instance.ContentType = fileType - instance.Get = func(cache *readOnlyInstance) (string, []byte) { - return cache.Instance.ContentType, cache.Instance.Data - } - - return fileType, data -} diff --git a/cache/readwrite.go b/cache/readwrite.go deleted file mode 100644 index a3d7fea..0000000 --- a/cache/readwrite.go +++ /dev/null @@ -1,154 +0,0 @@ -package cache - -import ( - "github.com/cornelk/hashmap" - "github.com/fsnotify/fsnotify" - log "github.com/sirupsen/logrus" - "github.com/spf13/viper" - "path/filepath" - "strings" - "time" -) - -type readWriteInstance struct { - Instance *CachedInstance - Get func(instance *readWriteInstance) (string, []byte) -} - -type readWriteCache struct { - Cache - data *hashmap.HashMap -} - -func NewReadWriteCache() (*readWriteCache, error) { - data := &hashmap.HashMap{} - - c := &readWriteCache{ - data: data, - } - - if viper.GetBool("expiry") { - go func(c *readWriteCache) { - ticker := time.NewTicker(viper.GetDuration("expiry.interval")) - defer ticker.Stop() - for range ticker.C { - cleanBefore := time.Now().Add(viper.GetDuration("expiry.time") * -1) - for keyVal := range c.data.Iter() { - instance := keyVal.Value.(*readWriteInstance) - if instance.Instance.Data != nil && instance.Instance.LoadTime.Before(cleanBefore) { - instance.Get = loadReadWrite - instance.Instance.Data = nil - instance.Instance.ContentType = "" - log.Tracef("Evicted from cache: %s", keyVal.Key) - } - } - } - }(c) - } - - return c, nil -} - -func (c *readWriteCache) Index() (int64, error) { - indexFunc := func(absolutePath string, cleanedPath string) int64 { - instance := &readWriteInstance{ - Instance: &CachedInstance{ - RelativePath: cleanedPath, - AbsolutePath: absolutePath, - }, - Get: loadReadWrite, - } - - c.data.Set(cleanedPath, instance) - - if viper.GetBool("warmup") { - if viper.GetBool("expiry") { - panic("expiry not supported if warmup is enabled") - } - - instance.Get(instance) - return int64(len(instance.Instance.Data)) - } - - return 0 - } - - totalCount, err := index(indexFunc) - - if viper.GetBool("watch") { - watcher, err := fsnotify.NewWatcher() - - if err != nil { - return 0, err - } - - go func(c *readWriteCache) { - for event := range watcher.Events { - dirPath := "" - for _, path := range viper.GetStringSlice("paths") { - if strings.HasPrefix(event.Name, filepath.Clean(path)) { - dirPath = path - } - } - - if dirPath == "" { - log.Warnf("Received update about an unknown path: %s", event.Name) - continue - } - - cleanPath := cleanPath(event.Name, dirPath) - - switch { - case event.Op&fsnotify.Rename == fsnotify.Rename: - fallthrough - case event.Op&fsnotify.Remove == fsnotify.Remove: - c.data.Del(cleanPath) - log.Tracef("File removed from disk and cache: %s", cleanPath) - case event.Op&fsnotify.Create == fsnotify.Create: - absPath, _ := filepath.Abs(event.Name) - indexFunc(absPath, cleanPath) - log.Tracef("File created on disk and added to cache: %s", cleanPath) - case event.Op&fsnotify.Write == fsnotify.Write: - instance, ok := c.data.GetStringKey(cleanPath) - if ok { - instance.(*readWriteInstance).Get = loadReadWrite - instance.(*readWriteInstance).Instance.Data = nil - instance.(*readWriteInstance).Instance.ContentType = "" - log.Tracef("File updated on disk and evicted from cache: %s", cleanPath) - } - } - } - }(c) - - for _, dirPath := range viper.GetStringSlice("paths") { - log.Debugf("Watching path: %s", dirPath) - err = watcher.Add(dirPath) - if err != nil { - return 0, err - } - } - } - - return totalCount, err -} - -func (c *readWriteCache) Get(path []byte) (string, []byte) { - if instance, ok := c.data.GetStringKey(string(path)); ok { - return instance.(*readWriteInstance).Get(instance.(*readWriteInstance)) - } - - return "", nil -} - -func loadReadWrite(instance *readWriteInstance) (string, []byte) { - fileType, data := load(instance.Instance) - - instance.Instance.LoadTime = time.Now() - instance.Instance.Data = data - instance.Instance.ContentType = fileType - instance.Get = func(cache *readWriteInstance) (string, []byte) { - return cache.Instance.ContentType, cache.Instance.Data - } - - return fileType, data -} diff --git a/cache/shared.go b/cache/shared.go index 7cb7be9..4de362a 100644 --- a/cache/shared.go +++ b/cache/shared.go @@ -1,46 +1,47 @@ package cache import ( + "bytes" + "github.com/Vilsol/yeet/source" "github.com/Vilsol/yeet/utils" "github.com/pkg/errors" - log "github.com/sirupsen/logrus" + "github.com/rs/zerolog/log" "github.com/spf13/viper" - "io/ioutil" - "mime" - "net/http" - "os" + "io" "path" - "path/filepath" - "strings" + "reflect" + "time" + "unsafe" ) -type IndexFunc = func(absolutePath string, cleanedPath string) int64 - -func load(instance *CachedInstance) (string, []byte) { - fileName := filepath.Base(instance.AbsolutePath) - fileType := mime.TypeByExtension(filepath.Ext(fileName)) +func indexBase(c Cache) (int64, error) { + return index(c.Source(), func(absolutePath string, cleanedPath string) int64 { + instance := &commonInstance{ + Instance: &CachedInstance{ + RelativePath: cleanedPath, + AbsolutePath: absolutePath, + }, + Get: load(c), + } - data, err := ioutil.ReadFile(instance.AbsolutePath) - if err != nil { - log.Error(errors.Wrap(err, "error reading file")) - return "", nil - } + // Host is not used when indexing is supported + c.Store(unsafeGetBytes(cleanedPath), nil, instance) - if fileType == "" { - fileType = http.DetectContentType(data[:512]) - } - - log.Debugf("Loaded into cache: %s", instance.AbsolutePath) + if viper.GetBool("warmup") { + instance.Get(instance) + return int64(len(instance.Instance.Data)) + } - return fileType, data + return 0 + }) } -func index(f IndexFunc) (int64, error) { +func index(source source.Source, f source.IndexFunc) (int64, error) { totalSize := int64(0) totalCount := int64(0) for _, dirPath := range viper.GetStringSlice("paths") { cleanPath := path.Clean(dirPath) - pathSize, pathCount, err := indexPath(cleanPath, f) + pathSize, pathCount, err := source.IndexPath(cleanPath, f) if err != nil { return 0, errors.Wrap(err, "error indexing path "+cleanPath) } @@ -49,72 +50,57 @@ func index(f IndexFunc) (int64, error) { } if viper.GetBool("warmup") { - log.Infof("Indexed %d files with %s of memory usage", totalCount, utils.ByteCountToHuman(totalSize)) + log.Info().Msgf("Indexed %d files with %s of memory usage", totalCount, utils.ByteCountToHuman(totalSize)) } else { - log.Infof("Indexed %d files", totalCount) + log.Info().Msgf("Indexed %d files", totalCount) } return totalSize, nil } -func indexPath(dirPath string, f IndexFunc) (int64, int64, error) { - totalSize := int64(0) - totalCount := int64(0) - - if err := filepath.Walk(dirPath, func(path string, info os.FileInfo, err error) error { - if err != nil { - return err - } +func load(c Cache) func(*commonInstance) (string, io.Reader, int) { + return func(instance *commonInstance) (string, io.Reader, int) { + hijacker := c.Source().Get(instance.Instance.AbsolutePath, nil) - filePath := path + log.Debug().Msgf("Loaded file [%d][%s]: %s", hijacker.Size, hijacker.FileType(), instance.Instance.AbsolutePath) - if info.IsDir() { - indexFile := viper.GetString("index.file") - if indexFile != "" { - joined := filepath.Join(path, indexFile) - _, err := os.Stat(joined) - if err != nil && !os.IsNotExist(err) { - return err - } else if err != nil { - return nil - } - filePath = joined - } else { - return nil + hijacker.OnClose = func(h *utils.StreamHijacker) { + instance.Instance.LoadTime = time.Now() + instance.Instance.Data = hijacker.Buffer + instance.Instance.ContentType = hijacker.FileType() + instance.Get = func(cache *commonInstance) (string, io.Reader, int) { + return cache.Instance.ContentType, bytes.NewReader(cache.Instance.Data), len(cache.Instance.Data) } } - absPath, _ := filepath.Abs(filePath) - cleanedPath := cleanPath(path, dirPath) - totalSize += f(absPath, cleanedPath) - totalCount++ - - log.Tracef("Indexed: %s -> %s", cleanedPath, absPath) - - return nil - }); err != nil { - return 0, 0, err + return hijacker.FileType(), hijacker, hijacker.Size } - - return totalSize, totalCount, nil } -func cleanPath(path string, dirPath string) string { - trimmed := strings.Trim(strings.ReplaceAll(filepath.Clean(dirPath), "\\", "/"), "/") - toRemove := len(strings.Split(trimmed, "/")) - - if trimmed == "." || trimmed == "" { - toRemove = 0 - } - - cleanedPath := strings.ReplaceAll(filepath.Clean(path), "\\", "/") - - // Remove the initial path - cleanedPath = strings.Join(strings.Split(cleanedPath, "/")[toRemove:], "/") +func expiry(c Cache) { + go func(c Cache) { + ticker := time.NewTicker(viper.GetDuration("expiry.interval")) + defer ticker.Stop() + for range ticker.C { + cleanBefore := time.Now().Add(viper.GetDuration("expiry.time") * -1) + for keyVal := range c.Iter() { + if keyVal.Value.Instance.Data != nil && keyVal.Value.Instance.LoadTime.Before(cleanBefore) { + keyVal.Value.Get = load(c) + keyVal.Value.Instance.Data = nil + keyVal.Value.Instance.ContentType = "" + log.Trace().Msgf("Evicted from cache: %s", keyVal.Key) + } + } + } + }(c) +} - if !strings.HasPrefix(cleanedPath, "/") { - cleanedPath = "/" + cleanedPath - } +func unsafeGetBytes(s string) []byte { + return (*[0x7fff0000]byte)(unsafe.Pointer( + (*reflect.StringHeader)(unsafe.Pointer(&s)).Data), + )[:len(s):len(s)] +} - return cleanedPath +func byteSliceToString(bs []byte) string { + return *(*string)(unsafe.Pointer(&bs)) } diff --git a/cmd/root.go b/cmd/root.go index ff8d732..57f4a51 100644 --- a/cmd/root.go +++ b/cmd/root.go @@ -1,17 +1,20 @@ package cmd import ( - log "github.com/sirupsen/logrus" + "github.com/pkg/errors" + "github.com/rs/zerolog" + "github.com/rs/zerolog/log" "github.com/spf13/cobra" "github.com/spf13/viper" + "io" "os" "time" ) -var rootCmd = &cobra.Command{ +var RootCMD = &cobra.Command{ Use: "yeet", Short: "yeet is an in-memory indexed static file webserver", - PersistentPreRun: func(cmd *cobra.Command, args []string) { + PersistentPreRunE: func(cmd *cobra.Command, args []string) error { viper.SetConfigName("config") viper.AddConfigPath(".") viper.SetEnvPrefix("yeet") @@ -19,17 +22,34 @@ var rootCmd = &cobra.Command{ _ = viper.ReadInConfig() - level, err := log.ParseLevel(viper.GetString("log")) - + level, err := zerolog.ParseLevel(viper.GetString("log")) if err != nil { panic(err) } - log.SetFormatter(&log.TextFormatter{ - ForceColors: viper.GetBool("colors"), - }) - log.SetOutput(os.Stdout) - log.SetLevel(level) + zerolog.SetGlobalLevel(level) + + writers := make([]io.Writer, 0) + if !viper.GetBool("quiet") { + writers = append(writers, zerolog.ConsoleWriter{ + Out: os.Stdout, + TimeFormat: time.RFC3339, + }) + } + + if viper.GetString("log-file") != "" { + logFile, err := os.OpenFile(viper.GetString("log-file"), os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0644) + + if err != nil { + return errors.Wrap(err, "failed to open log file") + } + + writers = append(writers, logFile) + } + + log.Logger = zerolog.New(io.MultiWriter(writers...)).With().Timestamp().Logger() + + return nil }, } @@ -37,48 +57,24 @@ func Execute() { // Allow running from explorer cobra.MousetrapHelpText = "" - // Execute serve command as default - cmd, _, err := rootCmd.Find(os.Args[1:]) - if (len(os.Args) <= 1 || os.Args[1] != "help") && (err != nil || cmd == rootCmd) { - args := append([]string{"serve"}, os.Args[1:]...) - rootCmd.SetArgs(args) + // Execute serve local command as default + cmd, _, err := RootCMD.Find(os.Args[1:]) + if (len(os.Args) <= 1 || os.Args[1] != "help") && (err != nil || cmd == RootCMD) { + args := append([]string{"serve", "local"}, os.Args[1:]...) + RootCMD.SetArgs(args) } - if err := rootCmd.Execute(); err != nil { + if err := RootCMD.Execute(); err != nil { panic(err) } } func init() { - rootCmd.PersistentFlags().String("log", "info", "The log level to output") - rootCmd.PersistentFlags().Bool("colors", false, "Force output with colors") - - rootCmd.PersistentFlags().String("host", "", "Hostname to bind the webserver") - rootCmd.PersistentFlags().Int("port", 8080, "Port to run the webserver on") - - rootCmd.PersistentFlags().StringSlice("paths", []string{"./www"}, "Paths to serve on the webserver") - rootCmd.PersistentFlags().Bool("warmup", false, "Load all files into memory on startup") - rootCmd.PersistentFlags().Bool("watch", false, "Watch filesystem for changes") - - rootCmd.PersistentFlags().Bool("expiry", false, "Use cache expiry") - rootCmd.PersistentFlags().Duration("expiry-time", time.Minute*60, "Lifetime of a cache entry") - rootCmd.PersistentFlags().Duration("expiry-interval", time.Minute*10, "Interval between cache GC's") - - rootCmd.PersistentFlags().String("index-file", "index.html", "The directory default index file") - - _ = viper.BindPFlag("log", rootCmd.PersistentFlags().Lookup("log")) - _ = viper.BindPFlag("colors", rootCmd.PersistentFlags().Lookup("colors")) - - _ = viper.BindPFlag("host", rootCmd.PersistentFlags().Lookup("host")) - _ = viper.BindPFlag("port", rootCmd.PersistentFlags().Lookup("port")) - - _ = viper.BindPFlag("paths", rootCmd.PersistentFlags().Lookup("paths")) - _ = viper.BindPFlag("warmup", rootCmd.PersistentFlags().Lookup("warmup")) - _ = viper.BindPFlag("watch", rootCmd.PersistentFlags().Lookup("watch")) - - _ = viper.BindPFlag("expiry", rootCmd.PersistentFlags().Lookup("expiry")) - _ = viper.BindPFlag("expiry.time", rootCmd.PersistentFlags().Lookup("expiry-time")) - _ = viper.BindPFlag("expiry.interval", rootCmd.PersistentFlags().Lookup("expiry-interval")) + RootCMD.PersistentFlags().String("log", "info", "The log level to output") + RootCMD.PersistentFlags().String("log-file", "", "File to output logs to") + RootCMD.PersistentFlags().Bool("quiet", false, "Do not log anything to console") - _ = viper.BindPFlag("index.file", rootCmd.PersistentFlags().Lookup("index-file")) + _ = viper.BindPFlag("log", RootCMD.PersistentFlags().Lookup("log")) + _ = viper.BindPFlag("log-file", RootCMD.PersistentFlags().Lookup("log-file")) + _ = viper.BindPFlag("quiet", RootCMD.PersistentFlags().Lookup("quiet")) } diff --git a/cmd/serve.go b/cmd/serve.go index 40a5a4b..670a1b1 100644 --- a/cmd/serve.go +++ b/cmd/serve.go @@ -1,18 +1,51 @@ package cmd import ( - "github.com/Vilsol/yeet/server" + "github.com/pkg/errors" "github.com/spf13/cobra" + "github.com/spf13/viper" + "time" ) func init() { - rootCmd.AddCommand(serveCmd) + ServeCMD.PersistentFlags().String("host", "", "Hostname to bind the webserver") + ServeCMD.PersistentFlags().Int("port", 8080, "Port to run the webserver on") + + ServeCMD.PersistentFlags().Bool("warmup", false, "Load all files into memory on startup") + + ServeCMD.PersistentFlags().Bool("expiry", false, "Use cache expiry") + ServeCMD.PersistentFlags().Duration("expiry-time", time.Minute*60, "Lifetime of a cache entry") + ServeCMD.PersistentFlags().Duration("expiry-interval", time.Minute*10, "Interval between cache GC's") + + ServeCMD.PersistentFlags().String("index-file", "index.html", "The directory default index file") + + ServeCMD.PersistentFlags().StringSliceP("paths", "p", []string{"./www"}, "Paths to serve on the webserver") + ServeCMD.PersistentFlags().BoolP("watch", "w", false, "Watch filesystem for changes") + + _ = viper.BindPFlag("paths", ServeCMD.PersistentFlags().Lookup("paths")) + _ = viper.BindPFlag("watch", ServeCMD.PersistentFlags().Lookup("watch")) + + _ = viper.BindPFlag("host", ServeCMD.PersistentFlags().Lookup("host")) + _ = viper.BindPFlag("port", ServeCMD.PersistentFlags().Lookup("port")) + + _ = viper.BindPFlag("warmup", ServeCMD.PersistentFlags().Lookup("warmup")) + + _ = viper.BindPFlag("expiry", ServeCMD.PersistentFlags().Lookup("expiry")) + _ = viper.BindPFlag("expiry.time", ServeCMD.PersistentFlags().Lookup("expiry-time")) + _ = viper.BindPFlag("expiry.interval", ServeCMD.PersistentFlags().Lookup("expiry-interval")) + + _ = viper.BindPFlag("index.file", ServeCMD.PersistentFlags().Lookup("index-file")) + + RootCMD.AddCommand(ServeCMD) } -var serveCmd = &cobra.Command{ +var ServeCMD = &cobra.Command{ Use: "serve", - Short: "Run the webserver", - RunE: func(cmd *cobra.Command, args []string) error { - return server.Run() + Short: "Serve files with yeet", + PreRunE: func(cmd *cobra.Command, args []string) error { + if viper.GetBool("warmup") && viper.GetBool("expiry") { + return errors.New("expiry not supported if warmup is enabled") + } + return nil }, } diff --git a/cmd/serve/local.go b/cmd/serve/local.go new file mode 100644 index 0000000..ddf4c59 --- /dev/null +++ b/cmd/serve/local.go @@ -0,0 +1,32 @@ +package serve + +import ( + "github.com/Vilsol/yeet/cache" + "github.com/Vilsol/yeet/cmd" + "github.com/Vilsol/yeet/server" + "github.com/Vilsol/yeet/source" + "github.com/spf13/cobra" +) + +func init() { + cmd.ServeCMD.AddCommand(localCmd) +} + +var localCmd = &cobra.Command{ + Use: "local", + Short: "Serve a local directory", + RunE: func(cmd *cobra.Command, args []string) error { + src := source.Local{} + c, err := cache.NewHashMapCache(src, false) + + if err != nil { + return err + } + + if _, err := c.Index(); err != nil { + return err + } + + return server.Run(c) + }, +} diff --git a/cmd/serve/s3.go b/cmd/serve/s3.go new file mode 100644 index 0000000..3aaa022 --- /dev/null +++ b/cmd/serve/s3.go @@ -0,0 +1,66 @@ +package serve + +import ( + "github.com/Vilsol/yeet/cache" + "github.com/Vilsol/yeet/cmd" + "github.com/Vilsol/yeet/server" + "github.com/Vilsol/yeet/source" + "github.com/pkg/errors" + "github.com/spf13/cobra" + "github.com/spf13/viper" +) + +func init() { + s3Cmd.Flags().StringP("bucket", "b", "", "S3 Bucket to fetch from") + s3Cmd.Flags().StringP("key", "k", "", "S3 Key of the account") + s3Cmd.Flags().StringP("secret", "s", "", "S3 Secret of the account") + s3Cmd.Flags().StringP("endpoint", "e", "", "S3 Endpoint") + s3Cmd.Flags().String("region", "us-west-002", "S3 Region of the bucket") + + _ = s3Cmd.MarkFlagRequired("bucket") + _ = s3Cmd.MarkFlagRequired("key") + _ = s3Cmd.MarkFlagRequired("secret") + _ = s3Cmd.MarkFlagRequired("endpoint") + + _ = viper.BindPFlag("bucket", s3Cmd.Flags().Lookup("bucket")) + _ = viper.BindPFlag("key", s3Cmd.Flags().Lookup("key")) + _ = viper.BindPFlag("secret", s3Cmd.Flags().Lookup("secret")) + _ = viper.BindPFlag("endpoint", s3Cmd.Flags().Lookup("endpoint")) + _ = viper.BindPFlag("region", s3Cmd.Flags().Lookup("region")) + + cmd.ServeCMD.AddCommand(s3Cmd) +} + +var s3Cmd = &cobra.Command{ + Use: "s3", + Short: "Serve an S3 bucket", + RunE: func(cmd *cobra.Command, args []string) error { + if viper.GetBool("watch") { + return errors.New("watch is not supported for s3") + } + + src, err := source.NewS3( + viper.GetString("bucket"), + viper.GetString("key"), + viper.GetString("secret"), + viper.GetString("endpoint"), + viper.GetString("region"), + ) + + if err != nil { + return err + } + + c, err := cache.NewHashMapCache(src, false) + + if err != nil { + return err + } + + if _, err := c.Index(); err != nil { + return err + } + + return server.Run(c) + }, +} diff --git a/docker-compose-dev.yml b/docker-compose-dev.yml new file mode 100644 index 0000000..2d4e173 --- /dev/null +++ b/docker-compose-dev.yml @@ -0,0 +1,17 @@ +version: '2' + +services: + redis: + image: redis:alpine + ports: + - 6379:6379 + + minio: + image: minio/minio + ports: + - 9000:9000 + - 9001:9001 + command: server /data --console-address ":9001" + environment: + MINIO_ROOT_USER: minio + MINIO_ROOT_PASSWORD: minio123 \ No newline at end of file diff --git a/docs/yeet.md b/docs/yeet.md new file mode 100644 index 0000000..745c751 --- /dev/null +++ b/docs/yeet.md @@ -0,0 +1,18 @@ +## yeet + +yeet is an in-memory indexed static file webserver + +### Options + +``` + -h, --help help for yeet + --log string The log level to output (default "info") + --log-file string File to output logs to + --quiet Do not log anything to console +``` + +### SEE ALSO + +* [yeet serve](yeet_serve.md) - Serve files with yeet + +###### Auto generated by spf13/cobra on 5-Jan-2022 diff --git a/docs/yeet_serve.md b/docs/yeet_serve.md new file mode 100644 index 0000000..225b4b8 --- /dev/null +++ b/docs/yeet_serve.md @@ -0,0 +1,34 @@ +## yeet serve + +Serve files with yeet + +### Options + +``` + --expiry Use cache expiry + --expiry-interval duration Interval between cache GC's (default 10m0s) + --expiry-time duration Lifetime of a cache entry (default 1h0m0s) + -h, --help help for serve + --host string Hostname to bind the webserver + --index-file string The directory default index file (default "index.html") + -p, --paths strings Paths to serve on the webserver (default [./www]) + --port int Port to run the webserver on (default 8080) + --warmup Load all files into memory on startup + -w, --watch Watch filesystem for changes +``` + +### Options inherited from parent commands + +``` + --log string The log level to output (default "info") + --log-file string File to output logs to + --quiet Do not log anything to console +``` + +### SEE ALSO + +* [yeet](yeet.md) - yeet is an in-memory indexed static file webserver +* [yeet serve local](yeet_serve_local.md) - Serve a local directory +* [yeet serve s3](yeet_serve_s3.md) - Serve an S3 bucket + +###### Auto generated by spf13/cobra on 5-Jan-2022 diff --git a/docs/yeet_serve_local.md b/docs/yeet_serve_local.md new file mode 100644 index 0000000..39c7e7d --- /dev/null +++ b/docs/yeet_serve_local.md @@ -0,0 +1,36 @@ +## yeet serve local + +Serve a local directory + +``` +yeet serve local [flags] +``` + +### Options + +``` + -h, --help help for local +``` + +### Options inherited from parent commands + +``` + --expiry Use cache expiry + --expiry-interval duration Interval between cache GC's (default 10m0s) + --expiry-time duration Lifetime of a cache entry (default 1h0m0s) + --host string Hostname to bind the webserver + --index-file string The directory default index file (default "index.html") + --log string The log level to output (default "info") + --log-file string File to output logs to + -p, --paths strings Paths to serve on the webserver (default [./www]) + --port int Port to run the webserver on (default 8080) + --quiet Do not log anything to console + --warmup Load all files into memory on startup + -w, --watch Watch filesystem for changes +``` + +### SEE ALSO + +* [yeet serve](yeet_serve.md) - Serve files with yeet + +###### Auto generated by spf13/cobra on 5-Jan-2022 diff --git a/docs/yeet_serve_s3.md b/docs/yeet_serve_s3.md new file mode 100644 index 0000000..cceb28e --- /dev/null +++ b/docs/yeet_serve_s3.md @@ -0,0 +1,41 @@ +## yeet serve s3 + +Serve an S3 bucket + +``` +yeet serve s3 [flags] +``` + +### Options + +``` + -b, --bucket string S3 Bucket to fetch from + -e, --endpoint string S3 Endpoint + -h, --help help for s3 + -k, --key string S3 Key of the account + --region string S3 Region of the bucket (default "us-west-002") + -s, --secret string S3 Secret of the account +``` + +### Options inherited from parent commands + +``` + --expiry Use cache expiry + --expiry-interval duration Interval between cache GC's (default 10m0s) + --expiry-time duration Lifetime of a cache entry (default 1h0m0s) + --host string Hostname to bind the webserver + --index-file string The directory default index file (default "index.html") + --log string The log level to output (default "info") + --log-file string File to output logs to + -p, --paths strings Paths to serve on the webserver (default [./www]) + --port int Port to run the webserver on (default 8080) + --quiet Do not log anything to console + --warmup Load all files into memory on startup + -w, --watch Watch filesystem for changes +``` + +### SEE ALSO + +* [yeet serve](yeet_serve.md) - Serve files with yeet + +###### Auto generated by spf13/cobra on 5-Jan-2022 diff --git a/go.mod b/go.mod index 487a5f8..e382e40 100644 --- a/go.mod +++ b/go.mod @@ -3,10 +3,12 @@ module github.com/Vilsol/yeet go 1.17 require ( + github.com/aws/aws-sdk-go v1.42.23 + github.com/bits-and-blooms/bloom/v3 v3.1.0 github.com/cornelk/hashmap v1.0.1 github.com/fsnotify/fsnotify v1.5.1 github.com/pkg/errors v0.9.1 - github.com/sirupsen/logrus v1.8.1 + github.com/rs/zerolog v1.26.1 github.com/spf13/cobra v1.2.1 github.com/spf13/viper v1.9.0 github.com/valyala/fasthttp v1.31.0 @@ -14,13 +16,18 @@ require ( require ( github.com/andybalholm/brotli v1.0.2 // indirect + github.com/bits-and-blooms/bitset v1.2.0 // indirect + github.com/cpuguy83/go-md2man/v2 v2.0.0 // indirect github.com/dchest/siphash v1.1.0 // indirect github.com/hashicorp/hcl v1.0.0 // indirect github.com/inconshreveable/mousetrap v1.0.0 // indirect + github.com/jmespath/go-jmespath v0.4.0 // indirect github.com/klauspost/compress v1.13.4 // indirect github.com/magiconair/properties v1.8.5 // indirect github.com/mitchellh/mapstructure v1.4.2 // indirect github.com/pelletier/go-toml v1.9.4 // indirect + github.com/russross/blackfriday/v2 v2.0.1 // indirect + github.com/shurcooL/sanitized_anchor_name v1.0.0 // indirect github.com/spf13/afero v1.6.0 // indirect github.com/spf13/cast v1.4.1 // indirect github.com/spf13/jwalterweatherman v1.1.0 // indirect diff --git a/go.sum b/go.sum index 0219c84..7fcc07a 100644 --- a/go.sum +++ b/go.sum @@ -43,14 +43,9 @@ cloud.google.com/go/storage v1.6.0/go.mod h1:N7U0C8pVQ/+NIKOBQyamJIeKQKkZ+mxpohl cloud.google.com/go/storage v1.8.0/go.mod h1:Wv1Oy7z6Yz3DshWRJFhqM/UCfaWIRTdp0RXyy7KQOVs= cloud.google.com/go/storage v1.10.0/go.mod h1:FLPqc6j+Ki4BU591ie1oL6qBQGu2Bl/tZ9ullr3+Kg0= dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU= -github.com/BurntSushi/toml v0.3.1 h1:WXkYYl6Yr3qBf1K79EBnL4mak0OimBfB0XUf9Vl28OQ= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo= github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU= -github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= -github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= -github.com/andybalholm/brotli v1.0.0 h1:7UCwP93aiSfvWpapti8g88vVVGp2qqtGyePsSuDafo4= -github.com/andybalholm/brotli v1.0.0/go.mod h1:loMXtMfwqflxFJPmdbJO0a3KNoPuLBgiu3qAvBg8x/Y= github.com/andybalholm/brotli v1.0.2 h1:JKnhI/XQ75uFBTiuzXpzFrUriDPiZjlOSzh6wXogP0E= github.com/andybalholm/brotli v1.0.2/go.mod h1:loMXtMfwqflxFJPmdbJO0a3KNoPuLBgiu3qAvBg8x/Y= github.com/antihax/optional v1.0.0/go.mod h1:uupD/76wgC+ih3iEmQUL+0Ugr19nfwCT1kdvxnR2qWY= @@ -58,10 +53,13 @@ github.com/armon/circbuf v0.0.0-20150827004946-bbbad097214e/go.mod h1:3U/XgcO3hC 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/armon/go-radix v1.0.0/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8= -github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q= -github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8= +github.com/aws/aws-sdk-go v1.42.23 h1:V0V5hqMEyVelgpu1e4gMPVCJ+KhmscdNxP/NWP1iCOA= +github.com/aws/aws-sdk-go v1.42.23/go.mod h1:gyRszuZ/icHmHAVE4gc/r+cfCmhA1AD+vqfWbgI+eHs= github.com/bgentry/speakeasy v0.1.0/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kBD4zp0CCIs= -github.com/bketelsen/crypt v0.0.3-0.20200106085610-5cbc8cc4026c/go.mod h1:MKsuJmJgSg28kpZDP6UIiPt0e0Oz0kqKNGyRaWEPv84= +github.com/bits-and-blooms/bitset v1.2.0 h1:Kn4yilvwNtMACtf1eYDlG8H77R07mZSPbMjLyS07ChA= +github.com/bits-and-blooms/bitset v1.2.0/go.mod h1:gIdJ4wp64HaoK2YrL1Q5/N7Y16edYb8uY+O0FJTyyDA= +github.com/bits-and-blooms/bloom/v3 v3.1.0 h1:o3Adl6bGuD9eZzMiLDepS5jqmoEAv/ZH+fFe/MH1quA= +github.com/bits-and-blooms/bloom/v3 v3.1.0/go.mod h1:MC8muvBzzPOFsrcdND/A7kU7kMhkqb9KI70JlZCP+C8= github.com/bketelsen/crypt v0.0.4/go.mod h1:aI6NrJ0pMGgvZKL1iVgXLnfIFJtfV+bKCoqOes/6LfM= github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc= @@ -73,22 +71,17 @@ github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGX 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/cncf/xds/go v0.0.0-20210312221358-fbca930ec8ed/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= -github.com/coreos/bbolt v1.3.2/go.mod h1:iRUV2dpdMOn7Bo10OQBFzIJO9kkE559Wcmn+qkEiiKk= -github.com/coreos/etcd v3.3.13+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE= github.com/coreos/go-semver v0.3.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk= -github.com/coreos/go-systemd v0.0.0-20190321100706-95778dfbb74e/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4= github.com/coreos/go-systemd/v22 v22.3.2/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc= -github.com/coreos/pkg v0.0.0-20180928190104-399ea9e2e55f/go.mod h1:E3G3o1h8I7cfcXa63jLwjI0eiQQMgzzUDFVpN/nH/eA= github.com/cornelk/hashmap v1.0.1 h1:RXGcy29hEdLLV8T6aK4s+BAd4tq4+3Hq50N2GoG0uIg= github.com/cornelk/hashmap v1.0.1/go.mod h1:8wbysTUDnwJGrPZ1Iwsou3m+An6sldFrJItjRhfegCw= +github.com/cpuguy83/go-md2man/v2 v2.0.0 h1:EoUDS0afbrsXAZ9YQ9jdu/mZ2sXgT1/2yyNng4PGlyM= github.com/cpuguy83/go-md2man/v2 v2.0.0/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/dchest/siphash v1.1.0 h1:1Rs9eTUlZLPBEvV+2sTaM8O0NWn0ppbgqS7p11aWawI= github.com/dchest/siphash v1.1.0/go.mod h1:q+IRvb2gOSrUnYoPqHiyHXS0FOBBOdl6tONBlVnOnt4= -github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ= -github.com/dgryski/go-sip13 v0.0.0-20181026042036-e10d5fee7954/go.mod h1:vAd38F8PWV+bWy6jNmig1y/TA+kYO4g3RSRF0IAv0no= github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98= @@ -99,8 +92,6 @@ github.com/envoyproxy/go-control-plane v0.9.9-0.20210512163311-63b5d3c536b0/go.m 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= -github.com/fsnotify/fsnotify v1.4.7 h1:IXs+QLmnXW2CcXuY+8Mzv/fWEsPGWxqefPtCP5CnV9I= -github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ= github.com/fsnotify/fsnotify v1.5.1 h1:mZcQUHVQUQWoPXXtuf9yuEXKudkV2sx1E06UadKWpgI= github.com/fsnotify/fsnotify v1.5.1/go.mod h1:T3375wBYaZdLLcVNkcVbzGHY7f1l/uK5T5Ai1i3InKU= @@ -108,16 +99,9 @@ github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeME github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU= github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= -github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= -github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE= -github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk= -github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= 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.2.1/go.mod h1:hp+jE20tsWTFYpLwKvXlhS1hjn+gTNwPg2I6zVXpSg4= github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= -github.com/golang/groupcache v0.0.0-20190129154638-5b532d6fd5ef/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= github.com/golang/groupcache v0.0.0-20191227052852-215e87163ea7/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= @@ -186,12 +170,7 @@ github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+ github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg= github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk= github.com/googleapis/gax-go/v2 v2.1.0/go.mod h1:Q3nei7sK6ybPYH7twZdmQpAd1MKb7pfu6SK+H1/DsU0= -github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1 h1:EGx4pi6eqNxGaHF6qqu48+N2wcFQ5qg5FXgOdqsJ5d8= github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY= -github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= -github.com/grpc-ecosystem/go-grpc-middleware v1.0.0/go.mod h1:FiyG127CGDf3tlThmgyCl78X/SZQqEOJBCDaAfeWzPs= -github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0/go.mod h1:8NvIoxWQoOIhqOTXgfV/d3M/q6VIi02HzZEHgUlZvzk= -github.com/grpc-ecosystem/grpc-gateway v1.9.0/go.mod h1:vNeuVxBJEsws4ogUvrchl83t/GYV9WGTSLVdBhOQFDY= github.com/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/api v1.10.1/go.mod h1:XjsvQN+RJGWI2TWy1/kqaE16HrR2J/FWgkYjdZQsX9M= @@ -226,32 +205,25 @@ github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1: github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= github.com/inconshreveable/mousetrap v1.0.0 h1:Z8tu5sraLXCXIcARxBp/8cbvlwVa7Z1NHg9XEKhtSvM= github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8= -github.com/jonboulle/clockwork v0.1.0/go.mod h1:Ii8DK3G1RaLaWxj9trq07+26W01tbo22gdxWY5EU2bo= -github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU= +github.com/jmespath/go-jmespath v0.4.0 h1:BEgLn5cpjn8UN1mAw4NjwDrS35OdebyEtFe+9YPoQUg= +github.com/jmespath/go-jmespath v0.4.0/go.mod h1:T8mJZnbsbmF+m6zOOFylbeCJqk5+pHWvzYPziyZiYoo= +github.com/jmespath/go-jmespath/internal/testify v1.5.1 h1:shLQSRRSCCPj3f2gpwzGwWFoC7ycTf1rcQZHOlsJ6N8= +github.com/jmespath/go-jmespath/internal/testify v1.5.1/go.mod h1:L3OGu8Wl2/fWfCI6z80xFu9LTZmf1ZRjMHUOPmWr69U= github.com/json-iterator/go v1.1.11/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= 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/kisielk/errcheck v1.1.0/go.mod h1:EZBBE59ingxPouuu3KfxchcWSUPOHkagtvWXihfKN4Q= github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8= github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= -github.com/klauspost/compress v1.10.7 h1:7rix8v8GpI3ZBb0nSozFRgbtXKv+hOe+qfEpZqybrAg= -github.com/klauspost/compress v1.10.7/go.mod h1:aoV0uJVorq1K+umq18yTdKaF57EivdYsUV+/s2qKfXs= github.com/klauspost/compress v1.13.4 h1:0zhec2I8zGnjWcKyLl6i3gPqKANCCn5e9xmviEEeX6s= github.com/klauspost/compress v1.13.4/go.mod h1:8dP1Hq4DHOhN9w426knH3Rhby4rFm6D8eO+e+Dq5Gzg= -github.com/konsorten/go-windows-terminal-sequences v1.0.1/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 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI= github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= +github.com/kr/pretty v0.2.0 h1:s5hAObm+yFO5uHYt5dYjxi2rXrsnmRpJx4OYvIWUaQs= github.com/kr/pretty v0.2.0/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= -github.com/magiconair/properties v1.8.1 h1:ZC2Vc7/ZFkGmsVC9KvOjumD+G5lXy2RtTKyzRKO2BQ4= -github.com/magiconair/properties v1.8.1/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ= github.com/magiconair/properties v1.8.5 h1:b6kJs+EmPFMYGkow9GiUyCyOvIwYetYJ3fSaWak/Gls= github.com/magiconair/properties v1.8.5/go.mod h1:y3VJvCyxH9uVvJTWEGAELF3aiYNyPKd5NZ3oSwXrF60= github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU= @@ -262,7 +234,6 @@ github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hd github.com/mattn/go-isatty v0.0.10/go.mod h1:qgIWMr58cqv1PHHyhnkY9lrL7etaEgOFcMEpPG5Rm84= github.com/mattn/go-isatty v0.0.11/go.mod h1:PhnuNfih5lzO57/f3n+odYbM4JtupLOxQOAqxQCu2WE= github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU= -github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0= github.com/miekg/dns v1.0.14/go.mod h1:W1PPwlIAgtquWBMBEV9nkV9Cazfe8ScdGz/Lj7v3Nrg= github.com/miekg/dns v1.1.26/go.mod h1:bPDLeHnStXmXAq1m/Ch/hvfNHr14JKNPMBo3VZKjuso= github.com/mitchellh/cli v1.0.0/go.mod h1:hNIlj7HEI86fIcpObd7a0FcrxTWetlwJDGcceTlRvqc= @@ -273,25 +244,17 @@ github.com/mitchellh/go-testing-interface v1.0.0/go.mod h1:kRemZodwjscx+RGhAo8eI 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 h1:fmNYVwqnSfB9mZU6OS2O6GsXM+wcskZDuKQzvN1EDeE= github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= github.com/mitchellh/mapstructure v1.4.1/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= github.com/mitchellh/mapstructure v1.4.2 h1:6h7AQ0yhTcIsmFmnAwQls75jp2Gzs4iB8W7pjMO+rqo= github.com/mitchellh/mapstructure v1.4.2/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= github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= -github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= -github.com/oklog/ulid v1.3.1/go.mod h1:CirwcVhetQ6Lv90oh/F+FBtV6XMibvdAFo93nm5qn4U= github.com/pascaldekloe/goe v0.0.0-20180627143212-57f6aae5913c/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc= -github.com/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.9.3/go.mod h1:u1nR/EPcESfeI/szUZKdtJ0xRNbUoANCkoOuaOx1Y+c= github.com/pelletier/go-toml v1.9.4 h1:tjENF6MfZAg8e4ZmZTeWaWiT2vXtsoO6+iuOjFhECwM= github.com/pelletier/go-toml v1.9.4/go.mod h1:u1nR/EPcESfeI/szUZKdtJ0xRNbUoANCkoOuaOx1Y+c= -github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= -github.com/pkg/errors v0.8.1 h1:iURUrRGxPUNPdy5/HRSm+Yj6okJ6UtLINN0Q9M4+h3I= 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= @@ -300,88 +263,59 @@ github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZb 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/posener/complete v1.2.3/go.mod h1:WZIdtGGp+qx0sLrYKtIRAruyNpv6hFCicSgv7Sy7s/s= -github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw= -github.com/prometheus/client_golang v0.9.3/go.mod h1:/TN21ttK/J9q6uSwhBd54HahCDft0ttaMvbicHlPoso= -github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo= -github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= -github.com/prometheus/common v0.0.0-20181113130724-41aa239b4cce/go.mod h1:daVV7qP5qjZbuso7PdcryaAu0sAZbrN9i7WWcTMWvro= -github.com/prometheus/common v0.4.0/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4= -github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= -github.com/prometheus/procfs v0.0.0-20190507164030-5867b95ac084/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA= -github.com/prometheus/tsdb v0.7.1/go.mod h1:qhTCs0VvXwvX/y3TZrWD7rabWM+ijKTux40TwIPHuXU= -github.com/rogpeppe/fastuuid v0.0.0-20150106093220-6724a57986af/go.mod h1:XWv6SoW27p1b0cqNHllgS5HIMJraePCO15w5zCzIWYg= github.com/rogpeppe/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/rs/xid v1.3.0/go.mod h1:trrq9SKmegXys3aeAKXMUTdJsYXVwGY3RLcfgqegfbg= +github.com/rs/zerolog v1.26.1 h1:/ihwxqH+4z8UxyI70wM1z9yCvkWcfz/a3mj48k/Zngc= +github.com/rs/zerolog v1.26.1/go.mod h1:/wSSJWX7lVrsOwlbyTRSOJvqRlc+WjWlfes+CiJ+tmc= +github.com/russross/blackfriday/v2 v2.0.1 h1:lPqVAte+HuHNfhJ/0LC98ESWRz8afy9tM/0RK8m9o+Q= 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/sagikazarmark/crypt v0.1.0/go.mod h1:B/mN0msZuINBtQ1zZLEQcegFJJf9vnYIR88KRMEuODE= github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529/go.mod h1:DxrIzT+xaE7yg65j358z/aeFdxmN0P9QXhEzd20vsDc= +github.com/shurcooL/sanitized_anchor_name v1.0.0 h1:PdmoCO6wvbs+7yrJyMORt4/BmY5IYyJwS/kOiWx8mHo= 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.7.0 h1:ShrD1U9pZB12TX0cVy0DtePoCH97K8EtX+mg7ZARUtM= -github.com/sirupsen/logrus v1.7.0/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0= -github.com/sirupsen/logrus v1.8.1 h1:dJKuHgqk1NNQlqoA6BTlM1Wf9DOH3NBjQyu0h9+AZZE= -github.com/sirupsen/logrus v1.8.1/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0= -github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d 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/soheilhy/cmux v0.1.4/go.mod h1:IM3LyeVVIOuxMH7sFAkER9+bJ4dT7Ms6E4xg4kGIyLM= github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA= -github.com/spf13/afero v1.1.2 h1:m8/z1t7/fwjysjQRYbP0RD+bUIF/8tJwPdEZsI83ACI= -github.com/spf13/afero v1.1.2/go.mod h1:j4pytiNVoe2o6bmDsKpLACNPDBIoEAkihy7loJ1B0CQ= +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.6.0 h1:xoax2sJ2DT8S8xA2paPFjDCScCNeWsg75VG0DLRreiY= github.com/spf13/afero v1.6.0/go.mod h1:Ai8FlHk4v/PARR026UzYexafAt9roJ7LcLMAmO6Z93I= -github.com/spf13/cast v1.3.0 h1:oget//CVOEoFewqQxwr0Ej5yjygnqGkvggSE/gB35Q8= -github.com/spf13/cast v1.3.0/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE= github.com/spf13/cast v1.3.1/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE= github.com/spf13/cast v1.4.1 h1:s0hze+J0196ZfEMTs80N7UlFt0BDuQ7Q+JDnHiMWKdA= github.com/spf13/cast v1.4.1/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE= -github.com/spf13/cobra v1.1.1 h1:KfztREH0tPxJJ+geloSLaAkaPkr4ki2Er5quFV1TDo4= -github.com/spf13/cobra v1.1.1/go.mod h1:WnodtKOvamDL/PwE2M4iKs8aMDBZ5Q5klgD3qfVJQMI= 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 h1:XHEdyB+EcvlqZamSM4ZOMGlc93t6AcsBEu9Gc1vn7yk= -github.com/spf13/jwalterweatherman v1.0.0/go.mod h1:cQK4TGJAtQXfYWX+Ddv3mKDzgVb68N+wFjFa4jdeBTo= github.com/spf13/jwalterweatherman v1.1.0 h1:ue6voC5bR5F8YxI5S67j9i582FU4Qvo2bmqnqMYADFk= github.com/spf13/jwalterweatherman v1.1.0/go.mod h1:aNWZUN0dPAAO/Ljvb5BEdw96iTZ0EXowPYD95IqWIGo= -github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= -github.com/spf13/viper v1.7.0/go.mod h1:8WkrPz2fc9jxqZNCJI/76HCieCp4Q8HaLFoCha5qpdg= -github.com/spf13/viper v1.7.1 h1:pM5oEahlgWv/WnHXpgbKz7iLIxRf65tye2Ci+XFK5sk= -github.com/spf13/viper v1.7.1/go.mod h1:8WkrPz2fc9jxqZNCJI/76HCieCp4Q8HaLFoCha5qpdg= github.com/spf13/viper v1.8.1/go.mod h1:o0Pch8wJ9BVSWGQMbra6iw0oQ5oktSIBaujf1rJH9Ns= github.com/spf13/viper v1.9.0 h1:yR6EXjTp0y0cLN8OZg1CRZmOBdI88UcGkhgyJhu6nZk= github.com/spf13/viper v1.9.0/go.mod h1:+i6ajR7OX2XaiBkrcZJFK21htRk7eDeLg7+O6bhUPP4= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= -github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= -github.com/stretchr/testify v1.3.0 h1:TivCn/peBQ7UY8ooIcPgZFpTNSz0Q2U6UrFlUfqbe0Q= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.7.0 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/tmc/grpc-websocket-proxy v0.0.0-20190109142713-0ad062ec5ee5/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U= github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw= github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc= -github.com/valyala/fasthttp v1.16.0 h1:9zAqOYLl8Tuy3E5R6ckzGDJ1g8+pw15oQp2iL9Jl6gQ= -github.com/valyala/fasthttp v1.16.0/go.mod h1:YOKImeEosDdBPnxc0gy7INqi3m1zK6A+xl6TwOBhHCA= github.com/valyala/fasthttp v1.31.0 h1:lrauRLII19afgCs2fnWRJ4M5IkV0lo2FqA61uGkNBfE= github.com/valyala/fasthttp v1.31.0/go.mod h1:2rsYD01CKFrjjsvFxx75KlEUNpWNBY9JWD3K/7o2Cus= -github.com/valyala/tcplisten v0.0.0-20161114210144-ceec8f93295a/go.mod h1:v3UYOV9WzVtRmSR+PDvWpU/qWl4Wa5LApYYX4ZtKbio= github.com/valyala/tcplisten v1.0.0/go.mod h1:T0xQ8SeCZGxckz9qRXTfG43PvQ/mcWh7FwZEA7Ioqkc= -github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2/go.mod h1:UETIi67q53MR2AWcXfiuqkDkRtnGDLqkBTpCHuJHxtU= github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= -go.etcd.io/bbolt v1.3.2/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU= +github.com/yuin/goldmark v1.4.0/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= @@ -393,13 +327,9 @@ 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.opentelemetry.io/proto/otlp v0.7.0/go.mod h1:PqfVotwruBrMGOCsRd/89rSnXhoiJIqeYNgFYFoEGnI= -go.uber.org/atomic v1.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE= go.uber.org/atomic v1.7.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc= -go.uber.org/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0= go.uber.org/multierr v1.6.0/go.mod h1:cdWPpRnG4AhwMwsgIHip0KRBQjJy5kYEpYjJxpXp9iU= -go.uber.org/zap v1.10.0/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q= go.uber.org/zap v1.17.0/go.mod h1:MXVU+bhUf/A7Xi2HNOnopQOrmycQ5Ih87HtOu4q5SSo= -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-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= @@ -410,6 +340,7 @@ golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8U golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20210513164829-c07d793c2f9a/go.mod h1:P+XmwS30IXTQdn5tA2iutPOUgjI07+tq3H3K9MVA1s8= golang.org/x/crypto v0.0.0-20210817164053-32db794688a5/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= +golang.org/x/crypto v0.0.0-20211215165025-cf75a172585e/go.mod h1:P+XmwS30IXTQdn5tA2iutPOUgjI07+tq3H3K9MVA1s8= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8= @@ -448,9 +379,7 @@ golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20181023162649-9b4f9f5ad519/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= -golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20181201002055-351d144fa1fc/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= -golang.org/x/net v0.0.0-20181220203305-927f97764cc3/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= @@ -473,7 +402,6 @@ golang.org/x/net v0.0.0-20200501053045-e0ff5e5a1de5/go.mod h1:qpuaurCH72eLCgpAm/ golang.org/x/net v0.0.0-20200506145744-7e3656a0809f/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= golang.org/x/net v0.0.0-20200513185701-a91f0712d120/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= golang.org/x/net v0.0.0-20200520182314-0ba52f642ac2/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= -golang.org/x/net v0.0.0-20200602114024-627f9648deb9/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= golang.org/x/net v0.0.0-20200625001655-4c5254603344/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= golang.org/x/net v0.0.0-20200707034311-ab3426394381/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= @@ -487,6 +415,9 @@ golang.org/x/net v0.0.0-20210316092652-d523dce5a7f4/go.mod h1:RBQZq4jEuRlivfhVLd golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM= golang.org/x/net v0.0.0-20210503060351-7fd8e65b6420/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20210510120150-4163338589ed/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= +golang.org/x/net v0.0.0-20210805182204-aaa1db679c0d/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= +golang.org/x/net v0.0.0-20211209124913-491a49abca63 h1:iocB37TsdFuN6IBRZ+ry36wrkoV51/tl5vOWqkcPGvY= +golang.org/x/net v0.0.0-20211209124913-491a49abca63/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= @@ -516,10 +447,7 @@ golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJ golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sys v0.0.0-20180823144017-11551d06cbcc/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20181026203630-95b1ffbd15a5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20181107165924-66b7b1311ac8/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= @@ -551,8 +479,6 @@ golang.org/x/sys v0.0.0-20200501052902-10377860bb8e/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20200511232937-7e40ca221e25/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200515095857-1151b9dac4a9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200523222454-059865788121/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200602225109-6fdc65e7d980 h1:OjiUf46hAmXblsZdnoSXsEUSKU8r1UEzcL5RVZ4gO9Y= -golang.org/x/sys v0.0.0-20200602225109-6fdc65e7d980/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200803210538-64077c9b5642/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200905004654-be1d3432aa8f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= @@ -572,16 +498,15 @@ golang.org/x/sys v0.0.0-20210514084401-e8d321eab015/go.mod h1:oPkhp1MJrh7nUepCBc golang.org/x/sys v0.0.0-20210603125802-9665404d3644/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210616094352-59db8d763f22/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c h1:F1jZWGFhYfh0Ci55sIpILtKKK8p3i2/krTr0H1rg74I= golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210806184541-e5e7981a1069/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20210809222454-d867a43fc93e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210823070655-63515b42dcdf h1:2ucpDCmfkl8Bd/FsLtiD653Wf96cW37s+iGx93zsu4k= golang.org/x/sys v0.0.0-20210823070655-63515b42dcdf/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= -golang.org/x/text v0.3.2 h1:tW2bmiBqwgJj/UpqtC8EpXEZVYOwU0yG4iWbprSVAcs= 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= @@ -591,7 +516,6 @@ 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= golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= -golang.org/x/tools v0.0.0-20180221164845-07fd8470d635/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY= @@ -648,6 +572,7 @@ golang.org/x/tools v0.1.2/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= golang.org/x/tools v0.1.3/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= golang.org/x/tools v0.1.4/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= golang.org/x/tools v0.1.5/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= +golang.org/x/tools v0.1.7/go.mod h1:LGqMHiF4EqQNHR1JncWGqT5BVaXmza+X+BDGol+dOxo= 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= @@ -778,28 +703,21 @@ google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlba google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= google.golang.org/protobuf v1.27.1/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= -gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= -gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 h1:YR8cESwS4TdDjEe65xsg0ogRM/Nc3DYOhEAlW+xobZo= 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/ini.v1 v1.51.0 h1:AQvPpx3LzTDM0AjnIRlVFwFFGC+npRopjZxLJj6gdno= -gopkg.in/ini.v1 v1.51.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= gopkg.in/ini.v1 v1.62.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= gopkg.in/ini.v1 v1.63.2 h1:tGK/CyBg7SMzb60vP1M03vNZ3VDu3wGQJwn7Sxi9r3c= gopkg.in/ini.v1 v1.63.2/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= -gopkg.in/resty.v1 v1.12.0/go.mod h1:mDo4pnntr5jdWRML875a/NmxYqAlA73dVijT2AXvQQo= -gopkg.in/yaml.v2 v2.0.0-20170812160011-eb3733d160e7/go.mod h1:JAlM8MvJe8wmxCU4Bli9HhUf9+ttbYbLASfIpnQbh74= -gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/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.8 h1:obN1ZagJSUGI0Ek/LBmuj4SNLPfIny3KsKFopxRdj10= gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= 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= diff --git a/main.go b/main.go index 5316dfd..d90afef 100644 --- a/main.go +++ b/main.go @@ -1,6 +1,11 @@ package main -import "github.com/Vilsol/yeet/cmd" +import ( + "github.com/Vilsol/yeet/cmd" + + // Import sub-commands + _ "github.com/Vilsol/yeet/cmd/serve" +) func main() { cmd.Execute() diff --git a/server/webserver.go b/server/webserver.go index 43631dc..2540014 100644 --- a/server/webserver.go +++ b/server/webserver.go @@ -2,18 +2,16 @@ package server import ( "github.com/Vilsol/yeet/cache" - log "github.com/sirupsen/logrus" + "github.com/rs/zerolog/log" "github.com/spf13/viper" "github.com/valyala/fasthttp" "net" "strconv" ) -func Run() error { - ws, err := GetWebserver() - - if err != nil { - return err +func Run(c cache.Cache) error { + ws := &Webserver{ + Cache: c, } address := viper.GetString("host") + ":" + strconv.Itoa(viper.GetInt("port")) @@ -23,7 +21,7 @@ func Run() error { return err } - log.Infof("Starting webserver on %s", address) + log.Info().Msgf("Starting webserver on %s", address) if err := fasthttp.Serve(ln, ws.HandleFastHTTP); err != nil { return err @@ -37,34 +35,10 @@ type Webserver struct { } func (h *Webserver) HandleFastHTTP(ctx *fasthttp.RequestCtx) { - if fileType, b := h.Cache.Get(ctx.Path()); b != nil { - ctx.Success(fileType, b) + if fileType, stream, size := h.Cache.Get(ctx.Path(), ctx.Host()); size > 0 { + ctx.SetContentType(fileType) + ctx.SetBodyStream(stream, size) } else { ctx.SetStatusCode(404) } } - -func GetWebserver() (*Webserver, error) { - var c cache.Cache - var err error - - if viper.GetBool("watch") { - c, err = cache.NewReadWriteCache() - } else { - c, err = cache.NewReadOnlyCache() - } - - if err != nil { - return nil, err - } - - if _, err := c.Index(); err != nil { - return nil, err - } - - ws := &Webserver{ - Cache: c, - } - - return ws, nil -} diff --git a/server/webserver_test.go b/server/webserver_test.go index f8b3af9..5c17573 100644 --- a/server/webserver_test.go +++ b/server/webserver_test.go @@ -1,7 +1,9 @@ package server import ( - log "github.com/sirupsen/logrus" + "github.com/Vilsol/yeet/cache" + "github.com/Vilsol/yeet/source" + "github.com/rs/zerolog" "github.com/spf13/viper" "github.com/valyala/fasthttp" "io" @@ -17,24 +19,28 @@ const getRequest = "GET /webserver_test.go HTTP/1.1\r\nHost: google.com\r\n\r\n" func init() { viper.Set("paths", []string{"."}) - log.SetLevel(log.FatalLevel) + zerolog.SetGlobalLevel(zerolog.FatalLevel) } -func benchmarkServerGet(b *testing.B, clientsCount, requestsPerConn int, expiry bool, watch bool) { +func benchmarkServerGet(b *testing.B, clientsCount, requestsPerConn int, expiry bool) { if expiry { viper.Set("expiry", true) viper.Set("expiry.time", time.Minute*10) viper.Set("expiry.interval", time.Minute*60) } - if watch { - viper.Set("watch", true) + c, err := cache.NewHashMapCache(source.Local{}, false) + + if err != nil { + b.Fatal(err) } - ws, err := GetWebserver() + if _, err := c.Index(); err != nil { + b.Fatal(err) + } - if err != nil { - panic(err) + ws := &Webserver{ + Cache: c, } s := &fasthttp.Server{ @@ -189,99 +195,66 @@ func newFakeListener(requestsCount, clientsCount, requestsPerConn int, request s // Baseline func BenchmarkServerGet1ReqPerConn(b *testing.B) { - benchmarkServerGet(b, runtime.NumCPU(), 1, false, false) + benchmarkServerGet(b, runtime.NumCPU(), 1, false) } func BenchmarkServerGet2ReqPerConn(b *testing.B) { - benchmarkServerGet(b, runtime.NumCPU(), 2, false, false) + benchmarkServerGet(b, runtime.NumCPU(), 2, false) } func BenchmarkServerGet10ReqPerConn(b *testing.B) { - benchmarkServerGet(b, runtime.NumCPU(), 10, false, false) + benchmarkServerGet(b, runtime.NumCPU(), 10, false) } func BenchmarkServerGet10KReqPerConn(b *testing.B) { - benchmarkServerGet(b, runtime.NumCPU(), 10000, false, false) + benchmarkServerGet(b, runtime.NumCPU(), 10000, false) } func BenchmarkServerGet1ReqPerConn10KClients(b *testing.B) { - benchmarkServerGet(b, 10000, 1, false, false) + benchmarkServerGet(b, 10000, 1, false) } func BenchmarkServerGet2ReqPerConn10KClients(b *testing.B) { - benchmarkServerGet(b, 10000, 2, false, false) + benchmarkServerGet(b, 10000, 2, false) } func BenchmarkServerGet10ReqPerConn10KClients(b *testing.B) { - benchmarkServerGet(b, 10000, 10, false, false) + benchmarkServerGet(b, 10000, 10, false) } func BenchmarkServerGet100ReqPerConn10KClients(b *testing.B) { - benchmarkServerGet(b, 10000, 100, false, false) + benchmarkServerGet(b, 10000, 100, false) } // With expiry func BenchmarkServerGet1ReqPerConnExpiry(b *testing.B) { - benchmarkServerGet(b, runtime.NumCPU(), 1, true, false) + benchmarkServerGet(b, runtime.NumCPU(), 1, true) } func BenchmarkServerGet2ReqPerConnExpiry(b *testing.B) { - benchmarkServerGet(b, runtime.NumCPU(), 2, true, false) + benchmarkServerGet(b, runtime.NumCPU(), 2, true) } func BenchmarkServerGet10ReqPerConnExpiry(b *testing.B) { - benchmarkServerGet(b, runtime.NumCPU(), 10, true, false) + benchmarkServerGet(b, runtime.NumCPU(), 10, true) } func BenchmarkServerGet10KReqPerConnExpiry(b *testing.B) { - benchmarkServerGet(b, runtime.NumCPU(), 10000, true, false) + benchmarkServerGet(b, runtime.NumCPU(), 10000, true) } func BenchmarkServerGet1ReqPerConn10KClientsExpiry(b *testing.B) { - benchmarkServerGet(b, 10000, 1, true, false) + benchmarkServerGet(b, 10000, 1, true) } func BenchmarkServerGet2ReqPerConn10KClientsExpiry(b *testing.B) { - benchmarkServerGet(b, 10000, 2, true, false) + benchmarkServerGet(b, 10000, 2, true) } func BenchmarkServerGet10ReqPerConn10KClientsExpiry(b *testing.B) { - benchmarkServerGet(b, 10000, 10, true, false) + benchmarkServerGet(b, 10000, 10, true) } func BenchmarkServerGet100ReqPerConn10KClientsExpiry(b *testing.B) { - benchmarkServerGet(b, 10000, 100, true, false) -} - -// With watch -func BenchmarkServerGet1ReqPerConnWatch(b *testing.B) { - benchmarkServerGet(b, runtime.NumCPU(), 1, false, true) -} - -func BenchmarkServerGet2ReqPerConnWatch(b *testing.B) { - benchmarkServerGet(b, runtime.NumCPU(), 2, false, true) -} - -func BenchmarkServerGet10ReqPerConnWatch(b *testing.B) { - benchmarkServerGet(b, runtime.NumCPU(), 10, false, true) -} - -func BenchmarkServerGet10KReqPerConnWatch(b *testing.B) { - benchmarkServerGet(b, runtime.NumCPU(), 10000, false, true) -} - -func BenchmarkServerGet1ReqPerConn10KClientsWatch(b *testing.B) { - benchmarkServerGet(b, 10000, 1, false, true) -} - -func BenchmarkServerGet2ReqPerConn10KClientsWatch(b *testing.B) { - benchmarkServerGet(b, 10000, 2, false, true) -} - -func BenchmarkServerGet10ReqPerConn10KClientsWatch(b *testing.B) { - benchmarkServerGet(b, 10000, 10, false, true) -} - -func BenchmarkServerGet100ReqPerConn10KClientsWatch(b *testing.B) { - benchmarkServerGet(b, 10000, 100, false, true) + benchmarkServerGet(b, 10000, 100, true) } diff --git a/source/local.go b/source/local.go new file mode 100644 index 0000000..227cf62 --- /dev/null +++ b/source/local.go @@ -0,0 +1,158 @@ +package source + +import ( + "github.com/Vilsol/yeet/utils" + "github.com/bits-and-blooms/bloom/v3" + "github.com/fsnotify/fsnotify" + "github.com/rs/zerolog/log" + "github.com/spf13/viper" + "mime" + "os" + "path/filepath" + "strings" +) + +var _ Source = (*Local)(nil) + +type Local struct { +} + +func (l Local) Get(path string, host []byte) *utils.StreamHijacker { + file, err := os.OpenFile(path, os.O_RDONLY, 0664) + if err != nil { + log.Err(err).Msg("error reading file") + return nil + } + + stat, err := file.Stat() + if err != nil { + log.Err(err).Msg("error reading file") + return nil + } + + fileType := mime.TypeByExtension(filepath.Ext(filepath.Base(path))) + + return utils.NewStreamHijacker(int(stat.Size()), fileType, file) +} + +func (l Local) IndexPath(dir string, f IndexFunc) (int64, int64, error) { + totalSize := int64(0) + totalCount := int64(0) + fileNames := make([]string, 0) + + if err := filepath.Walk(dir, func(path string, info os.FileInfo, err error) error { + if err != nil { + return err + } + + filePath := path + + if info.IsDir() { + indexFile := viper.GetString("index.file") + if indexFile != "" { + joined := filepath.Join(path, indexFile) + _, err := os.Stat(joined) + if err != nil && !os.IsNotExist(err) { + return err + } else if err != nil { + return nil + } + filePath = joined + } else { + return nil + } + } + + absPath, _ := filepath.Abs(filePath) + cleanedPath := cleanPath(path, dir) + totalSize += f(absPath, cleanedPath) + totalCount++ + + log.Trace().Msgf("Indexed: %s -> %s", cleanedPath, absPath) + + fileNames = append(fileNames, cleanedPath) + + return nil + }); err != nil { + return 0, 0, err + } + + filter := bloom.NewWithEstimates(uint(totalCount), 0.001) + for _, fileName := range fileNames { + filter.AddString(fileName) + } + + log.Trace().Msgf("Created bloom filter of size %d and hash count %d", filter.Cap(), filter.K()) + + return totalSize, totalCount, nil +} + +func (l Local) Watch() (<-chan WatchEvent, error) { + events := make(chan WatchEvent, 0) + + watcher, err := fsnotify.NewWatcher() + + if err != nil { + return nil, err + } + + go func() { + for event := range watcher.Events { + dirPath := "" + for _, path := range viper.GetStringSlice("paths") { + if strings.HasPrefix(event.Name, filepath.Clean(path)) { + dirPath = path + } + } + + if dirPath == "" { + log.Warn().Msgf("Received update about an unknown path: %s", event.Name) + continue + } + + clean := cleanPath(event.Name, dirPath) + absPath, _ := filepath.Abs(event.Name) + + switch { + case event.Op&fsnotify.Rename == fsnotify.Rename: + log.Trace().Msgf("Received rename: %s", clean) + events <- WatchEvent{ + CleanPath: clean, + AbsPath: absPath, + Op: WatchRename, + } + case event.Op&fsnotify.Remove == fsnotify.Remove: + log.Trace().Msgf("Received remove: %s", clean) + events <- WatchEvent{ + CleanPath: clean, + AbsPath: absPath, + Op: WatchDelete, + } + case event.Op&fsnotify.Create == fsnotify.Create: + log.Trace().Msgf("Received create: %s", clean) + events <- WatchEvent{ + CleanPath: clean, + AbsPath: absPath, + Op: WatchCreate, + } + case event.Op&fsnotify.Write == fsnotify.Write: + log.Trace().Msgf("Received write: %s", clean) + events <- WatchEvent{ + CleanPath: clean, + AbsPath: absPath, + Op: WatchModify, + } + } + } + }() + + for _, dirPath := range viper.GetStringSlice("paths") { + log.Debug().Msgf("Watching path: %s", dirPath) + err = watcher.Add(dirPath) + if err != nil { + return nil, err + } + } + + return events, nil +} diff --git a/source/s3.go b/source/s3.go new file mode 100644 index 0000000..339e3d2 --- /dev/null +++ b/source/s3.go @@ -0,0 +1,91 @@ +package source + +import ( + "github.com/Vilsol/yeet/utils" + "github.com/aws/aws-sdk-go/aws" + "github.com/aws/aws-sdk-go/aws/credentials" + "github.com/aws/aws-sdk-go/aws/session" + "github.com/aws/aws-sdk-go/service/s3" + "github.com/pkg/errors" + "github.com/rs/zerolog/log" + "mime" + "path/filepath" + "strings" +) + +var _ Source = (*S3)(nil) + +type S3 struct { + S3Client *s3.S3 + S3Session *session.Session + Bucket string +} + +func NewS3(bucket string, key string, secret string, endpoint string, region string) (*S3, error) { + s3Config := &aws.Config{ + Credentials: credentials.NewStaticCredentials(key, secret, ""), + Endpoint: aws.String(endpoint), + Region: aws.String(region), + S3ForcePathStyle: aws.Bool(true), + } + + newSession, err := session.NewSession(s3Config) + + if err != nil { + return nil, errors.Wrap(err, "failed to create S3 session") + } + + s3Client := s3.New(newSession) + + return &S3{ + S3Client: s3Client, + S3Session: newSession, + Bucket: bucket, + }, nil +} + +func (s S3) Get(path string, host []byte) *utils.StreamHijacker { + cleanedKey := strings.TrimPrefix(path, "/") + + object, err := s.S3Client.GetObject(&s3.GetObjectInput{ + Bucket: aws.String(s.Bucket), + Key: aws.String(cleanedKey), + }) + + if err != nil { + log.Err(err).Msg("failed to get object") + return nil + } + + fileType := mime.TypeByExtension(filepath.Ext(filepath.Base(path))) + + return utils.NewStreamHijacker(int(*object.ContentLength), fileType, object.Body) +} + +func (s S3) IndexPath(dirPath string, f IndexFunc) (int64, int64, error) { + totalSize := int64(0) + totalCount := int64(0) + + if err := s.S3Client.ListObjectsPages(&s3.ListObjectsInput{ + Bucket: aws.String(s.Bucket), + Prefix: aws.String(dirPath), + }, func(page *s3.ListObjectsOutput, lastPage bool) bool { + for _, object := range page.Contents { + cleanedPath := cleanPath(*object.Key, dirPath) + totalSize += f(*object.Key, cleanedPath) + totalCount++ + + log.Trace().Msgf("Indexed: %s -> %s", cleanedPath, *object.Key) + } + return true + }); err != nil { + return 0, 0, err + } + + return totalSize, totalCount, nil +} + +func (s S3) Watch() (<-chan WatchEvent, error) { + // TODO Index bucket every N minutes + return nil, errors.New("s3 does not support watching") +} diff --git a/source/shared.go b/source/shared.go new file mode 100644 index 0000000..5ba73ae --- /dev/null +++ b/source/shared.go @@ -0,0 +1,26 @@ +package source + +import ( + "path/filepath" + "strings" +) + +func cleanPath(path string, dirPath string) string { + trimmed := strings.Trim(strings.ReplaceAll(filepath.Clean(dirPath), "\\", "/"), "/") + toRemove := len(strings.Split(trimmed, "/")) + + if trimmed == "." || trimmed == "" { + toRemove = 0 + } + + cleanedPath := strings.ReplaceAll(filepath.Clean(path), "\\", "/") + + // Remove the initial path + cleanedPath = strings.Join(strings.Split(cleanedPath, "/")[toRemove:], "/") + + if !strings.HasPrefix(cleanedPath, "/") { + cleanedPath = "/" + cleanedPath + } + + return cleanedPath +} diff --git a/source/types.go b/source/types.go new file mode 100644 index 0000000..e7c9daa --- /dev/null +++ b/source/types.go @@ -0,0 +1,28 @@ +package source + +import ( + "github.com/Vilsol/yeet/utils" +) + +type IndexFunc = func(absolutePath string, cleanedPath string) int64 + +type Source interface { + Get(path string, host []byte) *utils.StreamHijacker + IndexPath(dir string, f IndexFunc) (int64, int64, error) + Watch() (<-chan WatchEvent, error) +} + +type WatchOp int + +const ( + WatchCreate WatchOp = iota + WatchModify + WatchDelete + WatchRename +) + +type WatchEvent struct { + CleanPath string + AbsPath string + Op WatchOp +} diff --git a/tools.go b/tools.go new file mode 100644 index 0000000..5cbc8f8 --- /dev/null +++ b/tools.go @@ -0,0 +1,21 @@ +//go:build tools +// +build tools + +package main + +import ( + "github.com/Vilsol/yeet/cmd" + "github.com/spf13/cobra/doc" + + // Import sub-commands + _ "github.com/Vilsol/yeet/cmd/serve" +) + +//go:generate go run -tags tools tools.go + +func main() { + err := doc.GenMarkdownTree(cmd.RootCMD, "./docs/") + if err != nil { + panic(err) + } +} diff --git a/utils/hijacker.go b/utils/hijacker.go new file mode 100644 index 0000000..b9d250b --- /dev/null +++ b/utils/hijacker.go @@ -0,0 +1,54 @@ +package utils + +import ( + "io" +) + +var _ io.ReadCloser = (*StreamHijacker)(nil) + +type StreamHijacker struct { + Buffer []byte + Offset int + Upstream io.ReadCloser + fileType string + Size int + OnClose func(*StreamHijacker) +} + +func NewStreamHijacker(size int, fileType string, upstream io.ReadCloser) *StreamHijacker { + return &StreamHijacker{ + Buffer: make([]byte, size), + Offset: 0, + Upstream: upstream, + fileType: fileType, + Size: size, + } +} + +func (s *StreamHijacker) Read(p []byte) (n int, err error) { + read, err := s.Upstream.Read(p) + copy(s.Buffer[s.Offset:], p[:read]) + s.Offset += read + return read, err +} + +func (s *StreamHijacker) Close() error { + if s.OnClose != nil && s.Offset == s.Size { + s.OnClose(s) + } + return s.Upstream.Close() +} + +func (s *StreamHijacker) FileType() string { + if s.fileType == "" { + // TODO Read the first few bytes and guess the file type from upstream + // if s.Offset >= 512 { + // s.fileType = http.DetectContentType(s.Buffer[:512]) + // } + + // Fallback + s.fileType = "application/octet-stream" + } + + return s.fileType +} diff --git a/utils/utils.go b/utils/utils.go index a1a7060..aa4b38b 100644 --- a/utils/utils.go +++ b/utils/utils.go @@ -12,5 +12,16 @@ func ByteCountToHuman(b int64) string { div *= unit exp++ } - return fmt.Sprintf("%.1f %cB", float64(b)/float64(div), "kMGTPE"[exp]) + return fmt.Sprintf("%.1f %cB", float64(b)/float64(div), "kMGTPEZY"[exp]) } + +/* +function humanFileSize(size) { + if (size < 1024) return size + ' B' + let i = Math.floor(Math.log(size) / Math.log(1024)) + let num = (size / Math.pow(1024, i)) + let round = Math.round(num) + num = round < 10 ? num.toFixed(2) : round < 100 ? num.toFixed(1) : round + return `${num} ${'KMGTPEZY'[i-1]}B` +} +*/