feat(2): refactor — SSO-dep plan refinement (deps AFTER generic + setup_custom_tests + failure isolation)
Per operator-2026-05-28 SSO-dep plan (plan-sso-dep-testing.md). Substantial orchestrator
restructuring:
NEW LIFECYCLE ORDER:
1. Recipe deploy ALONE (no deps).
2. install / upgrade / backup / restore — recipe-only generic tiers.
3. setup_custom_tests step (NEW):
a. Deploy each declared dep + provision realm/client/test-user via harness.sso.
b. Write $CCCI_DEPS_FILE in dict shape {dep_recipe: {domain, realm, client_id, client_secret,
admin_user, admin_password, discovery_url, token_url, ...}}.
c. Run tests/<recipe>/setup_custom_tests.sh hook (jq-readable; wires OIDC env via abra
secret insert + .env edits + in-place 'abra app deploy --force --chaos').
4. CUSTOM tier with deps-ready flag; @pytest.mark.requires_deps tests skip with
'deps-not-ready: <reason>' when setup_custom_tests fails. NON-deps custom tests still run
normally — FAILURE ISOLATION (a DoD item per plan).
5. Teardown: recipe first, deps in reverse declaration order.
Harness changes:
- runner/run_recipe_ci.py: deps deploy moves from BEFORE recipe deploy to AFTER restore tier.
Adds _enrich_deps_with_sso() + _run_setup_custom_tests_hook(). DG4.1 generalised to
'one abra app new per app' (recipe + each dep); in-place redeploys (\--force) don't count.
- runner/harness/deps.py: write_run_state + load_run_state accept dict OR list shape;
deps_as_dict() coerces either to a recipe→entry map.
- runner/harness/sso.py: admin_password_inside() public re-export.
- tests/conftest.py: deps_creds fixture (full creds dict); deps_apps fixture flattens to
recipe→domain string. pytest_collection_modifyitems hook skips
\@pytest.mark.requires_deps tests when CCCI_DEPS_READY=0.
pytest_configure registers the marker.
Recipe content:
- tests/lasuite-docs/setup_custom_tests.sh: NEW hook reads $CCCI_DEPS_FILE via jq;
inserts oidc_rpcs secret at BUMPED version (v1→v2) since abra app new -S generates v1 first
and Swarm forbids overwriting; updates SECRET_OIDC_RPCS_VERSION in .env; writes 9 OIDC env
vars (REALM/DISCOVERY/AUTH/TOKEN/USERINFO/LOGOUT/JWKS/CLIENT_ID/SCOPES); ensures trailing
newline on .env so writes don't concatenate (caught a 'TIMEOUT=900OIDC_REALM=...' bug);
triggers in-place 'abra app deploy --force --chaos --no-input'.
- tests/lasuite-docs/functional/test_oidc_with_keycloak.py: refactored to consume deps_creds
fixture (no longer calls setup_keycloak_realm itself — the orchestrator does it in
setup_custom_tests). Marked \@pytest.mark.requires_deps.
Cold-verifiable on cc-ci (log /root/ccci-refactor-lasuite-r5.log):
RECIPE=lasuite-docs STAGES=install,custom cc-ci-run runner/run_recipe_ci.py
install: PASS, custom: 3 PASS incl. test_oidc_password_grant_against_dep_keycloak.
deploy-count = 2 (expect 2) — DG4.1 generalised holds.
Smoke regression: RECIPE=custom-html STAGES=install,custom → 5 PASS, deploy-count=1.
Closes DEFERRED.md #5 (lasuite-docs OIDC parity ports via this plan).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@ -60,9 +60,17 @@ def dep_domain(parent_recipe: str, pr: str, ref: str | None, dep_recipe: str) ->
|
||||
return naming.app_domain(dep_recipe, pr, synthetic_ref)
|
||||
|
||||
|
||||
def write_run_state(deps_state: list[dict]) -> None:
|
||||
"""Write the deps state file ($CCCI_DEPS_FILE) so dependent tests can find their dep apps via
|
||||
the `deps_apps` fixture. No-op if the env var isn't set."""
|
||||
def write_run_state(deps_state) -> None:
|
||||
"""Write the deps state file ($CCCI_DEPS_FILE). Two shapes supported (canonical=keyed dict):
|
||||
|
||||
1. **Legacy list-of-entries:** `[{"recipe": "<dep>", "domain": "<d>"}, ...]` (Q2.3 original).
|
||||
Still accepted by `load_run_state` for backwards compat — `deps_apps` fixture flattens.
|
||||
2. **NEW per-spec dict (operator-2026-05-28 SSO-dep plan §3.2):**
|
||||
`{"<dep_recipe>": {"recipe": "<dep>", "domain": "<d>", "realm": "...",
|
||||
"client_id": "...", "client_secret": "...", "admin_user": "...", "admin_password": "..."}}`.
|
||||
The `setup_custom_tests.sh` per-recipe hook reads this via `jq` to wire OIDC env.
|
||||
|
||||
No-op if `$CCCI_DEPS_FILE` isn't set."""
|
||||
path = os.environ.get("CCCI_DEPS_FILE")
|
||||
if not path:
|
||||
return
|
||||
@ -143,8 +151,9 @@ def teardown_deps(state: list[dict]) -> None:
|
||||
raise lifecycle.TeardownError("dep teardown failures: " + " ; ".join(errors))
|
||||
|
||||
|
||||
def load_run_state() -> list[dict]:
|
||||
"""Read the current run's deps state (used by the `deps_apps` fixture). Returns [] if unset."""
|
||||
def load_run_state():
|
||||
"""Read the current run's deps state. Returns the JSON content (list OR dict — both shapes
|
||||
supported, see write_run_state). Returns [] if file is empty/unset."""
|
||||
path = os.environ.get("CCCI_DEPS_FILE")
|
||||
if not path or not os.path.exists(path):
|
||||
return []
|
||||
@ -153,3 +162,15 @@ def load_run_state() -> list[dict]:
|
||||
return json.load(f) or []
|
||||
except (OSError, ValueError):
|
||||
return []
|
||||
|
||||
|
||||
def deps_as_dict(state) -> dict[str, dict]:
|
||||
"""Coerce either shape (legacy list or new dict) into a recipe→entry dict for the deps_apps
|
||||
fixture + dependent-tests consumption."""
|
||||
if isinstance(state, dict):
|
||||
return state
|
||||
out: dict[str, dict] = {}
|
||||
for entry in state or []:
|
||||
if isinstance(entry, dict) and entry.get("recipe"):
|
||||
out[entry["recipe"]] = entry
|
||||
return out
|
||||
|
||||
@ -264,6 +264,12 @@ def assert_discovery_endpoint(creds: dict) -> dict:
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
|
||||
def admin_password_inside(provider_domain: str) -> str:
|
||||
"""Read the abra-generated admin_password from inside the provider container.
|
||||
Public re-export of the previously-private _kc_admin_password for the orchestrator wiring."""
|
||||
return _kc_admin_password(provider_domain)
|
||||
|
||||
|
||||
def write_sso_creds(creds: dict) -> None:
|
||||
"""Persist creds to $CCCI_SSO_CREDS_FILE for the dependent recipe's tests to read. The file is
|
||||
in /tmp (the runner's per-process tempdir) and deleted at run end alongside the deps file."""
|
||||
|
||||
Reference in New Issue
Block a user