Using psql command instead of postgres in initialScript (#66)

* Use psql instead of postgres, as a result, use `pg_ctl` to temporarily start the postgres server in `${name}-init` process.
* Avoid creating a custom `postgresPkg` with extensions by using `apply` attribute provided by `mkOption`
* Refactor setup script, use `writeShellApplication` and resolve shell-check errors

---------

Co-authored-by: shivaraj-bh <sbh69840@gmail.com>
This commit is contained in:
Rohit Singh 2023-12-25 00:35:03 +05:30 committed by GitHub
parent 8cd80d7a16
commit 22e121b246
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 150 additions and 119 deletions

View File

@ -8,7 +8,7 @@ in
./elasticsearch.nix
./mysql.nix
./nginx.nix
./postgres.nix
./postgres
./redis-cluster.nix
./redis.nix
./zookeeper.nix

View File

@ -7,7 +7,24 @@ in
options = {
enable = lib.mkEnableOption name;
package = lib.mkPackageOption pkgs "postgresql" { };
package = lib.mkOption {
type = types.package;
description = "Which package of postgresql to use";
default = pkgs.postgresql;
defaultText = lib.literalExpression "pkgs.postgresql";
apply = postgresPkg:
if config.extensions != null then
if builtins.hasAttr "withPackages" postgresPkg
then postgresPkg.withPackages config.extensions
else
builtins.throw ''
Cannot add extensions to the PostgreSQL package.
`services.postgres.package` is missing the `withPackages` attribute. Did you already add extensions to the package?
''
else postgresPkg;
};
extensions = lib.mkOption {
type = with types; nullOr (functionTo (listOf package));
default = null;
@ -254,125 +271,15 @@ in
internal = true;
readOnly = true;
default =
let
postgresPkg =
if config.extensions != null then
if builtins.hasAttr "withPackages" config.package
then config.package.withPackages config.extensions
else
builtins.throw ''
Cannot add extensions to the PostgreSQL package.
`services.postgres.package` is missing the `withPackages` attribute. Did you already add extensions to the package?
''
else config.package;
in
{
processes = {
# DB initialization
"${name}-init" =
let
setupInitialDatabases =
if config.initialDatabases != [ ] then
(lib.concatMapStrings
(database: ''
echo "Checking presence of database: ${database.name}"
# Create initial databases
dbAlreadyExists="$(
echo "SELECT 1 as exists FROM pg_database WHERE datname = '${database.name}';" | \
postgres --single -E postgres | \
${pkgs.gnugrep}/bin/grep -c 'exists = "1"' || true
)"
echo $dbAlreadyExists
if [ 1 -ne "$dbAlreadyExists" ]; then
echo "Creating database: ${database.name}"
echo 'create database "${database.name}";' | postgres --single -E postgres
${lib.optionalString (database.schema != null) ''
echo "Applying database schema on ${database.name}"
if [ -f "${database.schema}" ]
then
echo "Running file ${database.schema}"
${pkgs.gawk}/bin/awk 'NF' "${database.schema}" | postgres --single -j -E ${database.name}
elif [ -d "${database.schema}" ]
then
# Read sql files in version order. Apply one file
# at a time to handle files where the last statement
# doesn't end in a ;.
ls -1v "${database.schema}"/*.sql | while read f ; do
echo "Applying sql file: $f"
${pkgs.gawk}/bin/awk 'NF' "$f" | postgres --single -j -E ${database.name}
done
else
echo "ERROR: Could not determine how to apply schema with ${database.schema}"
exit 1
fi
''}
fi
'')
config.initialDatabases)
else
lib.optionalString config.createDatabase ''
echo "CREATE DATABASE ''${USER:-$(id -nu)};" | postgres --single -E postgres '';
runInitialScript =
let
scriptCmd = sqlScript: ''
echo "${sqlScript}" | postgres --single -E postgres
'';
in
{
before = with config.initialScript;
lib.optionalString (before != null) (scriptCmd before);
after = with config.initialScript;
lib.optionalString (after != null) (scriptCmd after);
};
toStr = value:
if true == value then
"yes"
else if false == value then
"no"
else if lib.isString value then
"'${lib.replaceStrings [ "'" ] [ "''" ] value}'"
else
toString value;
configFile = pkgs.writeText "postgresql.conf" (lib.concatStringsSep "\n"
(lib.mapAttrsToList (n: v: "${n} = ${toStr v}") (config.defaultSettings // config.settings)));
initdbArgs =
config.initdbArgs
++ (lib.optionals (config.superuser != null) [ "-U" config.superuser ])
++ [ "-D" config.dataDir ];
setupScript = pkgs.writeShellScriptBin "setup-postgres" ''
set -euo pipefail
export PATH=${postgresPkg}/bin:${pkgs.coreutils}/bin
if [[ ! -d "$PGDATA" ]]; then
set -x
mkdir -p ${config.dataDir}
initdb ${lib.concatStringsSep " " initdbArgs}
set +x
${runInitialScript.before}
${setupInitialDatabases}
${runInitialScript.after}
else
echo "Postgres data directory already exists. Skipping initialization."
fi
# Setup config
set -x
cp ${configFile} "$PGDATA/postgresql.conf"
'';
setupScript = import ./setup-script.nix { inherit config pkgs lib; };
in
{
command = ''
export PGDATA="${config.dataDir}"
${lib.getExe setupScript}
'';
command = setupScript;
namespace = name;
};
@ -381,9 +288,9 @@ in
let
startScript = pkgs.writeShellApplication {
name = "start-postgres";
runtimeInputs = [ config.package pkgs.coreutils ];
text = ''
export PATH="${postgresPkg}"/bin:$PATH
set -x
set -euo pipefail
PGDATA=$(readlink -f "${config.dataDir}")
export PGDATA
postgres -k "$PGDATA"
@ -397,11 +304,10 @@ in
in
{
command = startScript;
depends_on."${name}-init".condition = "process_completed_successfully";
# SIGINT (= 2) for faster shutdown: https://www.postgresql.org/docs/current/server-shutdown.html
shutdown.signal = 2;
readiness_probe = {
exec.command = "${postgresPkg}/bin/pg_isready ${lib.concatStringsSep " " pg_isreadyArgs}";
exec.command = "${config.package}/bin/pg_isready ${lib.concatStringsSep " " pg_isreadyArgs}";
initial_delay_seconds = 2;
period_seconds = 10;
timeout_seconds = 4;
@ -409,6 +315,7 @@ in
failure_threshold = 5;
};
namespace = name;
depends_on."${name}-init".condition = "process_completed_successfully";
# https://github.com/F1bonacc1/process-compose#-auto-restart-if-not-healthy
availability.restart = "on_failure";
};

View File

@ -0,0 +1,124 @@
{ config, pkgs, lib }:
let
setupInitialDatabases =
if config.initialDatabases != [ ] then
(lib.concatMapStrings
(database: ''
echo "Checking presence of database: ${database.name}"
# Create initial databases
dbAlreadyExists=$(
echo "SELECT 1 as exists FROM pg_database WHERE datname = '${database.name}';" | \
psql -d postgres | \
grep -c 'exists = "1"' || true
)
echo "$dbAlreadyExists"
if [ 1 -ne "$dbAlreadyExists" ]; then
echo "Creating database: ${database.name}"
echo 'create database "${database.name}";' | psql -d postgres
${lib.optionalString (database.schema != null) ''
echo "Applying database schema on ${database.name}"
if [ -f "${database.schema}" ]
then
echo "Running file ${database.schema}"
awk 'NF' "${database.schema}" | psql -d ${database.name}
elif [ -d "${database.schema}" ]
then
# Read sql files in version order. Apply one file
# at a time to handle files where the last statement
# doesn't end in a ;.
find "${database.schema}"/*.sql | while read -r f ; do
echo "Applying sql file: $f"
awk 'NF' "$f" | psql -d ${database.name}
done
else
echo "ERROR: Could not determine how to apply schema with ${database.schema}"
exit 1
fi
''}
fi
'')
config.initialDatabases)
else
lib.optionalString config.createDatabase ''
echo "CREATE DATABASE ''${USER:-$(id -nu)};" | psql -d postgres '';
runInitialScript =
let
scriptCmd = sqlScript: ''
echo "${sqlScript}" | psql -d postgres
'';
in
{
before = with config.initialScript;
lib.optionalString (before != null) (scriptCmd before);
after = with config.initialScript;
lib.optionalString (after != null) (scriptCmd after);
};
toStr = value:
if true == value then
"yes"
else if false == value then
"no"
else if lib.isString value then
"'${lib.replaceStrings [ "'" ] [ "''" ] value}'"
else
toString value;
configFile = pkgs.writeText "postgresql.conf" (lib.concatStringsSep "\n"
(lib.mapAttrsToList (n: v: "${n} = ${toStr v}") (config.defaultSettings // config.settings)));
initdbArgs =
config.initdbArgs
++ (lib.optionals (config.superuser != null) [ "-U" config.superuser ])
++ [ "-D" config.dataDir ];
in
(pkgs.writeShellApplication {
name = "setup-postgres";
runtimeInputs = with pkgs; [ config.package coreutils gnugrep gawk ];
text = ''
set -euo pipefail
# Setup postgres ENVs
export PGDATA="${config.dataDir}"
export PGPORT="${toString config.port}"
POSTGRES_RUN_INITIAL_SCRIPT="false"
if [[ ! -d "$PGDATA" ]]; then
initdb ${lib.concatStringsSep " " initdbArgs}
POSTGRES_RUN_INITIAL_SCRIPT="true"
echo
echo "PostgreSQL initdb process complete."
echo
fi
# Setup config
cp ${configFile} "$PGDATA/postgresql.conf"
if [[ "$POSTGRES_RUN_INITIAL_SCRIPT" = "true" ]]; then
echo
echo "PostgreSQL is setting up the initial database."
echo
PGHOST=$(mktemp -d "$(readlink -f ${config.dataDir})/pg-init-XXXXXX")
export PGHOST
function remove_tmp_pg_init_sock_dir() {
if [[ -d "$1" ]]; then
rm -rf "$1"
fi
}
trap 'remove_tmp_pg_init_sock_dir "$PGHOST"' EXIT
pg_ctl -D "$PGDATA" -w start -o "-c unix_socket_directories=$PGHOST -c listen_addresses= -p ${toString config.port}"
${runInitialScript.before}
${setupInitialDatabases}
${runInitialScript.after}
pg_ctl -D "$PGDATA" -m fast -w stop
remove_tmp_pg_init_sock_dir "$PGHOST"
else
echo
echo "PostgreSQL database directory appears to contain a database; Skipping initialization"
echo
fi
unset POSTGRES_RUN_INITIAL_SCRIPT
'';
})

View File

@ -41,7 +41,7 @@
../nix/elasticsearch_test.nix
../nix/mysql_test.nix
../nix/nginx_test.nix
../nix/postgres_test.nix
../nix/postgres/postgres_test.nix
../nix/redis_test.nix
../nix/redis-cluster_test.nix
../nix/zookeeper_test.nix