@astralarium/repo-postgres 0.0.0-stage → 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +9 -2
- package/container/init/.containerignore +2 -0
- package/container/init/Containerfile +26 -0
- package/container/init/entrypoint.d/init-db.sh +20 -0
- package/container/snapshot/run-snapshot.sh +23 -0
- package/container/util/.containerignore +2 -0
- package/container/util/Containerfile +7 -0
- package/container/util/entrypoint.d/dump-db.sh +19 -0
- package/container/util/entrypoint.d/upgrade-db.sh +103 -0
- package/dist/bin/stage-schema.d.ts +2 -0
- package/dist/bin/stage-schema.js +20 -0
- package/dist/src/provision/connection.d.ts +11 -0
- package/dist/src/provision/connection.js +33 -0
- package/dist/src/provision/container.d.ts +87 -0
- package/dist/src/provision/container.js +244 -0
- package/dist/src/provision/data.d.ts +8 -0
- package/dist/src/provision/data.js +47 -0
- package/dist/src/provision/disposable.d.ts +49 -0
- package/dist/src/provision/disposable.js +188 -0
- package/dist/src/provision/image.d.ts +63 -0
- package/dist/src/provision/image.js +159 -0
- package/dist/src/provision/index.d.ts +17 -0
- package/dist/src/provision/index.js +205 -0
- package/dist/src/provision/options.d.ts +23 -0
- package/dist/src/provision/options.js +1 -0
- package/dist/src/provision/schema.d.ts +11 -0
- package/dist/src/provision/schema.js +134 -0
- package/dist/src/provision/test-database.d.ts +29 -0
- package/dist/src/provision/test-database.js +130 -0
- package/dist/src/schema/index.d.ts +3 -0
- package/dist/src/schema/index.js +3 -0
- package/dist/src/schema/module.d.ts +59 -0
- package/dist/src/schema/module.js +122 -0
- package/dist/src/schema/modules.d.ts +15 -0
- package/dist/src/schema/modules.js +48 -0
- package/dist/src/sql.d.ts +83 -0
- package/dist/src/sql.js +146 -0
- package/dist/testkit/index.d.ts +13 -0
- package/dist/testkit/index.js +31 -0
- package/dist/testkit/schema.d.ts +14 -0
- package/dist/testkit/schema.js +89 -0
- package/package.json +62 -3
- package/scripts/build-images.sh +52 -0
- package/scripts/dump-db.sh +32 -0
- package/sql/app/schema.sql +8 -0
- package/sql/bootstrap.sql +27 -0
- package/sql/database-grants.sql +3 -0
- package/sql/drop-schemas.sql +11 -0
- package/sql/extensions/cron/schema.sql +23 -0
- package/sql/extensions/partman/before-reset.sql +38 -0
- package/sql/extensions/partman/schema.sql +8 -0
- package/sql/extensions/trgm/schema.sql +1 -0
- package/sql/reset.sql +22 -0
- package/sql/roles.sql +10 -0
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# Usage: build-images.sh <runtime> <schema-hash> [db|util|all] [db-iidfile]
|
|
3
|
+
# <schema-hash>: labels the db image and stamps the initialized database
|
|
4
|
+
# volume; required for db and all, empty for util.
|
|
5
|
+
# DB_NAME: the app's image base name (PostgresOptions.name); required.
|
|
6
|
+
# DB_SCHEMA_DIR: the staged schema directory (schema.sql beside sql/, as
|
|
7
|
+
# `@astralarium/repo-postgres/provision` stageSchema writes it), baked into the db image.
|
|
8
|
+
# DB_EXTENSIONS: extension names the db image installs, space-separated
|
|
9
|
+
# (Debian postgresql-<major>-<name> packages).
|
|
10
|
+
# DB_REFRESH_BASE=true: pull the base images.
|
|
11
|
+
set -euo pipefail
|
|
12
|
+
|
|
13
|
+
cd "$( dirname "${BASH_SOURCE[0]}" )/.."
|
|
14
|
+
|
|
15
|
+
DB_IMAGE=${DB_NAME:?set to the app image base name (PostgresOptions.name)}
|
|
16
|
+
UTIL_IMAGE=$DB_IMAGE-util
|
|
17
|
+
|
|
18
|
+
runtime=${1:?container runtime}
|
|
19
|
+
hash=${2:-}
|
|
20
|
+
target=${3:-all}
|
|
21
|
+
case $target in
|
|
22
|
+
db|util|all) ;;
|
|
23
|
+
*) echo "build-images: unknown target $target (db, util or all)" >&2; exit 1 ;;
|
|
24
|
+
esac
|
|
25
|
+
build_args=(--file container/init/Containerfile)
|
|
26
|
+
util_args=(--file container/util/Containerfile)
|
|
27
|
+
if [[ ${DB_REFRESH_BASE:-false} == true ]]; then
|
|
28
|
+
build_args+=(--pull)
|
|
29
|
+
util_args+=(--pull)
|
|
30
|
+
fi
|
|
31
|
+
if [[ -n ${4:-} ]]; then
|
|
32
|
+
build_args+=(--iidfile "$4")
|
|
33
|
+
fi
|
|
34
|
+
|
|
35
|
+
if [[ $target == db || $target == all ]]; then
|
|
36
|
+
[[ -n $hash ]] || { echo "build-images: $target needs a schema hash" >&2; exit 1; }
|
|
37
|
+
schema_dir=${DB_SCHEMA_DIR:?staged schema directory}
|
|
38
|
+
[[ -f $schema_dir/schema.sql ]] || { echo "build-images: $schema_dir holds no schema.sql" >&2; exit 1; }
|
|
39
|
+
"$runtime" build container/init \
|
|
40
|
+
"${build_args[@]}" \
|
|
41
|
+
--build-context "schema=$schema_dir" \
|
|
42
|
+
--build-arg DB_EXTENSIONS="${DB_EXTENSIONS:-}" \
|
|
43
|
+
--build-arg DB_SCHEMA="$hash" \
|
|
44
|
+
--tag "$DB_IMAGE:$hash" \
|
|
45
|
+
--tag "$DB_IMAGE:latest" >&2
|
|
46
|
+
fi
|
|
47
|
+
|
|
48
|
+
if [[ $target == util || $target == all ]]; then
|
|
49
|
+
"$runtime" build container/util \
|
|
50
|
+
"${util_args[@]}" \
|
|
51
|
+
--tag "$UTIL_IMAGE:latest" >&2
|
|
52
|
+
fi
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# Usage: postgres-dump-db > data.sql
|
|
3
|
+
# Data-only dump of the PG* database via the util image, rebuilt on each
|
|
4
|
+
# run (layer-cached); the replica role prefix skips triggers and FK checks
|
|
5
|
+
# on load.
|
|
6
|
+
# Source connection via PGHOST/PGPORT/PGUSER/PGPASSWORD/PGDATABASE.
|
|
7
|
+
# DB_NAME: the app's image base name (PostgresOptions.name); required.
|
|
8
|
+
# DB_DUMP_EXCLUDE: schema-qualified pg_dump table patterns whose rows stay
|
|
9
|
+
# out of the dump, space-separated (`app.session`, `app.*_log`); a
|
|
10
|
+
# partitioned table covers its partitions.
|
|
11
|
+
# CONTAINER_RUNTIME: container runtime (default podman).
|
|
12
|
+
set -euo pipefail
|
|
13
|
+
|
|
14
|
+
runtime=${CONTAINER_RUNTIME:-podman}
|
|
15
|
+
name=${DB_NAME:?set to the app image base name (PostgresOptions.name)}
|
|
16
|
+
read -ra patterns <<< "${DB_DUMP_EXCLUDE:-}"
|
|
17
|
+
for pattern in ${patterns[@]+"${patterns[@]}"}; do
|
|
18
|
+
[[ $pattern == *.* ]] || { echo "postgres-dump-db: DB_DUMP_EXCLUDE pattern $pattern lacks a schema" >&2; exit 1; }
|
|
19
|
+
done
|
|
20
|
+
|
|
21
|
+
# Through the real path: a package manager's bin link sits outside the package.
|
|
22
|
+
scripts=$(dirname "$(realpath "${BASH_SOURCE[0]}")")
|
|
23
|
+
bash "$scripts/build-images.sh" "$runtime" "" util
|
|
24
|
+
|
|
25
|
+
"$runtime" run --rm --interactive \
|
|
26
|
+
--env PGHOST \
|
|
27
|
+
--env PGPORT \
|
|
28
|
+
--env PGUSER \
|
|
29
|
+
--env PGPASSWORD \
|
|
30
|
+
--env PGDATABASE \
|
|
31
|
+
--env DB_DUMP_EXCLUDE \
|
|
32
|
+
"$name-util" /entrypoint.d/dump-db.sh
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
-- Fresh remote (Neon) init: roles + app database + the app's schema.
|
|
2
|
+
-- Local-container equivalent: container/init/entrypoint.d/init-db.sh (first boot).
|
|
3
|
+
-- Run with psql from a staged schema directory (schema.sql beside sql/, as
|
|
4
|
+
-- `@astralarium/repo-postgres/provision` stageSchema writes it), with -v pw=<app_service
|
|
5
|
+
-- password>. With existing roles and database, use reset.sql.
|
|
6
|
+
|
|
7
|
+
\i roles.sql
|
|
8
|
+
-- app_service is the Hyperdrive origin login.
|
|
9
|
+
ALTER USER app_service WITH PASSWORD :'pw';
|
|
10
|
+
|
|
11
|
+
CREATE DATABASE app;
|
|
12
|
+
|
|
13
|
+
-- Neon: extension objects are cloud_admin-owned, so the schema's per-object
|
|
14
|
+
-- grants on them no-op. These stand in for them.
|
|
15
|
+
DO $$
|
|
16
|
+
BEGIN
|
|
17
|
+
IF to_regrole('neon_superuser') IS NOT NULL THEN
|
|
18
|
+
SET ROLE neon_superuser;
|
|
19
|
+
GRANT pg_read_all_data, pg_write_all_data TO app_admin;
|
|
20
|
+
RESET ROLE;
|
|
21
|
+
END IF;
|
|
22
|
+
END $$;
|
|
23
|
+
|
|
24
|
+
\connect app
|
|
25
|
+
|
|
26
|
+
\i database-grants.sql
|
|
27
|
+
\i schema.sql
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
-- Drops every schema the current role (app_admin, under reset.sql) owns.
|
|
2
|
+
DO $$
|
|
3
|
+
DECLARE
|
|
4
|
+
schema_name text;
|
|
5
|
+
BEGIN
|
|
6
|
+
FOR schema_name IN
|
|
7
|
+
SELECT nspname FROM pg_namespace WHERE nspowner = to_regrole(current_user)
|
|
8
|
+
LOOP
|
|
9
|
+
EXECUTE format('DROP SCHEMA %I CASCADE', schema_name);
|
|
10
|
+
END LOOP;
|
|
11
|
+
END $$;
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
-- pg_cron in the database `cron.database_name` selects, with a daily
|
|
2
|
+
-- `cron-history-ttl` deleting run history past 7 days. Skipped where
|
|
3
|
+
-- pg_cron is not loaded: disposable instances run stock Postgres.
|
|
4
|
+
DO $$
|
|
5
|
+
BEGIN
|
|
6
|
+
IF current_setting('cron.database_name', true) IS NULL THEN
|
|
7
|
+
RAISE NOTICE 'pg_cron is not loaded; skipping';
|
|
8
|
+
RETURN;
|
|
9
|
+
END IF;
|
|
10
|
+
|
|
11
|
+
CREATE EXTENSION IF NOT EXISTS pg_cron;
|
|
12
|
+
|
|
13
|
+
PERFORM cron.schedule(
|
|
14
|
+
'cron-history-ttl',
|
|
15
|
+
'0 3 * * *',
|
|
16
|
+
$job$
|
|
17
|
+
SET statement_timeout = '30s';
|
|
18
|
+
DELETE FROM cron.job_run_details
|
|
19
|
+
WHERE end_time < CURRENT_TIMESTAMP - interval '7 days';
|
|
20
|
+
$job$
|
|
21
|
+
);
|
|
22
|
+
END;
|
|
23
|
+
$$;
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
-- pg_partman outlives reset.sql's drop (cloud_admin-owned on Neon) and
|
|
2
|
+
-- keeps what it owns: a surviving part_config row makes create_parent
|
|
3
|
+
-- reject its parent on reload, and each parent's template table lives in
|
|
4
|
+
-- partman's schema with its app-typed columns. Clears both for every
|
|
5
|
+
-- schema the current role (app_admin, under reset.sql) owns, so the
|
|
6
|
+
-- reload takes the new shape.
|
|
7
|
+
DO $$
|
|
8
|
+
DECLARE
|
|
9
|
+
partman text;
|
|
10
|
+
schema_name text;
|
|
11
|
+
template text;
|
|
12
|
+
BEGIN
|
|
13
|
+
SELECT n.nspname INTO partman
|
|
14
|
+
FROM pg_extension e JOIN pg_namespace n ON n.oid = e.extnamespace
|
|
15
|
+
WHERE e.extname = 'pg_partman';
|
|
16
|
+
|
|
17
|
+
IF partman IS NULL THEN
|
|
18
|
+
RETURN;
|
|
19
|
+
END IF;
|
|
20
|
+
|
|
21
|
+
FOR schema_name IN
|
|
22
|
+
SELECT nspname FROM pg_namespace WHERE nspowner = to_regrole(current_user)
|
|
23
|
+
LOOP
|
|
24
|
+
FOR template IN
|
|
25
|
+
EXECUTE format(
|
|
26
|
+
'SELECT template_table FROM %I.part_config WHERE template_table IS NOT NULL AND split_part(parent_table, ''.'', 1) = %L',
|
|
27
|
+
partman, schema_name)
|
|
28
|
+
LOOP
|
|
29
|
+
EXECUTE format('DROP TABLE IF EXISTS %s', template);
|
|
30
|
+
END LOOP;
|
|
31
|
+
EXECUTE format(
|
|
32
|
+
'DELETE FROM %I.part_config_sub WHERE split_part(sub_parent, ''.'', 1) = %L',
|
|
33
|
+
partman, schema_name);
|
|
34
|
+
EXECUTE format(
|
|
35
|
+
'DELETE FROM %I.part_config WHERE split_part(parent_table, ''.'', 1) = %L',
|
|
36
|
+
partman, schema_name);
|
|
37
|
+
END LOOP;
|
|
38
|
+
END $$;
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
-- pg_partman in schema `partman`; app_admin creates and maintains parents.
|
|
2
|
+
CREATE SCHEMA IF NOT EXISTS partman;
|
|
3
|
+
CREATE EXTENSION IF NOT EXISTS pg_partman SCHEMA partman;
|
|
4
|
+
|
|
5
|
+
GRANT USAGE, CREATE ON SCHEMA partman TO app_admin;
|
|
6
|
+
GRANT EXECUTE ON ALL FUNCTIONS IN SCHEMA partman TO app_admin;
|
|
7
|
+
GRANT EXECUTE ON ALL PROCEDURES IN SCHEMA partman TO app_admin;
|
|
8
|
+
GRANT SELECT, INSERT, UPDATE, DELETE ON ALL TABLES IN SCHEMA partman TO app_admin;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
CREATE EXTENSION IF NOT EXISTS pg_trgm;
|
package/sql/reset.sql
ADDED
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
-- Drops every schema app_admin owns and reloads the app's schema, in one
|
|
2
|
+
-- transaction. Roles, the `app` database and the app_service password
|
|
3
|
+
-- survive, so the Hyperdrive origin keeps working.
|
|
4
|
+
-- Run with psql from a staged schema directory (schema.sql beside sql/, as
|
|
5
|
+
-- `@astralarium/repo-postgres/provision` stageSchema writes it).
|
|
6
|
+
|
|
7
|
+
\connect app
|
|
8
|
+
|
|
9
|
+
BEGIN;
|
|
10
|
+
|
|
11
|
+
SET LOCAL ROLE app_admin;
|
|
12
|
+
|
|
13
|
+
-- Each schema module's beforeReset, in load order.
|
|
14
|
+
\i before-reset.sql
|
|
15
|
+
|
|
16
|
+
\i drop-schemas.sql
|
|
17
|
+
|
|
18
|
+
RESET ROLE;
|
|
19
|
+
|
|
20
|
+
\i schema.sql
|
|
21
|
+
|
|
22
|
+
COMMIT;
|
package/sql/roles.sql
ADDED
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
-- The app roles, ahead of the schema; they outlive reset.sql.
|
|
2
|
+
-- Run by the image's init-db.sh and by bootstrap.sql.
|
|
3
|
+
|
|
4
|
+
-- app_admin owns the schemas and is assumed via SET ROLE (NOLOGIN).
|
|
5
|
+
CREATE ROLE app_admin NOLOGIN;
|
|
6
|
+
-- app_service is the app's login.
|
|
7
|
+
CREATE USER app_service;
|
|
8
|
+
GRANT app_service TO app_admin;
|
|
9
|
+
GRANT app_admin TO CURRENT_USER;
|
|
10
|
+
ALTER ROLE app_service SET idle_in_transaction_session_timeout = '30s';
|