feat: db-tool: +secrets load
This commit is contained in:
+1
-1
@@ -103,7 +103,7 @@ in {
|
|||||||
|
|
||||||
# Consolidated SQL bundles for the `hectic` schema. Single source of truth
|
# Consolidated SQL bundles for the `hectic` schema. Single source of truth
|
||||||
# for everything that creates objects in the `hectic` namespace, used by
|
# for everything that creates objects in the `hectic` namespace, used by
|
||||||
# migrator (init-time) and db-tool (postgres-init + hydrate). Consumers apply
|
# migrator (init-time), db-dev/database hydrate, and db-ops secrets loading. Consumers apply
|
||||||
# the full bundle via lib/hook/apply-hectic-bundle.sh.
|
# the full bundle via lib/hook/apply-hectic-bundle.sh.
|
||||||
#
|
#
|
||||||
# The whole hectic system shares one `versionString`; `hectic-version.sql`
|
# The whole hectic system shares one `versionString`; `hectic-version.sql`
|
||||||
|
|||||||
@@ -10,8 +10,8 @@
|
|||||||
# Usage:
|
# Usage:
|
||||||
# apply_hectic_bundle <PGURL> [<DOTENV_CONTENT>]
|
# apply_hectic_bundle <PGURL> [<DOTENV_CONTENT>]
|
||||||
#
|
#
|
||||||
# If DOTENV_CONTENT is non-empty, it is loaded into hectic.secret via
|
# If DOTENV_CONTENT is non-empty, it is base64-encoded and then loaded into
|
||||||
# hectic.load_secrets_from_env() after the bundle is applied.
|
# hectic.secret via hectic.load_secrets_from_env() after the bundle is applied.
|
||||||
# SQL file paths are substituted by Nix evaluation time.
|
# SQL file paths are substituted by Nix evaluation time.
|
||||||
|
|
||||||
apply_hectic_bundle() {
|
apply_hectic_bundle() {
|
||||||
@@ -41,11 +41,9 @@ apply_hectic_bundle() {
|
|||||||
done
|
done
|
||||||
|
|
||||||
if [ -n "$env_content" ]; then
|
if [ -n "$env_content" ]; then
|
||||||
# Dollar-quote with $ps_env$ tag to preserve all content verbatim.
|
env_content_b64="$(printf '%s' "$env_content" | base64 | tr -d '\n')" || return 1
|
||||||
psql "$pgurl" -v ON_ERROR_STOP=1 <<SQL || return 1
|
psql "$pgurl" -v ON_ERROR_STOP=1 <<SQL || return 1
|
||||||
SELECT hectic.load_secrets_from_env(\$ps_env\$
|
SELECT hectic.load_secrets_from_env(convert_from(decode('$env_content_b64', 'base64'), 'UTF8'));
|
||||||
$env_content
|
|
||||||
\$ps_env\$);
|
|
||||||
SQL
|
SQL
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
|||||||
@@ -4,8 +4,8 @@ Single source of truth for every object created in the `hectic` PostgreSQL
|
|||||||
schema. Consumed by:
|
schema. Consumed by:
|
||||||
|
|
||||||
- `package/migrator` — applies the bundle on `migrator init` (mandatory).
|
- `package/migrator` — applies the bundle on `migrator init` (mandatory).
|
||||||
- `package/db-tool` — applies the bundle in `database hydrate` (default; opt
|
- `package/db-tool` — applies the bundle in `db-dev` / `database hydrate`
|
||||||
out with `--no-hook`).
|
(default; opt out with `--no-hook`) and in `db-ops secrets load`.
|
||||||
- External consumers (e.g. `proxydoe`) — invoke `psql -f` directly against the
|
- External consumers (e.g. `proxydoe`) — invoke `psql -f` directly against the
|
||||||
paths exposed via `self.lib.hectic.*.path`.
|
paths exposed via `self.lib.hectic.*.path`.
|
||||||
|
|
||||||
@@ -60,7 +60,7 @@ verbatim source files in the store.
|
|||||||
|
|
||||||
`lib/hook/apply-hectic-bundle.sh` is a dash-compatible helper template.
|
`lib/hook/apply-hectic-bundle.sh` is a dash-compatible helper template.
|
||||||
`self.lib.hectic.applyBundleScript` is the generated shell source with concrete
|
`self.lib.hectic.applyBundleScript` is the generated shell source with concrete
|
||||||
SQL paths embedded at Nix evaluation time. `migrator` and `db-tool` splice that
|
SQL paths embedded at Nix evaluation time. `migrator`, `db-dev`, and `db-ops` splice that
|
||||||
shell source directly into their generated scripts. Public entry point:
|
shell source directly into their generated scripts. Public entry point:
|
||||||
|
|
||||||
```sh
|
```sh
|
||||||
@@ -87,8 +87,9 @@ External consumers that do not want to source the helper can still invoke
|
|||||||
4. Add a matching placeholder/replacement in `lib.hectic.applyBundleScript` and
|
4. Add a matching placeholder/replacement in `lib.hectic.applyBundleScript` and
|
||||||
update `lib/hook/apply-hectic-bundle.sh` to apply the file.
|
update `lib/hook/apply-hectic-bundle.sh` to apply the file.
|
||||||
5. Bump `HECTIC_VERSION` if the new content changes existing semantics.
|
5. Bump `HECTIC_VERSION` if the new content changes existing semantics.
|
||||||
6. Update tests in `test/package/migrator/test/postgresql/init-hectic-bundle/`
|
6. Update tests in `test/package/migrator/test/postgresql/init-hectic-bundle/`,
|
||||||
and `test/package/db-tool/test/postgresql/hydrate-hook/`.
|
`test/package/db-tool/test/postgresql/hydrate-hook/`, and any `db-ops`
|
||||||
|
bundle-loading coverage.
|
||||||
|
|
||||||
## Versioning
|
## Versioning
|
||||||
|
|
||||||
|
|||||||
@@ -1,18 +1,21 @@
|
|||||||
# db-tool
|
# db-tool
|
||||||
|
|
||||||
PostgreSQL development database management tool. Drop-in replacement for per-project database.sh / postgres-init.sh / postgres-cleanup.sh scripts. Provides database, postgres-init, and postgres-cleanup binaries.
|
PostgreSQL utility package exposing separate development and operations binaries.
|
||||||
|
`db-dev` preserves the current development workflow via the `database` binary;
|
||||||
|
`db-ops` provides target-safe operational helpers such as secrets hydration.
|
||||||
|
|
||||||
## Provided Binaries
|
## Provided Binaries
|
||||||
|
|
||||||
| Binary | Description |
|
| Binary | Description |
|
||||||
| --- | --- |
|
| --- | --- |
|
||||||
| `database` | Main script for managing migrations, deployments, and logs. |
|
| `database` | Main `db-dev` script for managing local development databases, migrations, deployments, and logs. |
|
||||||
|
| `db-ops` | Operational helper binary for target/runtime database actions. |
|
||||||
| `postgres-init` | Ephemeral PostgreSQL cluster initialization and startup. |
|
| `postgres-init` | Ephemeral PostgreSQL cluster initialization and startup. |
|
||||||
| `postgres-cleanup` | Graceful shutdown and cleanup of the PostgreSQL cluster. |
|
| `postgres-cleanup` | Graceful shutdown and cleanup of the PostgreSQL cluster. |
|
||||||
|
|
||||||
## Required Environment Variables
|
## Required Environment Variables
|
||||||
|
|
||||||
These variables must be set for `db-tool` to function.
|
These variables must be set for `db-dev` / `database` to function.
|
||||||
|
|
||||||
| Variable | Description |
|
| Variable | Description |
|
||||||
| --- | --- |
|
| --- | --- |
|
||||||
@@ -39,7 +42,7 @@ These variables must be set for `db-tool` to function.
|
|||||||
|
|
||||||
## Postgres Package Override
|
## Postgres Package Override
|
||||||
|
|
||||||
By default, `db-tool`/`postgres-init`/`postgres-cleanup` use plain `postgresql_17` from nixpkgs. If you need extensions (e.g. `pg_cron`), override the postgres package per-output:
|
By default, `db-dev`/`db-ops`/`postgres-init`/`postgres-cleanup` use plain `postgresql_17` from nixpkgs. If you need extensions (e.g. `pg_cron`), override the postgres package per-output:
|
||||||
|
|
||||||
```nix
|
```nix
|
||||||
let
|
let
|
||||||
@@ -48,13 +51,16 @@ let
|
|||||||
]);
|
]);
|
||||||
in {
|
in {
|
||||||
packages = [
|
packages = [
|
||||||
(pkgs.hectic."db-tool".override { postgresql = myPg; })
|
(pkgs.hectic."db-dev".override { postgresql = myPg; })
|
||||||
|
(pkgs.hectic."db-ops".override { postgresql = myPg; })
|
||||||
(pkgs.hectic."postgres-init".override { postgresql = myPg; })
|
(pkgs.hectic."postgres-init".override { postgresql = myPg; })
|
||||||
(pkgs.hectic."postgres-cleanup".override { postgresql = myPg; })
|
(pkgs.hectic."postgres-cleanup".override { postgresql = myPg; })
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
|
`pkgs.hectic."db-tool"` remains as a compatibility alias to `db-dev`.
|
||||||
|
|
||||||
## pull_staging Contract
|
## pull_staging Contract
|
||||||
|
|
||||||
The `pull_staging` subcommand allows importing data from a remote staging environment into the local `test-data.sql` file. This functionality requires four specific environment variables to be defined:
|
The `pull_staging` subcommand allows importing data from a remote staging environment into the local `test-data.sql` file. This functionality requires four specific environment variables to be defined:
|
||||||
@@ -102,14 +108,14 @@ used by `database restore`.
|
|||||||
|
|
||||||
## shellHook Example
|
## shellHook Example
|
||||||
|
|
||||||
To use `db-tool` in a Nix development shell, add the following to your `flake.nix` or `shell.nix`:
|
To use `db-dev` in a Nix development shell, add the following to your `flake.nix` or `shell.nix`:
|
||||||
|
|
||||||
```nix
|
```nix
|
||||||
{
|
{
|
||||||
# ...
|
# ...
|
||||||
devShells.default = pkgs.mkShell {
|
devShells.default = pkgs.mkShell {
|
||||||
packages = [
|
packages = [
|
||||||
pkgs.hectic.db-tool
|
pkgs.hectic.db-dev
|
||||||
pkgs.hectic.postgres-init
|
pkgs.hectic.postgres-init
|
||||||
pkgs.hectic.postgres-cleanup
|
pkgs.hectic.postgres-cleanup
|
||||||
];
|
];
|
||||||
@@ -134,7 +140,7 @@ To use `db-tool` in a Nix development shell, add the following to your `flake.ni
|
|||||||
|
|
||||||
## hectic Bundle
|
## hectic Bundle
|
||||||
|
|
||||||
`db-tool` and `migrator` apply a single bundle of SQL files that bootstrap the
|
`db-dev`, `db-ops`, and `migrator` apply a single bundle of SQL files that bootstrap the
|
||||||
`hectic` schema. The bundle lives in
|
`hectic` schema. The bundle lives in
|
||||||
[`lib/hook/sql/`](../../lib/hook/sql/README.md) — see that README for full
|
[`lib/hook/sql/`](../../lib/hook/sql/README.md) — see that README for full
|
||||||
contract, file layout, and the `self.lib.hectic.*` Nix API.
|
contract, file layout, and the `self.lib.hectic.*` Nix API.
|
||||||
@@ -190,6 +196,7 @@ ALTER DATABASE mydb SET hectic.inheritance_extra_excluded_schemas = 'legacy,etl'
|
|||||||
| `postgres-init` | **No.** Pure PostgreSQL provisioner — starts a vanilla cluster, nothing more. |
|
| `postgres-init` | **No.** Pure PostgreSQL provisioner — starts a vanilla cluster, nothing more. |
|
||||||
| `migrator init` | **Yes, mandatory.** The bundle is a hard prerequisite for `hectic.migration`. |
|
| `migrator init` | **Yes, mandatory.** The bundle is a hard prerequisite for `hectic.migration`. |
|
||||||
| `database hydrate` | **Yes, by default.** Re-applied on every hydrate. Skip with `--no-hook`. After applying the bundle, hydrate also calls `hectic.load_secrets_from_env(<dotenv>)` if `HECTIC_DOTENV_FILE` (or `${LOCAL_DIR}/.env.${ENVIRONMENT}`) is readable. |
|
| `database hydrate` | **Yes, by default.** Re-applied on every hydrate. Skip with `--no-hook`. After applying the bundle, hydrate also calls `hectic.load_secrets_from_env(<dotenv>)` if `HECTIC_DOTENV_FILE` (or `${LOCAL_DIR}/.env.${ENVIRONMENT}`) is readable. |
|
||||||
|
| `db-ops secrets load` | **Yes, mandatory.** Applies the bundle and requires a dotenv source before loading secrets into `hectic.secret`. |
|
||||||
|
|
||||||
The bundle is idempotent — repeated application is safe.
|
The bundle is idempotent — repeated application is safe.
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,222 @@
|
|||||||
|
# shellcheck shell=dash
|
||||||
|
|
||||||
|
: "${SCRIPT_NAME:=$(basename "$0")}"
|
||||||
|
|
||||||
|
help() {
|
||||||
|
# shellcheck disable=SC2059
|
||||||
|
printf "$(cat <<EOF
|
||||||
|
${BGREEN}Usage:${NC} $SCRIPT_NAME [OPTIONS] <SUBCOMMAND> [OPTIONS]
|
||||||
|
|
||||||
|
PostgreSQL operations utility.
|
||||||
|
|
||||||
|
${BGREEN}Global Options:${NC}
|
||||||
|
${BCYAN}-h${NC}, ${BCYAN}--help${NC} Show this help message
|
||||||
|
${BCYAN}-u${NC}, ${BCYAN}--url${NC} <url> PostgreSQL connection string
|
||||||
|
|
||||||
|
${BGREEN}Subcommands:${NC}
|
||||||
|
${BCYAN}secrets load${NC} [OPTIONS] Apply hectic bundle and load secrets
|
||||||
|
|
||||||
|
${BGREEN}Environment:${NC}
|
||||||
|
${BBLACK}PGURL${NC} PostgreSQL connection string fallback
|
||||||
|
${BBLACK}DB_URL${NC} PostgreSQL connection string fallback
|
||||||
|
${BBLACK}HECTIC_DOTENV_CONTENT${NC} Raw dotenv content to load
|
||||||
|
${BBLACK}HECTIC_DOTENV_FILE${NC} Dotenv file to read when readable
|
||||||
|
${BBLACK}LOCAL_DIR${NC} Used with ENVIRONMENT for .env fallback
|
||||||
|
${BBLACK}ENVIRONMENT${NC} Falls back to ${BBLACK}\$LOCAL_DIR/.env.\$ENVIRONMENT${NC}
|
||||||
|
|
||||||
|
EOF
|
||||||
|
)"
|
||||||
|
}
|
||||||
|
|
||||||
|
help_secrets_load() {
|
||||||
|
# shellcheck disable=SC2059
|
||||||
|
printf "$(cat <<EOF
|
||||||
|
${BGREEN}Usage:${NC} $SCRIPT_NAME ${BCYAN}secrets load${NC} [OPTIONS]
|
||||||
|
|
||||||
|
Apply the hectic SQL bundle and load dotenv-backed secrets into
|
||||||
|
${BBLACK}hectic.secret${NC}.
|
||||||
|
|
||||||
|
${BGREEN}Options:${NC}
|
||||||
|
${BCYAN}-h${NC}, ${BCYAN}--help${NC} Show this help message
|
||||||
|
${BCYAN}-u${NC}, ${BCYAN}--url${NC} <url> PostgreSQL connection string
|
||||||
|
${BCYAN}-f${NC}, ${BCYAN}--dotenv-file${NC} <path> Dotenv file path
|
||||||
|
|
||||||
|
${BGREEN}Resolution order:${NC}
|
||||||
|
1. ${BBLACK}HECTIC_DOTENV_CONTENT${NC}
|
||||||
|
2. ${BBLACK}--dotenv-file${NC} / ${BBLACK}HECTIC_DOTENV_FILE${NC}
|
||||||
|
3. ${BBLACK}\$LOCAL_DIR/.env.\$ENVIRONMENT${NC}
|
||||||
|
|
||||||
|
Fails with exit code ${BBLACK}3${NC} when neither a PostgreSQL URL nor a dotenv
|
||||||
|
source can be resolved.
|
||||||
|
|
||||||
|
EOF
|
||||||
|
)"
|
||||||
|
}
|
||||||
|
|
||||||
|
resolve_pgurl() {
|
||||||
|
if [ -n "${PGURL:-}" ]; then
|
||||||
|
printf '%s' "$PGURL"
|
||||||
|
elif [ -n "${DB_URL:-}" ]; then
|
||||||
|
printf '%s' "$DB_URL"
|
||||||
|
else
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
resolve_dotenv_content() {
|
||||||
|
dotenv_file="${1:-}"
|
||||||
|
|
||||||
|
if [ -n "${HECTIC_DOTENV_CONTENT:-}" ]; then
|
||||||
|
printf '%s' "$HECTIC_DOTENV_CONTENT"
|
||||||
|
elif [ -n "$dotenv_file" ]; then
|
||||||
|
if [ ! -f "$dotenv_file" ] || [ ! -r "$dotenv_file" ]; then
|
||||||
|
return 2
|
||||||
|
fi
|
||||||
|
cat "$dotenv_file"
|
||||||
|
elif [ -n "${ENVIRONMENT:-}" ] && [ -n "${LOCAL_DIR:-}" ] && [ -r "${LOCAL_DIR}/.env.${ENVIRONMENT}" ]; then
|
||||||
|
cat "${LOCAL_DIR}/.env.${ENVIRONMENT}"
|
||||||
|
else
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
subcommand_secrets_load() {
|
||||||
|
change_namespace 'db ops secrets load'
|
||||||
|
|
||||||
|
dotenv_file="${HECTIC_DOTENV_FILE:-}"
|
||||||
|
|
||||||
|
while [ $# -gt 0 ]; do
|
||||||
|
case $1 in
|
||||||
|
-h|--help)
|
||||||
|
help_secrets_load
|
||||||
|
restore_namespace
|
||||||
|
exit 0
|
||||||
|
;;
|
||||||
|
-u|--url)
|
||||||
|
if [ $# -lt 2 ]; then
|
||||||
|
log error "missing value for $1"
|
||||||
|
restore_namespace
|
||||||
|
exit 3
|
||||||
|
fi
|
||||||
|
PGURL=$2
|
||||||
|
DB_URL=$2
|
||||||
|
shift 2
|
||||||
|
;;
|
||||||
|
-f|--dotenv-file)
|
||||||
|
if [ $# -lt 2 ]; then
|
||||||
|
log error "missing value for $1"
|
||||||
|
restore_namespace
|
||||||
|
exit 3
|
||||||
|
fi
|
||||||
|
dotenv_file=$2
|
||||||
|
shift 2
|
||||||
|
;;
|
||||||
|
--*|-*)
|
||||||
|
log error "secrets load argument $1 does not exist"
|
||||||
|
restore_namespace
|
||||||
|
exit 9
|
||||||
|
;;
|
||||||
|
*)
|
||||||
|
log error "secrets load subcommand $1 does not exist"
|
||||||
|
restore_namespace
|
||||||
|
exit 9
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
done
|
||||||
|
|
||||||
|
if ! pgurl="$(resolve_pgurl)"; then
|
||||||
|
log error "PGURL or DB_URL is required"
|
||||||
|
restore_namespace
|
||||||
|
exit 3
|
||||||
|
fi
|
||||||
|
|
||||||
|
if dotenv_content="$(resolve_dotenv_content "$dotenv_file")"; then
|
||||||
|
:
|
||||||
|
else
|
||||||
|
dotenv_content_exit_code=$?
|
||||||
|
if [ "$dotenv_content_exit_code" -eq 2 ]; then
|
||||||
|
log error "dotenv file is not readable: $dotenv_file"
|
||||||
|
else
|
||||||
|
log error "dotenv source is required (HECTIC_DOTENV_CONTENT, readable dotenv file, or \$LOCAL_DIR/.env.\$ENVIRONMENT)"
|
||||||
|
fi
|
||||||
|
restore_namespace
|
||||||
|
exit 3
|
||||||
|
fi
|
||||||
|
|
||||||
|
log notice "apply hectic bundle and load secrets"
|
||||||
|
apply_hectic_bundle "$pgurl" "$dotenv_content"
|
||||||
|
restore_namespace
|
||||||
|
}
|
||||||
|
|
||||||
|
subcommand_secrets() {
|
||||||
|
change_namespace 'db ops secrets'
|
||||||
|
|
||||||
|
if [ $# -eq 0 ]; then
|
||||||
|
help_secrets_load
|
||||||
|
restore_namespace
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
subcommand=$1
|
||||||
|
shift
|
||||||
|
|
||||||
|
case $subcommand in
|
||||||
|
load)
|
||||||
|
subcommand_secrets_load "$@"
|
||||||
|
;;
|
||||||
|
-h|--help)
|
||||||
|
help_secrets_load
|
||||||
|
restore_namespace
|
||||||
|
exit 0
|
||||||
|
;;
|
||||||
|
*)
|
||||||
|
log error "secrets subcommand $subcommand does not exist"
|
||||||
|
restore_namespace
|
||||||
|
exit 9
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
}
|
||||||
|
|
||||||
|
while [ $# -gt 0 ]; do
|
||||||
|
case $1 in
|
||||||
|
-h|--help)
|
||||||
|
help
|
||||||
|
exit 0
|
||||||
|
;;
|
||||||
|
-u|--url)
|
||||||
|
if [ $# -lt 2 ]; then
|
||||||
|
log error "missing value for $1"
|
||||||
|
exit 3
|
||||||
|
fi
|
||||||
|
PGURL=$2
|
||||||
|
DB_URL=$2
|
||||||
|
shift 2
|
||||||
|
;;
|
||||||
|
--*|-*)
|
||||||
|
log error "argument $1 does not exist"
|
||||||
|
exit 9
|
||||||
|
;;
|
||||||
|
*)
|
||||||
|
break
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
done
|
||||||
|
|
||||||
|
subcommand="${1:-}"
|
||||||
|
|
||||||
|
if [ -z "$subcommand" ]; then
|
||||||
|
help
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
shift
|
||||||
|
|
||||||
|
case $subcommand in
|
||||||
|
secrets)
|
||||||
|
subcommand_secrets "$@"
|
||||||
|
;;
|
||||||
|
*)
|
||||||
|
log error "subcommand $subcommand does not exist"
|
||||||
|
exit 9
|
||||||
|
;;
|
||||||
|
esac
|
||||||
@@ -8,7 +8,7 @@ let
|
|||||||
|
|
||||||
applyBundle = self.lib.hectic.applyBundleScript;
|
applyBundle = self.lib.hectic.applyBundleScript;
|
||||||
|
|
||||||
mkDatabase =
|
mkDbDev =
|
||||||
{ postgresql ? postgresql_17 }:
|
{ postgresql ? postgresql_17 }:
|
||||||
hectic.writeShellApplication {
|
hectic.writeShellApplication {
|
||||||
inherit shell;
|
inherit shell;
|
||||||
@@ -36,6 +36,31 @@ let
|
|||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
mkDbOps =
|
||||||
|
{ postgresql ? postgresql_17 }:
|
||||||
|
hectic.writeShellApplication {
|
||||||
|
inherit shell;
|
||||||
|
bashOptions = [
|
||||||
|
"errexit"
|
||||||
|
"nounset"
|
||||||
|
];
|
||||||
|
excludeShellChecks = [ "SC2209" ];
|
||||||
|
name = "db-ops";
|
||||||
|
runtimeInputs = [ postgresql coreutils ];
|
||||||
|
|
||||||
|
text = ''
|
||||||
|
${builtins.readFile hectic.helpers.posix-shell.log}
|
||||||
|
${builtins.readFile hectic.helpers.posix-shell.change_namespace}
|
||||||
|
${applyBundle}
|
||||||
|
${builtins.readFile ./db-ops.sh}
|
||||||
|
'';
|
||||||
|
|
||||||
|
meta = {
|
||||||
|
description = "PostgreSQL operations utility";
|
||||||
|
mainProgram = "db-ops";
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
mkPostgresInit =
|
mkPostgresInit =
|
||||||
{ postgresql ? postgresql_17 }:
|
{ postgresql ? postgresql_17 }:
|
||||||
hectic.writeShellApplication {
|
hectic.writeShellApplication {
|
||||||
@@ -73,7 +98,8 @@ let
|
|||||||
};
|
};
|
||||||
in
|
in
|
||||||
{
|
{
|
||||||
"db-tool" = lib.makeOverridable mkDatabase { };
|
"db-dev" = lib.makeOverridable mkDbDev { };
|
||||||
|
"db-ops" = lib.makeOverridable mkDbOps { };
|
||||||
"postgres-init" = lib.makeOverridable mkPostgresInit { };
|
"postgres-init" = lib.makeOverridable mkPostgresInit { };
|
||||||
"postgres-cleanup" = lib.makeOverridable mkPostgresCleanup { };
|
"postgres-cleanup" = lib.makeOverridable mkPostgresCleanup { };
|
||||||
"hectic-inheritance" = hecticInheritance;
|
"hectic-inheritance" = hecticInheritance;
|
||||||
|
|||||||
@@ -151,6 +151,8 @@ in {
|
|||||||
onlinepubs2man = pkgs.callPackage ./onlinepubs2man {};
|
onlinepubs2man = pkgs.callPackage ./onlinepubs2man {};
|
||||||
migrator = pkgs.callPackage ./migrator { inherit self; };
|
migrator = pkgs.callPackage ./migrator { inherit self; };
|
||||||
"parse-uri" = pkgs.callPackage ./parse-uri {};
|
"parse-uri" = pkgs.callPackage ./parse-uri {};
|
||||||
|
"db-dev" = dbToolPkgs."db-dev";
|
||||||
|
"db-ops" = dbToolPkgs."db-ops";
|
||||||
"db-tool" = dbToolPkgs."db-tool";
|
"db-tool" = dbToolPkgs."db-tool";
|
||||||
"postgres-init" = dbToolPkgs."postgres-init";
|
"postgres-init" = dbToolPkgs."postgres-init";
|
||||||
"postgres-cleanup" = dbToolPkgs."postgres-cleanup";
|
"postgres-cleanup" = dbToolPkgs."postgres-cleanup";
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
{ dash, hectic, sqlite, postgresql_17, gawk, self }:
|
{ dash, hectic, sqlite, postgresql_17, gawk, coreutils, self }:
|
||||||
let
|
let
|
||||||
shell = "${dash}/bin/dash";
|
shell = "${dash}/bin/dash";
|
||||||
bashOptions = [
|
bashOptions = [
|
||||||
@@ -11,7 +11,7 @@ let
|
|||||||
migrator = hectic.writeShellApplication {
|
migrator = hectic.writeShellApplication {
|
||||||
inherit shell bashOptions;
|
inherit shell bashOptions;
|
||||||
name = "migrator";
|
name = "migrator";
|
||||||
runtimeInputs = [ sqlite postgresql_17 gawk ];
|
runtimeInputs = [ sqlite postgresql_17 gawk coreutils ];
|
||||||
|
|
||||||
text = ''
|
text = ''
|
||||||
${builtins.readFile hectic.helpers.posix-shell.log}
|
${builtins.readFile hectic.helpers.posix-shell.log}
|
||||||
|
|||||||
@@ -31,7 +31,8 @@
|
|||||||
) (lib.filterAttrs (_: v: v != null)
|
) (lib.filterAttrs (_: v: v != null)
|
||||||
(lib.mapAttrs (n: t: mkTestDrv folder n t) (testDir folder)));
|
(lib.mapAttrs (n: t: mkTestDrv folder n t) (testDir folder)));
|
||||||
|
|
||||||
database = self.packages.${system}."db-tool";
|
database = self.packages.${system}."db-dev";
|
||||||
|
dbOps = self.packages.${system}."db-ops";
|
||||||
postgresInit = self.packages.${system}."postgres-init";
|
postgresInit = self.packages.${system}."postgres-init";
|
||||||
postgresCleanup = self.packages.${system}."postgres-cleanup";
|
postgresCleanup = self.packages.${system}."postgres-cleanup";
|
||||||
|
|
||||||
@@ -48,7 +49,7 @@
|
|||||||
mkNonPgTest = testName: testDrv: pkgs.runCommand "db-tool-${testName}"
|
mkNonPgTest = testName: testDrv: pkgs.runCommand "db-tool-${testName}"
|
||||||
{
|
{
|
||||||
nativeBuildInputs = [ pkgs.coreutils pkgs.gnugrep pkgs.gnused ];
|
nativeBuildInputs = [ pkgs.coreutils pkgs.gnugrep pkgs.gnused ];
|
||||||
buildInputs = [ database postgresInit postgresCleanup pkgs.postgresql_17 pkgs.dash ];
|
buildInputs = [ database dbOps postgresInit postgresCleanup pkgs.postgresql_17 pkgs.dash ];
|
||||||
} ''
|
} ''
|
||||||
${builtins.readFile self.legacyPackages.${system}.helpers.posix-shell.log}
|
${builtins.readFile self.legacyPackages.${system}.helpers.posix-shell.log}
|
||||||
test=${testDrv}
|
test=${testDrv}
|
||||||
@@ -64,7 +65,7 @@
|
|||||||
mkPgTest = testName: testDrv: pkgs.runCommand "db-tool-${testName}"
|
mkPgTest = testName: testDrv: pkgs.runCommand "db-tool-${testName}"
|
||||||
{
|
{
|
||||||
nativeBuildInputs = [ pkgs.coreutils pkgs.gnugrep pkgs.gnused ];
|
nativeBuildInputs = [ pkgs.coreutils pkgs.gnugrep pkgs.gnused ];
|
||||||
buildInputs = [ database postgresInit postgresCleanup pkgs.postgresql_17 pkgs.dash pkgs.netcat-openbsd ];
|
buildInputs = [ database dbOps postgresInit postgresCleanup pkgs.postgresql_17 pkgs.dash pkgs.netcat-openbsd ];
|
||||||
} ''
|
} ''
|
||||||
${builtins.readFile self.legacyPackages.${system}.helpers.posix-shell.log}
|
${builtins.readFile self.legacyPackages.${system}.helpers.posix-shell.log}
|
||||||
test=${testDrv}
|
test=${testDrv}
|
||||||
|
|||||||
@@ -0,0 +1,19 @@
|
|||||||
|
# shellcheck shell=dash
|
||||||
|
|
||||||
|
export HECTIC_NAMESPACE=test-db-ops-help
|
||||||
|
|
||||||
|
log notice "test case: db-ops --help exits 0"
|
||||||
|
if ! db-ops --help > /tmp/db-ops-help-out.txt 2>&1; then
|
||||||
|
log error "test failed: db-ops --help exited non-zero"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
for tok in secrets load HECTIC_DOTENV_FILE PGURL DB_URL; do
|
||||||
|
if ! grep -qF "$tok" /tmp/db-ops-help-out.txt; then
|
||||||
|
log error "test failed: db-ops --help output missing token: $tok"
|
||||||
|
cat /tmp/db-ops-help-out.txt >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
|
||||||
|
log notice "test passed"
|
||||||
@@ -0,0 +1,24 @@
|
|||||||
|
# shellcheck shell=dash
|
||||||
|
|
||||||
|
export HECTIC_NAMESPACE=test-db-ops-missing-dotenv
|
||||||
|
|
||||||
|
if db-ops --url 'postgresql://example.invalid/test' secrets load > /tmp/db-ops-missing-dotenv.out 2>&1; then
|
||||||
|
log error "expected db-ops secrets load to fail without dotenv source"
|
||||||
|
exit 1
|
||||||
|
else
|
||||||
|
exit_code=$?
|
||||||
|
fi
|
||||||
|
|
||||||
|
[ "$exit_code" -eq 3 ] || {
|
||||||
|
log error "expected exit code 3 without dotenv source, got: $exit_code"
|
||||||
|
cat /tmp/db-ops-missing-dotenv.out >&2
|
||||||
|
exit 1
|
||||||
|
}
|
||||||
|
|
||||||
|
if ! grep -qF 'dotenv source is required' /tmp/db-ops-missing-dotenv.out; then
|
||||||
|
log error "missing dotenv source error message"
|
||||||
|
cat /tmp/db-ops-missing-dotenv.out >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
log notice "test passed"
|
||||||
@@ -0,0 +1,28 @@
|
|||||||
|
# shellcheck shell=dash
|
||||||
|
|
||||||
|
export HECTIC_NAMESPACE=test-db-ops-missing-pgurl
|
||||||
|
|
||||||
|
dotenv_file=$(mktemp)
|
||||||
|
trap 'rm -f "$dotenv_file"' EXIT INT TERM
|
||||||
|
printf 'TEST_SECRET=hello-world\n' > "$dotenv_file"
|
||||||
|
|
||||||
|
if db-ops secrets load --dotenv-file "$dotenv_file" > /tmp/db-ops-missing-pgurl.out 2>&1; then
|
||||||
|
log error "expected db-ops secrets load to fail without PGURL"
|
||||||
|
exit 1
|
||||||
|
else
|
||||||
|
exit_code=$?
|
||||||
|
fi
|
||||||
|
|
||||||
|
[ "$exit_code" -eq 3 ] || {
|
||||||
|
log error "expected exit code 3 without PGURL, got: $exit_code"
|
||||||
|
cat /tmp/db-ops-missing-pgurl.out >&2
|
||||||
|
exit 1
|
||||||
|
}
|
||||||
|
|
||||||
|
if ! grep -qF 'PGURL or DB_URL is required' /tmp/db-ops-missing-pgurl.out; then
|
||||||
|
log error "missing PGURL error message"
|
||||||
|
cat /tmp/db-ops-missing-pgurl.out >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
log notice "test passed"
|
||||||
@@ -0,0 +1,28 @@
|
|||||||
|
# shellcheck shell=dash
|
||||||
|
|
||||||
|
export HECTIC_NAMESPACE=test-db-ops-unreadable-dotenv
|
||||||
|
|
||||||
|
dotenv_dir=$(mktemp -d)
|
||||||
|
dotenv_file="$dotenv_dir/missing.env"
|
||||||
|
trap 'rm -rf "$dotenv_dir"' EXIT INT TERM
|
||||||
|
|
||||||
|
if db-ops --url 'postgresql://example.invalid/test' secrets load --dotenv-file "$dotenv_file" > /tmp/db-ops-unreadable-dotenv.out 2>&1; then
|
||||||
|
log error "expected db-ops secrets load to fail for unreadable explicit dotenv path"
|
||||||
|
exit 1
|
||||||
|
else
|
||||||
|
exit_code=$?
|
||||||
|
fi
|
||||||
|
|
||||||
|
[ "$exit_code" -eq 3 ] || {
|
||||||
|
log error "expected exit code 3 for unreadable explicit dotenv path, got: $exit_code"
|
||||||
|
cat /tmp/db-ops-unreadable-dotenv.out >&2
|
||||||
|
exit 1
|
||||||
|
}
|
||||||
|
|
||||||
|
if ! grep -qF 'dotenv file is not readable' /tmp/db-ops-unreadable-dotenv.out; then
|
||||||
|
log error "missing unreadable dotenv file error message"
|
||||||
|
cat /tmp/db-ops-unreadable-dotenv.out >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
log notice "test passed"
|
||||||
@@ -0,0 +1,56 @@
|
|||||||
|
# shellcheck shell=dash
|
||||||
|
|
||||||
|
HECTIC_NAMESPACE=test-db-ops-secrets-load
|
||||||
|
|
||||||
|
PG_WORKING_DIR=$(mktemp -d)
|
||||||
|
export PG_WORKING_DIR PG_DATABASE=testdb PG_PORT=5432 PG_SHARED_PRELOAD_LIBRARIES=''
|
||||||
|
|
||||||
|
cleanup() {
|
||||||
|
postgres-cleanup >/dev/null 2>&1 || :
|
||||||
|
rm -rf "$PG_WORKING_DIR"
|
||||||
|
}
|
||||||
|
trap 'cleanup' EXIT INT TERM
|
||||||
|
|
||||||
|
if ! postgres-init; then
|
||||||
|
log error "postgres-init failed"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
PGURL="postgresql://$(id -un)@/testdb?host=${PG_WORKING_DIR}/sock&port=5432"
|
||||||
|
export PGURL
|
||||||
|
|
||||||
|
dotenv_file=$(mktemp)
|
||||||
|
printf 'TEST_SECRET=hello-world\nQUOTED_SECRET="quoted value"\nDELIMITER_SECRET=before$ps_env$after\n' > "$dotenv_file"
|
||||||
|
|
||||||
|
if ! db-ops secrets load --dotenv-file "$dotenv_file"; then
|
||||||
|
log error "db-ops secrets load failed"
|
||||||
|
rm -f "$dotenv_file"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
rm -f "$dotenv_file"
|
||||||
|
|
||||||
|
secret_value=$(psql "$PGURL" -v ON_ERROR_STOP=1 -tAc "SELECT value FROM hectic.secret WHERE key = 'TEST_SECRET';") || exit 1
|
||||||
|
[ "$secret_value" = "hello-world" ] || {
|
||||||
|
log error "expected TEST_SECRET to be loaded, got: $secret_value"
|
||||||
|
exit 1
|
||||||
|
}
|
||||||
|
|
||||||
|
quoted_value=$(psql "$PGURL" -v ON_ERROR_STOP=1 -tAc "SELECT value FROM hectic.secret WHERE key = 'QUOTED_SECRET';") || exit 1
|
||||||
|
[ "$quoted_value" = "quoted value" ] || {
|
||||||
|
log error "expected QUOTED_SECRET to strip outer quotes, got: $quoted_value"
|
||||||
|
exit 1
|
||||||
|
}
|
||||||
|
|
||||||
|
delimiter_value=$(psql "$PGURL" -v ON_ERROR_STOP=1 -tAc "SELECT value FROM hectic.secret WHERE key = 'DELIMITER_SECRET';") || exit 1
|
||||||
|
[ "$delimiter_value" = 'before$ps_env$after' ] || {
|
||||||
|
log error "expected DELIMITER_SECRET to preserve dollar-quote delimiter text, got: $delimiter_value"
|
||||||
|
exit 1
|
||||||
|
}
|
||||||
|
|
||||||
|
hectic_schema=$(psql "$PGURL" -v ON_ERROR_STOP=1 -tAc "SELECT count(*) FROM pg_namespace WHERE nspname='hectic';") || exit 1
|
||||||
|
[ "$hectic_schema" = 1 ] || {
|
||||||
|
log error "expected hectic schema to exist, got: $hectic_schema"
|
||||||
|
exit 1
|
||||||
|
}
|
||||||
|
|
||||||
|
log notice "test passed"
|
||||||
Reference in New Issue
Block a user