@@ -7,6 +7,7 @@ package standalone_pgadmin
77import (
88 "context"
99 "fmt"
10+ "path"
1011 "strings"
1112
1213 corev1 "k8s.io/api/core/v1"
@@ -17,6 +18,7 @@ import (
1718 "github.com/crunchydata/postgres-operator/internal/initialize"
1819 "github.com/crunchydata/postgres-operator/internal/kubernetes"
1920 "github.com/crunchydata/postgres-operator/internal/naming"
21+ "github.com/crunchydata/postgres-operator/internal/shell"
2022 "github.com/crunchydata/postgres-operator/pkg/apis/postgres-operator.crunchydata.com/v1beta1"
2123)
2224
@@ -28,8 +30,17 @@ const (
2830 ldapFilePath = "~postgres-operator/ldap-bind-password"
2931 gunicornConfigFilePath = "~postgres-operator/" + gunicornConfigKey
3032
31- // Nothing should be mounted to this location except the script our initContainer writes
33+ // scriptMountPath is where to mount a temporary directory that is only
34+ // writable during Pod initialization.
35+ //
36+ // NOTE: No ConfigMap nor Secret should ever be mounted here because they
37+ // could be used to inject code through "config_system.py".
3238 scriptMountPath = "/etc/pgadmin"
39+
40+ dataMountPath = "/var/lib/pgadmin"
41+ LogDirectoryAbsolutePath = dataMountPath + "/logs"
42+ GunicornLogFileAbsolutePath = LogDirectoryAbsolutePath + "/gunicorn.log"
43+ LogFileAbsolutePath = LogDirectoryAbsolutePath + "/pgadmin.log"
3344)
3445
3546// pod populates a PodSpec with the container and volumes needed to run pgAdmin.
@@ -39,54 +50,28 @@ func pod(
3950 outPod * corev1.PodSpec ,
4051 pgAdminVolume * corev1.PersistentVolumeClaim ,
4152) {
42- const (
43- // config and data volume names
44- configVolumeName = "pgadmin-config"
45- dataVolumeName = "pgadmin-data"
46- pgAdminLogVolumeName = "pgadmin-log"
47- gunicornLogVolumeName = "gunicorn-log"
48- scriptVolumeName = "pgadmin-config-system"
49- tempVolumeName = "tmp"
50- )
51-
5253 // create the projected volume of config maps for use in
5354 // 1. dynamic server discovery
5455 // 2. adding the config variables during pgAdmin startup
55- configVolume := corev1.Volume {Name : configVolumeName }
56+ configVolume := corev1.Volume {Name : "pgadmin-config" }
5657 configVolume .VolumeSource = corev1.VolumeSource {
5758 Projected : & corev1.ProjectedVolumeSource {
5859 Sources : podConfigFiles (inConfigMap , * inPGAdmin ),
5960 },
6061 }
6162
6263 // create the data volume for the persistent database
63- dataVolume := corev1.Volume {Name : dataVolumeName }
64+ dataVolume := corev1.Volume {Name : "pgadmin-data" }
6465 dataVolume .VolumeSource = corev1.VolumeSource {
6566 PersistentVolumeClaim : & corev1.PersistentVolumeClaimVolumeSource {
6667 ClaimName : pgAdminVolume .Name ,
6768 ReadOnly : false ,
6869 },
6970 }
7071
71- // create the temp volume for logs
72- pgAdminLogVolume := corev1.Volume {Name : pgAdminLogVolumeName }
73- pgAdminLogVolume .VolumeSource = corev1.VolumeSource {
74- EmptyDir : & corev1.EmptyDirVolumeSource {
75- Medium : corev1 .StorageMediumMemory ,
76- },
77- }
78-
79- // create the temp volume for gunicorn logs
80- gunicornLogVolume := corev1.Volume {Name : gunicornLogVolumeName }
81- gunicornLogVolume .VolumeSource = corev1.VolumeSource {
82- EmptyDir : & corev1.EmptyDirVolumeSource {
83- Medium : corev1 .StorageMediumMemory ,
84- },
85- }
86-
8772 // Volume used to write a custom config_system.py file in the initContainer
8873 // which then loads the configs found in the `configVolume`
89- scriptVolume := corev1.Volume {Name : scriptVolumeName }
74+ scriptVolume := corev1.Volume {Name : "pgadmin-config-system" }
9075 scriptVolume .VolumeSource = corev1.VolumeSource {
9176 EmptyDir : & corev1.EmptyDirVolumeSource {
9277 Medium : corev1 .StorageMediumMemory ,
@@ -101,7 +86,7 @@ func pod(
10186
10287 // create a temp volume for restart pid/other/debugging use
10388 // TODO: discuss tmp vol vs. persistent vol
104- tmpVolume := corev1.Volume {Name : tempVolumeName }
89+ tmpVolume := corev1.Volume {Name : "tmp" }
10590 tmpVolume .VolumeSource = corev1.VolumeSource {
10691 EmptyDir : & corev1.EmptyDirVolumeSource {
10792 Medium : corev1 .StorageMediumMemory ,
@@ -142,29 +127,21 @@ func pod(
142127 },
143128 VolumeMounts : []corev1.VolumeMount {
144129 {
145- Name : configVolumeName ,
130+ Name : configVolume . Name ,
146131 MountPath : configMountPath ,
147132 ReadOnly : true ,
148133 },
149134 {
150- Name : dataVolumeName ,
151- MountPath : "/var/lib/pgadmin" ,
152- },
153- {
154- Name : gunicornLogVolumeName ,
155- MountPath : "/var/log/gunicorn" ,
156- },
157- {
158- Name : pgAdminLogVolumeName ,
159- MountPath : "/var/log/pgadmin" ,
135+ Name : dataVolume .Name ,
136+ MountPath : dataMountPath ,
160137 },
161138 {
162- Name : scriptVolumeName ,
139+ Name : scriptVolume . Name ,
163140 MountPath : scriptMountPath ,
164141 ReadOnly : true ,
165142 },
166143 {
167- Name : tempVolumeName ,
144+ Name : tmpVolume . Name ,
168145 MountPath : "/tmp" ,
169146 },
170147 },
@@ -199,19 +176,21 @@ func pod(
199176 VolumeMounts : []corev1.VolumeMount {
200177 // Volume to write a custom `config_system.py` file to.
201178 {
202- Name : scriptVolumeName ,
179+ Name : scriptVolume . Name ,
203180 MountPath : scriptMountPath ,
204181 ReadOnly : false ,
205182 },
183+ {
184+ Name : dataVolume .Name ,
185+ MountPath : dataMountPath ,
186+ },
206187 },
207188 }
208189
209190 // add volumes and containers
210191 outPod .Volumes = []corev1.Volume {
211192 configVolume ,
212193 dataVolume ,
213- pgAdminLogVolume ,
214- gunicornLogVolume ,
215194 scriptVolume ,
216195 tmpVolume ,
217196 }
@@ -426,8 +405,8 @@ if os.path.isfile('` + configDatabaseURIPathAbsolutePath + `'):
426405 with open('` + configDatabaseURIPathAbsolutePath + `') as _f:
427406 CONFIG_DATABASE_URI = _f.read()
428407
429- DATA_DIR = '/var/lib/pgadmin '
430- LOG_FILE = '/var/lib/pgadmin/logs/pgadmin.log '
408+ DATA_DIR = '` + dataMountPath + ` '
409+ LOG_FILE = '` + LogFileAbsolutePath + ` '
431410LOG_ROTATION_AGE = 24 * 60 # minutes
432411LOG_ROTATION_SIZE = 5 # MiB
433412LOG_ROTATION_MAX_LOG_FILES = 1
@@ -437,18 +416,18 @@ CONSOLE_LOG_LEVEL = logging.WARNING
437416FILE_LOG_LEVEL = logging.INFO
438417FILE_LOG_FORMAT_JSON = {'time': 'created', 'name': 'name', 'level': 'levelname', 'message': 'message'}
439418`
440- // gunicorn reads from the `/etc/pgadmin/gunicorn_config.py` file during startup
419+ // Gunicorn reads from the `/etc/pgadmin/gunicorn_config.py` file during startup
441420 // after all other config files.
442421 // - https://docs.gunicorn.org/en/latest/configure.html#configuration-file
443422 //
444423 // This command writes a script in `/etc/pgadmin/gunicorn_config.py` that reads
445424 // from the `gunicorn-config.json` file and sets those variables globally.
446- // That way those values are available as settings when gunicorn starts.
425+ // That way those values are available as settings when Gunicorn starts.
447426 //
448- // Note: All gunicorn settings are lowercase with underscores, so ignore
427+ // Note: All Gunicorn settings are lowercase with underscores, so ignore
449428 // any keys/names that are not.
450429 //
451- // gunicorn uses the Python logging package, which sets the following attributes:
430+ // Gunicorn uses the Python logging package, which sets the following attributes:
452431 // https://docs.python.org/3/library/logging.html#logrecord-attributes.
453432 // JsonFormatter is used to format the log: https://pypi.org/project/jsonformatter/
454433 gunicornConfig = `
@@ -457,13 +436,14 @@ with open('` + configMountPath + `/` + gunicornConfigFilePath + `') as _f:
457436 _conf, _data = re.compile(r'[a-z_]+'), json.load(_f)
458437 if type(_data) is dict:
459438 globals().update({k: v for k, v in _data.items() if _conf.fullmatch(k)})
439+
460440gunicorn.SERVER_SOFTWARE = 'Python'
461441logconfig_dict = copy.deepcopy(gunicorn.glogging.CONFIG_DEFAULTS)
462442logconfig_dict['loggers']['gunicorn.access']['handlers'] = ['file']
463443logconfig_dict['loggers']['gunicorn.error']['handlers'] = ['file']
464444logconfig_dict['handlers']['file'] = {
465445 'class': 'logging.handlers.RotatingFileHandler',
466- 'filename': '/var/lib/pgadmin/logs/gunicorn.log ',
446+ 'filename': '` + GunicornLogFileAbsolutePath + ` ',
467447 'backupCount': 1, 'maxBytes': 2 << 20, # MiB
468448 'formatter': 'json',
469449}
@@ -483,9 +463,15 @@ logconfig_dict['formatters']['json'] = {
483463 args := []string {strings .TrimLeft (configSystem , "\n " ), strings .TrimLeft (gunicornConfig , "\n " )}
484464
485465 script := strings .Join ([]string {
486- // Use the initContainer to create this path to avoid the error noted here:
466+ // Create the config directory so Kubernetes can mount it later.
487467 // - https://issue.k8s.io/121294
488- `mkdir -p ` + configMountPath ,
468+ shell .MakeDirectories (0o775 , scriptMountPath , configMountPath ),
469+
470+ // Create the logs directory with g+rwx so the OTel Collector can
471+ // write to it as well.
472+ // TODO(log-rotation): Move the last segment into the Collector startup.
473+ shell .MakeDirectories (0o775 , dataMountPath , path .Join (LogDirectoryAbsolutePath , "receiver" )),
474+
489475 // Write the system and server configurations.
490476 `echo "$1" > ` + scriptMountPath + `/config_system.py` ,
491477 `echo "$2" > ` + scriptMountPath + `/gunicorn_config.py` ,
0 commit comments