From 4a1b31af5b845a1c1046046531e42d2908558a43 Mon Sep 17 00:00:00 2001 From: Agent Date: Thu, 16 Apr 2026 10:54:46 +0000 Subject: [PATCH] =?UTF-8?q?fix:=20[nomad-step-1]=20S1.3=20=E2=80=94=20wire?= =?UTF-8?q?=20--with=20forgejo=20into=20bin/disinto=20init=20--backend=3Dn?= =?UTF-8?q?omad=20(#842)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- bin/disinto | 134 +++++++++++++++--- nomad/jobs/{forgejo.nomad.hcl => forgejo.hcl} | 2 +- tests/disinto-init-nomad.bats | 48 +++++++ 3 files changed, 160 insertions(+), 24 deletions(-) rename nomad/jobs/{forgejo.nomad.hcl => forgejo.hcl} (98%) diff --git a/bin/disinto b/bin/disinto index 4f06b5e..1d5e01e 100755 --- a/bin/disinto +++ b/bin/disinto @@ -82,6 +82,7 @@ Init options: --ci-id Woodpecker CI repo ID (default: 0 = no CI) --forge-url Forge base URL (default: http://localhost:3000) --backend Orchestration backend: docker (default) | nomad + --with (nomad) Deploy services: forgejo[,...] (S1.3) --empty (nomad) Bring up cluster only, no jobs (S0.4) --bare Skip compose generation (bare-metal setup) --build Use local docker build instead of registry images (dev mode) @@ -662,14 +663,20 @@ prompt_admin_password() { # init run); operators running without sudo-NOPASSWD should invoke # `sudo disinto init ...` directly. _disinto_init_nomad() { - local dry_run="${1:-false}" empty="${2:-false}" + local dry_run="${1:-false}" empty="${2:-false}" with_services="${3:-}" local cluster_up="${FACTORY_ROOT}/lib/init/nomad/cluster-up.sh" + local deploy_sh="${FACTORY_ROOT}/lib/init/nomad/deploy.sh" if [ ! -x "$cluster_up" ]; then echo "Error: ${cluster_up} not found or not executable" >&2 exit 1 fi + if [ -n "$with_services" ] && [ ! -x "$deploy_sh" ]; then + echo "Error: ${deploy_sh} not found or not executable" >&2 + exit 1 + fi + # --empty and default both invoke cluster-up today. Log the requested # mode so the dispatch is visible in factory bootstrap logs — Step 1 # will branch on $empty to gate the job-deployment path. @@ -679,31 +686,106 @@ _disinto_init_nomad() { echo "nomad backend: default (cluster-up; jobs deferred to Step 1)" fi - # Dry-run forwards straight through; cluster-up.sh prints its own step - # list and exits 0 without touching the box. - local -a cmd=("$cluster_up") + # Dry-run: print cluster-up plan + deploy.sh plan if [ "$dry_run" = "true" ]; then - cmd+=("--dry-run") - "${cmd[@]}" - exit $? + echo "" + echo "── Cluster-up dry-run ─────────────────────────────────" + local -a cmd=("$cluster_up" "--dry-run") + "${cmd[@]}" || true + echo "" + + if [ -n "$with_services" ]; then + echo "── Deploy services dry-run ────────────────────────────" + echo "[deploy] services to deploy: ${with_services}" + local IFS=',' + for svc in $with_services; do + svc=$(echo "$svc" | xargs) # trim whitespace + # Validate known services first + case "$svc" in + forgejo) ;; + *) + echo "Error: unknown service '${svc}' — known: forgejo" >&2 + exit 1 + ;; + esac + local jobspec_path="${FACTORY_ROOT}/nomad/jobs/${svc}.hcl" + if [ ! -f "$jobspec_path" ]; then + echo "Error: jobspec not found: ${jobspec_path}" >&2 + exit 1 + fi + echo "[deploy] [dry-run] nomad job validate ${jobspec_path}" + echo "[deploy] [dry-run] nomad job run -detach ${jobspec_path}" + done + echo "[deploy] dry-run complete" + fi + exit 0 fi - # Real run — needs root. Invoke via sudo if we're not already root so - # the command's exit code propagates directly. We don't distinguish - # "sudo denied" from "cluster-up.sh failed" here; both surface as a - # non-zero exit, and cluster-up.sh's own error messages cover the - # latter case. - local rc=0 + # Real run: cluster-up + deploy services + local -a cluster_cmd=("$cluster_up") if [ "$(id -u)" -eq 0 ]; then - "${cmd[@]}" || rc=$? + "${cluster_cmd[@]}" || exit $? else if ! command -v sudo >/dev/null 2>&1; then echo "Error: cluster-up.sh must run as root and sudo is not installed" >&2 exit 1 fi - sudo -n -- "${cmd[@]}" || rc=$? + sudo -n -- "${cluster_cmd[@]}" || exit $? fi - exit "$rc" + + # Deploy services if requested + if [ -n "$with_services" ]; then + echo "" + echo "── Deploying services ─────────────────────────────────" + local -a deploy_cmd=("$deploy_sh") + # Split comma-separated service list into positional args + local IFS=',' + for svc in $with_services; do + svc=$(echo "$svc" | xargs) # trim whitespace + if ! echo "$svc" | grep -qE '^[a-zA-Z0-9_-]+$'; then + echo "Error: invalid service name '${svc}' — must match ^[a-zA-Z0-9_-]+$" >&2 + exit 1 + fi + # Validate known services FIRST (before jobspec check) + case "$svc" in + forgejo) ;; + *) + echo "Error: unknown service '${svc}' — known: forgejo" >&2 + exit 1 + ;; + esac + # Check jobspec exists + local jobspec_path="${FACTORY_ROOT}/nomad/jobs/${svc}.hcl" + if [ ! -f "$jobspec_path" ]; then + echo "Error: jobspec not found: ${jobspec_path}" >&2 + exit 1 + fi + deploy_cmd+=("$svc") + done + deploy_cmd+=("--dry-run") # deploy.sh supports --dry-run + + if [ "$(id -u)" -eq 0 ]; then + "${deploy_cmd[@]}" || exit $? + else + if ! command -v sudo >/dev/null 2>&1; then + echo "Error: deploy.sh must run as root and sudo is not installed" >&2 + exit 1 + fi + sudo -n -- "${deploy_cmd[@]}" || exit $? + fi + + # Print final summary + echo "" + echo "── Summary ────────────────────────────────────────────" + echo "Cluster: Nomad+Vault cluster is up" + echo "Deployed: ${with_services}" + if echo "$with_services" | grep -q "forgejo"; then + echo "Ports: forgejo: 3000" + fi + echo "────────────────────────────────────────────────────────" + fi + + exit 0 } disinto_init() { @@ -721,7 +803,7 @@ disinto_init() { fi # Parse flags - local branch="" repo_root="" ci_id="0" auto_yes=false forge_url_flag="" bare=false rotate_tokens=false use_build=false dry_run=false backend="docker" empty=false + local branch="" repo_root="" ci_id="0" auto_yes=false forge_url_flag="" bare=false rotate_tokens=false use_build=false dry_run=false backend="docker" empty=false with_services="" while [ $# -gt 0 ]; do case "$1" in --branch) branch="$2"; shift 2 ;; @@ -730,6 +812,8 @@ disinto_init() { --forge-url) forge_url_flag="$2"; shift 2 ;; --backend) backend="$2"; shift 2 ;; --backend=*) backend="${1#--backend=}"; shift ;; + --with) with_services="$2"; shift 2 ;; + --with=*) with_services="${1#--with=}"; shift ;; --bare) bare=true; shift ;; --build) use_build=true; shift ;; --empty) empty=true; shift ;; @@ -756,11 +840,15 @@ disinto_init() { exit 1 fi - # --empty is nomad-only today (the docker path has no concept of an - # "empty cluster"). Reject explicitly rather than letting it silently - # do nothing on --backend=docker. - if [ "$empty" = true ] && [ "$backend" != "nomad" ]; then - echo "Error: --empty is only valid with --backend=nomad" >&2 + # --with requires --backend=nomad + if [ -n "$with_services" ] && [ "$backend" != "nomad" ]; then + echo "Error: --with requires --backend=nomad" >&2 + exit 1 + fi + + # --empty and --with are mutually exclusive + if [ "$empty" = true ] && [ -n "$with_services" ]; then + echo "Error: --empty and --with are mutually exclusive" >&2 exit 1 fi @@ -768,7 +856,7 @@ disinto_init() { # (S0.4). The default and --empty variants are identical today; Step 1 # will branch on $empty to add job deployment to the default path. if [ "$backend" = "nomad" ]; then - _disinto_init_nomad "$dry_run" "$empty" + _disinto_init_nomad "$dry_run" "$empty" "$with_services" # shellcheck disable=SC2317 # _disinto_init_nomad always exits today; # `return` is defensive against future refactors. return diff --git a/nomad/jobs/forgejo.nomad.hcl b/nomad/jobs/forgejo.hcl similarity index 98% rename from nomad/jobs/forgejo.nomad.hcl rename to nomad/jobs/forgejo.hcl index c7a0326..b2c057f 100644 --- a/nomad/jobs/forgejo.nomad.hcl +++ b/nomad/jobs/forgejo.hcl @@ -1,5 +1,5 @@ # ============================================================================= -# nomad/jobs/forgejo.nomad.hcl — Forgejo git server (Nomad service job) +# nomad/jobs/forgejo.hcl — Forgejo git server (Nomad service job) # # Part of the Nomad+Vault migration (S1.1, issue #840). First jobspec to # land under nomad/jobs/ — proves the docker driver + host_volume plumbing diff --git a/tests/disinto-init-nomad.bats b/tests/disinto-init-nomad.bats index 5b2648b..8616e2d 100644 --- a/tests/disinto-init-nomad.bats +++ b/tests/disinto-init-nomad.bats @@ -143,3 +143,51 @@ setup_file() { [[ "$output" == *"repo URL required"* ]] [[ "$output" != *"Unknown option"* ]] } + +# ── --with flag tests ───────────────────────────────────────────────────────── + +@test "disinto init --backend=nomad --with forgejo --dry-run prints deploy plan" { + run "$DISINTO_BIN" init placeholder/repo --backend=nomad --with forgejo --dry-run + [ "$status" -eq 0 ] + [[ "$output" == *"services to deploy: forgejo"* ]] + [[ "$output" == *"[deploy] [dry-run] nomad job validate"* ]] + [[ "$output" == *"[deploy] [dry-run] nomad job run -detach"* ]] + [[ "$output" == *"[deploy] dry-run complete"* ]] +} + +@test "disinto init --backend=nomad --with forgejo,forgejo --dry-run handles comma-separated services" { + run "$DISINTO_BIN" init placeholder/repo --backend=nomad --with forgejo,forgejo --dry-run + [ "$status" -eq 0 ] + [[ "$output" == *"services to deploy: forgejo,forgejo"* ]] +} + +@test "disinto init --backend=docker --with forgejo errors with '--with requires --backend=nomad'" { + run "$DISINTO_BIN" init placeholder/repo --backend=docker --with forgejo + [ "$status" -ne 0 ] + [[ "$output" == *"--with requires --backend=nomad"* ]] +} + +@test "disinto init --backend=nomad --empty --with forgejo errors with mutually exclusive" { + run "$DISINTO_BIN" init placeholder/repo --backend=nomad --empty --with forgejo + [ "$status" -ne 0 ] + [[ "$output" == *"--empty and --with are mutually exclusive"* ]] +} + +@test "disinto init --backend=nomad --with unknown-service errors with unknown service" { + run "$DISINTO_BIN" init placeholder/repo --backend=nomad --with unknown-service --dry-run + [ "$status" -ne 0 ] + [[ "$output" == *"unknown service"* ]] + [[ "$output" == *"known: forgejo"* ]] +} + +@test "disinto init --backend=nomad --with forgejo (flag=value syntax) works" { + run "$DISINTO_BIN" init placeholder/repo --backend=nomad --with=forgejo --dry-run + [ "$status" -eq 0 ] + [[ "$output" == *"services to deploy: forgejo"* ]] +} + +@test "disinto init --backend=nomad --with forgejo --empty --dry-run rejects in any order" { + run "$DISINTO_BIN" init placeholder/repo --with forgejo --backend=nomad --empty --dry-run + [ "$status" -ne 0 ] + [[ "$output" == *"--empty and --with are mutually exclusive"* ]] +}