Skip to content

Commit 9a46076

Browse files
committed
Move config to configmap
1 parent 2e8593b commit 9a46076

File tree

5 files changed

+341
-155
lines changed

5 files changed

+341
-155
lines changed

internal/controller/standalone_pgadmin/config.go

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -7,9 +7,10 @@ package standalone_pgadmin
77
// Include configs here used by multiple files
88
const (
99
// ConfigMap keys used also in mounting volume to pod
10-
settingsConfigMapKey = "pgadmin-settings.json"
11-
settingsClusterMapKey = "pgadmin-shared-clusters.json"
12-
gunicornConfigKey = "gunicorn-config.json"
10+
settingsConfigMapKey = "pgadmin-settings.json"
11+
settingsClusterMapKey = "pgadmin-shared-clusters.json"
12+
gunicornLoggingConfigKey = "gunicorn-logging-config.json"
13+
gunicornConfigKey = "gunicorn-config.json"
1314

1415
// Port address used to define pod and service
1516
pgAdminPort = 5050

internal/controller/standalone_pgadmin/configmap.go

Lines changed: 157 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ import (
1919
"github.com/pkg/errors"
2020

2121
"github.com/crunchydata/postgres-operator/internal/collector"
22+
"github.com/crunchydata/postgres-operator/internal/feature"
2223
"github.com/crunchydata/postgres-operator/internal/initialize"
2324
"github.com/crunchydata/postgres-operator/internal/naming"
2425
"github.com/crunchydata/postgres-operator/pkg/apis/postgres-operator.crunchydata.com/v1beta1"
@@ -32,7 +33,7 @@ func (r *PGAdminReconciler) reconcilePGAdminConfigMap(
3233
ctx context.Context, pgadmin *v1beta1.PGAdmin,
3334
clusters map[string][]*v1beta1.PostgresCluster,
3435
) (*corev1.ConfigMap, error) {
35-
configmap, err := configmap(pgadmin, clusters)
36+
configmap, err := configmap(ctx, pgadmin, clusters)
3637
if err != nil {
3738
return configmap, err
3839
}
@@ -50,7 +51,7 @@ func (r *PGAdminReconciler) reconcilePGAdminConfigMap(
5051
}
5152

5253
// configmap returns a v1.ConfigMap for pgAdmin.
53-
func configmap(pgadmin *v1beta1.PGAdmin,
54+
func configmap(ctx context.Context, pgadmin *v1beta1.PGAdmin,
5455
clusters map[string][]*v1beta1.PostgresCluster,
5556
) (*corev1.ConfigMap, error) {
5657
configmap := &corev1.ConfigMap{ObjectMeta: naming.StandalonePGAdmin(pgadmin)}
@@ -63,26 +64,29 @@ func configmap(pgadmin *v1beta1.PGAdmin,
6364

6465
// TODO(tjmoore4): Populate configuration details.
6566
initialize.Map(&configmap.Data)
66-
configSettings, err := generateConfig(pgadmin)
67+
pgadminConfigSettings, err := generateConfig(ctx, pgadmin)
6768
if err == nil {
68-
configmap.Data[settingsConfigMapKey] = configSettings
69+
configmap.Data[settingsConfigMapKey] = pgadminConfigSettings
6970
}
7071

7172
clusterSettings, err := generateClusterConfig(clusters)
7273
if err == nil {
7374
configmap.Data[settingsClusterMapKey] = clusterSettings
7475
}
7576

76-
gunicornSettings, err := generateGunicornConfig(pgadmin)
77+
gunicornSettings, gunicornLoggingSettings, err := generateGunicornConfig(ctx, pgadmin)
7778
if err == nil {
7879
configmap.Data[gunicornConfigKey] = gunicornSettings
80+
configmap.Data[gunicornLoggingConfigKey] = gunicornLoggingSettings
7981
}
8082

8183
return configmap, err
8284
}
8385

84-
// generateConfig generates the config settings for the pgAdmin
85-
func generateConfig(pgadmin *v1beta1.PGAdmin) (string, error) {
86+
// generateConfigs generates the config settings for the pgAdmin and gunicorn
87+
func generateConfig(ctx context.Context, pgadmin *v1beta1.PGAdmin) (
88+
string, error,
89+
) {
8690
settings := map[string]any{
8791
// Bind to all IPv4 addresses by default. "0.0.0.0" here represents INADDR_ANY.
8892
// - https://flask.palletsprojects.com/en/2.2.x/api/#flask.Flask.run
@@ -102,6 +106,48 @@ func generateConfig(pgadmin *v1beta1.PGAdmin) (string, error) {
102106
settings["UPGRADE_CHECK_ENABLED"] = false
103107
settings["UPGRADE_CHECK_URL"] = ""
104108
settings["UPGRADE_CHECK_KEY"] = ""
109+
settings["DATA_DIR"] = dataMountPath
110+
settings["LOG_FILE"] = LogFileAbsolutePath
111+
112+
// If OTel logs feature gate is enabled, we want to change the pgAdmin/gunicorn logging
113+
if feature.Enabled(ctx, feature.OpenTelemetryLogs) && pgadmin.Spec.Instrumentation != nil {
114+
115+
var (
116+
maxBackupRetentionNumber = 1
117+
// One day in minutes for pgadmin rotation
118+
pgAdminRetentionPeriod = 24 * 60
119+
)
120+
121+
// If the user has set a retention period, we will use those values for log rotation,
122+
// which is otherwise managed by python.
123+
if pgadmin.Spec.Instrumentation.Logs != nil &&
124+
pgadmin.Spec.Instrumentation.Logs.RetentionPeriod != nil {
125+
126+
retentionNumber, period := collector.ParseDurationForLogrotate(pgadmin.Spec.Instrumentation.Logs.RetentionPeriod.AsDuration())
127+
// `LOG_ROTATION_MAX_LOG_FILES`` in pgadmin refers to the already rotated logs.
128+
// `backupCount` for gunicorn is similar.
129+
// Our retention unit is for total number of log files, so subtract 1 to account
130+
// for the currently-used log file.
131+
maxBackupRetentionNumber = retentionNumber - 1
132+
if period == "hourly" {
133+
// If the period is hourly, set the pgadmin
134+
// and gunicorn retention periods to hourly.
135+
pgAdminRetentionPeriod = 60
136+
}
137+
}
138+
139+
settings["LOG_ROTATION_AGE"] = pgAdminRetentionPeriod
140+
settings["LOG_ROTATION_MAX_LOG_FILES"] = maxBackupRetentionNumber
141+
settings["JSON_LOGGER"] = true
142+
settings["CONSOLE_LOG_LEVEL"] = "WARNING"
143+
settings["FILE_LOG_LEVEL"] = "INFO"
144+
settings["FILE_LOG_FORMAT_JSON"] = map[string]string{
145+
"time": "created",
146+
"name": "name",
147+
"level": "levelname",
148+
"message": "message",
149+
}
150+
}
105151

106152
// To avoid spurious reconciles, the following value must not change when
107153
// the spec does not change. [json.Encoder] and [json.Marshal] do this by
@@ -185,7 +231,9 @@ func generateClusterConfig(
185231

186232
// generateGunicornConfig generates the config settings for the gunicorn server
187233
// - https://docs.gunicorn.org/en/latest/settings.html
188-
func generateGunicornConfig(pgadmin *v1beta1.PGAdmin) (string, error) {
234+
func generateGunicornConfig(ctx context.Context, pgadmin *v1beta1.PGAdmin) (
235+
string, string, error,
236+
) {
189237
settings := map[string]any{
190238
// Bind to all IPv4 addresses and set 25 threads by default.
191239
// - https://docs.gunicorn.org/en/latest/settings.html#bind
@@ -213,5 +261,105 @@ func generateGunicornConfig(pgadmin *v1beta1.PGAdmin) (string, error) {
213261
encoder.SetIndent("", " ")
214262
err := encoder.Encode(settings)
215263

216-
return buffer.String(), err
264+
// Gunicorn logging dict settings
265+
logSettings := map[string]any{}
266+
267+
// If OTel logs feature gate is enabled, we want to change the pgAdmin/gunicorn logging
268+
if feature.Enabled(ctx, feature.OpenTelemetryLogs) &&
269+
pgadmin.Spec.Instrumentation != nil {
270+
271+
var (
272+
maxBackupRetentionNumber = "1"
273+
// Daily rotation for gunicorn rotation
274+
gunicornRetentionPeriod = "D"
275+
)
276+
277+
// If the user has set a retention period, we will use those values for log rotation,
278+
// which is otherwise managed by python.
279+
if pgadmin.Spec.Instrumentation.Logs != nil &&
280+
pgadmin.Spec.Instrumentation.Logs.RetentionPeriod != nil {
281+
282+
retentionNumber, period := collector.ParseDurationForLogrotate(pgadmin.Spec.Instrumentation.Logs.RetentionPeriod.AsDuration())
283+
// `LOG_ROTATION_MAX_LOG_FILES`` in pgadmin refers to the already rotated logs.
284+
// `backupCount` for gunicorn is similar.
285+
// Our retention unit is for total number of log files, so subtract 1 to account
286+
// for the currently-used log file.
287+
maxBackupRetentionNumber = strconv.Itoa(retentionNumber - 1)
288+
if period == "hourly" {
289+
// If the period is hourly, set the pgadmin
290+
// and gunicorn retention periods to hourly.
291+
gunicornRetentionPeriod = "H"
292+
}
293+
}
294+
295+
// Gunicorn uses the Python logging package, which sets the following attributes:
296+
// https://docs.python.org/3/library/logging.html#logrecord-attributes.
297+
// JsonFormatter is used to format the log: https://pypi.org/project/jsonformatter/
298+
// We override the gunicorn defaults (using `logconfig_dict`) to set our own file handler.
299+
// - https://docs.gunicorn.org/en/stable/settings.html#logconfig-dict
300+
// - https://github.com/benoitc/gunicorn/blob/23.0.0/gunicorn/glogging.py#L47
301+
logSettings = map[string]any{
302+
303+
"loggers": map[string]any{
304+
"gunicorn.access": map[string]any{
305+
"handlers": []string{"file"},
306+
"level": "INFO",
307+
"propagate": true,
308+
"qualname": "gunicorn.access",
309+
},
310+
"gunicorn.error": map[string]any{
311+
"handlers": []string{"file"},
312+
"level": "INFO",
313+
"propagate": true,
314+
"qualname": "gunicorn.error",
315+
},
316+
},
317+
"handlers": map[string]any{
318+
"file": map[string]any{
319+
"class": "logging.handlers.TimedRotatingFileHandler",
320+
"filename": GunicornLogFileAbsolutePath,
321+
"backupCount": maxBackupRetentionNumber,
322+
"interval": 1,
323+
"when": gunicornRetentionPeriod,
324+
"formatter": "json",
325+
},
326+
"console": map[string]any{
327+
"class": "logging.StreamHandler",
328+
"formatter": "generic",
329+
"stream": "ext://sys.stdout",
330+
},
331+
},
332+
"formatters": map[string]any{
333+
"generic": map[string]any{
334+
"class": "logging.Formatter",
335+
"datefmt": "[%Y-%m-%d %H:%M:%S %z]",
336+
"format": "%(asctime)s [%(process)d] [%(levelname)s] %(message)s",
337+
},
338+
"json": map[string]any{
339+
"class": "jsonformatter.JsonFormatter",
340+
"separators": []string{",", ":"},
341+
"format": map[string]string{
342+
"time": "created",
343+
"name": "name",
344+
"level": "levelname",
345+
"message": "message",
346+
},
347+
},
348+
},
349+
}
350+
}
351+
352+
// To avoid spurious reconciles, the following value must not change when
353+
// the spec does not change. [json.Encoder] and [json.Marshal] do this by
354+
// emitting map keys in sorted order. Indent so the value is not rendered
355+
// as one long line by `kubectl`.
356+
logBuffer := new(bytes.Buffer)
357+
logEncoder := json.NewEncoder(logBuffer)
358+
logEncoder.SetEscapeHTML(false)
359+
logEncoder.SetIndent("", " ")
360+
361+
// Combine errors
362+
err = logEncoder.Encode(logSettings)
363+
364+
return buffer.String(), logBuffer.String(), err
217365
}

0 commit comments

Comments
 (0)