├── .gitignore ├── .dockerignore ├── pkg ├── operations │ ├── signal.go │ ├── logging.go │ ├── peek.go │ └── config.go ├── routing │ ├── file │ │ ├── monitor.go │ │ └── file.go │ ├── provider.go │ ├── consul │ │ ├── client.go │ │ ├── monitor.go │ │ └── consul.go │ ├── nomad │ │ ├── monitor.go │ │ └── nomad.go │ └── http │ │ └── monitor.go └── proxy │ ├── strategy.go │ ├── backend.go │ ├── frontend.go │ ├── controller.go │ ├── table.go │ └── proxy.go ├── Dockerfile ├── cmd └── dill │ └── main.go ├── LICENSE ├── go.mod ├── Makefile ├── README.md └── go.sum /.gitignore: -------------------------------------------------------------------------------- 1 | .idea/ 2 | bin/ 3 | dist/ 4 | configs/ 5 | contrib/ 6 | venv/ 7 | .DS_Store 8 | 9 | -------------------------------------------------------------------------------- /.dockerignore: -------------------------------------------------------------------------------- 1 | bin/ 2 | dist/ 3 | configs/ 4 | contrib/ 5 | venv/ 6 | .idea/ 7 | .git/ 8 | .gitignore 9 | .dockerignore 10 | Makefile 11 | LICENSE 12 | README.md 13 | Dockerfile 14 | .DS_Store 15 | 16 | -------------------------------------------------------------------------------- /pkg/operations/signal.go: -------------------------------------------------------------------------------- 1 | package operations 2 | 3 | import ( 4 | "os" 5 | "os/signal" 6 | "syscall" 7 | ) 8 | 9 | func ShutdownChannel() <-chan os.Signal { 10 | c := make(chan os.Signal, 1) 11 | signal.Notify(c, os.Interrupt, syscall.SIGTERM) 12 | return c 13 | } 14 | -------------------------------------------------------------------------------- /pkg/operations/logging.go: -------------------------------------------------------------------------------- 1 | package operations 2 | 3 | import ( 4 | "log/slog" 5 | "os" 6 | ) 7 | 8 | var logLevel = new(slog.LevelVar) 9 | 10 | func SetupLogging() { 11 | h := slog.NewJSONHandler(os.Stderr, &slog.HandlerOptions{Level: logLevel}) 12 | slog.SetDefault(slog.New(h)) 13 | } 14 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM golang:1.19-alpine AS builder 2 | WORKDIR /tmp/dill 3 | COPY . . 4 | RUN GOOS=linux GOARCH=amd64 go build -o dist/dill cmd/dill/main.go 5 | 6 | FROM alpine:3.17.2 7 | RUN apk update && apk --no-cache upgrade 8 | COPY --from=builder /tmp/dill/dist/dill /usr/local/bin 9 | 10 | ENTRYPOINT ["/usr/local/bin/dill"] -------------------------------------------------------------------------------- /pkg/routing/file/monitor.go: -------------------------------------------------------------------------------- 1 | package file 2 | 3 | import ( 4 | "github.com/fsnotify/fsnotify" 5 | "github.com/spf13/viper" 6 | 7 | "dill/pkg/proxy" 8 | ) 9 | 10 | func MonitorServices(c chan<- *proxy.RoutingTable) { 11 | v := viper.New() 12 | cfg := readRoutingConfig(v) 13 | rt := BuildRoutingTable(cfg) 14 | c <- &rt 15 | 16 | if viper.GetBool("routing.file.watch") { 17 | v.OnConfigChange(func(e fsnotify.Event) { 18 | rc := readRoutingConfig(viper.New()) 19 | t := BuildRoutingTable(rc) 20 | c <- &t 21 | }) 22 | v.WatchConfig() 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /pkg/operations/peek.go: -------------------------------------------------------------------------------- 1 | package operations 2 | 3 | import ( 4 | "log/slog" 5 | "net" 6 | 7 | "dill/pkg/proxy" 8 | ) 9 | 10 | func Peek(addr string) { 11 | l, err := net.Listen("tcp", addr) 12 | if err != nil { 13 | slog.Error("Failed to start Peek", "error", err) 14 | return 15 | } 16 | slog.Info("Starting Peek", "address", addr) 17 | for { 18 | c, err := l.Accept() 19 | if err != nil { 20 | slog.Warn("Failed to accept a connection", "error", err) 21 | continue 22 | } 23 | 24 | go func() { 25 | d := proxy.Dump() 26 | if d == "" { 27 | d = "No registered backends. Please verify routing configuration.\n" 28 | } 29 | 30 | c.Write([]byte(d)) 31 | c.Close() 32 | }() 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /pkg/proxy/strategy.go: -------------------------------------------------------------------------------- 1 | package proxy 2 | 3 | import ( 4 | "math/rand" 5 | "sync/atomic" 6 | "time" 7 | ) 8 | 9 | type strategy interface { 10 | Select(*[]Upstream) Upstream 11 | Name() string 12 | } 13 | 14 | type roundrobin struct { 15 | next uint32 16 | } 17 | 18 | func (r *roundrobin) Select(upstreams *[]Upstream) Upstream { 19 | n := atomic.AddUint32(&r.next, 1) 20 | return (*upstreams)[(int(n)-1)%len(*upstreams)] 21 | } 22 | 23 | func (r *roundrobin) Name() string { 24 | return "round_robin" 25 | } 26 | 27 | type random struct { 28 | } 29 | 30 | func (r *random) Select(upstreams *[]string) string { 31 | rand.Seed(time.Now().UnixNano()) 32 | return (*upstreams)[rand.Intn(len(*upstreams))] 33 | } 34 | 35 | func (r *random) Name() string { 36 | return "random" 37 | } 38 | -------------------------------------------------------------------------------- /cmd/dill/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "log/slog" 5 | "os" 6 | "runtime" 7 | 8 | "github.com/spf13/viper" 9 | 10 | "dill/pkg/operations" 11 | "dill/pkg/proxy" 12 | "dill/pkg/routing" 13 | ) 14 | 15 | var version = "0.0.0" // will inserted dynamically at build time 16 | var sch <-chan os.Signal 17 | 18 | func init() { 19 | operations.SetupLogging() 20 | operations.SetupConfig() 21 | sch = operations.ShutdownChannel() 22 | } 23 | 24 | func main() { 25 | slog.Info("Starting dill", "version", version) 26 | 27 | procs := viper.GetInt("runtime.gomaxprocs") 28 | slog.Info("Setting GOMAXPROCS", "count", procs) 29 | runtime.GOMAXPROCS(procs) 30 | 31 | l := viper.GetString("peek.listener") 32 | if l != "" { 33 | go operations.Peek(l) 34 | } 35 | 36 | c := make(chan *proxy.RoutingTable) 37 | 38 | name, monitor, err := routing.GetRoutingMonitor() 39 | if err != nil { 40 | slog.Error("Failed to setup routing provider", "error", err) 41 | os.Exit(1) 42 | } 43 | slog.Info("Starting routing provider", "provider", name) 44 | go monitor(c) 45 | 46 | proxy.ControlRoutes(c, sch) 47 | } 48 | -------------------------------------------------------------------------------- /pkg/routing/provider.go: -------------------------------------------------------------------------------- 1 | package routing 2 | 3 | import ( 4 | "errors" 5 | "fmt" 6 | 7 | "github.com/spf13/viper" 8 | 9 | "dill/pkg/proxy" 10 | "dill/pkg/routing/consul" 11 | "dill/pkg/routing/file" 12 | "dill/pkg/routing/http" 13 | "dill/pkg/routing/nomad" 14 | ) 15 | 16 | type routingeMonitor func(chan<- *proxy.RoutingTable) 17 | 18 | var routingMonitors = map[string]routingeMonitor{ 19 | "http": http.MonitorServices, 20 | "consul": consul.MonitorServices, 21 | "nomad": nomad.MonitorServices, 22 | "file": file.MonitorServices, 23 | } 24 | 25 | // GetRoutingMonitor selects routing provider based on configuration file 26 | func GetRoutingMonitor() (string, routingeMonitor, error) { 27 | cfg := viper.GetStringMap("routing") 28 | if len(cfg) > 1 { 29 | return "", nil, errors.New("multiple routing providers declared") 30 | } 31 | 32 | for k := range cfg { 33 | monitor, ok := routingMonitors[k] 34 | if !ok { 35 | return k, nil, fmt.Errorf("unknown routing provider: %s", k) 36 | } 37 | return k, monitor, nil 38 | } 39 | return "", nil, errors.New("no routing provider declared") 40 | } 41 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020 Mariusz Kupidura 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /pkg/proxy/backend.go: -------------------------------------------------------------------------------- 1 | package proxy 2 | 3 | import ( 4 | "strings" 5 | "sync" 6 | ) 7 | 8 | type Upstream struct { 9 | address string 10 | prx string 11 | } 12 | 13 | func NewBackend(upstreams []Upstream) *Backend { 14 | b := Backend{upstreams: upstreams, strategy: &roundrobin{}} 15 | return &b 16 | } 17 | 18 | type Backend struct { 19 | upstreams []Upstream 20 | strategy strategy 21 | rwm sync.RWMutex 22 | } 23 | 24 | func (b *Backend) Select() Upstream { 25 | b.rwm.RLock() 26 | u := b.strategy.Select(&b.upstreams) 27 | b.rwm.RUnlock() 28 | return u 29 | } 30 | 31 | func (b *Backend) SetUpstreams(upstreams []Upstream) { 32 | b.rwm.Lock() 33 | b.upstreams = upstreams 34 | b.rwm.Unlock() 35 | } 36 | 37 | func (b *Backend) Dump() string { 38 | var bd strings.Builder 39 | bd.WriteString(" ├ ") 40 | bd.WriteString(b.strategy.Name()) 41 | bd.WriteString("\n") 42 | b.rwm.RLock() 43 | for _, u := range b.upstreams { 44 | bd.WriteString(" ├──➤ ") 45 | if u.prx != "" { 46 | bd.WriteString(u.prx) 47 | bd.WriteString(" ─➤ ") 48 | } 49 | bd.WriteString(u.address) 50 | bd.WriteString("\n") 51 | } 52 | b.rwm.RUnlock() 53 | return bd.String() 54 | } 55 | -------------------------------------------------------------------------------- /pkg/routing/consul/client.go: -------------------------------------------------------------------------------- 1 | package consul 2 | 3 | import ( 4 | "log/slog" 5 | "net/http" 6 | "os" 7 | 8 | "golang.org/x/exp/slices" 9 | ) 10 | 11 | var validConsistencyModes = []string{"stale", "consistent", "leader"} 12 | 13 | type consulConfig struct { 14 | Address string 15 | Token string 16 | Datacenter string 17 | Namespace string 18 | Wait string 19 | ConsistencyMode string `mapstructure:"consistency_mode"` 20 | } 21 | 22 | func (c *consulConfig) Validate() { 23 | if c.ConsistencyMode != "" && !slices.Contains(validConsistencyModes, c.ConsistencyMode) { 24 | slog.Error("Invalid Consul's consistency mode") 25 | os.Exit(1) 26 | } 27 | } 28 | 29 | type httpClient struct { 30 | client http.Client 31 | config *consulConfig 32 | } 33 | 34 | func (c *httpClient) Do(req *http.Request) (*http.Response, error) { 35 | if c.config.Token != "" { 36 | req.Header.Add("X-Consul-Token", c.config.Token) 37 | } 38 | 39 | q := req.URL.Query() 40 | if c.config.Datacenter != "" { 41 | q.Add("dc", c.config.Datacenter) 42 | } 43 | if c.config.Namespace != "" { 44 | q.Add("ns", c.config.Namespace) 45 | } 46 | if c.config.ConsistencyMode != "" { 47 | q.Add(c.config.ConsistencyMode, "") 48 | } 49 | req.URL.RawQuery = q.Encode() 50 | return c.client.Do(req) 51 | } 52 | -------------------------------------------------------------------------------- /pkg/operations/config.go: -------------------------------------------------------------------------------- 1 | package operations 2 | 3 | import ( 4 | "flag" 5 | "fmt" 6 | "os" 7 | "runtime" 8 | "strings" 9 | 10 | "github.com/spf13/viper" 11 | ) 12 | 13 | var configPath string 14 | 15 | func init() { 16 | flag.StringVar(&configPath, "config", "", "Configuration file path") 17 | } 18 | 19 | func SetupConfig() { 20 | viper.SetEnvPrefix("dill") 21 | viper.AutomaticEnv() 22 | viper.SetEnvKeyReplacer(strings.NewReplacer(".", "_")) 23 | 24 | viper.SetDefault("consul.address", "http://127.0.0.1:8500") 25 | viper.SetDefault("runtime.gomaxprocs", runtime.NumCPU()) 26 | viper.SetDefault("peek.listener", "") 27 | viper.SetDefault("listeners.port_min", 1024) 28 | viper.SetDefault("listeners.port_max", 49151) 29 | viper.SetDefault( 30 | "listeners.allowed", 31 | map[string]string{"local": "127.0.0.1", "any": "0.0.0.0"}, 32 | ) 33 | 34 | viper.SetDefault("routing.http.poll_interval", 5) 35 | viper.SetDefault("routing.http.poll_timeout", 5) 36 | viper.SetDefault("routing.file.watch", true) 37 | 38 | flag.Parse() 39 | if configPath != "" { 40 | viper.SetConfigFile(configPath) 41 | err := viper.ReadInConfig() // Find and read the config file 42 | if err != nil { // Handle errors reading the config file 43 | fmt.Printf("config error: %s\n", err) 44 | os.Exit(1) 45 | } 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /pkg/routing/consul/monitor.go: -------------------------------------------------------------------------------- 1 | package consul 2 | 3 | import ( 4 | "log/slog" 5 | "time" 6 | 7 | "github.com/spf13/viper" 8 | 9 | "dill/pkg/proxy" 10 | ) 11 | 12 | var waitTime time.Duration = 5 * time.Second 13 | 14 | // MonitorServices fetches healthy services that was tagged as `dill` 15 | func MonitorServices(c chan<- *proxy.RoutingTable) { 16 | cfg := consulConfig{} 17 | viper.UnmarshalKey("routing.consul", &cfg) 18 | cfg.Validate() 19 | 20 | consulClient := &httpClient{config: &cfg} 21 | 22 | index := 1 23 | for { 24 | services, newIndex, err := fetchHealthyServices(index, consulClient) 25 | if err != nil { 26 | slog.Warn("Fetching healthy services failed", "error", err) 27 | time.Sleep(waitTime) 28 | continue 29 | } 30 | index = newIndex 31 | rt := &proxy.RoutingTable{Table: map[string][]proxy.Upstream{}, ConsulIndex: newIndex} 32 | for _, s := range services { 33 | details, err := fetchServiceDetails(s, consulClient) 34 | if err != nil { 35 | slog.Warn("Fetching service details failed", 36 | "error", err, "service", s, 37 | ) 38 | continue 39 | } 40 | for _, i := range details { 41 | rt.Update(&i) 42 | } 43 | } 44 | c <- rt 45 | // TODO: naive rate limiting use something 46 | // more resilient like token bucket algorithm 47 | time.Sleep(waitTime) 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module dill 2 | 3 | go 1.21 4 | 5 | require ( 6 | github.com/fsnotify/fsnotify v1.6.0 7 | github.com/hashicorp/nomad/api v0.0.0-20231005165617-e3c8700ded89 8 | github.com/spf13/viper v1.16.0 9 | golang.org/x/exp v0.0.0-20231005195138-3e424a577f31 10 | golang.org/x/net v0.16.0 11 | ) 12 | 13 | require ( 14 | github.com/gorilla/websocket v1.5.0 // indirect 15 | github.com/hashicorp/cronexpr v1.1.2 // indirect 16 | github.com/hashicorp/errwrap v1.1.0 // indirect 17 | github.com/hashicorp/go-cleanhttp v0.5.2 // indirect 18 | github.com/hashicorp/go-multierror v1.1.1 // indirect 19 | github.com/hashicorp/go-rootcerts v1.0.2 // indirect 20 | github.com/hashicorp/hcl v1.0.0 // indirect 21 | github.com/magiconair/properties v1.8.7 // indirect 22 | github.com/mitchellh/go-homedir v1.1.0 // indirect 23 | github.com/mitchellh/mapstructure v1.5.0 // indirect 24 | github.com/pelletier/go-toml/v2 v2.1.0 // indirect 25 | github.com/spf13/afero v1.10.0 // indirect 26 | github.com/spf13/cast v1.5.1 // indirect 27 | github.com/spf13/jwalterweatherman v1.1.0 // indirect 28 | github.com/spf13/pflag v1.0.5 // indirect; indirectgo m 29 | github.com/subosito/gotenv v1.6.0 // indirect 30 | golang.org/x/sys v0.13.0 // indirect 31 | golang.org/x/text v0.13.0 // indirect 32 | gopkg.in/ini.v1 v1.67.0 // indirect 33 | gopkg.in/yaml.v3 v3.0.1 // indirect 34 | ) 35 | -------------------------------------------------------------------------------- /pkg/proxy/frontend.go: -------------------------------------------------------------------------------- 1 | package proxy 2 | 3 | import ( 4 | "log/slog" 5 | "net" 6 | "sync" 7 | ) 8 | 9 | func NewFrontend(address string) *Frontend { 10 | f := &Frontend{Address: address, conns: make(map[string]net.Conn)} 11 | return f 12 | } 13 | 14 | type Frontend struct { 15 | Address string 16 | listener net.Listener 17 | conns map[string]net.Conn 18 | rwm sync.RWMutex 19 | } 20 | 21 | func (f *Frontend) Listen() error { 22 | slog.Info("Frontend is starting to listen", "address", f.Address) 23 | l, err := net.Listen("tcp", f.Address) 24 | if err != nil { 25 | return err 26 | } 27 | f.listener = l 28 | return nil 29 | } 30 | 31 | func (f *Frontend) Close() { 32 | slog.Info("Closing frontend", "address", f.Address) 33 | f.rwm.Lock() 34 | for _, c := range f.conns { 35 | c.Close() 36 | } 37 | f.conns = make(map[string]net.Conn) 38 | f.rwm.Unlock() 39 | 40 | if f.listener != nil { 41 | f.listener.Close() 42 | } 43 | } 44 | 45 | func (f *Frontend) Accept() (net.Conn, error) { 46 | c, err := f.listener.Accept() 47 | if err != nil { 48 | return nil, err 49 | } 50 | f.rwm.Lock() 51 | f.conns[c.RemoteAddr().String()] = c 52 | f.rwm.Unlock() 53 | 54 | return c, nil 55 | } 56 | 57 | func (f *Frontend) RemoveConn(c net.Conn) { 58 | f.rwm.Lock() 59 | delete(f.conns, c.RemoteAddr().String()) 60 | f.rwm.Unlock() 61 | } 62 | -------------------------------------------------------------------------------- /pkg/routing/nomad/monitor.go: -------------------------------------------------------------------------------- 1 | package nomad 2 | 3 | import ( 4 | "log/slog" 5 | "os" 6 | "time" 7 | 8 | "github.com/spf13/viper" 9 | 10 | "dill/pkg/proxy" 11 | ) 12 | 13 | var waitTime time.Duration = 5 * time.Second 14 | 15 | // MonitorServices fetches healthy services that was tagged as `dill` 16 | func MonitorServices(c chan<- *proxy.RoutingTable) { 17 | cfg := nomadConfig{} 18 | viper.UnmarshalKey("routing.nomad", &cfg) 19 | 20 | nomadClient, err := newNomadClient(&cfg) 21 | if err != nil { 22 | slog.Error("Invalid configuration of 'nomad' routing provider", "error", err) 23 | os.Exit(1) 24 | } 25 | 26 | var index uint64 27 | var exposedServices []string 28 | for { 29 | rt := proxy.RoutingTable{Table: map[string][]proxy.Upstream{}, ConsulIndex: 0} 30 | exposedServices, index, err = nomadClient.fetchExposedServices(index) 31 | if err != nil { 32 | slog.Warn("Failed to query Nomad's service catalog", "error", err) 33 | time.Sleep(waitTime) 34 | continue 35 | } 36 | 37 | for _, s := range exposedServices { 38 | details, err := nomadClient.fetchMatchingAllocations(s) 39 | if err != nil { 40 | slog.Warn("Failed to fetch service details", "error", err, "name", s) 41 | continue 42 | } 43 | for _, d := range details { 44 | rt.Update(&service{details: d}) 45 | } 46 | } 47 | c <- &rt 48 | time.Sleep(waitTime) 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | .PHONY: build fmt mkdistdir clean image release $(PLATFORMS) 2 | 3 | VERSION := $(shell git describe --tags) 4 | PLATFORMS := \ 5 | darwin/amd64 \ 6 | darwin/arm64 \ 7 | linux/386 \ 8 | linux/amd64 \ 9 | linux/arm \ 10 | linux/arm64 \ 11 | windows/386/.exe \ 12 | windows/amd64/.exe 13 | DIST_DIR := $(PWD)/dist 14 | OUTPUT_BINARY := $(DIST_DIR)/dill 15 | 16 | build: mkdistdir clean fmt 17 | go build \ 18 | -ldflags="-X 'main.version=$(VERSION)'" \ 19 | -o $(OUTPUT_BINARY) \ 20 | $(PWD)/cmd/dill/main.go 21 | 22 | fmt: 23 | goimports -w -local dill/ $(PWD) 24 | gofmt -s -w $(PWD) 25 | 26 | mkdistdir: 27 | -mkdir -p $(DIST_DIR) 28 | 29 | clean: 30 | -rm $(DIST_DIR)/* 31 | 32 | image: 33 | docker build -t dill:$(VERSION) . 34 | docker tag dill:$(VERSION) dill:latest 35 | 36 | temp = $(subst /, ,$@) 37 | os = $(word 1, $(temp)) 38 | arch = $(word 2, $(temp)) 39 | ext = $(word 3, $(temp)) 40 | 41 | release: $(PLATFORMS) image 42 | $(shell cd $(DIST_DIR); shasum -a 256 * > dill_$(VERSION)_sha256_checksums.txt) 43 | 44 | $(PLATFORMS): mkdistdir clean fmt 45 | GOOS=$(os) GOARCH=$(arch) go build \ 46 | -ldflags="-X 'main.version=$(VERSION)'" \ 47 | -o $(OUTPUT_BINARY)$(ext) \ 48 | $(PWD)/cmd/dill/main.go 49 | 50 | zip -jmq $(OUTPUT_BINARY)_$(VERSION)_$(os)_$(arch).zip $(OUTPUT_BINARY)$(ext) 51 | 52 | .PHONY: dill 53 | dill: build 54 | $(shell $(OUTPUT_BINARY) -config configs/config.toml) 55 | -------------------------------------------------------------------------------- /pkg/routing/file/file.go: -------------------------------------------------------------------------------- 1 | package file 2 | 3 | import ( 4 | "log/slog" 5 | 6 | "github.com/spf13/viper" 7 | 8 | "dill/pkg/proxy" 9 | ) 10 | 11 | type RoutingConfig []struct { 12 | Name string 13 | Listener string 14 | Backends []string 15 | Proxy string 16 | } 17 | 18 | func readRoutingConfig(v *viper.Viper) RoutingConfig { 19 | p := viper.GetString("routing.file.path") 20 | slog.Info("Reading routing config file", "path", p) 21 | 22 | v.SetConfigFile(p) 23 | err := v.ReadInConfig() 24 | if err != nil { 25 | slog.Error("Invalid routing config", "error", err) 26 | return nil 27 | } 28 | cfg := RoutingConfig{} 29 | v.UnmarshalKey("services", &cfg) 30 | 31 | return cfg 32 | } 33 | 34 | type service struct { 35 | name string 36 | listener string 37 | backend string 38 | proxy string 39 | } 40 | 41 | func (s *service) Name() string { 42 | return s.name 43 | } 44 | 45 | func (s *service) Routing() ([]string, string) { 46 | return []string{s.listener}, s.backend 47 | } 48 | 49 | func (s *service) Proxy() string { 50 | return s.proxy 51 | } 52 | 53 | // BuildRoutingTable builds routing table ouf of static routing configuration file 54 | func BuildRoutingTable(cfg RoutingConfig) proxy.RoutingTable { 55 | rt := proxy.RoutingTable{ConsulIndex: 1, Table: map[string][]proxy.Upstream{}} 56 | for _, e := range cfg { 57 | for _, b := range e.Backends { 58 | srv := service{ 59 | name: e.Name, 60 | listener: e.Listener, 61 | backend: b, 62 | proxy: e.Proxy, 63 | } 64 | rt.Update(&srv) 65 | } 66 | } 67 | return rt 68 | } 69 | -------------------------------------------------------------------------------- /pkg/proxy/controller.go: -------------------------------------------------------------------------------- 1 | package proxy 2 | 3 | import ( 4 | "log/slog" 5 | "os" 6 | ) 7 | 8 | // ControlRoutes waits for a new version of the routing table, 9 | // compares it with previous version and apply approriate changes. 10 | func ControlRoutes(c <-chan *RoutingTable, shutdown <-chan os.Signal) { 11 | slog.Info("Starting routes controller") 12 | prt := &RoutingTable{map[string][]Upstream{}, 1} 13 | for { 14 | select { 15 | case rt := <-c: 16 | updateRouting(rt, prt) 17 | prt = rt 18 | case <-shutdown: 19 | slog.Info("Closing routes controller") 20 | Shutdown() 21 | return 22 | } 23 | } 24 | } 25 | 26 | func updateRouting(routingTable *RoutingTable, previousRoutingTable *RoutingTable) { 27 | slog.Info( 28 | "Change occurred, updating the routing.", 29 | "consul_index", routingTable.ConsulIndex, 30 | ) 31 | for _, f := range difference( 32 | previousRoutingTable.FrontendAddresses(), 33 | routingTable.FrontendAddresses(), 34 | ) { 35 | if p := Lookup(f); p != nil { 36 | slog.Info("No backends for frontend", "address", f) 37 | p.Close() 38 | } 39 | } 40 | for port, upstreams := range routingTable.Table { 41 | if p := Lookup(port); p == nil { 42 | p = NewProxy( 43 | NewFrontend(port), 44 | NewBackend(upstreams), 45 | ) 46 | p.ListenAndServe() 47 | } else { 48 | p.UpdateBackend(upstreams) 49 | } 50 | } 51 | } 52 | 53 | // difference returns elements in `a` that aren't in `b` 54 | func difference(a, b []string) []string { 55 | mb := make(map[string]struct{}, len(b)) 56 | for _, x := range b { 57 | mb[x] = struct{}{} 58 | } 59 | var diff []string 60 | for _, x := range a { 61 | if _, found := mb[x]; !found { 62 | diff = append(diff, x) 63 | } 64 | } 65 | return diff 66 | } 67 | -------------------------------------------------------------------------------- /pkg/routing/http/monitor.go: -------------------------------------------------------------------------------- 1 | package http 2 | 3 | import ( 4 | "bytes" 5 | "crypto/sha256" 6 | "errors" 7 | "io" 8 | "log/slog" 9 | "net/http" 10 | "strings" 11 | "time" 12 | 13 | "github.com/spf13/viper" 14 | "golang.org/x/exp/slices" 15 | 16 | "dill/pkg/proxy" 17 | "dill/pkg/routing/file" 18 | ) 19 | 20 | var supportedFormats = []string{ 21 | "json", 22 | "toml", 23 | "yaml", 24 | } 25 | 26 | func computeHash(value []byte) []byte { 27 | h := sha256.New() 28 | h.Write(value) 29 | sum := h.Sum(nil) 30 | return sum 31 | } 32 | 33 | // setConfigType sets config type based on Content-Type HTTP header. 34 | func setConfigType(content_type string, v *viper.Viper) error { 35 | err := errors.New("unsupported config type") 36 | ct := strings.Split(content_type, "/") 37 | if len(ct) != 2 { 38 | return err 39 | } 40 | 41 | t := ct[len(ct)-1] 42 | if !slices.Contains(supportedFormats, t) { 43 | return err 44 | } 45 | v.SetConfigType(t) 46 | return nil 47 | } 48 | 49 | func MonitorServices(c chan<- *proxy.RoutingTable) { 50 | prevSum := []byte{} 51 | consulIndex := 0 52 | e := viper.GetString("routing.http.endpoint") 53 | pollInterval := viper.GetDuration("routing.http.poll_interval") 54 | pollTimeout := viper.GetDuration("routing.http.poll_timeout") 55 | client := http.Client{Timeout: pollTimeout} 56 | 57 | for { 58 | res, err := client.Get(e) 59 | if err != nil { 60 | slog.Error("Failed to fetch routing configuration", "error", err) 61 | time.Sleep(pollInterval) 62 | continue 63 | } 64 | if !(res.StatusCode >= 200 && res.StatusCode <= 299) { 65 | slog.Error("Failed to fetch routing configuration", "status_code", res.StatusCode) 66 | time.Sleep(pollInterval) 67 | continue 68 | } 69 | 70 | data, _ := io.ReadAll(res.Body) 71 | res.Body.Close() 72 | 73 | ct := res.Header.Get("Content-Type") 74 | v := viper.New() 75 | err = setConfigType(ct, v) 76 | if err != nil { 77 | slog.Error("Failed to set routing config type", "content_type", ct, "error", err) 78 | time.Sleep(pollInterval) 79 | continue 80 | } 81 | 82 | sum := computeHash(data) 83 | if bytes.Equal(sum, prevSum) { 84 | time.Sleep(pollInterval) 85 | continue 86 | } 87 | consulIndex += 1 88 | prevSum = sum 89 | 90 | err = v.ReadConfig(bytes.NewBuffer(data)) 91 | if err != nil { 92 | slog.Info("Invalid routing config", "error", err) 93 | } 94 | 95 | cfg := file.RoutingConfig{} 96 | v.UnmarshalKey("services", &cfg) 97 | 98 | rt := file.BuildRoutingTable(cfg) 99 | c <- &rt 100 | time.Sleep(pollInterval) 101 | } 102 | } 103 | -------------------------------------------------------------------------------- /pkg/proxy/table.go: -------------------------------------------------------------------------------- 1 | package proxy 2 | 3 | import ( 4 | "fmt" 5 | "log/slog" 6 | "strconv" 7 | "strings" 8 | 9 | "github.com/spf13/viper" 10 | ) 11 | 12 | type Service interface { 13 | Routing() ([]string, string) 14 | Name() string 15 | Proxy() string 16 | } 17 | 18 | type RoutingTable struct { 19 | Table map[string][]Upstream 20 | ConsulIndex int 21 | } 22 | 23 | // Update validates the service's routing settings 24 | // and updates routing table if it's valid. 25 | func (rt *RoutingTable) Update(service Service) { 26 | listeners, upstreamAddr := service.Routing() 27 | if len(listeners) == 0 { 28 | slog.Warn("No listeners found", 29 | "service_name", service.Name(), 30 | "upstream", upstreamAddr, 31 | ) 32 | return 33 | } 34 | 35 | for _, addr := range listeners { 36 | a := strings.Split(addr, ":") 37 | if len(a) != 2 { 38 | slog.Warn("Invalid listener address, port missing.", 39 | "address", addr, 40 | "service_name", service.Name(), 41 | "upstream", upstreamAddr, 42 | ) 43 | continue 44 | } 45 | label, port := a[0], a[1] 46 | 47 | if p, err := strconv.Atoi(port); p <= viper.GetInt("listeners.port_min") || 48 | p >= viper.GetInt("listeners.port_max") || 49 | err != nil { 50 | slog.Warn("Invalid listener port", 51 | "port", port, 52 | "service_name", service.Name(), 53 | "upstream", upstreamAddr, 54 | ) 55 | continue 56 | } 57 | 58 | allowedListeners := viper.GetStringMapString("listeners.allowed") 59 | labelValid := false 60 | for l, ip := range allowedListeners { 61 | if label == l { 62 | labelValid = true 63 | addr = fmt.Sprintf("%s:%s", ip, a[1]) 64 | break 65 | } 66 | } 67 | if !labelValid { 68 | slog.Warn("Invalid listener label", 69 | "label", label, 70 | "allowed_listeners", allowedListeners, 71 | "service_name", service.Name(), 72 | "upstream", upstreamAddr, 73 | ) 74 | continue 75 | } 76 | 77 | rt.update(addr, Upstream{address: upstreamAddr, prx: service.Proxy()}) 78 | } 79 | } 80 | 81 | func (rt *RoutingTable) update(addr string, upstream Upstream) { 82 | if t, ok := rt.Table[addr]; ok { 83 | rt.Table[addr] = append(t, upstream) 84 | } else { 85 | rt.Table[addr] = []Upstream{upstream} 86 | } 87 | } 88 | 89 | func (rt *RoutingTable) FrontendAddresses() []string { 90 | addrs := make([]string, len(rt.Table)) 91 | i := 0 92 | for k := range rt.Table { 93 | addrs[i] = k 94 | i++ 95 | } 96 | return addrs 97 | } 98 | 99 | func (rt *RoutingTable) Dump() { 100 | for key, value := range rt.Table { 101 | fmt.Println(key) 102 | for _, ip := range value { 103 | fmt.Printf(" --> %s\n", ip) 104 | } 105 | } 106 | } 107 | -------------------------------------------------------------------------------- /pkg/routing/nomad/nomad.go: -------------------------------------------------------------------------------- 1 | package nomad 2 | 3 | import ( 4 | "fmt" 5 | "strings" 6 | "time" 7 | 8 | "github.com/hashicorp/nomad/api" 9 | "golang.org/x/exp/slices" 10 | ) 11 | 12 | type service struct { 13 | details *api.ServiceRegistration 14 | } 15 | 16 | func (s *service) Routing() ([]string, string) { 17 | listeners := []string{} 18 | for _, t := range s.details.Tags { 19 | if strings.HasPrefix(t, "dill.listener=") { 20 | v := strings.Split(t, "=") 21 | listeners = append(listeners, v[1]) 22 | } 23 | } 24 | return listeners, fmt.Sprintf("%s:%d", s.details.Address, s.details.Port) 25 | } 26 | 27 | func (s *service) Name() string { 28 | return s.details.ServiceName 29 | } 30 | 31 | func (s *service) Proxy() string { 32 | for _, t := range s.details.Tags { 33 | if strings.HasPrefix(t, "dill.proxy=") { 34 | v := strings.Split(t, "=") 35 | return v[1] 36 | } 37 | } 38 | return "" 39 | } 40 | 41 | type nomadConfig struct { 42 | Address string 43 | Token string 44 | Namespace string 45 | Wait time.Duration 46 | Stale bool 47 | TLS struct { 48 | CA string 49 | Cert string 50 | Key string 51 | Insecure bool 52 | } 53 | } 54 | 55 | type nomadClient struct { 56 | client *api.Client 57 | config *nomadConfig 58 | } 59 | 60 | // fetchExposedServices fetches services that was registered 61 | // in Nomad's service catalog with tag `dill` 62 | func (c *nomadClient) fetchExposedServices(index uint64) ([]string, uint64, error) { 63 | namespacedServices, meta, err := c.client.Services().List( 64 | &api.QueryOptions{AllowStale: c.config.Stale, WaitIndex: index, WaitTime: c.config.Wait}, 65 | ) 66 | if err != nil { 67 | return nil, 0, err 68 | } 69 | 70 | services := []string{} 71 | for _, ns := range namespacedServices { 72 | for _, s := range ns.Services { 73 | if slices.Contains(s.Tags, "dill") { 74 | services = append(services, s.ServiceName) 75 | } 76 | } 77 | } 78 | return services, meta.LastIndex, nil 79 | } 80 | 81 | // fetchMatchingAllocations fetches all alocations of the service tagged as `dill`. 82 | // As `api.Client.Services().List()` of `fetchExposedServices()` will return services 83 | // with aggregated list of tags from old and new allocations, matching tags once again 84 | // is required to cover case when you deploy already running job with new `dill` tags. 85 | func (c *nomadClient) fetchMatchingAllocations(name string) ([]*api.ServiceRegistration, error) { 86 | services, _, err := c.client.Services().Get( 87 | name, 88 | &api.QueryOptions{AllowStale: c.config.Stale}, 89 | ) 90 | if err != nil { 91 | return nil, err 92 | } 93 | 94 | var matchingServices []*api.ServiceRegistration 95 | for _, s := range services { 96 | if slices.Contains(s.Tags, "dill") { 97 | matchingServices = append(matchingServices, s) 98 | } 99 | } 100 | return matchingServices, nil 101 | } 102 | 103 | func newNomadClient(config *nomadConfig) (*nomadClient, error) { 104 | client, err := api.NewClient(&api.Config{ 105 | Address: config.Address, 106 | SecretID: config.Token, 107 | Namespace: config.Namespace, 108 | WaitTime: config.Wait, 109 | TLSConfig: &api.TLSConfig{ 110 | CACert: config.TLS.CA, 111 | ClientCert: config.TLS.Cert, 112 | ClientKey: config.TLS.Key, 113 | Insecure: config.TLS.Insecure, 114 | }, 115 | }) 116 | if err != nil { 117 | return nil, err 118 | } 119 | 120 | return &nomadClient{client: client, config: config}, nil 121 | } 122 | -------------------------------------------------------------------------------- /pkg/routing/consul/consul.go: -------------------------------------------------------------------------------- 1 | package consul 2 | 3 | import ( 4 | "encoding/json" 5 | "fmt" 6 | "io" 7 | "net/http" 8 | "net/url" 9 | "strconv" 10 | "strings" 11 | ) 12 | 13 | type service struct { 14 | ID string `json:"ID"` 15 | Name_ string `json:"Service"` 16 | Tags []string `json:"Tags"` 17 | Address string `json:"Address"` 18 | Port int `json:"Port"` 19 | } 20 | 21 | func (s *service) Routing() ([]string, string) { 22 | listeners := []string{} 23 | for _, t := range s.Tags { 24 | if strings.HasPrefix(t, "dill.listener=") { 25 | v := strings.Split(t, "=") 26 | listeners = append(listeners, v[1]) 27 | } 28 | } 29 | return listeners, fmt.Sprintf("%s:%d", s.Address, s.Port) 30 | } 31 | 32 | func (s *service) Name() string { 33 | return s.Name_ 34 | } 35 | 36 | func (s *service) Proxy() string { 37 | for _, t := range s.Tags { 38 | if strings.HasPrefix(t, "dill.proxy=") { 39 | v := strings.Split(t, "=") 40 | return v[1] 41 | } 42 | } 43 | return "" 44 | } 45 | 46 | func fetchHealthyServices(index int, consulClient *httpClient) ([]string, int, error) { 47 | req, err := http.NewRequest( 48 | "GET", 49 | consulClient.config.Address+"/v1/health/state/passing", 50 | nil, 51 | ) 52 | if err != nil { 53 | return nil, 0, err 54 | } 55 | q := url.Values{} 56 | q.Add("filter", "ServiceTags contains `dill`") 57 | if index <= 0 { 58 | index = 1 59 | } 60 | q.Add("index", strconv.Itoa(index)) 61 | if consulClient.config.Wait != "" { 62 | q.Add("wait", consulClient.config.Wait) 63 | } 64 | req.URL.RawQuery = q.Encode() 65 | res, err := consulClient.Do(req) 66 | if err != nil { 67 | return nil, 0, err 68 | } 69 | defer res.Body.Close() 70 | data, _ := io.ReadAll(res.Body) 71 | 72 | if res.StatusCode != 200 { 73 | return nil, 0, fmt.Errorf("%d: %s", res.StatusCode, data) 74 | } 75 | 76 | var healthyServices []struct { 77 | Name string `json:"ServiceName"` 78 | } 79 | err = json.Unmarshal([]byte(data), &healthyServices) 80 | if err != nil { 81 | return nil, 0, err 82 | } 83 | 84 | unique := []string{} 85 | keys := map[string]struct{}{} 86 | for _, s := range healthyServices { 87 | if _, ok := keys[s.Name]; !ok { 88 | keys[s.Name] = struct{}{} 89 | unique = append(unique, s.Name) 90 | } 91 | } 92 | 93 | newIndex, err := strconv.Atoi(res.Header.Get("X-Consul-Index")) 94 | if err != nil || newIndex < index || newIndex < 0 { 95 | newIndex = 1 96 | } 97 | return unique, newIndex, nil 98 | } 99 | 100 | func fetchServiceDetails(name string, consulClient *httpClient) ([]service, error) { 101 | req, err := http.NewRequest( 102 | "GET", 103 | consulClient.config.Address+"/v1/health/service/"+name, 104 | nil, 105 | ) 106 | if err != nil { 107 | return nil, err 108 | } 109 | q := url.Values{} 110 | q.Add("passing", "true") 111 | req.URL.RawQuery = q.Encode() 112 | res, err := consulClient.Do(req) 113 | if err != nil { 114 | return nil, err 115 | } 116 | 117 | defer res.Body.Close() 118 | data, _ := io.ReadAll(res.Body) 119 | 120 | if res.StatusCode != 200 { 121 | return nil, fmt.Errorf("%d: %s", res.StatusCode, data) 122 | } 123 | 124 | var parsed []struct { 125 | Node struct { 126 | Address string `json:"Address"` 127 | } `json:"Node"` 128 | Service service `json:"Service"` 129 | } 130 | err = json.Unmarshal([]byte(data), &parsed) 131 | if err != nil { 132 | return nil, err 133 | } 134 | services := []service{} 135 | for _, r := range parsed { 136 | s := r.Service 137 | // IP address of the service host — if empty, node address should be used 138 | // Ref. https://www.consul.io/api-docs/catalog#serviceaddress 139 | if s.Address == "" { 140 | s.Address = r.Node.Address 141 | } 142 | services = append(services, s) 143 | } 144 | return services, nil 145 | } 146 | -------------------------------------------------------------------------------- /pkg/proxy/proxy.go: -------------------------------------------------------------------------------- 1 | package proxy 2 | 3 | import ( 4 | "io" 5 | "log/slog" 6 | "net" 7 | "net/url" 8 | "strings" 9 | "sync" 10 | "time" 11 | 12 | proxy_ "golang.org/x/net/proxy" 13 | ) 14 | 15 | var ( 16 | proxies = make(map[string]*Proxy) 17 | rwm sync.RWMutex 18 | ) 19 | 20 | var bufferPool = sync.Pool{ 21 | New: func() interface{} { 22 | b := make([]byte, 32*1024) 23 | return &b 24 | }, 25 | } 26 | 27 | func NewProxy(frontend *Frontend, backend *Backend) *Proxy { 28 | p := &Proxy{frontend: frontend, backend: backend, quit: make(chan struct{})} 29 | rwm.Lock() 30 | proxies[frontend.Address] = p 31 | rwm.Unlock() 32 | return p 33 | } 34 | 35 | func Lookup(listenerAddr string) *Proxy { 36 | rwm.RLock() 37 | p, ok := proxies[listenerAddr] 38 | rwm.RUnlock() 39 | if ok { 40 | return p 41 | } 42 | return nil 43 | } 44 | 45 | // Shutdown gracefully closes proxies, its listeners and all the connections. 46 | func Shutdown() { 47 | rwm.Lock() 48 | for _, p := range proxies { 49 | close(p.quit) 50 | p.frontend.Close() 51 | p.wg.Wait() 52 | } 53 | proxies = make(map[string]*Proxy) 54 | rwm.Unlock() 55 | } 56 | 57 | type Proxy struct { 58 | frontend *Frontend 59 | backend *Backend 60 | quit chan struct{} 61 | wg sync.WaitGroup 62 | } 63 | 64 | func (p *Proxy) ListenAndServe() { 65 | err := p.frontend.Listen() 66 | if err != nil { 67 | slog.Warn("Can't establish frontend listener", "error", err) 68 | return 69 | } 70 | p.wg.Add(1) 71 | go func() { 72 | err := p.serve() 73 | if err != nil { 74 | slog.Error( 75 | "Can't serve requests", 76 | "error", err, 77 | "listener", p.frontend.Address, 78 | ) 79 | } 80 | p.wg.Done() 81 | }() 82 | } 83 | 84 | func (p *Proxy) Close() { 85 | close(p.quit) 86 | p.frontend.Close() 87 | p.wg.Wait() 88 | 89 | rwm.Lock() 90 | delete(proxies, p.frontend.Address) 91 | rwm.Unlock() 92 | } 93 | 94 | func (p *Proxy) UpdateBackend(upstreams []Upstream) { 95 | p.backend.SetUpstreams(upstreams) 96 | } 97 | 98 | func (p *Proxy) serve() error { 99 | for { 100 | c, err := p.frontend.Accept() 101 | if err != nil { 102 | select { 103 | case <-p.quit: 104 | return nil 105 | default: 106 | if ne, ok := err.(net.Error); ok && ne.Temporary() { 107 | time.Sleep(50 * time.Millisecond) 108 | continue 109 | } 110 | return err 111 | } 112 | } 113 | p.wg.Add(1) 114 | go func() { 115 | p.handle(c, p.backend.Select()) 116 | p.frontend.RemoveConn(c) 117 | p.wg.Done() 118 | }() 119 | } 120 | } 121 | 122 | type dialer interface { 123 | Dial(string, string) (net.Conn, error) 124 | } 125 | 126 | func (p *Proxy) dial(u Upstream) (net.Conn, error) { 127 | var d dialer 128 | if u.prx != "" { 129 | url, err := url.Parse(u.prx) 130 | if err != nil { 131 | slog.Info("Failed to parse proxy URL", "error", err) 132 | return nil, err 133 | } 134 | d, err = proxy_.FromURL(url, proxy_.Direct) 135 | if err != nil { 136 | slog.Info("SOCKS proxy connection failed", "error", err) 137 | return nil, err 138 | } 139 | } else { 140 | d = &net.Dialer{} 141 | } 142 | 143 | out, err := d.Dial("tcp", u.address) 144 | return out, err 145 | } 146 | 147 | func (p *Proxy) handle(in net.Conn, u Upstream) { 148 | out, err := p.dial(u) 149 | if err != nil { 150 | in.Close() 151 | slog.Info("Connection to upstream failed", "error", err) 152 | return 153 | } 154 | once := sync.Once{} 155 | cp := func(dst net.Conn, src net.Conn) { 156 | buf := bufferPool.Get().(*[]byte) 157 | defer bufferPool.Put(buf) 158 | _, _ = io.CopyBuffer(dst, src, *buf) 159 | once.Do(func() { 160 | in.Close() 161 | out.Close() 162 | }) 163 | } 164 | go cp(in, out) 165 | cp(out, in) 166 | } 167 | 168 | func Dump() string { 169 | var bd strings.Builder 170 | rwm.RLock() 171 | for addr, p := range proxies { 172 | bd.WriteString(addr) 173 | bd.WriteString("\n") 174 | bd.WriteString(p.backend.Dump()) 175 | } 176 | rwm.RUnlock() 177 | return bd.String() 178 | } 179 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # dill 2 | 3 | `dill` is an L4 TCP proxy and load balancer specifically designed for dynamic, cloud-native environments. Its core strength lies in **dynamic listener management**. Unlike traditional proxies requiring predefined static listening ports in configuration files, `dill` automatically opens listening ports on the host based on configuration discovered through dynamic providers (like [Consul](#consul), [Nomad](#nomad), [HTTP](#http), or watched [Files](#file)). This enables effortless exposure of backend services on demand, eliminating the need for manual proxy reconfiguration and restarts as your services scale or change. 4 | 5 | Ready to see how simple dynamic TCP proxying can be? Jump into the [Getting Started](#getting-started) section to give `dill` a try! 6 | 7 | ### How `dill` differs from classic proxies? 8 | 9 | Traditional L4 proxies and load balancers often require operators to pre-define the complete set of listening ports in static configuration files. Exposing a new service on a previously unused port typically involves manually updating this configuration and reloading or restarting the proxy instance. `dill` fundamentally differs by embracing dynamic listener management. Instead of static declarations, `dill` discovers the required listeners (IP addresses and ports) through its configured [Routing Providers](#providers) (like Consul, Nomad, or even a watched file). This allows `dill` to open and close frontend listening ports on-demand as backend services appear and disappear, offering greater agility and automation, especially in dynamic, service-oriented environments where manual port management becomes a significant operational burden. 10 | 11 | ## Table of Contents 12 | - [dill](#dill) 13 | - [Table of Contents](#table-of-contents) 14 | - [Installation](#installation) 15 | - [Pre-build binaries](#pre-build-binaries) 16 | - [Build from sources](#build-from-sources) 17 | - [Docker](#docker) 18 | - [Getting Started](#getting-started) 19 | - [Routing](#routing) 20 | - [Providers](#providers) 21 | - [File](#file) 22 | - [HTTP](#http) 23 | - [Consul](#consul) 24 | - [Nomad](#nomad) 25 | - [Load balancing](#load-balancing) 26 | - [Schema](#schema) 27 | - [name](#name) 28 | - [listener](#listener) 29 | - [backends](#backends) 30 | - [proxy](#proxy) 31 | - [Proxying](#proxying) 32 | - [Configuration](#configuration) 33 | - [Values](#values) 34 | - [listeners.allowed `map`](#listenersallowed-map) 35 | - [listeners.port\_min `integer`](#listenersport_min-integer) 36 | - [listeners.port\_max `integer`](#listenersport_max-integer) 37 | - [peek.listener `string`](#peeklistener-string) 38 | - [runtime.gomaxprocs `integer`](#runtimegomaxprocs-integer) 39 | - [routing.file.path `string`](#routingfilepath-string) 40 | - [routing.file.watch `bool`](#routingfilewatch-bool) 41 | - [routing.http.endpoint `string`](#routinghttpendpoint-string) 42 | - [routing.http.poll\_interval `duration`](#routinghttppoll_interval-duration) 43 | - [routing.http.poll\_timeout `duration`](#routinghttppoll_timeout-duration) 44 | - [routing.consul.address `string`](#routingconsuladdress-string) 45 | - [routing.consul.token `string`](#routingconsultoken-string) 46 | - [routing.consul.datacenter `string`](#routingconsuldatacenter-string) 47 | - [routing.consul.namespace `string`](#routingconsulnamespace-string) 48 | - [routing.consul.wait `duration`](#routingconsulwait-duration) 49 | - [routing.consul.consistency\_mode `string`](#routingconsulconsistency_mode-string) 50 | - [routing.nomad.address `string`](#routingnomadaddress-string) 51 | - [routing.nomad.token `string`](#routingnomadtoken-string) 52 | - [routing.nomad.namespace `string`](#routingnomadnamespace-string) 53 | - [routing.nomad.wait `duration`](#routingnomadwait-duration) 54 | - [routing.nomad.stale `bool`](#routingnomadstale-bool) 55 | - [routing.nomad.tls.ca `string`](#routingnomadtlsca-string) 56 | - [routing.nomad.tls.cert `string`](#routingnomadtlscert-string) 57 | - [routing.nomad.tls.key `string`](#routingnomadtlskey-string) 58 | - [routing.nomad.tls.insecure `bool`](#routingnomadtlsinsecure-bool) 59 | - [Formats](#formats) 60 | - [TOML](#toml) 61 | - [YAML](#yaml) 62 | - [JSON](#json) 63 | - [Environment variables](#environment-variables) 64 | - [Project status](#project-status) 65 | - [Alternatives](#alternatives) 66 | 67 | ## Installation 68 | ### Pre-build binaries 69 | You can find pre-built binaries in [Releases](https://github.com/fwkz/dill/releases). 70 | ### Build from sources 71 | ``` 72 | $ make build 73 | ``` 74 | Compiled binary will be available inside `dist/` directory. 75 | ### Docker 76 | ``` 77 | $ docker pull fwkz/dill 78 | ``` 79 | or build the image yourself from the sources 80 | ``` 81 | make image 82 | ``` 83 | ## Getting Started 84 | 85 | `dill`'s core strength lies in its ability to dynamically manage listening ports based on your service configuration, eliminating the need for static port definitions typical in traditional proxies. Let's see how this works. 86 | 87 | First, you need to tell `dill` how to discover your services. We'll start with the simple `file` provider, which reads service definitions from a file. Configure `dill` to use it: 88 | 89 | ```toml 90 | # /etc/dill/config.toml 91 | [routing.file] 92 | # Path to the file containing service definitions 93 | path = "/etc/dill/routing.toml" 94 | # Automatically reload routing when the file changes 95 | watch = true 96 | 97 | # Define which listener labels map to which IP addresses 98 | [listeners.allowed] 99 | # By default, 'local' maps to localhost and 'any' maps to all interfaces 100 | local = "127.0.0.1" 101 | any = "0.0.0.0" 102 | # You could add more, e.g., internal = "192.168.1.10" 103 | ``` 104 | 105 | The `listeners.allowed` map in your main `dill` configuration (`config.toml` in this case) defines labels (like `local`, `any`) and maps them to specific IP addresses that `dill` is allowed to bind to. This gives you control over which network interfaces services can be exposed on. 106 | 107 | Now, define your initial service(s) in the routing file: 108 | 109 | ```toml 110 | # /etc/dill/routing.toml 111 | [[services]] 112 | name = "foo" 113 | # Request dill to listen on port 1234 using the 'any' listener label. 114 | # Based on the default config above, 'any' means 0.0.0.0 (all interfaces). 115 | listener = "any:1234" 116 | backends = ["192.168.10.1:5050"] 117 | ``` 118 | 119 | Run `dill`: 120 | 121 | ```shell 122 | $ dill -config /etc/dill/config.toml 123 | ``` 124 | 125 | And that's it! Based *solely* on the `routing.toml` definition, `dill` looks up the `any` label in its `listeners.allowed` map, finds it corresponds to `0.0.0.0`, and **dynamically opens port 1234** on `0.0.0.0`. It then starts proxying traffic arriving on that port to `192.168.10.1:5050`. 126 | 127 | Now, imagine you deploy a new service (`bar`) that needs to be exposed on port `4321`. With a traditional proxy, you might need to modify the main proxy configuration and reload it. With `dill`, you simply update the routing source: 128 | 129 | ```toml 130 | # /etc/dill/routing.toml 131 | [[services]] 132 | name = "foo" 133 | listener = "any:1234" 134 | # Updated backend list for foo 135 | backends = ["192.168.10.1:5050", "192.168.10.2:4050"] 136 | 137 | [[services]] 138 | name = "bar" 139 | # Request dill to listen on a *new* port: 4321, also on the 'any' (0.0.0.0) interface. 140 | listener = "any:4321" 141 | backends = ["192.168.10.3:4444"] 142 | ``` 143 | 144 | Because `watch = true` was set, `dill` detects the change in `/etc/dill/routing.toml`. It automatically applies the necessary modifications: it updates the backends for the `foo` service and, crucially, **dynamically starts listening on the new port 4321** (using the IP address mapped to `any`, which is `0.0.0.0`) to route traffic to the `bar` service. No proxy restarts or manual configuration changes are needed to expose the new port. 145 | 146 | This dynamic listener capability becomes incredibly powerful when combined with service discovery systems like [Consul](#consul) or [Nomad](#nomad). Services can register themselves and specify their desired listener (e.g., `any:8080` or `local:9000`) via tags, allowing `dill` to automatically expose them on the correct interface and port without any manual intervention. 147 | 148 | ## Routing 149 | ### Providers 150 | `dill` offers multiple routing providers that allow you to apply live changes to the routing configuration. 151 | - [File](#file) 152 | - [HTTP](#http) 153 | - [Consul](#consul) 154 | - [Nomad](#nomad) 155 | 156 | You can help build the project and implement your routing provider based on your needs. If you need guidance, make sure to create Pull Request. 157 | #### File 158 | It is the simplest way of defining routing. All routing logic is being kept in a separate [config file](#schema). By setting `routing.file.watch = true` you can also subscribe to changes made to the routing configuration file which would give you the full power of dill's dynamic routing capabilities. 159 | ```toml 160 | [routing.file] 161 | path = "/etc/dill/routing.toml" 162 | watch = true 163 | ``` 164 | #### HTTP 165 | `dill` can poll the HTTP endpoint for its routing configuration with a predefined time interval. Fetched data should be compliant with [routing configuration schema](#schema) and it will be parsed based on the response `Content-Type` header. 166 | ```toml 167 | [routing.http] 168 | endpoint = "http://127.0.0.1:8000/config/routing.json" 169 | poll_interval = "5s" 170 | poll_timeout = "5s" 171 | ``` 172 | #### Consul 173 | `dill` can build its routing table based on services registered in `Consul`. All you need to do in order to expose Consul registered service in `dill` instace is to add appropriate tags. 174 | * `dill` tag registers service and its updates with `dill` instance. 175 | * `dill.listener` binds, based on predefined listeners declared by [`listeners.allowed`](#listenersallowed-map), service to specific address and port. 176 | ```json 177 | { 178 | "service": { 179 | "tags": [ 180 | "dill", 181 | "dill.listener=local:5555", 182 | ], 183 | } 184 | } 185 | ``` 186 | In order to pass traffic via [proxy](#proxying) make sure to add `dill.proxy` tag: 187 | ```json 188 | { 189 | "service": { 190 | "tags": [ 191 | "dill", 192 | "dill.listener=local:5555", 193 | "dill.proxy=socks5://admin:password@192.168.10.11:1080" 194 | ], 195 | } 196 | } 197 | ``` 198 | Example configuration of Consul routing provider: 199 | ```toml 200 | [routing.consul] 201 | address = "http://127.0.0.1:8500" 202 | token = "consul-communication-secret-token" 203 | datacenter = "foo-1" 204 | namespace = "bar-namespace" 205 | wait = "2m" 206 | consistency_mode = "consistent" 207 | ``` 208 | #### Nomad 209 | Since version [`1.3.0`](https://github.com/hashicorp/nomad/releases/tag/v1.3.0) Nomad introduced native service discovery. Principales of exposing Nomad workloads in `dill` are exactly the same as for [Consul routing provider](#consul). 210 | 211 | Example [`service`](https://developer.hashicorp.com/nomad/docs/job-specification/service) block of Nomad job. 212 | ``` 213 | service { 214 | name = "foobar" 215 | tags = ["dill", "dill.listener=any:3821"] 216 | port = "db" 217 | provider = "nomad" 218 | 219 | check { 220 | name = "alive" 221 | type = "tcp" 222 | interval = "10s" 223 | timeout = "2s" 224 | } 225 | } 226 | ``` 227 | Example configuration of Nomad routing provider: 228 | ```toml 229 | [routing.nomad] 230 | address = "http://127.0.0.1:4646" 231 | token = "nomad-communication-secret-token" 232 | namespace = "foobar-namespace" 233 | wait = "5m" 234 | stale = true 235 | [routing.nomad.tls] 236 | ca = "/foo/bar/ca" 237 | cert = "/foo/bar/cert" 238 | key = "/foo/bar/key" 239 | insecure = false 240 | ``` 241 | ### Load balancing 242 | `dill` distributes load across the backends using _round-robin_ strategy 243 | 244 | ### Schema 245 | The routing configuration should be compliant with following schema: 246 | ```json 247 | { 248 | "$schema": "https://json-schema.org/draft/2020-12/schema", 249 | "type": "object", 250 | "properties": { 251 | "services": { 252 | "type": "array", 253 | "items":{ 254 | "type": "object", 255 | "properties": { 256 | "name": {"type": "string"}, 257 | "listener": {"type": "string"}, 258 | "backends": {"type": "array", "items": {"type": "string"}}, 259 | "proxy": {"type": "string"} 260 | }, 261 | "required": [ 262 | "name", 263 | "listener", 264 | "backends" 265 | ] 266 | } 267 | } 268 | }, 269 | "required": [ 270 | "services" 271 | ] 272 | } 273 | ``` 274 | Example routing config: 275 | ```json 276 | { 277 | "services": [ 278 | { 279 | "name": "foo", 280 | "listener": "local:1234", 281 | "backends": [ 282 | "127.0.0.1:5050" 283 | ], 284 | "proxy": "socks5://user:pass@127.0.0.1:1080" 285 | }, 286 | { 287 | "name": "bar", 288 | "listener": "any:4444", 289 | "backends": [ 290 | "127.0.0.1:4000", 291 | "127.0.0.1:4001" 292 | ] 293 | } 294 | ] 295 | } 296 | ``` 297 | or equivalent in different format e.g. `TOML`, `YAML`: 298 | ```toml 299 | # /etc/dill/routing.toml 300 | [[services]] 301 | name = "foo" 302 | listener = "local:1234" 303 | backends = ["127.0.0.1:5050"] 304 | proxy = "socks5://user:pass@127.0.0.1:1080" # optional 305 | 306 | [[services]] 307 | name = "bar" 308 | listener = "any:4444" 309 | backends = ["127.0.0.1:4000", "127.0.0.1:4001"] 310 | ``` 311 | ```yaml 312 | services: 313 | - name: foo 314 | listener: local:1234 315 | backends: 316 | - 127.0.0.1:5050 317 | proxy: socks5://user:pass@127.0.0.1:1080 318 | - name: bar 319 | listener: any:4444 320 | backends: 321 | - 127.0.0.1:4000 322 | - 127.0.0.1:4001 323 | ``` 324 | #### name 325 | Name of the service. 326 | #### listener 327 | Listener that binds, based on predefined list declared by [`listeners.allowed`](#listenersallowed-map), backend to specific address and port. 328 | #### backends 329 | List of backend services that will be load balanced. 330 | #### proxy 331 | [Proxy](#proxying) address if you want to tunnel the traffic. 332 | 333 | _Optional_ 334 | ### Proxying 335 | `dill` is capable of tunneling traffic to backend services using SOCKS proxy. 336 | ```toml 337 | # /etc/dill/routing.toml 338 | [[services]] 339 | name = "foobar" 340 | listener = "any:4444" 341 | backends = ["192.168.10.11:4001"] 342 | proxy = "socks5://user:password@192.168.10.10:1080" 343 | ``` 344 | ```text 345 | incoming ┌───────────┐ ┌─────────────┐ ┌───────────────┐ 346 | ─────connection────►4444 dill ├────────►1080 SOCKS ├─────────►4001 Backend │ 347 | └───────────┘ └─────────────┘ └───────────────┘ 348 | 192.168.10.10 192.168.10.11 349 | ``` 350 | 351 | ## Configuration 352 | `dill` already comes with sane defaults but you can adjust its behaviour providing configuration file 353 | ```bash 354 | $ dill -config config.toml 355 | ``` 356 | or use environment variables 357 | ```bash 358 | $ export DILL_CONSUL_ADDRESS="http://127.0.0.1:8500" 359 | $ DILL_LISTENERS_PORT_MIN=1234 dill 360 | ``` 361 | 362 | ### Values 363 | #### listeners.allowed `map` 364 | Interface addresses that are allowed to be bind to by upstream services. Address labels (keys in the map) are opaque for `dill`. 365 | 366 | Imagine that a machine hosting `dill` has two interfaces, one is internal (192.168.10.10) and the other is external (12.42.22.65). You might want to use the following setup 367 | ```toml 368 | [listeners.allowed] 369 | internal = "192.168.10.10" 370 | public = "12.42.22.65" 371 | ``` 372 | with such configuration, upstream services that want to be accessible on `12.42.22.65:5555` can use the `public` listener in Consul tags `dill.listener=public:5555`. 373 | 374 | _default: `{"local": "127.0.0.1", "any": "0.0.0.0"}`_ 375 | #### listeners.port_min `integer` 376 | Minimal port value at which it will be allowed to expose upstream services. Backends requesting to be exposed on lower ports will be dropped from routing. 377 | 378 | _default: `1024`_ 379 | #### listeners.port_max `integer` 380 | Maximum port value at which it will be allowed to expose upstream services. Backends requesting to be exposed on higher ports will be dropped from routing. 381 | 382 | _default: `49151`_ 383 | #### peek.listener `string` 384 | Address on which `Peek` will be exposed. `Peek` is a TCP debug server spawned alongside the `dill`. Connecting to it will return the current state of the routing table. By default `Peek` is turned off. 385 | 386 | _default: `""`_ 387 | ``` 388 | $ nc 127.0.0.1 2323 389 | 0.0.0.0:4444 390 | ├ round_robin 391 | ├──➤ 192.168.10.17:1234 392 | ├──➤ 192.168.10.23:2042 393 | 0.0.0.0:8088 394 | ├ round_robin 395 | ├──➤ 192.168.10.11:5728 396 | ├──➤ 192.168.65.87:5942 397 | ``` 398 | #### runtime.gomaxprocs `integer` 399 | Value of Go's `runtime.GOMAXPROCS()` 400 | 401 | _default: equals to `runtime.NumCPU()`_ 402 | 403 | --- 404 | #### routing.file.path `string` 405 | Location of [routing configuration file](#schema). 406 | #### routing.file.watch `bool` 407 | Subscribe to changes made to the routing configuration file which would give you the full power of dill's dynamic routing capabilities. 408 | 409 | _default: `true`_ 410 | 411 | --- 412 | #### routing.http.endpoint `string` 413 | Endpoint which [http provider](#http) will poll for routing configuration 414 | #### routing.http.poll_interval `duration` 415 | How often [http provider](#http) will poll [endpoint](#routinghttpendpoint-string) for routing configuration 416 | 417 | _default: `5s`_ 418 | #### routing.http.poll_timeout `duration` 419 | Maximum time [http provider](#http) will wait when fetching routing configuration 420 | 421 | _default: `5s`_ 422 | 423 | --- 424 | #### routing.consul.address `string` 425 | Consul address from which `dill` will fetch the updates and build the routing table. 426 | #### routing.consul.token `string` 427 | Token giving access to Consul API. Required ACLs `node:read,service:read` 428 | 429 | _Optional_ 430 | #### routing.consul.datacenter `string` 431 | Defines what datacenter will be queried when building routing table. 432 | 433 | _Optional. If not provided `dill` uses Consul defaults._ 434 | #### routing.consul.namespace `string` 435 | Defines what namespace will be queried when building routing table. Namespaces are available only for Consul Enterprise users. 436 | 437 | _Optional. If not provided `dill` uses Consul defaults._ 438 | #### routing.consul.wait `duration` 439 | Defines how long [blocking API query](https://developer.hashicorp.com/consul/api-docs/features/blocking) will wait for a potential change using long polling. 440 | 441 | _Optional. If not provided `dill` uses Consul defaults._ 442 | #### routing.consul.consistency_mode `string` 443 | Defines what [consistency mode](https://developer.hashicorp.com/consul/api-docs/features/consistency) to use when `dill` fetches the updates. 444 | 445 | _Optional. Allowed values: `stale`, `consistent`, `leader`. If not provided `dill` uses Consul defaults._ 446 | 447 | --- 448 | #### routing.nomad.address `string` 449 | Nomad address from which `dill` will fetch the updates and build its routing table. 450 | 451 | #### routing.nomad.token `string` 452 | Token giving access to Nomad API. Required ACLs `namespace:read-job` 453 | 454 | _Optional._ 455 | #### routing.nomad.namespace `string` 456 | Defines what namespace will be queried when building routing table. 457 | 458 | _Optional. If not provided `dill` uses default Nomad namespace._ 459 | #### routing.nomad.wait `duration` 460 | Defines how long [blocking API query](https://developer.hashicorp.com/nomad/api-docs#blocking-queries) will wait for a potential change using long polling. 461 | 462 | _Optional. If not provided `dill` uses Nomad defaults._ 463 | 464 | #### routing.nomad.stale `bool` 465 | [`stale`](https://developer.hashicorp.com/nomad/api-docs#consistency-modes) allows any Nomad server (non-leader) to service a read. This allows for lower latency and higher throughput. This means reads can be arbitrarily stale; however, results are generally consistent to within 50 milliseconds of the leader. 466 | 467 | _Optional. Default: `false`_ 468 | 469 | #### routing.nomad.tls.ca `string` 470 | Path to a PEM-encoded CA cert file to use to verify the Nomad server SSL certificate. 471 | 472 | _Optional. Defaults to the system bundle._ 473 | #### routing.nomad.tls.cert `string` 474 | Path to the certificate used for secure communication with Nomad. 475 | 476 | _Optional._ 477 | #### routing.nomad.tls.key `string` 478 | Path to the key used for secure communication with Nomad. It's required if `routing.nomad.tls.cert` is used. 479 | 480 | _Optional._ 481 | #### routing.nomad.tls.insecure `bool` 482 | Disables certificate verification. 483 | 484 | _Optional. Default: `false`_ 485 | ### Formats 486 | Configuration is powered by [Viper](https://github.com/spf13/viper) so it's possible to use format that suits you best. 487 | 488 | > reading from JSON, TOML, YAML, HCL, envfile and Java properties config files 489 | 490 | `dill` uses the following precedence order: 491 | * environment variable 492 | * config file 493 | * default value 494 | 495 | 496 | #### TOML 497 | ```toml 498 | [listeners] 499 | port_min = 1024 500 | port_max = 49151 501 | 502 | [listeners.allowed] 503 | local = "127.0.0.1" 504 | any = "0.0.0.0" 505 | 506 | [routing.consul] 507 | address = "http://127.0.0.1:8500" 508 | 509 | [peek] 510 | listener = "127.0.0.1:4141" 511 | 512 | [runtime] 513 | gomaxprocs = 4 514 | ``` 515 | #### YAML 516 | ```yaml 517 | listeners: 518 | port_min: 1024 519 | port_max: 49151 520 | allowed: 521 | local: "127.0.0.1" 522 | any: "0.0.0.0" 523 | 524 | routing: 525 | http: 526 | endpoint: "http://127.0.0.1:8000/config/routing.json" 527 | poll_interval: "5s" 528 | poll_timeout: "5s" 529 | 530 | peek: 531 | listener: "127.0.0.1:4141" 532 | 533 | runtime: 534 | gomaxprocs: 4 535 | ``` 536 | #### JSON 537 | ```json 538 | { 539 | "listeners": { 540 | "port_min": 1024, 541 | "port_max": 49151, 542 | "allowed": { 543 | "local": "127.0.0.1", 544 | "any": "0.0.0.0" 545 | } 546 | }, 547 | "routing": { 548 | "file": { 549 | "path": "/Users/fwkz/Devel/dill/configs/routing.toml", 550 | "watch": true 551 | } 552 | }, 553 | "peek": { 554 | "listener": "127.0.0.1:4141" 555 | }, 556 | "runtime": { 557 | "gomaxprocs": 4 558 | } 559 | } 560 | ``` 561 | #### Environment variables 562 | Variables should be prefixed with `DILL` and delimited with underscore e.g. `consul.address` becomes `DILL_CONSUL_ADDRESS`. 563 | ```bash 564 | $ export DILL_CONSUL_ADDRESS="http://127.0.0.1:8500" 565 | $ DILL_LISTENERS_PORT_MIN=1234 dill 566 | ``` 567 | 568 | ## Project status 569 | Concept of dynamic listeners is experimental and should be used with 570 | responsibility. There might be some breaking changes in the future. 571 | 572 | ## Alternatives 573 | * [fabio](https://github.com/fabiolb/fabio) 574 | * [traefik](https://github.com/traefik/traefik) 575 | * [RSOCKS](https://github.com/tonyseek/rsocks) 576 | -------------------------------------------------------------------------------- /go.sum: -------------------------------------------------------------------------------- 1 | cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= 2 | cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= 3 | cloud.google.com/go v0.38.0/go.mod h1:990N+gfupTy94rShfmMCWGDn0LpTmnzTp2qbd1dvSRU= 4 | cloud.google.com/go v0.44.1/go.mod h1:iSa0KzasP4Uvy3f1mN/7PiObzGgflwredwwASm/v6AU= 5 | cloud.google.com/go v0.44.2/go.mod h1:60680Gw3Yr4ikxnPRS/oxxkBccT6SA1yMk63TGekxKY= 6 | cloud.google.com/go v0.44.3/go.mod h1:60680Gw3Yr4ikxnPRS/oxxkBccT6SA1yMk63TGekxKY= 7 | cloud.google.com/go v0.45.1/go.mod h1:RpBamKRgapWJb87xiFSdk4g1CME7QZg3uwTez+TSTjc= 8 | cloud.google.com/go v0.46.3/go.mod h1:a6bKKbmY7er1mI7TEI4lsAkts/mkhTSZK8w33B4RAg0= 9 | cloud.google.com/go v0.50.0/go.mod h1:r9sluTvynVuxRIOHXQEHMFffphuXHOMZMycpNR5e6To= 10 | cloud.google.com/go v0.52.0/go.mod h1:pXajvRH/6o3+F9jDHZWQ5PbGhn+o8w9qiu/CffaVdO4= 11 | cloud.google.com/go v0.53.0/go.mod h1:fp/UouUEsRkN6ryDKNW/Upv/JBKnv6WDthjR6+vze6M= 12 | cloud.google.com/go v0.54.0/go.mod h1:1rq2OEkV3YMf6n/9ZvGWI3GWw0VoqH/1x2nd8Is/bPc= 13 | cloud.google.com/go v0.56.0/go.mod h1:jr7tqZxxKOVYizybht9+26Z/gUq7tiRzu+ACVAMbKVk= 14 | cloud.google.com/go v0.57.0/go.mod h1:oXiQ6Rzq3RAkkY7N6t3TcE6jE+CIBBbA36lwQ1JyzZs= 15 | cloud.google.com/go v0.62.0/go.mod h1:jmCYTdRCQuc1PHIIJ/maLInMho30T/Y0M4hTdTShOYc= 16 | cloud.google.com/go v0.65.0/go.mod h1:O5N8zS7uWy9vkA9vayVHs65eM1ubvY4h553ofrNHObY= 17 | cloud.google.com/go v0.72.0/go.mod h1:M+5Vjvlc2wnp6tjzE102Dw08nGShTscUx2nZMufOKPI= 18 | cloud.google.com/go v0.74.0/go.mod h1:VV1xSbzvo+9QJOxLDaJfTjx5e+MePCpCWwvftOeQmWk= 19 | cloud.google.com/go v0.75.0/go.mod h1:VGuuCn7PG0dwsd5XPVm2Mm3wlh3EL55/79EKB6hlPTY= 20 | cloud.google.com/go/bigquery v1.0.1/go.mod h1:i/xbL2UlR5RvWAURpBYZTtm/cXjCha9lbfbpx4poX+o= 21 | cloud.google.com/go/bigquery v1.3.0/go.mod h1:PjpwJnslEMmckchkHFfq+HTD2DmtT67aNFKH1/VBDHE= 22 | cloud.google.com/go/bigquery v1.4.0/go.mod h1:S8dzgnTigyfTmLBfrtrhyYhwRxG72rYxvftPBK2Dvzc= 23 | cloud.google.com/go/bigquery v1.5.0/go.mod h1:snEHRnqQbz117VIFhE8bmtwIDY80NLUZUMb4Nv6dBIg= 24 | cloud.google.com/go/bigquery v1.7.0/go.mod h1://okPTzCYNXSlb24MZs83e2Do+h+VXtc4gLoIoXIAPc= 25 | cloud.google.com/go/bigquery v1.8.0/go.mod h1:J5hqkt3O0uAFnINi6JXValWIb1v0goeZM77hZzJN/fQ= 26 | cloud.google.com/go/datastore v1.0.0/go.mod h1:LXYbyblFSglQ5pkeyhO+Qmw7ukd3C+pD7TKLgZqpHYE= 27 | cloud.google.com/go/datastore v1.1.0/go.mod h1:umbIZjpQpHh4hmRpGhH4tLFup+FVzqBi1b3c64qFpCk= 28 | cloud.google.com/go/pubsub v1.0.1/go.mod h1:R0Gpsv3s54REJCy4fxDixWD93lHJMoZTyQ2kNxGRt3I= 29 | cloud.google.com/go/pubsub v1.1.0/go.mod h1:EwwdRX2sKPjnvnqCa270oGRyludottCI76h+R3AArQw= 30 | cloud.google.com/go/pubsub v1.2.0/go.mod h1:jhfEVHT8odbXTkndysNHCcx0awwzvfOlguIAii9o8iA= 31 | cloud.google.com/go/pubsub v1.3.1/go.mod h1:i+ucay31+CNRpDW4Lu78I4xXG+O1r/MAHgjpRVR+TSU= 32 | cloud.google.com/go/storage v1.0.0/go.mod h1:IhtSnM/ZTZV8YYJWCY8RULGVqBDmpoyjwiyrjsg+URw= 33 | cloud.google.com/go/storage v1.5.0/go.mod h1:tpKbwo567HUNpVclU5sGELwQWBDZ8gh0ZeosJ0Rtdos= 34 | cloud.google.com/go/storage v1.6.0/go.mod h1:N7U0C8pVQ/+NIKOBQyamJIeKQKkZ+mxpohlUTyfDhBk= 35 | cloud.google.com/go/storage v1.8.0/go.mod h1:Wv1Oy7z6Yz3DshWRJFhqM/UCfaWIRTdp0RXyy7KQOVs= 36 | cloud.google.com/go/storage v1.10.0/go.mod h1:FLPqc6j+Ki4BU591ie1oL6qBQGu2Bl/tZ9ullr3+Kg0= 37 | cloud.google.com/go/storage v1.14.0/go.mod h1:GrKmX003DSIwi9o29oFT7YDnHYwZoctc3fOKtUw0Xmo= 38 | dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU= 39 | github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= 40 | github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo= 41 | github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= 42 | github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI= 43 | github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI= 44 | github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU= 45 | github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= 46 | github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc= 47 | github.com/cncf/udpa/go v0.0.0-20200629203442-efcf912fb354/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk= 48 | github.com/cncf/udpa/go v0.0.0-20201120205902-5459f2c99403/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk= 49 | github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 50 | github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= 51 | github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 52 | github.com/docker/go-units v0.5.0 h1:69rxXcBk27SvSaaxTtLh/8llcHD8vYHT7WSdRZ/jvr4= 53 | github.com/docker/go-units v0.5.0/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk= 54 | github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= 55 | github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= 56 | github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98= 57 | github.com/envoyproxy/go-control-plane v0.9.7/go.mod h1:cwu0lG7PUMfa9snN8LXBig5ynNVH9qI8YYLbd1fK2po= 58 | github.com/envoyproxy/go-control-plane v0.9.9-0.20201210154907-fd9021fe5dad/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk= 59 | github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= 60 | github.com/felixge/httpsnoop v1.0.3 h1:s/nj+GCswXYzN5v2DpNMuMQYe+0DDwt5WVCU6CWBdXk= 61 | github.com/felixge/httpsnoop v1.0.3/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U= 62 | github.com/frankban/quicktest v1.14.4 h1:g2rn0vABPOOXmZUj+vbmUp0lPoXEMuhTpIluN0XL9UY= 63 | github.com/frankban/quicktest v1.14.4/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7zb5vbUoiM6w0= 64 | github.com/fsnotify/fsnotify v1.6.0 h1:n+5WquG0fcWoWp6xPWfHdbskMCQaFnG6PfBrh1Ky4HY= 65 | github.com/fsnotify/fsnotify v1.6.0/go.mod h1:sl3t1tCWJFWoRz9R8WJCbQihKKwmorjAbSClcnxKAGw= 66 | github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU= 67 | github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= 68 | github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= 69 | github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= 70 | github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= 71 | github.com/golang/groupcache v0.0.0-20191227052852-215e87163ea7/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= 72 | github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= 73 | github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= 74 | github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= 75 | github.com/golang/mock v1.3.1/go.mod h1:sBzyDLLjw3U8JLTeZvSv8jJB+tU5PVekmnlKIyFUx0Y= 76 | github.com/golang/mock v1.4.0/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= 77 | github.com/golang/mock v1.4.1/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= 78 | github.com/golang/mock v1.4.3/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= 79 | github.com/golang/mock v1.4.4/go.mod h1:l3mdAwkq5BuhzHwde/uurv3sEJeZMXNpwsxVWU71h+4= 80 | github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= 81 | github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= 82 | github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= 83 | github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw= 84 | github.com/golang/protobuf v1.3.4/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw= 85 | github.com/golang/protobuf v1.3.5/go.mod h1:6O5/vntMXwX2lRkT1hjjk0nAC1IDOTvTlVgjlRvqsdk= 86 | github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8= 87 | github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA= 88 | github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs= 89 | github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w= 90 | github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0= 91 | github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8= 92 | github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= 93 | github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= 94 | github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= 95 | github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= 96 | github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= 97 | github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= 98 | github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= 99 | github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= 100 | github.com/google/go-cmp v0.4.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= 101 | github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= 102 | github.com/google/go-cmp v0.5.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= 103 | github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= 104 | github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= 105 | github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38= 106 | github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= 107 | github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs= 108 | github.com/google/martian/v3 v3.0.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0= 109 | github.com/google/martian/v3 v3.1.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0= 110 | github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= 111 | github.com/google/pprof v0.0.0-20190515194954-54271f7e092f/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= 112 | github.com/google/pprof v0.0.0-20191218002539-d4f498aebedc/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= 113 | github.com/google/pprof v0.0.0-20200212024743-f11f1df84d12/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= 114 | github.com/google/pprof v0.0.0-20200229191704-1ebb73c60ed3/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= 115 | github.com/google/pprof v0.0.0-20200430221834-fc25d7d30c6d/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= 116 | github.com/google/pprof v0.0.0-20200708004538-1a94d8640e99/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= 117 | github.com/google/pprof v0.0.0-20201023163331-3e6fc7fc9c4c/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= 118 | github.com/google/pprof v0.0.0-20201203190320-1bf35d6f28c2/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= 119 | github.com/google/pprof v0.0.0-20201218002935-b9804c9f04c2/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= 120 | github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI= 121 | github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= 122 | github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg= 123 | github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk= 124 | github.com/googleapis/google-cloud-go-testing v0.0.0-20200911160855-bcd43fbb19e8/go.mod h1:dvDLG8qkwmyD9a/MJJN3XJcT3xFxOKAvTZGvuZmac9g= 125 | github.com/gorilla/websocket v1.5.0 h1:PPwGk2jz7EePpoHN/+ClbZu8SPxiqlu12wZP/3sWmnc= 126 | github.com/gorilla/websocket v1.5.0/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= 127 | github.com/hashicorp/cronexpr v1.1.2 h1:wG/ZYIKT+RT3QkOdgYc+xsKWVRgnxJ1OJtjjy84fJ9A= 128 | github.com/hashicorp/cronexpr v1.1.2/go.mod h1:P4wA0KBl9C5q2hABiMO7cp6jcIg96CDh1Efb3g1PWA4= 129 | github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= 130 | github.com/hashicorp/errwrap v1.1.0 h1:OxrOeh75EUXMY8TBjag2fzXGZ40LB6IKw45YeGUDY2I= 131 | github.com/hashicorp/errwrap v1.1.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= 132 | github.com/hashicorp/go-cleanhttp v0.5.2 h1:035FKYIWjmULyFRBKPs8TBQoi0x6d9G4xc9neXJWAZQ= 133 | github.com/hashicorp/go-cleanhttp v0.5.2/go.mod h1:kO/YDlP8L1346E6Sodw+PrpBSV4/SoxCXGY6BqNFT48= 134 | github.com/hashicorp/go-multierror v1.1.1 h1:H5DkEtf6CXdFp0N0Em5UCwQpXMWke8IA0+lD48awMYo= 135 | github.com/hashicorp/go-multierror v1.1.1/go.mod h1:iw975J/qwKPdAO1clOe2L8331t/9/fmwbPZ6JB6eMoM= 136 | github.com/hashicorp/go-rootcerts v1.0.2 h1:jzhAVGtqPKbwpyCPELlgNWhE1znq+qwJtW5Oi2viEzc= 137 | github.com/hashicorp/go-rootcerts v1.0.2/go.mod h1:pqUvnprVnM5bf7AOirdbb01K4ccR319Vf4pU3K5EGc8= 138 | github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= 139 | github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= 140 | github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4= 141 | github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ= 142 | github.com/hashicorp/nomad/api v0.0.0-20231005165617-e3c8700ded89 h1:BHQkCisIVboODzLBMpYHCHKPVmoW92KJA2AFvh5q2mw= 143 | github.com/hashicorp/nomad/api v0.0.0-20231005165617-e3c8700ded89/go.mod h1:glQSmiY2VCQDT0MBiWKr5YDU9PpwVNOcrovlDczoKoI= 144 | github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= 145 | github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= 146 | github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU= 147 | github.com/jstemmer/go-junit-report v0.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/XSXhF0NWZEnDohbsk= 148 | github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= 149 | github.com/kr/fs v0.1.0/go.mod h1:FFnZGqtBN9Gxj7eW1uZ42v5BccTP0vu6NEaFoC2HwRg= 150 | github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= 151 | github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= 152 | github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= 153 | github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= 154 | github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= 155 | github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= 156 | github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= 157 | github.com/magiconair/properties v1.8.7 h1:IeQXZAiQcpL9mgcAe1Nu6cX9LLw6ExEHKjN0VQdvPDY= 158 | github.com/magiconair/properties v1.8.7/go.mod h1:Dhd985XPs7jluiymwWYZ0G4Z61jb3vdS329zhj2hYo0= 159 | github.com/mitchellh/go-homedir v1.1.0 h1:lukF9ziXFxDFPkA1vsr5zpc1XuPDn/wFntq5mG+4E0Y= 160 | github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= 161 | github.com/mitchellh/go-testing-interface v1.14.1 h1:jrgshOhYAUVNMAJiKbEu7EqAwgJJ2JqpQmpLJOu07cU= 162 | github.com/mitchellh/go-testing-interface v1.14.1/go.mod h1:gfgS7OtZj6MA4U1UrDRp04twqAjfvlZyCfX3sDjEym8= 163 | github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY= 164 | github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= 165 | github.com/pelletier/go-toml/v2 v2.1.0 h1:FnwAJ4oYMvbT/34k9zzHuZNrhlz48GB3/s6at6/MHO4= 166 | github.com/pelletier/go-toml/v2 v2.1.0/go.mod h1:tJU2Z3ZkXwnxa4DPO899bsyIoywizdUvyaeZurnPPDc= 167 | github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= 168 | github.com/pkg/sftp v1.13.1/go.mod h1:3HaPG6Dq1ILlpPZRO0HVMrsydcdLt6HRDccSgb87qRg= 169 | github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= 170 | github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= 171 | github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= 172 | github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= 173 | github.com/rogpeppe/go-internal v1.9.0 h1:73kH8U+JUqXU8lRuOHeVHaa/SZPifC7BkcraZVejAe8= 174 | github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs= 175 | github.com/shoenig/test v0.6.7 h1:k92ohN9VyRfZn0ezNfwamtIBT/5byyfLVktRmL/Jmek= 176 | github.com/shoenig/test v0.6.7/go.mod h1:byHiCGXqrVaflBLAMq/srcZIHynQPQgeyvkvXnjqq0k= 177 | github.com/spf13/afero v1.10.0 h1:EaGW2JJh15aKOejeuJ+wpFSHnbd7GE6Wvp3TsNhb6LY= 178 | github.com/spf13/afero v1.10.0/go.mod h1:UBogFpq8E9Hx+xc5CNTTEpTnuHVmXDwZcZcE1eb/UhQ= 179 | github.com/spf13/cast v1.5.1 h1:R+kOtfhWQE6TVQzY+4D7wJLBgkdVasCEFxSUBYBYIlA= 180 | github.com/spf13/cast v1.5.1/go.mod h1:b9PdjNptOpzXr7Rq1q9gJML/2cdGQAo69NKzQ10KN48= 181 | github.com/spf13/jwalterweatherman v1.1.0 h1:ue6voC5bR5F8YxI5S67j9i582FU4Qvo2bmqnqMYADFk= 182 | github.com/spf13/jwalterweatherman v1.1.0/go.mod h1:aNWZUN0dPAAO/Ljvb5BEdw96iTZ0EXowPYD95IqWIGo= 183 | github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= 184 | github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= 185 | github.com/spf13/viper v1.16.0 h1:rGGH0XDZhdUOryiDWjmIvUSWpbNqisK8Wk0Vyefw8hc= 186 | github.com/spf13/viper v1.16.0/go.mod h1:yg78JgCJcbrQOvV9YLXgkLaZqUidkY9K+Dd1FofRzQg= 187 | github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= 188 | github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= 189 | github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= 190 | github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= 191 | github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= 192 | github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= 193 | github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= 194 | github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= 195 | github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= 196 | github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk= 197 | github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= 198 | github.com/subosito/gotenv v1.6.0 h1:9NlTDc1FTs4qu0DDq7AEtTPNw6SVm7uBMsUCUjABIf8= 199 | github.com/subosito/gotenv v1.6.0/go.mod h1:Dk4QP5c2W3ibzajGcXpNraDfq2IrhjMIvMSWPKKo0FU= 200 | github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= 201 | github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= 202 | github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= 203 | github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= 204 | go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU= 205 | go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8= 206 | go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= 207 | go.opencensus.io v0.22.3/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= 208 | go.opencensus.io v0.22.4/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= 209 | go.opencensus.io v0.22.5/go.mod h1:5pWMHQbX5EPX2/62yrJeAkowc+lfs/XD7Uxpq3pI6kk= 210 | golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= 211 | golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= 212 | golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= 213 | golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= 214 | golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= 215 | golang.org/x/crypto v0.0.0-20210421170649-83a5a9bb288b/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4= 216 | golang.org/x/crypto v0.0.0-20220722155217-630584e8d5aa/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= 217 | golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= 218 | golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= 219 | golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8= 220 | golang.org/x/exp v0.0.0-20190829153037-c13cbed26979/go.mod h1:86+5VVa7VpoJ4kLfm080zCjGlMRFzhUhsZKEZO7MGek= 221 | golang.org/x/exp v0.0.0-20191030013958-a1ab85dbe136/go.mod h1:JXzH8nQsPlswgeRAPE3MuO9GYsAcnJvJ4vnMwN/5qkY= 222 | golang.org/x/exp v0.0.0-20191129062945-2f5052295587/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= 223 | golang.org/x/exp v0.0.0-20191227195350-da58074b4299/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= 224 | golang.org/x/exp v0.0.0-20200119233911-0405dc783f0a/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= 225 | golang.org/x/exp v0.0.0-20200207192155-f17229e696bd/go.mod h1:J/WKrq2StrnmMY6+EHIKF9dgMWnmCNThgcyBT1FY9mM= 226 | golang.org/x/exp v0.0.0-20200224162631-6cc2880d07d6/go.mod h1:3jZMyOhIsHpP37uCMkUooju7aAi5cS1Q23tOzKc+0MU= 227 | golang.org/x/exp v0.0.0-20231005195138-3e424a577f31 h1:9k5exFQKQglLo+RoP+4zMjOFE14P6+vyR0baDAi0Rcs= 228 | golang.org/x/exp v0.0.0-20231005195138-3e424a577f31/go.mod h1:S2oDrQGGwySpoQPVqRShND87VCbxmc6bL1Yd2oYrm6k= 229 | golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js= 230 | golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= 231 | golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= 232 | golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU= 233 | golang.org/x/lint v0.0.0-20190301231843-5614ed5bae6f/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= 234 | golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= 235 | golang.org/x/lint v0.0.0-20190409202823-959b441ac422/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= 236 | golang.org/x/lint v0.0.0-20190909230951-414d861bb4ac/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= 237 | golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= 238 | golang.org/x/lint v0.0.0-20191125180803-fdd1cda4f05f/go.mod h1:5qLYkcX4OjUUV8bRuDixDT3tpyyb+LUpUlRWLxfhWrs= 239 | golang.org/x/lint v0.0.0-20200130185559-910be7a94367/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= 240 | golang.org/x/lint v0.0.0-20200302205851-738671d3881b/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= 241 | golang.org/x/lint v0.0.0-20201208152925-83fdc39ff7b5/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= 242 | golang.org/x/mobile v0.0.0-20190312151609-d3739f865fa6/go.mod h1:z+o9i4GpDbdi3rU15maQ/Ox0txvL9dWGYEHz965HBQE= 243 | golang.org/x/mobile v0.0.0-20190719004257-d2bd2a29d028/go.mod h1:E/iHnbuqvinMTCcRqshq8CkpyQDoeVncDDYHnLhea+o= 244 | golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc= 245 | golang.org/x/mod v0.1.0/go.mod h1:0QHyrYULN0/3qlju5TqG8bIK38QM8yzMo5ekMj3DlcY= 246 | golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= 247 | golang.org/x/mod v0.1.1-0.20191107180719-034126e5016b/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= 248 | golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= 249 | golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= 250 | golang.org/x/mod v0.4.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= 251 | golang.org/x/mod v0.4.1/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= 252 | golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= 253 | golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= 254 | golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= 255 | golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= 256 | golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= 257 | golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= 258 | golang.org/x/net v0.0.0-20190501004415-9ce7a6920f09/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= 259 | golang.org/x/net v0.0.0-20190503192946-f4e77d36d62c/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= 260 | golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks= 261 | golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= 262 | golang.org/x/net v0.0.0-20190628185345-da137c7871d7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= 263 | golang.org/x/net v0.0.0-20190724013045-ca1201d0de80/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= 264 | golang.org/x/net v0.0.0-20191209160850-c0dbc17a3553/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= 265 | golang.org/x/net v0.0.0-20200114155413-6afb5195e5aa/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= 266 | golang.org/x/net v0.0.0-20200202094626-16171245cfb2/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= 267 | golang.org/x/net v0.0.0-20200222125558-5a598a2470a0/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= 268 | golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= 269 | golang.org/x/net v0.0.0-20200301022130-244492dfa37a/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= 270 | golang.org/x/net v0.0.0-20200324143707-d3edc9973b7e/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= 271 | golang.org/x/net v0.0.0-20200501053045-e0ff5e5a1de5/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= 272 | golang.org/x/net v0.0.0-20200506145744-7e3656a0809f/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= 273 | golang.org/x/net v0.0.0-20200513185701-a91f0712d120/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= 274 | golang.org/x/net v0.0.0-20200520182314-0ba52f642ac2/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= 275 | golang.org/x/net v0.0.0-20200625001655-4c5254603344/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= 276 | golang.org/x/net v0.0.0-20200707034311-ab3426394381/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= 277 | golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= 278 | golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= 279 | golang.org/x/net v0.0.0-20201031054903-ff519b6c9102/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= 280 | golang.org/x/net v0.0.0-20201209123823-ac852fbbde11/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= 281 | golang.org/x/net v0.0.0-20201224014010-6772e930b67b/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= 282 | golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= 283 | golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= 284 | golang.org/x/net v0.16.0 h1:7eBu7KsSvFDtSXUIDbh3aqlK4DPsZ1rByC8PFfBThos= 285 | golang.org/x/net v0.16.0/go.mod h1:NxSsAGuq816PNPmqtQdLE42eU2Fs7NoRIZrHJAlaCOE= 286 | golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= 287 | golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= 288 | golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= 289 | golang.org/x/oauth2 v0.0.0-20191202225959-858c2ad4c8b6/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= 290 | golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= 291 | golang.org/x/oauth2 v0.0.0-20200902213428-5d25da1a8d43/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= 292 | golang.org/x/oauth2 v0.0.0-20201109201403-9fd604954f58/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= 293 | golang.org/x/oauth2 v0.0.0-20201208152858-08078c50e5b5/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= 294 | golang.org/x/oauth2 v0.0.0-20210218202405-ba52d332ba99/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= 295 | golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 296 | golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 297 | golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 298 | golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 299 | golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 300 | golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 301 | golang.org/x/sync v0.0.0-20200317015054-43a5402ce75a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 302 | golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 303 | golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 304 | golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 305 | golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 306 | golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 307 | golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 308 | golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 309 | golang.org/x/sys v0.0.0-20190502145724-3ef323f4f1fd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 310 | golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 311 | golang.org/x/sys v0.0.0-20190606165138-5da285871e9c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 312 | golang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 313 | golang.org/x/sys v0.0.0-20190726091711-fc99dfbffb4e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 314 | golang.org/x/sys v0.0.0-20191001151750-bb3f8db39f24/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 315 | golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 316 | golang.org/x/sys v0.0.0-20191228213918-04cbcbbfeed8/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 317 | golang.org/x/sys v0.0.0-20200113162924-86b910548bc1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 318 | golang.org/x/sys v0.0.0-20200122134326-e047566fdf82/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 319 | golang.org/x/sys v0.0.0-20200202164722-d101bd2416d5/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 320 | golang.org/x/sys v0.0.0-20200212091648-12a6c2dcc1e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 321 | golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 322 | golang.org/x/sys v0.0.0-20200302150141-5c8b2ff67527/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 323 | golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 324 | golang.org/x/sys v0.0.0-20200331124033-c3d80250170d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 325 | golang.org/x/sys v0.0.0-20200501052902-10377860bb8e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 326 | golang.org/x/sys v0.0.0-20200511232937-7e40ca221e25/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 327 | golang.org/x/sys v0.0.0-20200515095857-1151b9dac4a9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 328 | golang.org/x/sys v0.0.0-20200523222454-059865788121/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 329 | golang.org/x/sys v0.0.0-20200803210538-64077c9b5642/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 330 | golang.org/x/sys v0.0.0-20200905004654-be1d3432aa8f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 331 | golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 332 | golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 333 | golang.org/x/sys v0.0.0-20201201145000-ef89a241ccb3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 334 | golang.org/x/sys v0.0.0-20210104204734-6f8348627aad/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 335 | golang.org/x/sys v0.0.0-20210119212857-b64e53b001e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 336 | golang.org/x/sys v0.0.0-20210225134936-a50acf3fe073/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 337 | golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 338 | golang.org/x/sys v0.0.0-20210423185535-09eb48e85fd7/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 339 | golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 340 | golang.org/x/sys v0.0.0-20220908164124-27713097b956/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 341 | golang.org/x/sys v0.13.0 h1:Af8nKPmuFypiUBjVoU9V20FiaFXOcuZI21p0ycVYYGE= 342 | golang.org/x/sys v0.13.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 343 | golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= 344 | golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= 345 | golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= 346 | golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= 347 | golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= 348 | golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= 349 | golang.org/x/text v0.3.4/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= 350 | golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= 351 | golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= 352 | golang.org/x/text v0.13.0 h1:ablQoSUd0tRdKxZewP80B+BaqeKJuVhuRxj/dkrun3k= 353 | golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE= 354 | golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= 355 | golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= 356 | golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= 357 | golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= 358 | golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= 359 | golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY= 360 | golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= 361 | golang.org/x/tools v0.0.0-20190312151545-0bb0c0a6e846/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= 362 | golang.org/x/tools v0.0.0-20190312170243-e65039ee4138/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= 363 | golang.org/x/tools v0.0.0-20190425150028-36563e24a262/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= 364 | golang.org/x/tools v0.0.0-20190506145303-2d16b83fe98c/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= 365 | golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= 366 | golang.org/x/tools v0.0.0-20190606124116-d0a3d012864b/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= 367 | golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= 368 | golang.org/x/tools v0.0.0-20190628153133-6cdbf07be9d0/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= 369 | golang.org/x/tools v0.0.0-20190816200558-6889da9d5479/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= 370 | golang.org/x/tools v0.0.0-20190911174233-4f2ddba30aff/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= 371 | golang.org/x/tools v0.0.0-20191012152004-8de300cfc20a/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= 372 | golang.org/x/tools v0.0.0-20191113191852-77e3bb0ad9e7/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= 373 | golang.org/x/tools v0.0.0-20191115202509-3a792d9c32b2/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= 374 | golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= 375 | golang.org/x/tools v0.0.0-20191125144606-a911d9008d1f/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= 376 | golang.org/x/tools v0.0.0-20191130070609-6e064ea0cf2d/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= 377 | golang.org/x/tools v0.0.0-20191216173652-a0e659d51361/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= 378 | golang.org/x/tools v0.0.0-20191227053925-7b8e75db28f4/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= 379 | golang.org/x/tools v0.0.0-20200117161641-43d50277825c/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= 380 | golang.org/x/tools v0.0.0-20200122220014-bf1340f18c4a/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= 381 | golang.org/x/tools v0.0.0-20200130002326-2f3ba24bd6e7/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= 382 | golang.org/x/tools v0.0.0-20200204074204-1cc6d1ef6c74/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= 383 | golang.org/x/tools v0.0.0-20200207183749-b753a1ba74fa/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= 384 | golang.org/x/tools v0.0.0-20200212150539-ea181f53ac56/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= 385 | golang.org/x/tools v0.0.0-20200224181240-023911ca70b2/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= 386 | golang.org/x/tools v0.0.0-20200227222343-706bc42d1f0d/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= 387 | golang.org/x/tools v0.0.0-20200304193943-95d2e580d8eb/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw= 388 | golang.org/x/tools v0.0.0-20200312045724-11d5b4c81c7d/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw= 389 | golang.org/x/tools v0.0.0-20200331025713-a30bf2db82d4/go.mod h1:Sl4aGygMT6LrqrWclx+PTx3U+LnKx/seiNR+3G19Ar8= 390 | golang.org/x/tools v0.0.0-20200501065659-ab2804fb9c9d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= 391 | golang.org/x/tools v0.0.0-20200512131952-2bc93b1c0c88/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= 392 | golang.org/x/tools v0.0.0-20200515010526-7d3b6ebf133d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= 393 | golang.org/x/tools v0.0.0-20200618134242-20370b0cb4b2/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= 394 | golang.org/x/tools v0.0.0-20200729194436-6467de6f59a7/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= 395 | golang.org/x/tools v0.0.0-20200804011535-6c149bb5ef0d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= 396 | golang.org/x/tools v0.0.0-20200825202427-b303f430e36d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= 397 | golang.org/x/tools v0.0.0-20200904185747-39188db58858/go.mod h1:Cj7w3i3Rnn0Xh82ur9kSqwfTHTeVxaDqrfMjpcNT6bE= 398 | golang.org/x/tools v0.0.0-20201110124207-079ba7bd75cd/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= 399 | golang.org/x/tools v0.0.0-20201201161351-ac6f37ff4c2a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= 400 | golang.org/x/tools v0.0.0-20201208233053-a543418bbed2/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= 401 | golang.org/x/tools v0.0.0-20210105154028-b0ab187a4818/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= 402 | golang.org/x/tools v0.0.0-20210108195828-e2f9c7f1fc8e/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= 403 | golang.org/x/tools v0.1.0/go.mod h1:xkSsbof2nBLbhDlRMhhhyNLN/zl3eTqcnHD5viDpcZ0= 404 | golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 405 | golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 406 | golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 407 | golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 408 | google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE= 409 | google.golang.org/api v0.7.0/go.mod h1:WtwebWUNSVBH/HAw79HIFXZNqEvBhG+Ra+ax0hx3E3M= 410 | google.golang.org/api v0.8.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg= 411 | google.golang.org/api v0.9.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg= 412 | google.golang.org/api v0.13.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= 413 | google.golang.org/api v0.14.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= 414 | google.golang.org/api v0.15.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= 415 | google.golang.org/api v0.17.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= 416 | google.golang.org/api v0.18.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= 417 | google.golang.org/api v0.19.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= 418 | google.golang.org/api v0.20.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= 419 | google.golang.org/api v0.22.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= 420 | google.golang.org/api v0.24.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE= 421 | google.golang.org/api v0.28.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE= 422 | google.golang.org/api v0.29.0/go.mod h1:Lcubydp8VUV7KeIHD9z2Bys/sm/vGKnG1UHuDBSrHWM= 423 | google.golang.org/api v0.30.0/go.mod h1:QGmEvQ87FHZNiUVJkT14jQNYJ4ZJjdRF23ZXz5138Fc= 424 | google.golang.org/api v0.35.0/go.mod h1:/XrVsuzM0rZmrsbjJutiuftIzeuTQcEeaYcSk/mQ1dg= 425 | google.golang.org/api v0.36.0/go.mod h1:+z5ficQTmoYpPn8LCUNVpK5I7hwkpjbcgqA7I34qYtE= 426 | google.golang.org/api v0.40.0/go.mod h1:fYKFpnQN0DsDSKRVRcQSDQNtqWPfM9i+zNPxepjRCQ8= 427 | google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= 428 | google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= 429 | google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= 430 | google.golang.org/appengine v1.6.1/go.mod h1:i06prIuMbXzDqacNJfV5OdTW448YApPu5ww/cMBSeb0= 431 | google.golang.org/appengine v1.6.5/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= 432 | google.golang.org/appengine v1.6.6/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= 433 | google.golang.org/appengine v1.6.7/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= 434 | google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= 435 | google.golang.org/genproto v0.0.0-20190307195333-5fe7a883aa19/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= 436 | google.golang.org/genproto v0.0.0-20190418145605-e7d98fc518a7/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= 437 | google.golang.org/genproto v0.0.0-20190425155659-357c62f0e4bb/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= 438 | google.golang.org/genproto v0.0.0-20190502173448-54afdca5d873/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= 439 | google.golang.org/genproto v0.0.0-20190801165951-fa694d86fc64/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= 440 | google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= 441 | google.golang.org/genproto v0.0.0-20190911173649-1774047e7e51/go.mod h1:IbNlFCBrqXvoKpeg0TB2l7cyZUmoaFKYIwrEpbDKLA8= 442 | google.golang.org/genproto v0.0.0-20191108220845-16a3f7862a1a/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= 443 | google.golang.org/genproto v0.0.0-20191115194625-c23dd37a84c9/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= 444 | google.golang.org/genproto v0.0.0-20191216164720-4f79533eabd1/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= 445 | google.golang.org/genproto v0.0.0-20191230161307-f3c370f40bfb/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= 446 | google.golang.org/genproto v0.0.0-20200115191322-ca5a22157cba/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= 447 | google.golang.org/genproto v0.0.0-20200122232147-0452cf42e150/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= 448 | google.golang.org/genproto v0.0.0-20200204135345-fa8e72b47b90/go.mod h1:GmwEX6Z4W5gMy59cAlVYjN9JhxgbQH6Gn+gFDQe2lzA= 449 | google.golang.org/genproto v0.0.0-20200212174721-66ed5ce911ce/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= 450 | google.golang.org/genproto v0.0.0-20200224152610-e50cd9704f63/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= 451 | google.golang.org/genproto v0.0.0-20200228133532-8c2c7df3a383/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= 452 | google.golang.org/genproto v0.0.0-20200305110556-506484158171/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= 453 | google.golang.org/genproto v0.0.0-20200312145019-da6875a35672/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= 454 | google.golang.org/genproto v0.0.0-20200331122359-1ee6d9798940/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= 455 | google.golang.org/genproto v0.0.0-20200430143042-b979b6f78d84/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= 456 | google.golang.org/genproto v0.0.0-20200511104702-f5ebc3bea380/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= 457 | google.golang.org/genproto v0.0.0-20200515170657-fc4c6c6a6587/go.mod h1:YsZOwe1myG/8QRHRsmBRE1LrgQY60beZKjly0O1fX9U= 458 | google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo= 459 | google.golang.org/genproto v0.0.0-20200618031413-b414f8b61790/go.mod h1:jDfRM7FcilCzHH/e9qn6dsT145K34l5v+OpcnNgKAAA= 460 | google.golang.org/genproto v0.0.0-20200729003335-053ba62fc06f/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= 461 | google.golang.org/genproto v0.0.0-20200804131852-c06518451d9c/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= 462 | google.golang.org/genproto v0.0.0-20200825200019-8632dd797987/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= 463 | google.golang.org/genproto v0.0.0-20200904004341-0bd0a958aa1d/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= 464 | google.golang.org/genproto v0.0.0-20201109203340-2640f1f9cdfb/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= 465 | google.golang.org/genproto v0.0.0-20201201144952-b05cb90ed32e/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= 466 | google.golang.org/genproto v0.0.0-20201210142538-e3217bee35cc/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= 467 | google.golang.org/genproto v0.0.0-20201214200347-8c77b98c765d/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= 468 | google.golang.org/genproto v0.0.0-20210108203827-ffc7fda8c3d7/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= 469 | google.golang.org/genproto v0.0.0-20210226172003-ab064af71705/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= 470 | google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= 471 | google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38= 472 | google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM= 473 | google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= 474 | google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY= 475 | google.golang.org/grpc v1.26.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= 476 | google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= 477 | google.golang.org/grpc v1.27.1/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= 478 | google.golang.org/grpc v1.28.0/go.mod h1:rpkK4SK4GF4Ach/+MFLZUBavHOvF2JJB5uozKKal+60= 479 | google.golang.org/grpc v1.29.1/go.mod h1:itym6AZVZYACWQqET3MqgPpjcuV5QH3BxFS3IjizoKk= 480 | google.golang.org/grpc v1.30.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= 481 | google.golang.org/grpc v1.31.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= 482 | google.golang.org/grpc v1.31.1/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= 483 | google.golang.org/grpc v1.33.2/go.mod h1:JMHMWHQWaTccqQQlmk3MJZS+GWXOdAesneDmEnv2fbc= 484 | google.golang.org/grpc v1.34.0/go.mod h1:WotjhfgOW/POjDeRt8vscBtXq+2VjORFy659qA51WJ8= 485 | google.golang.org/grpc v1.35.0/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU= 486 | google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= 487 | google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= 488 | google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM= 489 | google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE= 490 | google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo= 491 | google.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= 492 | google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= 493 | google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= 494 | google.golang.org/protobuf v1.24.0/go.mod h1:r/3tXBNzIEhYS9I1OUVjXDlt8tc493IdKGjtUeSXeh4= 495 | google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c= 496 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 497 | gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY= 498 | gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 499 | gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= 500 | gopkg.in/ini.v1 v1.67.0 h1:Dgnx+6+nfE+IfzjUEISNeydPJh9AXNNsWbGP9KzCsOA= 501 | gopkg.in/ini.v1 v1.67.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= 502 | gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= 503 | gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 504 | gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= 505 | gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 506 | honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= 507 | honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= 508 | honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= 509 | honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= 510 | honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg= 511 | honnef.co/go/tools v0.0.1-2020.1.3/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k= 512 | honnef.co/go/tools v0.0.1-2020.1.4/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k= 513 | rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8= 514 | rsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0= 515 | rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA= 516 | --------------------------------------------------------------------------------