|
| 1 | +package cache |
| 2 | + |
| 3 | +import ( |
| 4 | + "fmt" |
| 5 | + "os" |
| 6 | + "strings" |
| 7 | + "time" |
| 8 | + |
| 9 | + cbuilder "github.com/scribd/go-sdk/internal/pkg/configuration/builder" |
| 10 | +) |
| 11 | + |
| 12 | +type ( |
| 13 | + StoreType int |
| 14 | + |
| 15 | + // Redis provides configuration for redis cache. |
| 16 | + Redis struct { |
| 17 | + // URL into Redis ClusterOptions that can be used to connect to Redis |
| 18 | + URL string `mapstructure:"url"` |
| 19 | + |
| 20 | + // Either a single address or a seed list of host:port addresses |
| 21 | + // of cluster/sentinel nodes. |
| 22 | + Addrs []string `mapstructure:"addrs"` |
| 23 | + |
| 24 | + // ClientName will execute the `CLIENT SETNAME ClientName` command for each conn. |
| 25 | + ClientName string `mapstructure:"client_name"` |
| 26 | + |
| 27 | + // Database to be selected after connecting to the server. |
| 28 | + // Only single-node and failover clients. |
| 29 | + DB int `mapstructure:"db"` |
| 30 | + |
| 31 | + // Protocol 2 or 3. Use the version to negotiate RESP version with redis-server. |
| 32 | + Protocol int `mapstructure:"protocol"` |
| 33 | + // Use the specified Username to authenticate the current connection |
| 34 | + // with one of the connections defined in the ACL list when connecting |
| 35 | + // to a Redis 6.0 instance, or greater, that is using the Redis ACL system. |
| 36 | + Username string `mapstructure:"username"` |
| 37 | + // Optional password. Must match the password specified in the |
| 38 | + // requirepass server configuration option (if connecting to a Redis 5.0 instance, or lower), |
| 39 | + // or the User Password when connecting to a Redis 6.0 instance, or greater, |
| 40 | + // that is using the Redis ACL system. |
| 41 | + Password string `mapstructure:"password"` |
| 42 | + |
| 43 | + // If specified with SentinelPassword, enables ACL-based authentication (via |
| 44 | + // AUTH <user> <pass>). |
| 45 | + SentinelUsername string `mapstructure:"sentinel_username"` |
| 46 | + // Sentinel password from "requirepass <password>" (if enabled) in Sentinel |
| 47 | + // configuration, or, if SentinelUsername is also supplied, used for ACL-based |
| 48 | + // authentication. |
| 49 | + SentinelPassword string `mapstructure:"sentinel_password"` |
| 50 | + |
| 51 | + // Maximum number of retries before giving up. |
| 52 | + MaxRetries int `mapstructure:"max_retries"` |
| 53 | + // Minimum backoff between each retry. |
| 54 | + MinRetryBackoff time.Duration `mapstructure:"min_retry_backoff"` |
| 55 | + // Maximum backoff between each retry. |
| 56 | + MaxRetryBackoff time.Duration `mapstructure:"max_retry_backoff"` |
| 57 | + |
| 58 | + // Dial timeout for establishing new connections. |
| 59 | + DialTimeout time.Duration `mapstructure:"dial_timeout"` |
| 60 | + // Timeout for socket reads. If reached, commands will fail |
| 61 | + // with a timeout instead of blocking. Supported values: |
| 62 | + // - `0` - default timeout (3 seconds). |
| 63 | + // - `-1` - no timeout (block indefinitely). |
| 64 | + // - `-2` - disables SetReadDeadline calls completely. |
| 65 | + ReadTimeout time.Duration `mapstructure:"read_timeout"` |
| 66 | + // Timeout for socket writes. If reached, commands will fail |
| 67 | + // with a timeout instead of blocking. Supported values: |
| 68 | + // - `0` - default timeout (3 seconds). |
| 69 | + // - `-1` - no timeout (block indefinitely). |
| 70 | + // - `-2` - disables SetWriteDeadline calls completely. |
| 71 | + WriteTimeout time.Duration `mapstructure:"write_timeout"` |
| 72 | + // ContextTimeoutEnabled controls whether the client respects context timeouts and deadlines. |
| 73 | + // See https://redis.uptrace.dev/guide/go-redis-debugging.html#timeouts |
| 74 | + ContextTimeoutEnabled bool `mapstructure:"context_timeout_enabled"` |
| 75 | + |
| 76 | + // Base number of socket connections. |
| 77 | + // If there is not enough connections in the pool, new connections will be allocated in excess of PoolSize, |
| 78 | + // you can limit it through MaxActiveConns |
| 79 | + PoolSize int `mapstructure:"pool_size"` |
| 80 | + // Amount of time client waits for connection if all connections |
| 81 | + // are busy before returning an error. |
| 82 | + PoolTimeout time.Duration `mapstructure:"pool_timeout"` |
| 83 | + // Maximum number of idle connections. |
| 84 | + MaxIdleConns int `mapstructure:"max_idle_conns"` |
| 85 | + // Minimum number of idle connections which is useful when establishing |
| 86 | + // new connection is slow. |
| 87 | + MinIdleConns int `mapstructure:"min_idle_conns"` |
| 88 | + // Maximum number of connections allocated by the pool at a given time. |
| 89 | + // When zero, there is no limit on the number of connections in the pool. |
| 90 | + MaxActiveConns int `mapstructure:"max_active_conns"` |
| 91 | + // ConnMaxIdleTime is the maximum amount of time a connection may be idle. |
| 92 | + // Should be less than server's timeout. |
| 93 | + // |
| 94 | + // Expired connections may be closed lazily before reuse. |
| 95 | + // If d <= 0, connections are not closed due to a connection's idle time. |
| 96 | + ConnMaxIdleTime time.Duration `mapstructure:"conn_max_idle_time"` |
| 97 | + // ConnMaxLifetime is the maximum amount of time a connection may be reused. |
| 98 | + // |
| 99 | + // Expired connections may be closed lazily before reuse. |
| 100 | + // If <= 0, connections are not closed due to a connection's age. |
| 101 | + ConnMaxLifetime time.Duration `mapstructure:"conn_max_lifetime"` |
| 102 | + |
| 103 | + // Only cluster clients. |
| 104 | + |
| 105 | + // The maximum number of retries before giving up. Command is retried |
| 106 | + // on network errors and MOVED/ASK redirects. |
| 107 | + MaxRedirects int `mapstructure:"max_redirects"` |
| 108 | + // Enables read-only commands on slave nodes. |
| 109 | + ReadOnly bool `mapstructure:"read_only"` |
| 110 | + // Allows routing read-only commands to the closest master or slave node. |
| 111 | + // It automatically enables ReadOnly. |
| 112 | + RouteByLatency bool `mapstructure:"route_by_latency"` |
| 113 | + // Allows routing read-only commands to the random master or slave node. |
| 114 | + // It automatically enables ReadOnly. |
| 115 | + RouteRandomly bool `mapstructure:"route_randomly"` |
| 116 | + |
| 117 | + // The sentinel master name. |
| 118 | + // Only failover clients. |
| 119 | + |
| 120 | + // The master name. |
| 121 | + MasterName string `mapstructure:"master_name"` |
| 122 | + |
| 123 | + // Disable set-lib on connect. |
| 124 | + DisableIndentity bool `mapstructure:"disable_indentity"` |
| 125 | + |
| 126 | + // Add suffix to client name. |
| 127 | + IdentitySuffix string `mapstructure:"identity_suffix"` |
| 128 | + |
| 129 | + // TLS configuration |
| 130 | + TLS TLS `mapstructure:"tls"` |
| 131 | + } |
| 132 | + |
| 133 | + TLS struct { |
| 134 | + // Enabled whether the TLS connection is enabled or not |
| 135 | + Enabled bool `mapstructure:"enabled"` |
| 136 | + |
| 137 | + // Ca Root CA certificate |
| 138 | + Ca string `mapstructure:"ca"` |
| 139 | + // Cert is a PEM certificate string |
| 140 | + Cert string `mapstructure:"cert_pem"` |
| 141 | + // CertKey is a PEM key certificate string |
| 142 | + CertKey string `mapstructure:"cert_pem_key"` |
| 143 | + // Passphrase is used in case the private key needs to be decrypted |
| 144 | + Passphrase string `mapstructure:"passphrase"` |
| 145 | + // InsecureSkipVerify whether to skip TLS verification or not |
| 146 | + InsecureSkipVerify bool `mapstructure:"insecure_skip_verify"` |
| 147 | + } |
| 148 | + |
| 149 | + // Config provides configuration for cache. |
| 150 | + Config struct { |
| 151 | + Store string `mapstructure:"store"` |
| 152 | + Redis Redis `mapstructure:"redis"` |
| 153 | + } |
| 154 | +) |
| 155 | + |
| 156 | +const ( |
| 157 | + storeTypeRedisName = "redis" |
| 158 | +) |
| 159 | + |
| 160 | +func NewConfig() (*Config, error) { |
| 161 | + config := &Config{} |
| 162 | + viperBuilder := cbuilder.New("cache") |
| 163 | + |
| 164 | + appName := strings.ReplaceAll(os.Getenv("APP_SETTINGS_NAME"), "-", "_") |
| 165 | + viperBuilder.SetDefault("cache", fmt.Sprintf("%s_%s", appName, os.Getenv("APP_ENV"))) |
| 166 | + |
| 167 | + vConf, err := viperBuilder.Build() |
| 168 | + if err != nil { |
| 169 | + return config, err |
| 170 | + } |
| 171 | + |
| 172 | + if err = vConf.Unmarshal(config); err != nil { |
| 173 | + return config, fmt.Errorf("unable to decode into struct: %s", err.Error()) |
| 174 | + } |
| 175 | + |
| 176 | + config.Redis.Addrs = vConf.GetStringSlice("redis.addrs") |
| 177 | + |
| 178 | + if err := config.validate(); err != nil { |
| 179 | + return config, err |
| 180 | + } |
| 181 | + |
| 182 | + return config, nil |
| 183 | +} |
| 184 | + |
| 185 | +func (c *Config) validate() error { |
| 186 | + if c.Store == "" { |
| 187 | + return fmt.Errorf("store is required") |
| 188 | + } |
| 189 | + |
| 190 | + switch c.Store { |
| 191 | + case storeTypeRedisName: |
| 192 | + if c.Redis.URL == "" && len(c.Redis.Addrs) == 0 { |
| 193 | + return fmt.Errorf("url or addrs is required for redis") |
| 194 | + } |
| 195 | + default: |
| 196 | + return fmt.Errorf("store %s is not supported", c.Store) |
| 197 | + } |
| 198 | + |
| 199 | + return nil |
| 200 | +} |
0 commit comments