@@ -21,6 +21,38 @@ import (
2121)
2222
2323const (
24+ // bashDataDirectory is a Bash function that ensures a directory has the correct permissions for PostgreSQL data.
25+ //
26+ // Postgres requires its data directories be writable by only itself.
27+ // Pod "securityContext.fsGroup" sets g+w on directories for *some* storage providers.
28+ // Ensure the current user owns the directory, and remove group-write permission.
29+ //
30+ // - https://www.postgresql.org/docs/current/creating-cluster.html
31+ // - https://git.postgresql.org/gitweb/?p=postgresql.git;f=src/backend/postmaster/postmaster.c;hb=REL_10_0#l1522
32+ // - https://git.postgresql.org/gitweb/?p=postgresql.git;f=src/backend/utils/init/miscinit.c;hb=REL_11_0#l142
33+ // - https://git.postgresql.org/gitweb/?p=postgresql.git;f=src/backend/utils/init/miscinit.c;hb=REL_17_0#l386
34+ // - https://issue.k8s.io/93802#issuecomment-717646167
35+ //
36+ // During CREATE TABLESPACE, Postgres sets the permissions of a tablespace directory to match the data directory.
37+ //
38+ // - https://www.postgresql.org/docs/current/manage-ag-tablespaces.html
39+ // - https://git.postgresql.org/gitweb/?p=postgresql.git;f=src/backend/commands/tablespace.c;hb=REL_14_0#l600
40+ // - https://git.postgresql.org/gitweb/?p=postgresql.git;f=src/common/file_perm.c;hb=REL_14_0#l27
41+ //
42+ bashDataDirectory = `dataDirectory() {` +
43+ // When the directory does not exist, create it with the correct permissions.
44+ // When the directory has the correct owner, set the correct permissions.
45+ ` if [[ ! -e "$1" || -O "$1" ]]; then install --directory --mode=0750 "$1";` +
46+ //
47+ // The directory exists but its owner is wrong.
48+ // When it is writable, the set-group-ID bit indicates that "fsGroup" probably ran on its contents making them safe to use.
49+ // In this case, we can make a new directory (owned by this user) and refill it.
50+ ` elif [[ -w "$1" && -g "$1" ]]; then recreate "$1" '0750';` +
51+ //
52+ // The directory exists, its owner is wrong, and it is not writable.
53+ // This is probably fatal, but indicate failure to let the caller decide.
54+ ` else false; fi; }`
55+
2456 // bashHalt is a Bash function that prints its arguments to stderr then
2557 // exits with a non-zero status. It uses the exit status of the prior
2658 // command if that was not zero.
@@ -327,47 +359,31 @@ func startupCommand(
327359 version := fmt .Sprint (cluster .Spec .PostgresVersion )
328360 walDir := WALDirectory (cluster , instance )
329361
330- // If the user requests tablespaces, we want to make sure the directories exist with the
331- // correct owner and permissions.
332- tablespaceCmd := ""
333- if feature .Enabled (ctx , feature .TablespaceVolumes ) {
334- // This command checks if a dir exists and if not, creates it;
335- // if the dir does exist, then we `recreate` it to make sure the owner is correct;
336- // if the dir exists with the wrong owner and is not writeable, we error.
337- // This is the same behavior we use for the main PGDATA directory.
338- // Note: Postgres requires the tablespace directory to be "an existing, empty directory
339- // that is owned by the PostgreSQL operating system user."
340- // - https://www.postgresql.org/docs/current/manage-ag-tablespaces.html
341- // However, unlike the PGDATA directory, Postgres will _not_ error out
342- // if the permissions are wrong on the tablespace directory.
343- // Instead, when a tablespace is created in Postgres, Postgres will `chmod` the
344- // tablespace directory to match permissions on the PGDATA directory (either 700 or 750).
345- // Postgres setting the tablespace directory permissions:
346- // - https://git.postgresql.org/gitweb/?p=postgresql.git;f=src/backend/commands/tablespace.c;hb=REL_14_0#l600
347- // Postgres choosing directory permissions:
348- // - https://git.postgresql.org/gitweb/?p=postgresql.git;f=src/common/file_perm.c;hb=REL_14_0#l27
349- // Note: This permission change seems to happen only when the tablespace is created in Postgres.
350- // If the user manually `chmod`'ed the directory after the creation of the tablespace, Postgres
351- // would not attempt to change the directory permissions.
352- // Note: as noted below, we mount the tablespace directory to the mountpoint `/tablespaces/NAME`,
353- // and so we add the subdirectory `data` in order to set the permissions.
354- checkInstallRecreateCmd := strings .Join ([]string {
355- `if [[ ! -e "${tablespace_dir}" || -O "${tablespace_dir}" ]]; then` ,
356- `install --directory --mode=0750 "${tablespace_dir}"` ,
357- `elif [[ -w "${tablespace_dir}" && -g "${tablespace_dir}" ]]; then` ,
358- `recreate "${tablespace_dir}" '0750'` ,
359- `else (halt Permissions!); fi ||` ,
360- `halt "$(permissions "${tablespace_dir}" ||:)"` ,
361- }, "\n " )
362+ mkdirs := make ([]string , 0 , 7 + len (instance .TablespaceVolumes ))
363+ mkdirs = append (mkdirs , `dataDirectory "${postgres_data_directory}" || halt "$(permissions "${postgres_data_directory}" ||:)"` )
362364
365+ // If the user requests tablespaces, we want to make sure the directories exist with the correct owner and permissions.
366+ //
367+ // The path for tablespaces volumes is /tablespaces/NAME/data -- the `data` directory is so we can arrange the permissions.
368+ if feature .Enabled (ctx , feature .TablespaceVolumes ) {
363369 for _ , tablespace := range instance .TablespaceVolumes {
364- // The path for tablespaces volumes is /tablespaces/NAME/data
365- // -- the `data` path is added so that we can arrange the permissions.
366- tablespaceCmd = tablespaceCmd + "\n tablespace_dir=/tablespaces/" + tablespace .Name + "/data" + "\n " +
367- checkInstallRecreateCmd
370+ dir := shell .QuoteWord ("/tablespaces/" + tablespace .Name + "/data" )
371+ mkdirs = append (mkdirs , `dataDirectory ` + dir + ` || halt "$(permissions ` + dir + ` ||:)"` )
368372 }
369373 }
370374
375+ // These directories are outside "data_directory" and can be created.
376+ mkdirs = append (mkdirs ,
377+ `(` + shell .MakeDirectories (dataMountPath , LogDirectory ())+ `) ||` ,
378+ `halt "$(permissions ` + shell .QuoteWord (LogDirectory ())+ ` ||:)"` ,
379+
380+ `(` + shell .MakeDirectories (dataMountPath , naming .PatroniPGDataLogPath )+ `) ||` ,
381+ `halt "$(permissions ` + shell .QuoteWord (naming .PatroniPGDataLogPath )+ ` ||:)"` ,
382+
383+ `(` + shell .MakeDirectories (dataMountPath , naming .PGBackRestPGDataLogPath )+ `) ||` ,
384+ `halt "$(permissions ` + shell .QuoteWord (naming .PGBackRestPGDataLogPath )+ ` ||:)"` ,
385+ )
386+
371387 pg_rewind_override := ""
372388 if config .FetchKeyCommand (& cluster .Spec ) != "" {
373389 // Quoting "EOF" disables parameter substitution during write.
@@ -384,6 +400,9 @@ chmod +x /tmp/pg_rewind_tde.sh
384400 script := strings .Join ([]string {
385401 `declare -r expected_major_version="$1" pgwal_directory="$2"` ,
386402
403+ // Function to create a Postgres data directory.
404+ bashDataDirectory ,
405+
387406 // Function to print the permissions of a file or directory and its parents.
388407 bashPermissions ,
389408
@@ -425,42 +444,10 @@ chmod +x /tmp/pg_rewind_tde.sh
425444
426445 // Determine if the data directory has been prepared for bootstrapping the cluster
427446 `bootstrap_dir="${postgres_data_directory}_bootstrap"` ,
428- `[[ -d "${bootstrap_dir}" ]] && results 'bootstrap directory' "${bootstrap_dir}"` ,
429- `[[ -d "${bootstrap_dir}" ]] && postgres_data_directory="${bootstrap_dir}"` ,
430-
431- // PostgreSQL requires its directory to be writable by only itself.
432- // Pod "securityContext.fsGroup" sets g+w on directories for *some*
433- // storage providers. Ensure the current user owns the directory, and
434- // remove group-write permission.
435- // - https://www.postgresql.org/docs/current/creating-cluster.html
436- // - https://git.postgresql.org/gitweb/?p=postgresql.git;f=src/backend/postmaster/postmaster.c;hb=REL_10_0#l1522
437- // - https://git.postgresql.org/gitweb/?p=postgresql.git;f=src/backend/utils/init/miscinit.c;hb=REL_11_0#l142
438- // - https://git.postgresql.org/gitweb/?p=postgresql.git;f=src/backend/utils/init/miscinit.c;hb=REL_17_0#l386
439- // - https://issue.k8s.io/93802#issuecomment-717646167
440- //
441- // When the directory does not exist, create it with the correct permissions.
442- // When the directory has the correct owner, set the correct permissions.
443- `if [[ ! -e "${postgres_data_directory}" || -O "${postgres_data_directory}" ]]; then` ,
444- `install --directory --mode=0750 "${postgres_data_directory}"` ,
445- //
446- // The directory exists but its owner is wrong. When it is writable,
447- // the set-group-ID bit indicates that "fsGroup" probably ran on its
448- // contents making them safe to use. In this case, we can make a new
449- // directory (owned by this user) and refill it.
450- `elif [[ -w "${postgres_data_directory}" && -g "${postgres_data_directory}" ]]; then` ,
451- `recreate "${postgres_data_directory}" '0750'` ,
452- //
453- // The directory exists, its owner is wrong, and it is not writable.
454- `else (halt Permissions!); fi ||` ,
455- `halt "$(permissions "${postgres_data_directory}" ||:)"` ,
447+ `[[ -d "${bootstrap_dir}" ]] && postgres_data_directory="${bootstrap_dir}" && results 'bootstrap directory' "${bootstrap_dir}"` ,
456448
457- // Create log directories.
458- `(` + shell .MakeDirectories (dataMountPath , naming .PGBackRestPGDataLogPath ) + `) ||` ,
459- `halt "$(permissions ` + naming .PGBackRestPGDataLogPath + ` ||:)"` ,
460- `(` + shell .MakeDirectories (dataMountPath , naming .PatroniPGDataLogPath ) + `) ||` ,
461- `halt "$(permissions ` + naming .PatroniPGDataLogPath + ` ||:)"` ,
462- `(` + shell .MakeDirectories (dataMountPath , LogDirectory ()) + `) ||` ,
463- `halt "$(permissions ` + LogDirectory () + ` ||:)"` ,
449+ // Create directories for and related to the data directory.
450+ strings .Join (mkdirs , "\n " ),
464451
465452 // Copy replication client certificate files
466453 // from the /pgconf/tls/replication directory to the /tmp/replication directory in order
@@ -478,7 +465,6 @@ chmod +x /tmp/pg_rewind_tde.sh
478465 // Add the pg_rewind wrapper script, if TDE is enabled.
479466 pg_rewind_override ,
480467
481- tablespaceCmd ,
482468 // When the data directory is empty, there's nothing more to do.
483469 `[[ -f "${postgres_data_directory}/PG_VERSION" ]] || exit 0` ,
484470
0 commit comments