8
"github.com/pkg/errors"
10
"github.com/kumahq/kuma/pkg/config"
11
config_types "github.com/kumahq/kuma/pkg/config/types"
12
"github.com/kumahq/kuma/pkg/core"
17
DriverNamePq = "postgres"
18
DefaultMaxIdleConnections = 50
19
DefaultMinOpenConnections = 0
23
_ config.Config = &PostgresStoreConfig{}
24
logger = core.Log.WithName("postgres-config")
28
DefaultMinReconnectInterval = config_types.Duration{Duration: 10 * time.Second}
29
DefaultMaxReconnectInterval = config_types.Duration{Duration: 60 * time.Second}
30
DefaultMaxConnectionLifetime = config_types.Duration{Duration: time.Hour}
31
DefaultMaxConnectionLifetimeJitter = config_types.Duration{Duration: 1 * time.Minute}
32
DefaultHealthCheckInterval = config_types.Duration{Duration: 30 * time.Second}
33
// some of the above settings taken from pgx https://github.com/jackc/pgx/blob/ca022267dbbfe7a8ba7070557352a5cd08f6cb37/pgxpool/pool.go#L18-L22
36
// PostgresStoreConfig defines Postgres store configuration
37
type PostgresStoreConfig struct {
40
// Host of the Postgres DB
41
Host string `json:"host" envconfig:"kuma_store_postgres_host"`
42
// Port of the Postgres DB
43
Port int `json:"port" envconfig:"kuma_store_postgres_port"`
44
// User of the Postgres DB
45
User string `json:"user" envconfig:"kuma_store_postgres_user"`
46
// Password of the Postgres DB
47
Password string `json:"password" envconfig:"kuma_store_postgres_password"`
48
// Database name of the Postgres DB
49
DbName string `json:"dbName" envconfig:"kuma_store_postgres_db_name"`
50
// Driver to use, one of: pgx, postgres
51
DriverName string `json:"driverName" envconfig:"kuma_store_postgres_driver_name"`
52
// Connection Timeout to the DB in seconds
53
ConnectionTimeout int `json:"connectionTimeout" envconfig:"kuma_store_postgres_connection_timeout"`
54
// MaxConnectionLifetime (applied only when driverName=pgx) is the duration since creation after which a connection will be automatically closed
55
MaxConnectionLifetime config_types.Duration `json:"maxConnectionLifetime" envconfig:"kuma_store_postgres_max_connection_lifetime"`
56
// MaxConnectionLifetimeJitter (applied only when driverName=pgx) is the duration after MaxConnectionLifetime to randomly decide to close a connection.
57
// This helps prevent all connections from being closed at the exact same time, starving the pool.
58
MaxConnectionLifetimeJitter config_types.Duration `json:"maxConnectionLifetimeJitter" envconfig:"kuma_store_postgres_max_connection_lifetime_jitter"`
59
// HealthCheckInterval (applied only when driverName=pgx) is the duration between checks of the health of idle connections.
60
HealthCheckInterval config_types.Duration `json:"healthCheckInterval" envconfig:"kuma_store_postgres_health_check_interval"`
61
// MinOpenConnections (applied only when driverName=pgx) is the minimum number of open connections to the database
62
MinOpenConnections int `json:"minOpenConnections" envconfig:"kuma_store_postgres_min_open_connections"`
63
// MaxOpenConnections is the maximum number of open connections to the database
64
// `0` value means number of open connections is unlimited
65
MaxOpenConnections int `json:"maxOpenConnections" envconfig:"kuma_store_postgres_max_open_connections"`
66
// MaxIdleConnections (applied only when driverName=postgres) is the maximum number of connections in the idle connection pool
67
// <0 value means no idle connections and 0 means default max idle connections
68
// Deprecated: it's only used when driverName=postgres (lib/pq) which is deprecated, use driverName=pgx instead.
69
MaxIdleConnections int `json:"maxIdleConnections" envconfig:"kuma_store_postgres_max_idle_connections"`
71
TLS TLSPostgresStoreConfig `json:"tls"`
72
// MinReconnectInterval (applied only when driverName=postgres) controls the duration to wait before trying to
73
// re-establish the database connection after connection loss. After each
74
// consecutive failure this interval is doubled, until MaxReconnectInterval
75
// is reached. Successfully completing the connection establishment procedure
76
// resets the interval back to MinReconnectInterval.
77
// Deprecated: it's only used when driverName=postgres (lib/pq) which is deprecated, use driverName=pgx instead.
78
MinReconnectInterval config_types.Duration `json:"minReconnectInterval" envconfig:"kuma_store_postgres_min_reconnect_interval"`
79
// MaxReconnectInterval (applied only when driverName=postgres) controls the maximum possible duration to wait before trying
80
// to re-establish the database connection after connection loss.
81
// Deprecated: it's only used when driverName=postgres (lib/pq) which is deprecated, use driverName=pgx instead.
82
MaxReconnectInterval config_types.Duration `json:"maxReconnectInterval" envconfig:"kuma_store_postgres_max_reconnect_interval"`
83
// MaxListQueryElements defines maximum number of changed elements before requesting full list of elements from the store.
84
MaxListQueryElements uint32 `json:"maxListQueryElements" envconfig:"kuma_store_postgres_max_list_query_elements"`
85
// ReadReplica is a setting for a DB replica used only for read queries
86
ReadReplica ReadReplica `json:"readReplica"`
89
type ReadReplica struct {
90
// Host of the Postgres DB read replica. If not set, read replica is not used.
91
Host string `json:"host" envconfig:"kuma_store_postgres_read_replica_host"`
92
// Port of the Postgres DB read replica
93
Port uint `json:"port" envconfig:"kuma_store_postgres_read_replica_port"`
94
// Ratio in [0-100] range. How many SELECT queries (out of 100) will use read replica.
95
Ratio uint `json:"ratio" envconfig:"kuma_store_postgres_read_replica_ratio"`
98
func (cfg ReadReplica) Validate() error {
100
return errors.New(".Ratio out of [0-100] range")
105
func (cfg PostgresStoreConfig) ConnectionString() (string, error) {
106
mode, err := cfg.TLS.Mode.postgresMode()
110
escape := func(value string) string { return strings.ReplaceAll(strings.ReplaceAll(value, `\`, `\\`), `'`, `\'`) }
111
intVariable := func(name string, value int) string {
112
return fmt.Sprintf("%s=%d", name, value)
114
variable := func(name, value string) string {
115
return fmt.Sprintf("%s=%s", name, value)
117
quotedVariable := func(name, value string) string {
118
return fmt.Sprintf("%s='%s'", name, escape(value))
120
variables := []string{
121
quotedVariable("host", cfg.Host),
122
intVariable("port", cfg.Port),
123
quotedVariable("user", cfg.User),
124
quotedVariable("password", cfg.Password),
125
quotedVariable("dbname", cfg.DbName),
126
intVariable("connect_timeout", cfg.ConnectionTimeout),
127
variable("sslmode", mode),
128
quotedVariable("sslcert", cfg.TLS.CertPath),
129
quotedVariable("sslkey", cfg.TLS.KeyPath),
130
quotedVariable("sslrootcert", cfg.TLS.CAPath),
132
if cfg.TLS.DisableSSLSNI {
133
variables = append(variables, "sslsni=0")
135
return strings.Join(variables, " "), nil
138
// TLSMode modes available here https://godoc.org/github.com/lib/pq
142
Disable TLSMode = "disable"
143
// VerifyNone represents Always TLS (skip verification)
144
VerifyNone TLSMode = "verifyNone"
145
// VerifyCa represents Always TLS (verify that the certificate presented by the server was signed by a trusted CA)
146
VerifyCa TLSMode = "verifyCa"
147
// VerifyFull represents Always TLS (verify that the certification presented by the server was signed by a trusted CA and the server host name matches the one in the certificate)
148
VerifyFull TLSMode = "verifyFull"
151
func (mode TLSMode) postgresMode() (string, error) {
154
return "disable", nil
156
return "require", nil
158
return "verify-ca", nil
160
return "verify-full", nil
162
return "", errors.Errorf("could not translate mode %q to postgres mode", mode)
166
type TLSPostgresStoreConfig struct {
169
// Mode of TLS connection. Available values (disable, verifyNone, verifyCa, verifyFull)
170
Mode TLSMode `json:"mode" envconfig:"kuma_store_postgres_tls_mode"`
171
// Path to TLS Certificate of the client. Required when server has METHOD=cert
172
CertPath string `json:"certPath" envconfig:"kuma_store_postgres_tls_cert_path"`
173
// Path to TLS Key of the client. Required when server has METHOD=cert
174
KeyPath string `json:"keyPath" envconfig:"kuma_store_postgres_tls_key_path"`
175
// Path to the root certificate. Used in verifyCa and verifyFull modes.
176
CAPath string `json:"caPath" envconfig:"kuma_store_postgres_tls_ca_path"`
177
// Whether to disable SNI the postgres `sslsni` option.
178
DisableSSLSNI bool `json:"disableSSLSNI" envconfig:"kuma_store_postgres_tls_disable_sslsni"`
181
func (s TLSPostgresStoreConfig) Validate() error {
187
return errors.New("CAPath cannot be empty")
192
return errors.Errorf("invalid mode: %s", s.Mode)
194
if s.KeyPath == "" && s.CertPath != "" {
195
return errors.New("KeyPath cannot be empty when CertPath is provided")
197
if s.CertPath == "" && s.KeyPath != "" {
198
return errors.New("CertPath cannot be empty when KeyPath is provided")
203
func (p *PostgresStoreConfig) Sanitize() {
204
p.Password = config.SanitizedValue
207
func (p *PostgresStoreConfig) Validate() error {
209
return errors.New("Host should not be empty")
212
return errors.New("Port cannot be negative")
215
return errors.New("User should not be empty")
217
if len(p.Password) < 1 {
218
return errors.New("Password should not be empty")
220
if len(p.DbName) < 1 {
221
return errors.New("DbName should not be empty")
223
if err := p.TLS.Validate(); err != nil {
224
return errors.Wrap(err, "TLS validation failed")
226
if p.MinReconnectInterval.Duration >= p.MaxReconnectInterval.Duration {
227
return errors.New("MinReconnectInterval should be less than MaxReconnectInterval")
229
if p.MinOpenConnections < 0 {
230
return errors.New("MinOpenConnections should be greater than 0")
232
if p.MinOpenConnections > p.MaxOpenConnections {
233
return errors.New("MinOpenConnections should be less than MaxOpenConnections")
235
if p.MaxConnectionLifetime.Duration < 0 {
236
return errors.New("MaxConnectionLifetime should be greater than 0")
238
if p.MaxConnectionLifetimeJitter.Duration < 0 {
239
return errors.New("MaxConnectionLifetimeJitter should be greater than 0")
241
if p.MaxConnectionLifetimeJitter.Duration > p.MaxConnectionLifetime.Duration {
242
return errors.New("MaxConnectionLifetimeJitter should be less than MaxConnectionLifetime")
244
if p.HealthCheckInterval.Duration < 0 {
245
return errors.New("HealthCheckInterval should be greater than 0")
247
if err := p.ReadReplica.Validate(); err != nil {
248
return errors.Wrapf(err, "ReadReplica validation failed")
250
warning := "[WARNING] "
251
switch p.DriverName {
253
pqAlternativeMessage := "does not have an equivalent for pgx driver. " +
254
"If you need this setting consider using lib/pq as a postgres driver by setting driverName='postgres' or " +
255
"setting KUMA_STORE_POSTGRES_DRIVER_NAME='postgres' env variable."
256
if p.MaxIdleConnections != DefaultMaxIdleConnections {
257
logger.Info(warning + "MaxIdleConnections " + pqAlternativeMessage)
259
if p.MinReconnectInterval != DefaultMinReconnectInterval {
260
logger.Info(warning + "MinReconnectInterval " + pqAlternativeMessage)
262
if p.MaxReconnectInterval != DefaultMaxReconnectInterval {
263
logger.Info(warning + "MaxReconnectInterval " + pqAlternativeMessage)
266
logger.Info(warning + "DriverName='postgres' (pq) is deprecated. Please use DriverName='pgx' instead.")
267
pgxAlternativeMessage := "does not have an equivalent for pq driver. " +
268
"If you need this setting consider using pgx as a postgres driver by setting driverName='pgx' or " +
269
"setting KUMA_STORE_POSTGRES_DRIVER_NAME='pgx' env variable."
270
if p.MinOpenConnections != DefaultMinOpenConnections {
271
logger.Info(warning + "MinOpenConnections " + pgxAlternativeMessage)
273
if p.MaxConnectionLifetime != DefaultMaxConnectionLifetime {
274
logger.Info(warning + "MaxConnectionLifetime " + pgxAlternativeMessage)
276
if p.MaxConnectionLifetimeJitter != DefaultMaxConnectionLifetimeJitter {
277
logger.Info(warning + "MaxConnectionLifetimeJitter " + pgxAlternativeMessage)
279
if p.HealthCheckInterval != DefaultHealthCheckInterval {
280
logger.Info(warning + "HealthCheckInterval " + pgxAlternativeMessage)
287
func DefaultPostgresStoreConfig() *PostgresStoreConfig {
288
return &PostgresStoreConfig{
294
DriverName: DriverNamePgx,
295
ConnectionTimeout: 5,
296
MaxOpenConnections: 50, // 0 for unlimited
297
MaxIdleConnections: DefaultMaxIdleConnections, // 0 for unlimited
298
TLS: DefaultTLSPostgresStoreConfig(),
299
MinReconnectInterval: DefaultMinReconnectInterval,
300
MaxReconnectInterval: DefaultMaxReconnectInterval,
301
MinOpenConnections: DefaultMinOpenConnections,
302
MaxConnectionLifetime: DefaultMaxConnectionLifetime,
303
MaxConnectionLifetimeJitter: DefaultMaxConnectionLifetimeJitter,
304
HealthCheckInterval: DefaultHealthCheckInterval,
305
MaxListQueryElements: 0,
306
ReadReplica: ReadReplica{
313
var _ config.Config = &TLSPostgresStoreConfig{}
315
func DefaultTLSPostgresStoreConfig() TLSPostgresStoreConfig {
316
return TLSPostgresStoreConfig{