fix: [nomad-step-2] S2.5 — bin/disinto init --import-env / --import-sops / --age-key wire-up (#883) #907

Merged
dev-bot merged 2 commits from fix/issue-883-2 into main 2026-04-16 19:44:45 +00:00
3 changed files with 67 additions and 4 deletions
Showing only changes of commit ece5d9b6cc - Show all commits

View file

@ -684,13 +684,21 @@ _disinto_init_nomad() {
exit 1 exit 1
fi fi
# Step 2/3/4 scripts must exist as soon as any --import-* flag is set, # --empty short-circuits after cluster-up: no policies, no auth, no
# since we unconditionally invoke policies+auth and optionally import. # import, no deploy. It's the "cluster-only escape hatch" for debugging
# (docs/nomad-migration.md). Caller-side validation already rejects
# --empty combined with --with or any --import-* flag, so reaching
# this branch with those set is a bug in the caller.
#
# On the default (non-empty) path, vault-apply-policies.sh and
# vault-nomad-auth.sh are invoked unconditionally — they are idempotent
# and cheap to re-run, and subsequent --with deployments depend on
# them. vault-import.sh is invoked only when an --import-* flag is set.
local import_any=false local import_any=false
if [ -n "$import_env" ] || [ -n "$import_sops" ]; then if [ -n "$import_env" ] || [ -n "$import_sops" ]; then
import_any=true import_any=true
fi fi
if [ "$import_any" = true ]; then if [ "$empty" != "true" ]; then
if [ ! -x "$vault_policies_sh" ]; then if [ ! -x "$vault_policies_sh" ]; then
echo "Error: ${vault_policies_sh} not found or not executable" >&2 echo "Error: ${vault_policies_sh} not found or not executable" >&2
exit 1 exit 1
@ -699,7 +707,7 @@ _disinto_init_nomad() {
echo "Error: ${vault_auth_sh} not found or not executable" >&2 echo "Error: ${vault_auth_sh} not found or not executable" >&2
exit 1 exit 1
fi fi
if [ ! -x "$vault_import_sh" ]; then if [ "$import_any" = true ] && [ ! -x "$vault_import_sh" ]; then
echo "Error: ${vault_import_sh} not found or not executable" >&2 echo "Error: ${vault_import_sh} not found or not executable" >&2
exit 1 exit 1
fi fi
@ -722,6 +730,13 @@ _disinto_init_nomad() {
"${cmd[@]}" || true "${cmd[@]}" || true
echo "" echo ""
# --empty skips policies/auth/import/deploy — cluster-up only, no
# workloads. The operator-visible dry-run plan must match the real
# run, so short-circuit here too.
if [ "$empty" = "true" ]; then
exit 0
fi
# Vault policies + auth are invoked on every nomad real-run path # Vault policies + auth are invoked on every nomad real-run path
# regardless of --import-* flags (they're idempotent; S2.1 + S2.3). # regardless of --import-* flags (they're idempotent; S2.1 + S2.3).
# Mirror that ordering in the dry-run plan so the operator sees the # Mirror that ordering in the dry-run plan so the operator sees the
@ -793,6 +808,12 @@ _disinto_init_nomad() {
sudo -n -- "${cluster_cmd[@]}" || exit $? sudo -n -- "${cluster_cmd[@]}" || exit $?
fi fi
# --empty short-circuits here: cluster-up only, no policies/auth/import
# and no deploy. Matches the dry-run plan above and the docs/runbook.
if [ "$empty" = "true" ]; then
exit 0
fi
# Apply Vault policies (S2.1) — idempotent, safe to re-run. # Apply Vault policies (S2.1) — idempotent, safe to re-run.
echo "" echo ""
echo "── Applying Vault policies ────────────────────────────" echo "── Applying Vault policies ────────────────────────────"
@ -1005,6 +1026,15 @@ disinto_init() {
exit 1 exit 1
fi fi
# --empty is the cluster-only escape hatch — it skips policies, auth,
# import, and deploy. Pairing it with --import-* silently does nothing,
# which is a worse failure mode than a clear error. Reject explicitly.
if [ "$empty" = true ] \
&& { [ -n "$import_env" ] || [ -n "$import_sops" ] || [ -n "$age_key" ]; }; then
echo "Error: --empty and --import-env/--import-sops/--age-key are mutually exclusive" >&2
exit 1
fi
# Dispatch on backend — the nomad path runs lib/init/nomad/cluster-up.sh # Dispatch on backend — the nomad path runs lib/init/nomad/cluster-up.sh
# (S0.4). The default and --empty variants are identical today; Step 1 # (S0.4). The default and --empty variants are identical today; Step 1
# will branch on $empty to add job deployment to the default path. # will branch on $empty to add job deployment to the default path.

View file

@ -60,6 +60,9 @@ This runs, in order:
- `--age-key` without `--import-sops` → error. - `--age-key` without `--import-sops` → error.
- `--import-env` alone (no sops) → OK (imports just the plaintext `.env`). - `--import-env` alone (no sops) → OK (imports just the plaintext `.env`).
- `--backend=docker` with any `--import-*` flag → error. - `--backend=docker` with any `--import-*` flag → error.
- `--empty` with any `--import-*` flag → error (mutually exclusive: `--empty`
skips the import step, so pairing them silently discards the import
intent).
## Idempotency ## Idempotency

View file

@ -280,3 +280,33 @@ setup_file() {
[ "$status" -eq 0 ] [ "$status" -eq 0 ]
[[ "$output" == *"env file: /tmp/.env"* ]] [[ "$output" == *"env file: /tmp/.env"* ]]
} }
# --empty short-circuits after cluster-up: no policies, no auth, no
# import, no deploy. The dry-run plan must match that — cluster-up plan
# appears, but none of the S2.x section banners do.
@test "disinto init --backend=nomad --empty --dry-run skips policies/auth/import sections" {
run "$DISINTO_BIN" init placeholder/repo --backend=nomad --empty --dry-run
[ "$status" -eq 0 ]
# Cluster-up still runs (it's what --empty brings up).
[[ "$output" == *"Cluster-up dry-run"* ]]
# Policies + auth + import must NOT appear under --empty.
[[ "$output" != *"Vault policies dry-run"* ]]
[[ "$output" != *"Vault auth dry-run"* ]]
[[ "$output" != *"Vault import dry-run"* ]]
[[ "$output" != *"no --import-env/--import-sops"* ]]
}
# --empty + any --import-* flag silently does nothing (import is skipped),
# so the CLI rejects the combination up front rather than letting it
# look like the import "succeeded".
@test "disinto init --backend=nomad --empty --import-env errors" {
run "$DISINTO_BIN" init placeholder/repo --backend=nomad --empty --import-env /tmp/.env --dry-run
[ "$status" -ne 0 ]
[[ "$output" == *"--empty and --import-env/--import-sops/--age-key are mutually exclusive"* ]]
}
@test "disinto init --backend=nomad --empty --import-sops --age-key errors" {
run "$DISINTO_BIN" init placeholder/repo --backend=nomad --empty --import-sops /tmp/.env.vault.enc --age-key /tmp/keys.txt --dry-run
[ "$status" -ne 0 ]
[[ "$output" == *"--empty and --import-env/--import-sops/--age-key are mutually exclusive"* ]]
}