diff --git a/README.md b/README.md
index 6ac453ea..27872a09 100644
--- a/README.md
+++ b/README.md
@@ -221,6 +221,7 @@ backend:
### Aerospike
Prebid Cache makes use of an Aerospike Go client that requires Aerospike server version 4.9+ and will not work properly with older versions. Full documentation of the Aerospike Go client can be found [here](https://github.com/aerospike/aerospike-client-go/tree/v6).
+
| Configuration field | Type | Description |
| --- | --- | --- |
| host | string | aerospike server URI |
@@ -229,6 +230,7 @@ Prebid Cache makes use of an Aerospike Go client that requires Aerospike server
### Cassandra
Prebid Cache makes use of a Cassandra client that supports latest 3 major releases of Cassandra (2.1.x, 2.2.x, and 3.x.x). Full documentation of the Cassandra Go client can be found [here](https://github.com/gocql/gocql).
+
| Configuration field | Type | Description |
| --- | --- | --- |
| hosts | string | Cassandra server URI |
@@ -243,6 +245,7 @@ Prebid Cache makes use of a Cassandra client that supports latest 3 major releas
### Redis:
Prebid Cache makes use of a Redis Go client compatible with Redis 6. Full documentation of the Redis Go client Prebid Cache uses can be found [here](https://github.com/go-redis/redis).
+
| Configuration field | Type | Description |
| --- | --- | --- |
| host | string | Redis server URI |
@@ -252,6 +255,19 @@ Prebid Cache makes use of a Redis Go client compatible with Redis 6. Full docume
| expiration | integer | Availability in the Redis system in Minutes |
| tls | field | Subfields:
`enabled`: whether or not pass the InsecureSkipVerify value to the Redis client's TLS config
`insecure_skip_verify`: In Redis, InsecureSkipVerify controls whether a client verifies the server's certificate chain and host name. If InsecureSkipVerify is true, crypto/t |
+### Redis Sentinel:
+Prebid Cache makes use of a Redis Go client compatible with Redis Sentinel. Full documentation of the Redis Go client can be found [here](https://github.com/go-redis/redis).
+
+| Configuration field | Type | Description |
+|---------------------|--------------|-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
+| sentinel_addrs | string array | List of Sentinel nodes (host:port) managing the same master |
+| master_name | string | Name of the Sentinel master (as declared in the `sentinel monitor` line of your Sentinel configurations) |
+| password | string | Redis password |
+| db | integer | Database to be selected after connecting to the server |
+| expiration | integer | Availability in the Redis system in Minutes |
+| tls | field | Subfields:
`enabled`: whether or not pass the InsecureSkipVerify value to the Redis client's TLS config
`insecure_skip_verify`: In Redis, InsecureSkipVerify controls whether a client verifies the server's certificate chain and host name. If InsecureSkipVerify is true, crypto/t |
+
+
Sample configuration file `config/configtest/sample_full_config.yaml` shown below:
```yaml
port: 9000
@@ -291,6 +307,15 @@ backend:
tls:
enabled: false
insecure_skip_verify: false
+ redis_sentinel:
+ sentinel_addrs: [ "127.0.0.1:26379", "127.0.0.1:26380", "127.0.0.1:26381" ]
+ master_name: "mymaster"
+ password: ""
+ db: 1
+ expiration: 10 # in Minutes
+ tls:
+ enabled: false
+ insecure_skip_verify: false
compression:
type: "snappy"
metrics:
diff --git a/backends/config/config.go b/backends/config/config.go
index 22c9c010..d5e5bb8a 100644
--- a/backends/config/config.go
+++ b/backends/config/config.go
@@ -63,6 +63,8 @@ func newBaseBackend(cfg config.Backend, appMetrics *metrics.Metrics) backends.Ba
return backends.NewAerospikeBackend(cfg.Aerospike, appMetrics)
case config.BackendRedis:
return backends.NewRedisBackend(cfg.Redis, ctx)
+ case config.BackendRedisSentinel:
+ return backends.NewRedisSentinelBackend(cfg.RedisSentinel, ctx)
case config.BackendIgnite:
return backends.NewIgniteBackend(cfg.Ignite)
default:
diff --git a/backends/redis-sentinel.go b/backends/redis-sentinel.go
new file mode 100644
index 00000000..025eb51d
--- /dev/null
+++ b/backends/redis-sentinel.go
@@ -0,0 +1,98 @@
+package backends
+
+import (
+ "context"
+ "crypto/tls"
+ "time"
+
+ "github.com/prebid/prebid-cache/config"
+ "github.com/prebid/prebid-cache/utils"
+ "github.com/redis/go-redis/v9"
+ log "github.com/sirupsen/logrus"
+)
+
+// RedisSentinelDB is an interface that helps us communicate with an instance of a
+// Redis Sentinel database. Its implementation is intended to use the "github.com/redis/go-redis"
+// client
+type RedisSentinelDB interface {
+ Get(ctx context.Context, key string) (string, error)
+ Put(ctx context.Context, key string, value string, ttlSeconds int) (bool, error)
+}
+
+// RedisSentinelDBClient is a wrapper for the Redis client that implements the RedisSentinelDB interface
+type RedisSentinelDBClient struct {
+ client *redis.Client
+}
+
+// Get returns the value associated with the provided `key` parameter
+func (db RedisSentinelDBClient) Get(ctx context.Context, key string) (string, error) {
+ return db.client.Get(ctx, key).Result()
+}
+
+// Put will set 'key' to hold string 'value' if 'key' does not exist in the redis storage.
+// When key already holds a value, no operation is performed. That's the reason this adapter
+// uses the 'github.com/go-redis/redis's library SetNX. SetNX is short for "SET if Not eXists".
+func (db RedisSentinelDBClient) Put(ctx context.Context, key, value string, ttlSeconds int) (bool, error) {
+ return db.client.SetNX(ctx, key, value, time.Duration(ttlSeconds)*time.Second).Result()
+}
+
+// RedisSentinelBackend when initialized will instantiate and configure the Redis client. It implements
+// the Backend interface.
+type RedisSentinelBackend struct {
+ cfg config.RedisSentinel
+ client RedisDB
+}
+
+// NewRedisSentinelBackend initializes the Redis Sentinel client and pings to make sure connection was successful
+func NewRedisSentinelBackend(cfg config.RedisSentinel, ctx context.Context) *RedisSentinelBackend {
+ options := &redis.FailoverOptions{
+ MasterName: cfg.MasterName,
+ SentinelAddrs: cfg.SentinelAddrs,
+ Password: cfg.Password,
+ DB: cfg.Db,
+ }
+
+ if cfg.TLS.Enabled {
+ options.TLSConfig = &tls.Config{InsecureSkipVerify: cfg.TLS.InsecureSkipVerify}
+ }
+
+ client := RedisSentinelDBClient{client: redis.NewFailoverClient(options)}
+
+ _, err := client.client.Ping(ctx).Result()
+ if err != nil {
+ log.Fatalf("Error creating Redis Sentinel backend: %v", err)
+ }
+ log.Infof("Connected to Redis Sentinels at %v", cfg.SentinelAddrs)
+
+ return &RedisSentinelBackend{
+ cfg: cfg,
+ client: client,
+ }
+}
+
+// Get calls the Redis Sentinel client to return the value associated with the provided `key`
+// parameter and interprets its response. A `Nil` error reply of the Redis client means
+// the `key` does not exist.
+func (b *RedisSentinelBackend) Get(ctx context.Context, key string) (string, error) {
+ res, err := b.client.Get(ctx, key)
+ if err == redis.Nil {
+ err = utils.NewPBCError(utils.KEY_NOT_FOUND)
+ }
+
+ return res, err
+}
+
+// Put writes the `value` under the provided `key` in the Redis Sentinel storage server. Because the backend
+// implementation of Put calls SetNX(item *Item), a `false` return value is interpreted as the data
+// not being written because the `key` already holds a value, and a RecordExistsError is returned
+func (b *RedisSentinelBackend) Put(ctx context.Context, key string, value string, ttlSeconds int) error {
+ success, err := b.client.Put(ctx, key, value, ttlSeconds)
+ if err != nil && err != redis.Nil {
+ return err
+ }
+ if !success {
+ return utils.NewPBCError(utils.RECORD_EXISTS)
+ }
+
+ return nil
+}
diff --git a/backends/redis-sentinel_test.go b/backends/redis-sentinel_test.go
new file mode 100644
index 00000000..0e5155aa
--- /dev/null
+++ b/backends/redis-sentinel_test.go
@@ -0,0 +1,195 @@
+package backends
+
+import (
+ "context"
+ "errors"
+ "testing"
+
+ "github.com/prebid/prebid-cache/utils"
+ "github.com/redis/go-redis/v9"
+ "github.com/stretchr/testify/assert"
+)
+
+func TestRedisSentinelClientGet(t *testing.T) {
+ redisSentinelBackend := &RedisSentinelBackend{}
+
+ type testInput struct {
+ client RedisDB
+ key string
+ }
+
+ type testExpectedValues struct {
+ value string
+ err error
+ }
+
+ testCases := []struct {
+ desc string
+ in testInput
+ expected testExpectedValues
+ }{
+ {
+ desc: "RedisSentinelBackend.Get() throws a redis.Nil error",
+ in: testInput{
+ client: FakeRedisClient{
+ Success: false,
+ ServerError: redis.Nil,
+ },
+ key: "someKeyThatWontBeFound",
+ },
+ expected: testExpectedValues{
+ value: "",
+ err: utils.NewPBCError(utils.KEY_NOT_FOUND),
+ },
+ },
+ {
+ desc: "RedisBackend.Get() throws an error different from redis.Nil",
+ in: testInput{
+ client: FakeRedisClient{
+ Success: false,
+ ServerError: errors.New("some other get error"),
+ },
+ key: "someKey",
+ },
+ expected: testExpectedValues{
+ value: "",
+ err: errors.New("some other get error"),
+ },
+ },
+ {
+ desc: "RedisBackend.Get() doesn't throw an error",
+ in: testInput{
+ client: FakeRedisClient{
+ Success: true,
+ StoredData: map[string]string{"defaultKey": "aValue"},
+ },
+ key: "defaultKey",
+ },
+ expected: testExpectedValues{
+ value: "aValue",
+ err: nil,
+ },
+ },
+ }
+
+ for _, tt := range testCases {
+ redisSentinelBackend.client = tt.in.client
+
+ // Run test
+ actualValue, actualErr := redisSentinelBackend.Get(context.Background(), tt.in.key)
+
+ // Assertions
+ assert.Equal(t, tt.expected.value, actualValue, tt.desc)
+ assert.Equal(t, tt.expected.err, actualErr, tt.desc)
+ }
+}
+
+func TestRedisSentinelClientPut(t *testing.T) {
+ redisSentinelBackend := &RedisSentinelBackend{}
+
+ type testInput struct {
+ redisSentinelClient RedisDB
+ key string
+ valueToStore string
+ ttl int
+ }
+
+ type testExpectedValues struct {
+ writtenValue string
+ redisClientErr error
+ }
+
+ testCases := []struct {
+ desc string
+ in testInput
+ expected testExpectedValues
+ }{
+ {
+ desc: "Try to overwrite already existing key. From redis client documentation, SetNX returns 'false' because no operation is performed",
+ in: testInput{
+ redisSentinelClient: FakeRedisClient{
+ Success: false,
+ StoredData: map[string]string{"key": "original value"},
+ ServerError: redis.Nil,
+ },
+ key: "key",
+ valueToStore: "overwrite value",
+ ttl: 10,
+ },
+ expected: testExpectedValues{
+ redisClientErr: utils.NewPBCError(utils.RECORD_EXISTS),
+ writtenValue: "original value",
+ },
+ },
+ {
+ desc: "When key does not exist, redis.Nil is returned. Other errors should be interpreted as a server side error. Expect error.",
+ in: testInput{
+ redisSentinelClient: FakeRedisClient{
+ Success: true,
+ StoredData: map[string]string{},
+ ServerError: errors.New("A Redis client side error"),
+ },
+ key: "someKey",
+ valueToStore: "someValue",
+ ttl: 10,
+ },
+ expected: testExpectedValues{
+ redisClientErr: errors.New("A Redis client side error"),
+ },
+ },
+ {
+ desc: "In Redis, a zero ttl value means no expiration. Expect value to be successfully set",
+ in: testInput{
+ redisSentinelClient: FakeRedisClient{
+ StoredData: map[string]string{},
+ Success: true,
+ ServerError: redis.Nil,
+ },
+ key: "defaultKey",
+ valueToStore: "aValue",
+ ttl: 0,
+ },
+ expected: testExpectedValues{
+ writtenValue: "aValue",
+ },
+ },
+ {
+ desc: "RedisBackend.Put() successful, no need to set defaultTTL because ttl is greater than zero",
+ in: testInput{
+ redisSentinelClient: FakeRedisClient{
+ StoredData: map[string]string{},
+ Success: true,
+ ServerError: redis.Nil,
+ },
+ key: "defaultKey",
+ valueToStore: "aValue",
+ ttl: 1,
+ },
+ expected: testExpectedValues{
+ writtenValue: "aValue",
+ },
+ },
+ }
+
+ for _, tt := range testCases {
+ // Assign redis backend client
+ redisSentinelBackend.client = tt.in.redisSentinelClient
+
+ // Run test
+ actualErr := redisSentinelBackend.Put(context.Background(), tt.in.key, tt.in.valueToStore, tt.in.ttl)
+
+ // Assertions
+ assert.Equal(t, tt.expected.redisClientErr, actualErr, tt.desc)
+
+ // Put error
+ assert.Equal(t, tt.expected.redisClientErr, actualErr, tt.desc)
+
+ if actualErr == nil || actualErr == utils.NewPBCError(utils.RECORD_EXISTS) {
+ // Either a value was inserted successfully or the record already existed.
+ // Assert data in the backend
+ storage, ok := tt.in.redisSentinelClient.(FakeRedisClient)
+ assert.True(t, ok, tt.desc)
+ assert.Equal(t, tt.expected.writtenValue, storage.StoredData[tt.in.key], tt.desc)
+ }
+ }
+}
diff --git a/config.yaml b/config.yaml
index 729d419b..635a87cd 100644
--- a/config.yaml
+++ b/config.yaml
@@ -22,7 +22,7 @@ backend:
# memcache:
# config_host: "" # Configuration endpoint for auto discovery. Replaced at docker build.
# poll_interval_seconds: 30 # Node change polling interval when auto discovery is used
-# hosts: "10.0.0.1:11211" # List of nodes when not using auto discovery. Can also use an array for multiple hosts.
+# hosts: "10.0.0.1:11211" # List of nodes when not using auto discovery. Can also use an array for multiple hosts.
# redis:
# host: "127.0.0.1"
# port: 6379
@@ -32,6 +32,15 @@ backend:
# tls:
# enabled: false
# insecure_skip_verify: false
+# redis_sentinel:
+# sentinel_addrs: [ "127.0.0.1:26379", "127.0.0.1:26380", "127.0.0.1:26381" ]
+# master_name: "mymaster"
+# password: ""
+# db: 1
+# expiration: 10 # in Minutes
+# tls:
+# enabled: false
+# insecure_skip_verify: false
# ignite:
# scheme: "http"
# host: "127.0.0.1"
diff --git a/config/backends.go b/config/backends.go
index 2f5c8694..122617a6 100644
--- a/config/backends.go
+++ b/config/backends.go
@@ -8,12 +8,13 @@ import (
)
type Backend struct {
- Type BackendType `mapstructure:"type"`
- Aerospike Aerospike `mapstructure:"aerospike"`
- Cassandra Cassandra `mapstructure:"cassandra"`
- Memcache Memcache `mapstructure:"memcache"`
- Redis Redis `mapstructure:"redis"`
- Ignite Ignite `mapstructure:"ignite"`
+ Type BackendType `mapstructure:"type"`
+ Aerospike Aerospike `mapstructure:"aerospike"`
+ Cassandra Cassandra `mapstructure:"cassandra"`
+ Memcache Memcache `mapstructure:"memcache"`
+ Redis Redis `mapstructure:"redis"`
+ RedisSentinel RedisSentinel `mapstructure:"redis_sentinel"`
+ Ignite Ignite `mapstructure:"ignite"`
}
func (cfg *Backend) validateAndLog() error {
@@ -28,25 +29,27 @@ func (cfg *Backend) validateAndLog() error {
return cfg.Memcache.validateAndLog()
case BackendRedis:
return cfg.Redis.validateAndLog()
+ case BackendRedisSentinel:
+ return cfg.RedisSentinel.validateAndLog()
case BackendIgnite:
return cfg.Ignite.validateAndLog()
case BackendMemory:
return nil
default:
- return fmt.Errorf(`invalid config.backend.type: %s. It must be "aerospike", "cassandra", "memcache", "redis", "ignite", or "memory".`, cfg.Type)
+ return fmt.Errorf(`invalid config.backend.type: %s. It must be "%s", "%s", "%s", "%s", "%s", "%s", or "%s"`, cfg.Type, BackendAerospike, BackendCassandra, BackendMemcache, BackendRedis, BackendRedisSentinel, BackendIgnite, BackendMemory)
}
- return nil
}
type BackendType string
const (
- BackendAerospike BackendType = "aerospike"
- BackendCassandra BackendType = "cassandra"
- BackendMemcache BackendType = "memcache"
- BackendMemory BackendType = "memory"
- BackendRedis BackendType = "redis"
- BackendIgnite BackendType = "ignite"
+ BackendAerospike BackendType = "aerospike"
+ BackendCassandra BackendType = "cassandra"
+ BackendMemcache BackendType = "memcache"
+ BackendMemory BackendType = "memory"
+ BackendRedis BackendType = "redis"
+ BackendRedisSentinel BackendType = "redis_sentinel"
+ BackendIgnite BackendType = "ignite"
)
type Aerospike struct {
@@ -176,6 +179,23 @@ func (cfg *Redis) validateAndLog() error {
return nil
}
+type RedisSentinel struct {
+ SentinelAddrs []string `mapstructure:"sentinel_addrs"`
+ MasterName string `mapstructure:"master_name"`
+ Password string `mapstructure:"password"`
+ Db int `mapstructure:"db"`
+ TLS RedisTLS `mapstructure:"tls"`
+}
+
+func (cfg *RedisSentinel) validateAndLog() error {
+ log.Infof("config.backend.redis-sentinel.sentinel_addrs: %s", cfg.SentinelAddrs)
+ log.Infof("config.backend.redis-sentinel.master_name: %s", cfg.MasterName)
+ log.Infof("config.backend.redis-sentinel.db: %d", cfg.Db)
+ log.Infof("config.backend.redis-sentinel.tls.enabled: %t", cfg.TLS.Enabled)
+ log.Infof("config.backend.redis-sentinel.tls.insecure_skip_verify: %t", cfg.TLS.InsecureSkipVerify)
+ return nil
+}
+
type Ignite struct {
Scheme string `mapstructure:"scheme"`
Host string `mapstructure:"host"`
diff --git a/config/configtest/sample_full_config.yaml b/config/configtest/sample_full_config.yaml
index 3c40fbb7..c51726f8 100644
--- a/config/configtest/sample_full_config.yaml
+++ b/config/configtest/sample_full_config.yaml
@@ -37,6 +37,15 @@ backend:
tls:
enabled: false
insecure_skip_verify: false
+ redis_sentinel:
+ sentinel_addrs: [ "127.0.0.1:26379", "127.0.0.1:26380", "127.0.0.1:26381" ]
+ master_name: "mymaster"
+ password: ""
+ db: 1
+ expiration: 10 # in Minutes
+ tls:
+ enabled: false
+ insecure_skip_verify: false
ignite:
scheme: "http"
host: "127.0.0.1"
diff --git a/go.mod b/go.mod
index 04c26750..0a9087a0 100644
--- a/go.mod
+++ b/go.mod
@@ -14,6 +14,7 @@ require (
github.com/prometheus/client_golang v1.12.2
github.com/prometheus/client_model v0.2.0
github.com/rcrowley/go-metrics v0.0.0-20201227073835-cf1acfcdf475
+ github.com/redis/go-redis/v9 v9.4.0
github.com/rs/cors v1.8.2
github.com/sirupsen/logrus v1.6.0
github.com/spf13/viper v1.11.0
@@ -24,7 +25,7 @@ require (
require (
github.com/beorn7/perks v1.0.1 // indirect
github.com/bitly/go-hostpool v0.1.0 // indirect
- github.com/cespare/xxhash/v2 v2.1.2 // indirect
+ github.com/cespare/xxhash/v2 v2.2.0 // indirect
github.com/davecgh/go-spew v1.1.1 // indirect
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f // indirect
github.com/fsnotify/fsnotify v1.5.1 // indirect
diff --git a/go.sum b/go.sum
index b5f8ea8f..9de446e5 100644
--- a/go.sum
+++ b/go.sum
@@ -54,10 +54,13 @@ github.com/bitly/go-hostpool v0.1.0 h1:XKmsF6k5el6xHG3WPJ8U0Ku/ye7njX7W81Ng7O2io
github.com/bitly/go-hostpool v0.1.0/go.mod h1:4gOCgp6+NZnVqlKyZ/iBZFTAJKembaVENUpMkpg42fw=
github.com/bmizerany/assert v0.0.0-20160611221934-b7ed37b82869 h1:DDGfHa7BWjL4YnC6+E63dPcxHo2sUxDIu8g3QgEJdRY=
github.com/bmizerany/assert v0.0.0-20160611221934-b7ed37b82869/go.mod h1:Ekp36dRnpXw/yCqJaO+ZrUyxD+3VXMFFr56k5XYrpB4=
+github.com/bsm/ginkgo/v2 v2.12.0 h1:Ny8MWAHyOepLGlLKYmXG4IEkioBysk6GpaRTLC8zwWs=
+github.com/bsm/gomega v1.27.10 h1:yeMWxP2pV2fG3FgAODIY8EiRE3dy0aeFYt4l7wh6yKA=
github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
-github.com/cespare/xxhash/v2 v2.1.2 h1:YRXhKfTDauu4ajMg1TPgFO5jnlC2HCbmLXMcTG5cbYE=
github.com/cespare/xxhash/v2 v2.1.2/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
+github.com/cespare/xxhash/v2 v2.2.0 h1:DC2CZ1Ep5Y4k3ZQ899DldepgrayRUGE6BBZ/cd9Cj44=
+github.com/cespare/xxhash/v2 v2.2.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI=
github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI=
github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU=
@@ -263,6 +266,8 @@ github.com/prometheus/procfs v0.7.3/go.mod h1:cz+aTbrPOrUb4q7XlbU9ygM+/jj0fzG6c1
github.com/rcrowley/go-metrics v0.0.0-20190826022208-cac0b30c2563/go.mod h1:bCqnVzQkZxMG4s8nGwiZ5l3QUCyqpo9Y+/ZMZ9VjZe4=
github.com/rcrowley/go-metrics v0.0.0-20201227073835-cf1acfcdf475 h1:N/ElC8H3+5XpJzTSTfLsJV/mx9Q9g7kxmchpfZyxgzM=
github.com/rcrowley/go-metrics v0.0.0-20201227073835-cf1acfcdf475/go.mod h1:bCqnVzQkZxMG4s8nGwiZ5l3QUCyqpo9Y+/ZMZ9VjZe4=
+github.com/redis/go-redis/v9 v9.4.0 h1:Yzoz33UZw9I/mFhx4MNrB6Fk+XHO1VukNcCa1+lwyKk=
+github.com/redis/go-redis/v9 v9.4.0/go.mod h1:hdY0cQFCN4fnSYT6TkisLufl/4W5UIXyv0b/CLO2V2M=
github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
github.com/rs/cors v1.8.2 h1:KCooALfAYGs415Cwu5ABvv9n9509fSiG5SQJn/AQo4U=
github.com/rs/cors v1.8.2/go.mod h1:XyqrcTp5zjWr1wsJ8PIRZssZ8b/WMcMf71DJnit4EMU=
@@ -629,8 +634,6 @@ google.golang.org/protobuf v1.24.0/go.mod h1:r/3tXBNzIEhYS9I1OUVjXDlt8tc493IdKGj
google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c=
google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=
google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
-google.golang.org/protobuf v1.28.0 h1:w43yiav+6bVFTBQFZX0r7ipe9JQ1QsbMgHwbBziscLw=
-google.golang.org/protobuf v1.28.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I=
google.golang.org/protobuf v1.33.0 h1:uNO2rsAINq/JlFpSdYEKIZ0uKD/R9cpdv0T+yoGwGmI=
google.golang.org/protobuf v1.33.0/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos=
gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw=