@@ -8,13 +8,16 @@ import (
88 "context"
99 "fmt"
1010 "path"
11+ "strconv"
1112 "strings"
1213
1314 corev1 "k8s.io/api/core/v1"
1415 "k8s.io/apimachinery/pkg/api/resource"
1516 "k8s.io/apimachinery/pkg/util/intstr"
1617
18+ "github.com/crunchydata/postgres-operator/internal/collector"
1719 "github.com/crunchydata/postgres-operator/internal/config"
20+ "github.com/crunchydata/postgres-operator/internal/feature"
1821 "github.com/crunchydata/postgres-operator/internal/initialize"
1922 "github.com/crunchydata/postgres-operator/internal/kubernetes"
2023 "github.com/crunchydata/postgres-operator/internal/naming"
@@ -45,6 +48,7 @@ const (
4548
4649// pod populates a PodSpec with the container and volumes needed to run pgAdmin.
4750func pod (
51+ ctx context.Context ,
4852 inPGAdmin * v1beta1.PGAdmin ,
4953 inConfigMap * corev1.ConfigMap ,
5054 outPod * corev1.PodSpec ,
@@ -168,7 +172,7 @@ func pod(
168172
169173 startup := corev1.Container {
170174 Name : naming .ContainerPGAdminStartup ,
171- Command : startupCommand (),
175+ Command : startupCommand (ctx , inPGAdmin ),
172176 Image : container .Image ,
173177 ImagePullPolicy : container .ImagePullPolicy ,
174178 Resources : container .Resources ,
352356}
353357
354358// startupCommand returns an entrypoint that prepares the filesystem for pgAdmin.
355- func startupCommand () []string {
359+ func startupCommand (ctx context. Context , inPgadmin * v1beta1. PGAdmin ) []string {
356360 // pgAdmin reads from the `/etc/pgadmin/config_system.py` file during startup
357361 // after all other config files.
358362 // - https://github.com/pgadmin-org/pgadmin4/blob/REL-7_7/docs/en_US/config_py.rst
@@ -374,10 +378,33 @@ func startupCommand() []string {
374378
375379 // configDatabaseURIPath is the path for mounting the database URI connection string
376380 configDatabaseURIPathAbsolutePath = configMountPath + "/" + configDatabaseURIPath
381+ )
382+ var (
383+ maxBackupRetentionNumber = "1"
384+ retentionPeriod = "24 * 60"
385+ )
377386
378- // The constants set in configSystem will not be overridden through
379- // spec.config.settings.
380- configSystem = `
387+ // If the OpenTelemetryLogs Feature is enabled and the user has set a retention period,
388+ // we will use those values for pgAdmin log rotation, which is otherwise managed by python.
389+ if feature .Enabled (ctx , feature .OpenTelemetryLogs ) &&
390+ inPgadmin .Spec .Instrumentation != nil &&
391+ inPgadmin .Spec .Instrumentation .Logs != nil &&
392+ inPgadmin .Spec .Instrumentation .Logs .RetentionPeriod != nil {
393+
394+ retentionNumber , period := collector .ParseDurationForLogrotate (inPgadmin .Spec .Instrumentation .Logs .RetentionPeriod )
395+ // `LOG_ROTATION_MAX_LOG_FILES`` in pgadmin refers to the already rotated logs.
396+ // `backupCount` for gunicorn is similar.
397+ // Our retention unit is for total number of log files, so subtract 1 to account
398+ // for the currently-used log file.
399+ maxBackupRetentionNumber = strconv .Itoa (retentionNumber - 1 )
400+ if period == "hourly" {
401+ retentionPeriod = "60"
402+ }
403+ }
404+
405+ // The constants set in configSystem will not be overridden through
406+ // spec.config.settings.
407+ var configSystem = `
381408import glob, json, re, os, logging
382409DEFAULT_BINARY_PATHS = {'pg': sorted([''] + glob.glob('/usr/pgsql-*/bin')).pop()}
383410with open('` + configMountPath + `/` + configFilePath + `') as _f:
@@ -393,30 +420,30 @@ if os.path.isfile('` + configDatabaseURIPathAbsolutePath + `'):
393420
394421DATA_DIR = '` + dataMountPath + `'
395422LOG_FILE = '` + LogFileAbsolutePath + `'
396- LOG_ROTATION_AGE = 24 * 60 # minutes
423+ LOG_ROTATION_AGE = ` + retentionPeriod + ` # minutes
397424LOG_ROTATION_SIZE = 5 # MiB
398- LOG_ROTATION_MAX_LOG_FILES = 1
425+ LOG_ROTATION_MAX_LOG_FILES = ` + maxBackupRetentionNumber + `
399426
400427JSON_LOGGER = True
401428CONSOLE_LOG_LEVEL = logging.WARNING
402429FILE_LOG_LEVEL = logging.INFO
403430FILE_LOG_FORMAT_JSON = {'time': 'created', 'name': 'name', 'level': 'levelname', 'message': 'message'}
404431`
405- // Gunicorn reads from the `/etc/pgadmin/gunicorn_config.py` file during startup
406- // after all other config files.
407- // - https://docs.gunicorn.org/en/latest/configure.html#configuration-file
408- //
409- // This command writes a script in `/etc/pgadmin/gunicorn_config.py` that reads
410- // from the `gunicorn-config.json` file and sets those variables globally.
411- // That way those values are available as settings when Gunicorn starts.
412- //
413- // Note: All Gunicorn settings are lowercase with underscores, so ignore
414- // any keys/names that are not.
415- //
416- // Gunicorn uses the Python logging package, which sets the following attributes:
417- // https://docs.python.org/3/library/logging.html#logrecord-attributes.
418- // JsonFormatter is used to format the log: https://pypi.org/project/jsonformatter/
419- gunicornConfig = `
432+ // Gunicorn reads from the `/etc/pgadmin/gunicorn_config.py` file during startup
433+ // after all other config files.
434+ // - https://docs.gunicorn.org/en/latest/configure.html#configuration-file
435+ //
436+ // This command writes a script in `/etc/pgadmin/gunicorn_config.py` that reads
437+ // from the `gunicorn-config.json` file and sets those variables globally.
438+ // That way those values are available as settings when Gunicorn starts.
439+ //
440+ // Note: All Gunicorn settings are lowercase with underscores, so ignore
441+ // any keys/names that are not.
442+ //
443+ // Gunicorn uses the Python logging package, which sets the following attributes:
444+ // https://docs.python.org/3/library/logging.html#logrecord-attributes.
445+ // JsonFormatter is used to format the log: https://pypi.org/project/jsonformatter/
446+ var gunicornConfig = `
420447import json, re, collections, copy, gunicorn, gunicorn.glogging
421448with open('` + configMountPath + `/` + gunicornConfigFilePath + `') as _f:
422449 _conf, _data = re.compile(r'[a-z_]+'), json.load(_f)
@@ -430,7 +457,7 @@ logconfig_dict['loggers']['gunicorn.error']['handlers'] = ['file']
430457logconfig_dict['handlers']['file'] = {
431458 'class': 'logging.handlers.RotatingFileHandler',
432459 'filename': '` + GunicornLogFileAbsolutePath + `',
433- 'backupCount': 1 , 'maxBytes': 2 << 20, # MiB
460+ 'backupCount': ` + maxBackupRetentionNumber + ` , 'maxBytes': 2 << 20, # MiB
434461 'formatter': 'json',
435462}
436463logconfig_dict['formatters']['json'] = {
@@ -444,7 +471,6 @@ logconfig_dict['formatters']['json'] = {
444471 ])
445472}
446473`
447- )
448474
449475 args := []string {strings .TrimLeft (configSystem , "\n " ), strings .TrimLeft (gunicornConfig , "\n " )}
450476
0 commit comments