From 64cadf8a7d774a55a1e51c3d09b69858489049af Mon Sep 17 00:00:00 2001 From: Claude Date: Fri, 17 Apr 2026 06:53:40 +0000 Subject: [PATCH] =?UTF-8?q?fix:=20[nomad-step-3]=20S3.4=20=E2=80=94=20wire?= =?UTF-8?q?=20--with=20woodpecker=20+=20deploy=20ordering=20+=20OAuth=20se?= =?UTF-8?q?ed=20(#937)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-Authored-By: Claude Opus 4.6 (1M context) --- bin/disinto | 134 ++++++++++++++++++++++++---------- tests/disinto-init-nomad.bats | 39 +++++++++- 2 files changed, 135 insertions(+), 38 deletions(-) diff --git a/bin/disinto b/bin/disinto index 5f57927..39817cf 100755 --- a/bin/disinto +++ b/bin/disinto @@ -82,7 +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) + --with (nomad) Deploy services: forgejo,woodpecker[,...] (S1.3, S3.4) --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) @@ -784,16 +784,24 @@ _disinto_init_nomad() { if [ -n "$with_services" ]; then # Vault seed plan (S2.6, #928): one line per service whose - # tools/vault-seed-.sh ships. Services without a seeder are - # silently skipped — the real-run loop below mirrors this, - # making `--with woodpecker` in Step 3 auto-invoke - # tools/vault-seed-woodpecker.sh once that file lands without - # any further change to bin/disinto. + # tools/vault-seed-.sh ships. Sub-services (woodpecker-server, + # woodpecker-agent) map to their parent seeder (vault-seed-woodpecker.sh). + # Deduplicated so the seeder runs once even when both sub-services + # are present. local seed_hdr_printed=false + local _seed_seen="" local IFS=',' for svc in $with_services; do svc=$(echo "$svc" | xargs) # trim whitespace - local seed_script="${FACTORY_ROOT}/tools/vault-seed-${svc}.sh" + # Map sub-services to parent seed name + local seed_name="$svc" + case "$svc" in + woodpecker-server|woodpecker-agent) seed_name="woodpecker" ;; + esac + # Deduplicate + if echo ",$_seed_seen," | grep -q ",$seed_name,"; then continue; fi + _seed_seen="${_seed_seen:+${_seed_seen},}${seed_name}" + local seed_script="${FACTORY_ROOT}/tools/vault-seed-${seed_name}.sh" if [ -x "$seed_script" ]; then if [ "$seed_hdr_printed" = false ]; then echo "── Vault seed dry-run ─────────────────────────────────" @@ -806,16 +814,18 @@ _disinto_init_nomad() { echo "── Deploy services dry-run ────────────────────────────" echo "[deploy] services to deploy: ${with_services}" - 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 + + # Build ordered deploy list: only include services present in with_services + local DEPLOY_ORDER="" + for ordered_svc in forgejo woodpecker-server woodpecker-agent; do + if echo ",$with_services," | grep -q ",$ordered_svc,"; then + DEPLOY_ORDER="${DEPLOY_ORDER:+${DEPLOY_ORDER} }${ordered_svc}" + fi + done + echo "[deploy] deployment order: ${DEPLOY_ORDER}" + + local IFS=' ' + for svc in $DEPLOY_ORDER; do local jobspec_path="${FACTORY_ROOT}/nomad/jobs/${svc}.hcl" if [ ! -f "$jobspec_path" ]; then echo "Error: jobspec not found: ${jobspec_path}" >&2 @@ -937,18 +947,27 @@ _disinto_init_nomad() { # sets VAULT_ADDR in the child process regardless of sudoers policy. if [ -n "$with_services" ]; then local vault_addr="${VAULT_ADDR:-http://127.0.0.1:8200}" + local _seed_seen="" local IFS=',' for svc in $with_services; do svc=$(echo "$svc" | xargs) # trim whitespace - local seed_script="${FACTORY_ROOT}/tools/vault-seed-${svc}.sh" + # Map sub-services to parent seed name (S3.4) + local seed_name="$svc" + case "$svc" in + woodpecker-server|woodpecker-agent) seed_name="woodpecker" ;; + esac + # Deduplicate + if echo ",$_seed_seen," | grep -q ",$seed_name,"; then continue; fi + _seed_seen="${_seed_seen:+${_seed_seen},}${seed_name}" + local seed_script="${FACTORY_ROOT}/tools/vault-seed-${seed_name}.sh" if [ -x "$seed_script" ]; then echo "" - echo "── Seeding Vault for ${svc} ───────────────────────────" + echo "── Seeding Vault for ${seed_name} ───────────────────────────" if [ "$(id -u)" -eq 0 ]; then VAULT_ADDR="$vault_addr" "$seed_script" || exit $? else if ! command -v sudo >/dev/null 2>&1; then - echo "Error: vault-seed-${svc}.sh must run as root and sudo is not installed" >&2 + echo "Error: vault-seed-${seed_name}.sh must run as root and sudo is not installed" >&2 exit 1 fi sudo -n -- env "VAULT_ADDR=$vault_addr" "$seed_script" || exit $? @@ -961,23 +980,18 @@ _disinto_init_nomad() { 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 + + # Build ordered deploy list (S3.4): forgejo → woodpecker-server → woodpecker-agent + local DEPLOY_ORDER="" + for ordered_svc in forgejo woodpecker-server woodpecker-agent; do + if echo ",$with_services," | grep -q ",$ordered_svc,"; then + DEPLOY_ORDER="${DEPLOY_ORDER:+${DEPLOY_ORDER} }${ordered_svc}" fi - # Validate known services FIRST (before jobspec check) - case "$svc" in - forgejo) ;; - *) - echo "Error: unknown service '${svc}' — known: forgejo" >&2 - exit 1 - ;; - esac + done + + local -a deploy_cmd=("$deploy_sh") + local IFS=' ' + for svc in $DEPLOY_ORDER; do # Check jobspec exists local jobspec_path="${FACTORY_ROOT}/nomad/jobs/${svc}.hcl" if [ ! -f "$jobspec_path" ]; then @@ -1012,9 +1026,15 @@ _disinto_init_nomad() { echo "Imported: (none — seed kv/disinto/* manually before deploying secret-dependent services)" fi echo "Deployed: ${with_services}" - if echo "$with_services" | grep -q "forgejo"; then + if echo ",$with_services," | grep -q ",forgejo,"; then echo "Ports: forgejo: 3000" fi + if echo ",$with_services," | grep -q ",woodpecker-server,"; then + echo " woodpecker-server: 8000" + fi + if echo ",$with_services," | grep -q ",woodpecker-agent,"; then + echo " woodpecker-agent: (agent connected)" + fi echo "────────────────────────────────────────────────────────" fi @@ -1100,6 +1120,46 @@ disinto_init() { exit 1 fi + # Normalize --with services (S3.4): expand 'woodpecker' shorthand to + # 'woodpecker-server,woodpecker-agent', auto-include forgejo when + # woodpecker is requested (OAuth dependency), and validate all names. + if [ -n "$with_services" ]; then + # Expand 'woodpecker' (bare) → 'woodpecker-server,woodpecker-agent'. + # Must not match already-expanded 'woodpecker-server'/'woodpecker-agent'. + local expanded="" + local IFS=',' + for _svc in $with_services; do + _svc=$(echo "$_svc" | xargs) + case "$_svc" in + woodpecker) _svc="woodpecker-server,woodpecker-agent" ;; + esac + expanded="${expanded:+${expanded},}${_svc}" + done + with_services="$expanded" + unset IFS + + # Auto-include forgejo when woodpecker is requested + if echo ",$with_services," | grep -q ",woodpecker-server,\|,woodpecker-agent," \ + && ! echo ",$with_services," | grep -q ",forgejo,"; then + echo "Note: --with woodpecker implies --with forgejo (OAuth dependency)" + with_services="forgejo,${with_services}" + fi + + # Validate all service names are known + local IFS=',' + for _svc in $with_services; do + _svc=$(echo "$_svc" | xargs) + case "$_svc" in + forgejo|woodpecker-server|woodpecker-agent) ;; + *) + echo "Error: unknown service '${_svc}' — known: forgejo, woodpecker-server, woodpecker-agent" >&2 + exit 1 + ;; + esac + done + unset IFS + fi + # --import-* flag validation (S2.5). These three flags form an import # triple and must be consistent before dispatch: sops encryption is # useless without the age key to decrypt it, so either both --import-sops diff --git a/tests/disinto-init-nomad.bats b/tests/disinto-init-nomad.bats index 21f4303..e27276e 100644 --- a/tests/disinto-init-nomad.bats +++ b/tests/disinto-init-nomad.bats @@ -215,7 +215,44 @@ setup_file() { run "$DISINTO_BIN" init placeholder/repo --backend=nomad --with unknown-service --dry-run [ "$status" -ne 0 ] [[ "$output" == *"unknown service"* ]] - [[ "$output" == *"known: forgejo"* ]] + [[ "$output" == *"known: forgejo, woodpecker-server, woodpecker-agent"* ]] +} + +# S3.4: woodpecker auto-expansion and forgejo auto-inclusion +@test "disinto init --backend=nomad --with woodpecker auto-expands to server+agent" { + run "$DISINTO_BIN" init placeholder/repo --backend=nomad --with woodpecker --dry-run + [ "$status" -eq 0 ] + [[ "$output" == *"services to deploy: forgejo,woodpecker-server,woodpecker-agent"* ]] + [[ "$output" == *"deployment order: forgejo woodpecker-server woodpecker-agent"* ]] +} + +@test "disinto init --backend=nomad --with woodpecker auto-includes forgejo with note" { + run "$DISINTO_BIN" init placeholder/repo --backend=nomad --with woodpecker --dry-run + [ "$status" -eq 0 ] + [[ "$output" == *"Note: --with woodpecker implies --with forgejo"* ]] +} + +@test "disinto init --backend=nomad --with forgejo,woodpecker expands woodpecker" { + run "$DISINTO_BIN" init placeholder/repo --backend=nomad --with forgejo,woodpecker --dry-run + [ "$status" -eq 0 ] + # Order follows input: forgejo first, then woodpecker expanded + [[ "$output" == *"services to deploy: forgejo,woodpecker-server,woodpecker-agent"* ]] + [[ "$output" == *"deployment order: forgejo woodpecker-server woodpecker-agent"* ]] +} + +@test "disinto init --backend=nomad --with woodpecker seeds both forgejo and woodpecker" { + run "$DISINTO_BIN" init placeholder/repo --backend=nomad --with woodpecker --dry-run + [ "$status" -eq 0 ] + [[ "$output" == *"tools/vault-seed-forgejo.sh --dry-run"* ]] + [[ "$output" == *"tools/vault-seed-woodpecker.sh --dry-run"* ]] +} + +@test "disinto init --backend=nomad --with forgejo,woodpecker deploys all three services" { + run "$DISINTO_BIN" init placeholder/repo --backend=nomad --with forgejo,woodpecker --dry-run + [ "$status" -eq 0 ] + [[ "$output" == *"[deploy] [dry-run] nomad job validate"*"forgejo.hcl"* ]] + [[ "$output" == *"[deploy] [dry-run] nomad job validate"*"woodpecker-server.hcl"* ]] + [[ "$output" == *"[deploy] [dry-run] nomad job validate"*"woodpecker-agent.hcl"* ]] } @test "disinto init --backend=nomad --with forgejo (flag=value syntax) works" {