diff --git a/machine-docs/JOURNAL-2.md b/machine-docs/JOURNAL-2.md index be242fb..afbcb26 100644 --- a/machine-docs/JOURNAL-2.md +++ b/machine-docs/JOURNAL-2.md @@ -796,3 +796,33 @@ LIFTING). After cc-ci is healthy I can: 3. Resume broad heavy-recipe coverage (immich, lasuite-meet) with real disk headroom. Note: with 70GB, I can also be less aggressive about teardown/prune churn between heavy runs. + +--- + +## 2026-05-29 — lasuite-drive Q3.2a Step 0: root-cause failure logs captured (BEFORE any fix) + +Resuming Q3.2a (plan-lasuite-drive-oidc-robustness.md) after Phase 2pc DONE. The Adversary's +cold-verify criterion #1 requires real captured failure logs before any fix. Captured from the +flaky run-4 deploy (`/root/.abra/logs/default/lasu-288dfd...2026-05-29T062401Z`, the +`abra app deploy --force --chaos` OIDC-setup redeploy that exited 1 / "FATA deploy failed"): + +1. **gunicorn perms race** — `backend [1] [ERROR] Control server error: [Errno 13] Permission + denied: '/.gunicorn'`. gunicorn tries to create its control-server temp dir under HOME=`/` + (not writable). (Part B fix: set perms / writable HOME in entrypoint before exec gunicorn.) +2. **WOPI-discovery race** — `celery RuntimeError: status code 404 return by discovery url for + wopi client collabora is invalid` at `/app/wopi/tasks/configure_wopi.py:53`. The celery + `configure_wopi_clients` task hits collabora's discovery URL at boot (06:21:54) while collabora + is still caching its 132+ l10n files (finishes ~06:24) → 404 → task raises. (Part B fix: + collabora WOPI healthcheck gating + backend retry/backoff on discovery.) +3. **transient db-not-ready** — `db FATAL: database "drive" does not exist` + celery + `Could not connect to database: failed to resolve host 'db'` — early-boot DNS/init races that + self-heal; harmless on a fresh deploy with the full TIMEOUT window. + +**Key observation that shapes the fix:** the FIRST install deploy converges reliably **every** run +(install: pass in runs 1–4, incl. run 4). Only the post-install in-place `--force --chaos` redeploy +(applied to push the OIDC env) is flaky. The OIDC env touches ONLY backend/app — re-converging +collabora/onlyoffice/minio is unnecessary exposure. → **Part A: wire OIDC into the .env at INSTALL +time (between `abra app new` and the single `abra app deploy`) so the recipe deploys ONCE with OIDC +already set; no post-deploy reconverge.** keycloak is live-warm (always up), so the per-run realm is +a lightweight API call provisioned before the single deploy. Part B (recipe robustness PR) remains +the deeper fix so ANY reconverge (incl. the upgrade-tier prev→PR-head crossover) is race-free. diff --git a/runner/run_recipe_ci.py b/runner/run_recipe_ci.py index 9376945..eee5d31 100644 --- a/runner/run_recipe_ci.py +++ b/runner/run_recipe_ci.py @@ -194,7 +194,7 @@ def _load_meta(recipe: str) -> dict: ns: dict = {} with open(path) as fh: exec(compile(fh.read(), path, "exec"), ns) # noqa: S102 (trusted, in-repo) - for k in list(meta) + ["BACKUP_CAPABLE", "SKIP_GENERIC"]: + for k in list(meta) + ["BACKUP_CAPABLE", "SKIP_GENERIC", "OIDC_AT_INSTALL"]: if k in ns: meta[k] = ns[k] return meta @@ -361,6 +361,45 @@ def _enrich_deps_with_sso(parent_recipe: str, parent_domain: str, deps_list) -> return out +def _provision_deps(recipe: str, domain: str, ref: str | None, declared: list[str]) -> dict[str, dict]: + """Provision a run's declared deps and write `$CCCI_DEPS_FILE`; return the recipe→entry deps_state. + + Splits deps into live-warm (shared provider at a stable domain + a per-run realm) vs cold + (co-deployed per run), provisions each dep's SSO realm/client/user, and persists the enriched + dict the `setup_custom_tests.sh`/`install_steps.sh` hooks + dependent tests read. Raises on any + failure (the caller marks deps-not-ready). Used by BOTH wiring paths: + - post-deploy (legacy): provision AFTER generic tiers, then `setup_custom_tests.sh` does an + in-place OIDC redeploy. + - install-time (`OIDC_AT_INSTALL`, Q3.2a): provision BEFORE the single deploy so the + install-tier `install_steps.sh` hook wires OIDC env into that one deploy — no reconverge. + """ + warm_deps, cold_deps = [], [] + for d in declared: + wd = warm.warm_domain(d) + if wd and warm.is_warm_up(d, wd): + warm_deps.append(d) + else: + if wd: + print(f" dep: {d} warm provider {wd} not up — cold fallback", flush=True) + cold_deps.append(d) + dep_metas = {d: _load_meta(d) for d in cold_deps} + deps_list = ( + deps_mod.deploy_deps(recipe, os.environ.get("PR", "0"), ref, cold_deps, meta_for=dep_metas) + if cold_deps + else [] + ) + for d in warm_deps: + wd = warm.warm_domain(d) + reaped = warm.reap_orphan_realms(d, wd) + if reaped: + print(f" dep: reaped {len(reaped)} orphan realm(s) on warm {d}: {reaped}", flush=True) + deps_list.append({"recipe": d, "domain": wd, "warm": True}) + print(f" dep: using live-warm {d} @ {wd} (per-run realm)", flush=True) + deps_state = _enrich_deps_with_sso(recipe, domain, deps_list) + deps_mod.write_run_state(deps_state) + return deps_state + + def _run_setup_custom_tests_hook(recipe: str, domain: str, deps_file: str) -> None: """Run `tests//setup_custom_tests.sh` if present (operator-2026-05-28 SSO-dep plan §3.2). The hook reads `$CCCI_DEPS_FILE`, sets OIDC env via `abra app config set` + secret @@ -712,8 +751,14 @@ def main() -> int: os.remove(skipfile) os.environ["CCCI_DEPS_SKIP_REPORT"] = skipfile declared = deps_mod.declared_deps(recipe) + # Q3.2a: a recipe that tolerates OIDC env at first boot AND whose deps are live-warm wires OIDC + # at INSTALL time (provision the realm BEFORE the single deploy; install_steps.sh writes the env + # into it) instead of the post-deploy in-place `--chaos` redeploy — which is flaky on the heavy + # 12-service lasuite-drive stack (collabora WOPI race; see JOURNAL Step 0). Opt-in per recipe. + oidc_at_install = bool(meta.get("OIDC_AT_INSTALL")) and bool(declared) if declared: - print(f"\n===== DEPS declared (deploy AFTER generic tiers): {declared} =====", flush=True) + when = "BEFORE deploy (install-time OIDC)" if oidc_at_install else "AFTER generic tiers" + print(f"\n===== DEPS declared (provision {when}): {declared} =====", flush=True) deps_state: dict[str, dict] = {} # new shape: recipe→entry dict (sso-dep plan §1) deps_ready = True deps_not_ready_reason: str = "" @@ -722,6 +767,20 @@ def main() -> int: lifecycle.janitor() dep_teardown_error: str | None = None try: + # ---- (Q3.2a) install-time OIDC: provision the warm-dep realm BEFORE the single deploy so + # install_steps.sh can read $CCCI_DEPS_FILE and wire the OIDC env into that one deploy. On + # failure we mark deps-not-ready but STILL deploy the recipe alone (install_steps.sh no-ops + # on an empty deps file) so the generic tiers run; the OIDC custom test then skips → F2-11. ---- + if oidc_at_install: + print(f"\n===== install-time OIDC: provisioning deps {declared} BEFORE deploy =====", flush=True) + try: + deps_state = _provision_deps(recipe, domain, ref, declared) + print(" install-time OIDC: deps provisioned; install_steps.sh will wire OIDC env", flush=True) + except Exception as e: # noqa: BLE001 — isolated; recipe still deploys, OIDC test skips + deps_ready = False + deps_not_ready_reason = _scrub(str(e))[:300] + print(f"!! install-time dep provisioning failed (deps-not-ready): {deps_not_ready_reason}", flush=True) + # ---- deploy RECIPE FIRST, alone (no deps yet — generic tiers run recipe-only) ---- try: lifecycle.deploy_app( @@ -784,44 +843,12 @@ def main() -> int: # setup_custom_tests.sh hook + in-place redeploy. Failure here marks deps-not-ready # but does NOT abort the run — @pytest.mark.requires_deps tests skip with reason; # non-deps custom tests still run normally. - if declared: + if declared and not oidc_at_install: + # LEGACY post-deploy path: provision deps AFTER generic tiers, then wire OIDC env + # into the parent via the setup_custom_tests.sh hook + an in-place `--chaos` redeploy. print("\n===== setup_custom_tests: deps + OIDC wiring =====", flush=True) try: - # WC1: split deps into live-warm (shared provider at a stable domain + per-run - # realm) vs cold (co-deploy per run). A warm dep is used ONLY if its provider is - # actually up right now; otherwise it falls back to cold so a from-scratch host - # (before the warm reconciler has run) still works. - warm_deps, cold_deps = [], [] - for d in declared: - wd = warm.warm_domain(d) - if wd and warm.is_warm_up(d, wd): - warm_deps.append(d) - else: - if wd: - print(f" dep: {d} warm provider {wd} not up — cold fallback", flush=True) - cold_deps.append(d) - # Cold deps: co-deploy per run (existing path). - dep_metas = {d: _load_meta(d) for d in cold_deps} - deps_list = ( - deps_mod.deploy_deps( - recipe, os.environ.get("PR", "0"), ref, cold_deps, meta_for=dep_metas - ) - if cold_deps - else [] - ) - # Warm deps: no deploy. Reap orphan realms first (concurrency-safe), then point - # at the stable domain; _enrich creates the per-run realm on it. - for d in warm_deps: - wd = warm.warm_domain(d) - reaped = warm.reap_orphan_realms(d, wd) - if reaped: - print(f" dep: reaped {len(reaped)} orphan realm(s) on warm {d}: {reaped}", flush=True) - deps_list.append({"recipe": d, "domain": wd, "warm": True}) - print(f" dep: using live-warm {d} @ {wd} (per-run realm)", flush=True) - # Enrich each dep entry with SSO creds (realm/client/secret). The dict form is - # what setup_custom_tests.sh reads. - deps_state = _enrich_deps_with_sso(recipe, domain, deps_list) - deps_mod.write_run_state(deps_state) + deps_state = _provision_deps(recipe, domain, ref, declared) # Run the per-recipe post-deps hook (jq-driven OIDC wiring + in-place redeploy) _run_setup_custom_tests_hook(recipe, domain, depsfile) except Exception as e: # noqa: BLE001 — setup failure is ISOLATED to dep-marked tests @@ -831,6 +858,21 @@ def main() -> int: f"!! setup_custom_tests failed (deps-not-ready): {deps_not_ready_reason}", flush=True, ) + elif declared and oidc_at_install and deps_ready: + # INSTALL-TIME path (Q3.2a): deps were provisioned BEFORE the single deploy and the + # install-tier install_steps.sh hook already wired OIDC env into that one deploy — + # so NO re-provision, NO reconverge here. Run only the post-deploy setup hook + # (e.g. lasuite-drive's minio-createbuckets one-shot), which needs the live stack. + print("\n===== post-deploy setup (OIDC already wired at install) =====", flush=True) + try: + _run_setup_custom_tests_hook(recipe, domain, depsfile) + except Exception as e: # noqa: BLE001 — isolated to dep-marked / state-dependent tests + deps_ready = False + deps_not_ready_reason = _scrub(str(e))[:300] + print( + f"!! post-deploy setup failed: {deps_not_ready_reason}", + flush=True, + ) # ---- CUSTOM tier ---- if "custom" in stages: diff --git a/tests/lasuite-drive/install_steps.sh b/tests/lasuite-drive/install_steps.sh new file mode 100755 index 0000000..14c41a2 --- /dev/null +++ b/tests/lasuite-drive/install_steps.sh @@ -0,0 +1,77 @@ +#!/usr/bin/env bash +# lasuite-drive — INSTALL-TIME OIDC wiring hook (Phase 2 Q3.2a; +# plan-lasuite-drive-oidc-robustness.md Part A). +# +# Runs during the install tier AFTER `abra app new` + EXTRA_ENV + `abra app secret generate`, and +# BEFORE the single `abra app deploy` (runner/harness/lifecycle.py::_run_install_steps). By writing +# the OIDC env + the real client secret into the app's `.env` HERE, the recipe deploys ONCE with +# OIDC already wired — eliminating the flaky post-deploy in-place `--force --chaos` 12-service +# reconverge that the old setup_custom_tests.sh did (collabora WOPI-discovery race; see JOURNAL +# Step 0). The orchestrator provisions the per-run realm/client on the live-warm keycloak BEFORE +# this hook and writes $CCCI_DEPS_FILE (the recipe→creds dict). +# +# Env supplied by the harness: +# CCCI_APP_DOMAIN — the per-run lasuite-drive app domain +# CCCI_APP_ENV — path to the app's .env (the one `abra app deploy` reads) +# CCCI_RECIPE — "lasuite-drive" +# CCCI_DEPS_FILE — JSON {keycloak: {domain, realm, client_id, client_secret, ...}} (may be empty) +set -euo pipefail + +: "${CCCI_APP_DOMAIN:?missing}" +ENV_PATH="${CCCI_APP_ENV:?missing}" + +# No deps file / no keycloak entry → install-time provisioning failed or was skipped. NO-OP so the +# recipe still boots without OIDC; the @requires_deps OIDC custom test then SKIPs and F2-11 flips +# the run RED (deps declared but SSO unverified). Never wire a partial/broken OIDC config. +if [ -z "${CCCI_DEPS_FILE:-}" ] || [ ! -s "${CCCI_DEPS_FILE}" ]; then + echo " install_steps: no deps file — skipping OIDC wiring (recipe boots without OIDC)" + exit 0 +fi +KC_DOMAIN=$(jq -r '.keycloak.domain // empty' "$CCCI_DEPS_FILE") +KC_REALM=$( jq -r '.keycloak.realm // empty' "$CCCI_DEPS_FILE") +KC_CLIENT=$(jq -r '.keycloak.client_id // empty' "$CCCI_DEPS_FILE") +KC_SECRET=$(jq -r '.keycloak.client_secret // empty' "$CCCI_DEPS_FILE") +if [ -z "$KC_DOMAIN" ] || [ -z "$KC_SECRET" ]; then + echo " install_steps: deps file has no keycloak domain/secret — skipping OIDC wiring" + exit 0 +fi + +echo " lasuite-drive install_steps: wiring OIDC at install against keycloak ${KC_DOMAIN}" + +# 1) Insert the OIDC client secret at a bumped version. `abra app secret generate` already created a +# random oidc_rpcs:v1; swarm forbids overwriting a secret at the same version, so insert v2 and +# point SECRET_OIDC_RPCS_VERSION at it. (The app is not deployed yet — a swarm secret can be created +# independently of a running stack — so the single deploy below picks up v2.) +CUR_VER=$(grep -E '^\s*SECRET_OIDC_RPCS_VERSION=' "$ENV_PATH" | tail -1 | cut -d= -f2 | tr -d '"\r' || echo "v1") +NEW_NUM=$(( ${CUR_VER#v} + 1 )) +NEW_VER="v${NEW_NUM}" +INSERT_LOG=$(abra app secret insert "$CCCI_APP_DOMAIN" oidc_rpcs "$NEW_VER" "$KC_SECRET" --no-input 2>&1) \ + || INSERT_LOG=$(script -qec "abra app secret insert $CCCI_APP_DOMAIN oidc_rpcs $NEW_VER $KC_SECRET --no-input" /dev/null 2>&1) \ + || { echo " install_steps: abra app secret insert oidc_rpcs@$NEW_VER failed: $INSERT_LOG"; exit 1; } +sed -i "s|^\s*SECRET_OIDC_RPCS_VERSION=.*|SECRET_OIDC_RPCS_VERSION=$NEW_VER|" "$ENV_PATH" +echo " install_steps: oidc_rpcs secret inserted at $NEW_VER (was $CUR_VER)" + +# 2) Write the OIDC env vars (explicit endpoints — deterministic, no reliance on ${AUTH_DOMAIN} +# expansion). Mirrors the recipe-maintainer impress/La Suite OIDC env contract. +write_env () { + local key="$1" val="$2" + sed -i "/^\s*#\?\s*${key}=/d" "$ENV_PATH" + [ -z "$(tail -c1 "$ENV_PATH" 2>/dev/null)" ] || printf '\n' >> "$ENV_PATH" + printf '%s=%s\n' "$key" "$val" >> "$ENV_PATH" +} +write_env AUTH_DOMAIN "$KC_DOMAIN" +write_env OIDC_REALM "$KC_REALM" +write_env OIDC_OP_JWKS_ENDPOINT "https://${KC_DOMAIN}/realms/${KC_REALM}/protocol/openid-connect/certs" +write_env OIDC_OP_AUTHORIZATION_ENDPOINT "https://${KC_DOMAIN}/realms/${KC_REALM}/protocol/openid-connect/auth" +write_env OIDC_OP_TOKEN_ENDPOINT "https://${KC_DOMAIN}/realms/${KC_REALM}/protocol/openid-connect/token" +write_env OIDC_OP_USER_ENDPOINT "https://${KC_DOMAIN}/realms/${KC_REALM}/protocol/openid-connect/userinfo" +write_env OIDC_OP_LOGOUT_ENDPOINT "https://${KC_DOMAIN}/realms/${KC_REALM}/protocol/openid-connect/logout" +write_env OIDC_RP_CLIENT_ID "$KC_CLIENT" +write_env OIDC_RP_SIGN_ALGO "RS256" +write_env OIDC_RP_SCOPES "openid email profile" +write_env OIDC_REDIRECT_ALLOWED_HOSTS "[\"https://${KC_DOMAIN}\", \"https://${CCCI_APP_DOMAIN}\"]" +# The recipe default acr_values=eidas1 is FranceConnect-specific; keycloak can't satisfy it and it +# would break the interactive auth flow. Clear it so the keycloak OIDC client works. +write_env OIDC_AUTH_REQUEST_EXTRA_PARAMS "{}" + +echo " lasuite-drive install_steps: OIDC env wired into .env (deploy will pick it up, no reconverge)" diff --git a/tests/lasuite-drive/recipe_meta.py b/tests/lasuite-drive/recipe_meta.py index b6b9049..551fb18 100644 --- a/tests/lasuite-drive/recipe_meta.py +++ b/tests/lasuite-drive/recipe_meta.py @@ -24,6 +24,15 @@ HTTP_TIMEOUT = 900 # in-place redeploy). functional/test_oidc_with_keycloak.py then exercises the SSO flow. DEPS = ["keycloak"] +# Q3.2a (plan-lasuite-drive-oidc-robustness.md Part A): wire OIDC at INSTALL time, not via a +# post-deploy in-place `--chaos` redeploy. The orchestrator provisions the per-run realm on the +# live-warm keycloak BEFORE the single `abra app deploy`, and tests/lasuite-drive/install_steps.sh +# writes the OIDC env + client secret into the .env that one deploy reads. This eliminates the flaky +# 12-service reconverge (collabora WOPI-discovery race; JOURNAL Step 0). Drive boots fine with OIDC +# env set because keycloak is live-warm (discovery reachable at boot). setup_custom_tests.sh now +# only triggers the post-deploy MinIO bucket one-shot. +OIDC_AT_INSTALL = True + def EXTRA_ENV(domain): # Two of lasuite-drive's services route on DOMAIN-DERIVED **nested** subdomains — diff --git a/tests/lasuite-drive/setup_custom_tests.sh b/tests/lasuite-drive/setup_custom_tests.sh old mode 100644 new mode 100755 index c1f62aa..1bd3d34 --- a/tests/lasuite-drive/setup_custom_tests.sh +++ b/tests/lasuite-drive/setup_custom_tests.sh @@ -1,48 +1,25 @@ #!/usr/bin/env bash -# lasuite-drive — post-deps setup hook (operator-2026-05-28 SSO-dep plan §3.2). +# lasuite-drive — POST-DEPLOY setup hook (Phase 2 Q3.2a). # -# Sibling of tests/lasuite-docs/setup_custom_tests.sh (same impress/La Suite OIDC env contract). -# Runs AFTER the generic tiers and AFTER the keycloak dep is deployed + provisioned with a -# realm/client/user by the harness. The orchestrator wrote $CCCI_DEPS_FILE with the keycloak dep's -# domain + realm + client_id + client_secret + admin creds. -# -# This hook: (1) inserts the OIDC client secret as the recipe-conventional `oidc_rpcs` swarm secret -# (at a bumped version, since abra already generated v1 and swarm forbids overwrite); (2) writes the -# OIDC env vars into the running app's .env; (3) triggers an in-place `abra app deploy --force -# --chaos` so the new env takes effect. NOT a fresh `abra app new` — the deploy-count guard (DG4.1) -# still sees one app_new per app. +# As of Q3.2a (plan-lasuite-drive-oidc-robustness.md Part A) OIDC is wired at INSTALL time by +# tests/lasuite-drive/install_steps.sh (before the single `abra app deploy`), so this hook NO LONGER +# does any OIDC env wiring or in-place redeploy — that eliminated the flaky 12-service reconverge +# (collabora WOPI race; see JOURNAL Step 0). What remains here is the ONE post-deploy step that +# genuinely needs the live stack: triggering the MinIO bucket-creation one-shot. The orchestrator +# runs this only on the install-time path AFTER the deploy is healthy (deps already provisioned). # # Env supplied by the orchestrator: # CCCI_APP_DOMAIN — the running per-run lasuite-drive app domain -# CCCI_RECIPE — "lasuite-drive" -# CCCI_DEPS_FILE — JSON (dict shape: {keycloak: {domain, realm, client_id, client_secret, ...}}) +# CCCI_DEPS_FILE — JSON deps creds dict (unused here now; OIDC handled at install) set -euo pipefail : "${CCCI_APP_DOMAIN:?missing}" -: "${CCCI_DEPS_FILE:?missing}" -test -s "$CCCI_DEPS_FILE" || { echo " setup_custom_tests: deps file empty"; exit 1; } -KC_DOMAIN=$(jq -r '.keycloak.domain' "$CCCI_DEPS_FILE") -KC_REALM=$( jq -r '.keycloak.realm' "$CCCI_DEPS_FILE") -KC_CLIENT=$(jq -r '.keycloak.client_id' "$CCCI_DEPS_FILE") -KC_SECRET=$(jq -r '.keycloak.client_secret' "$CCCI_DEPS_FILE") -[ -n "$KC_DOMAIN" ] && [ "$KC_DOMAIN" != "null" ] || { echo " setup_custom_tests: no keycloak.domain in deps"; exit 1; } -[ -n "$KC_SECRET" ] && [ "$KC_SECRET" != "null" ] || { echo " setup_custom_tests: no keycloak.client_secret"; exit 1; } - -echo " lasuite-drive setup_custom_tests: wiring OIDC against keycloak dep ${KC_DOMAIN}" - -# 0) Recipe post-deploy setup (lasuite-drive README): the deploy alone does NOT create the MinIO -# bucket — `minio-createbuckets` is a `replicas:0` one-shot that must be triggered. The MinIO -# storage test asserts the bucket exists, so create it here. We scale the one-shot to 1 directly -# (deterministic) rather than the README's finicky `abra app restart` (which it notes "will appear -# to fail"). (DB `backend migrate` is the README's other documented step; not run here because -# neither current test needs a migrated DB — add it when an upload-via-app test does.) -# -# `--detach` is REQUIRED: minio-createbuckets is a run-once job (restart_policy: none) that creates -# the bucket then EXITS 0, so the service never holds a steady 1/1 replica. A blocking -# `docker service scale ...=1` (the default) therefore waits forever for a convergence that can't -# happen and hangs the whole run (`|| true` does NOT help — the command hangs, it doesn't fail). -# With `--detach` the scale just submits the one-run and returns; the bucket-poll loop below +# The deploy alone does NOT create the MinIO bucket — `minio-createbuckets` is a `replicas:0` +# one-shot (restart_policy: none) that must be triggered. The MinIO storage test asserts the bucket +# exists, so create it here. `--detach` is REQUIRED: the job creates the bucket then EXITS 0, so it +# never holds a steady 1/1 replica; a blocking `docker service scale ...=1` would wait forever and +# hang the run. With `--detach` the scale just submits the one-run and returns; the poll loop below # confirms the bucket was actually created. STACK=$(printf '%s' "$CCCI_APP_DOMAIN" | tr '.' '_') echo " setup: creating MinIO bucket via the minio-createbuckets one-shot (scale 0->1)" @@ -59,46 +36,4 @@ for i in $(seq 1 30); do sleep 3 done -# 1) Insert the OIDC client secret at a bumped version (the recipe-maintainer pattern; abra already -# generated oidc_rpcs:v1 randomly and swarm forbids overwriting a secret at the same version). -ENV_PATH="$HOME/.abra/servers/default/${CCCI_APP_DOMAIN}.env" -CUR_VER=$(grep -E '^\s*SECRET_OIDC_RPCS_VERSION=' "$ENV_PATH" | tail -1 | cut -d= -f2 | tr -d '"\r' || echo "v1") -NEW_NUM=$(( ${CUR_VER#v} + 1 )) -NEW_VER="v${NEW_NUM}" - -INSERT_LOG=$(abra app secret insert $CCCI_APP_DOMAIN oidc_rpcs $NEW_VER $KC_SECRET --no-input 2>&1) \ - || INSERT_LOG=$(script -qec "abra app secret insert $CCCI_APP_DOMAIN oidc_rpcs $NEW_VER $KC_SECRET --no-input" /dev/null 2>&1) \ - || { echo " setup_custom_tests: abra app secret insert oidc_rpcs@$NEW_VER failed: $INSERT_LOG"; exit 1; } -sed -i "s|^\s*SECRET_OIDC_RPCS_VERSION=.*|SECRET_OIDC_RPCS_VERSION=$NEW_VER|" "$ENV_PATH" -echo " setup_custom_tests: oidc_rpcs secret inserted at $NEW_VER (was $CUR_VER)" - -# 2) Write the OIDC env vars (explicit endpoints — deterministic, no reliance on ${AUTH_DOMAIN} -# expansion). Drive's .env.sample templates the endpoints off ${AUTH_DOMAIN}; we set AUTH_DOMAIN too -# for completeness and override each endpoint with the concrete keycloak realm URL. -[ -z "$(tail -c1 "$ENV_PATH" 2>/dev/null)" ] || printf '\n' >> "$ENV_PATH" -write_env () { - local key="$1" val="$2" - sed -i "/^\s*#\?\s*${key}=/d" "$ENV_PATH" - [ -z "$(tail -c1 "$ENV_PATH" 2>/dev/null)" ] || printf '\n' >> "$ENV_PATH" - printf '%s=%s\n' "$key" "$val" >> "$ENV_PATH" -} -write_env AUTH_DOMAIN "$KC_DOMAIN" -write_env OIDC_REALM "$KC_REALM" -write_env OIDC_OP_JWKS_ENDPOINT "https://${KC_DOMAIN}/realms/${KC_REALM}/protocol/openid-connect/certs" -write_env OIDC_OP_AUTHORIZATION_ENDPOINT "https://${KC_DOMAIN}/realms/${KC_REALM}/protocol/openid-connect/auth" -write_env OIDC_OP_TOKEN_ENDPOINT "https://${KC_DOMAIN}/realms/${KC_REALM}/protocol/openid-connect/token" -write_env OIDC_OP_USER_ENDPOINT "https://${KC_DOMAIN}/realms/${KC_REALM}/protocol/openid-connect/userinfo" -write_env OIDC_OP_LOGOUT_ENDPOINT "https://${KC_DOMAIN}/realms/${KC_REALM}/protocol/openid-connect/logout" -write_env OIDC_RP_CLIENT_ID "$KC_CLIENT" -write_env OIDC_RP_SIGN_ALGO "RS256" -write_env OIDC_RP_SCOPES "openid email profile" -write_env OIDC_REDIRECT_ALLOWED_HOSTS "[\"https://${KC_DOMAIN}\", \"https://${CCCI_APP_DOMAIN}\"]" -# The recipe default acr_values=eidas1 is FranceConnect-specific; keycloak can't satisfy it and it -# would break the interactive auth flow. Clear it so the keycloak OIDC client works. -write_env OIDC_AUTH_REQUEST_EXTRA_PARAMS "{}" - -# 3) In-place redeploy so the env + secret take effect (--force: redeploy unchanged recipe; --chaos: -# no chaos prompt; --no-input: non-interactive). NOT a fresh app_new. -abra app deploy "$CCCI_APP_DOMAIN" --force --chaos --no-input 2>&1 | tail -10 - -echo " lasuite-drive setup_custom_tests: OIDC wired + redeployed" +echo " lasuite-drive setup_custom_tests: post-deploy MinIO bucket step complete (OIDC wired at install)"