feat(harness): P2 — delete legacy customization keys & paths (rcust)
All checks were successful
continuous-integration/drone/push Build is passing
All checks were successful
continuous-integration/drone/push Build is passing
a) compose.ccci.yml is FIRST-CLASS: the harness auto-copies tests/<recipe>/ compose.ccci.yml into the run's recipe checkout (ABRA_DIR-aware, lifecycle. provide_ccci_overlay) and auto-chaoses the pinned base deploy on its presence (kills the R7 implicit coupling). ghost/discourse install_steps.sh (copy-only boilerplate) deleted; CHAOS_BASE_DEPLOY removed from both metas + the registry. b) install-time deps wiring is the ONLY mode: deps with DEPS provision BEFORE the single deploy; legacy post-deploy provisioning + the setup_custom_tests.sh invocation machinery deleted. lasuite-docs migrated to install_steps.sh OIDC wiring (same env names/values as the old hook — only the timing moved); lasuite-drive's remaining post-deploy MinIO bucket one-shot moved to ops.py pre_install; both setup_custom_tests.sh files deleted; OIDC_AT_INSTALL removed from drive/meet metas + the registry. c) SKIP_GENERIC meta key deleted (zero users). Env form CCCI_SKIP_GENERIC* stays as the documented dev-only escape hatch; when active in a drone CI run the orchestrator prints a loud !! warning (manifest embedding lands in P5). d) conftest cleanup: dead pre-deploy-once fixtures deployed/deployed_app deleted (zero users), app_domain + _short + _wait_healthy dropped (only users were the deleted fixtures); deps_apps+deps_creds consolidated into ONE deps fixture (entries expose .domain etc. as attributes; dict access intact); the 6 lasuite test files renamed deps_creds->deps (fixture name only — assertions and flows byte-identical). requires_deps marker + F2-11 skip-report plumbing unchanged. Registry is now exactly the 14 final keys; docs §4 table regenerated. Stale setup_custom_tests/OIDC_AT_INSTALL prose in docstrings/comments/assert MESSAGES updated (no assert logic or expected value touched). Verified on cc-ci: cc-ci-run -m pytest tests/unit -q -> 175 passed; scripts/lint.sh -> PASS.
This commit is contained in:
@ -14,12 +14,7 @@ import pytest
|
||||
|
||||
sys.path.insert(0, os.path.join(os.path.dirname(__file__), "..", "runner"))
|
||||
from harness import deps as deps_mod # noqa: E402
|
||||
from harness import lifecycle, naming
|
||||
from harness import meta as meta_mod
|
||||
|
||||
|
||||
def _short(s: str, n: int = 8) -> str:
|
||||
return "".join(c for c in s if c.isalnum())[:n] or "local"
|
||||
from harness import meta as meta_mod # noqa: E402
|
||||
|
||||
|
||||
@pytest.fixture(scope="session")
|
||||
@ -27,16 +22,6 @@ def recipe() -> str:
|
||||
return os.environ.get("RECIPE", "custom-html")
|
||||
|
||||
|
||||
@pytest.fixture(scope="session")
|
||||
def app_domain(recipe) -> str:
|
||||
# Docker swarm config/secret names = <stackname>_<res>_<ver> must be <= 64 chars, and
|
||||
# stackname is the sanitized domain. ".ci.commoninternet.net" alone is 22 chars, so the
|
||||
# subdomain label must stay short. Use <recipe[:4]>-<6hex(recipe|pr|ref)> — unique per run,
|
||||
# collision-safe across recipes (full recipe in the hash), readable context lives in the
|
||||
# Drone build params + PR comment. (Deviation from plan §4.0 long name; see DECISIONS.md.)
|
||||
return naming.app_domain(recipe, os.environ.get("PR", "0"), os.environ.get("REF"))
|
||||
|
||||
|
||||
@pytest.fixture(scope="session")
|
||||
def meta(recipe):
|
||||
"""The recipe's FULL validated customization (RecipeMeta, attribute access) via the single
|
||||
@ -55,32 +40,33 @@ def live_app() -> str:
|
||||
return domain
|
||||
|
||||
|
||||
@pytest.fixture(scope="session")
|
||||
def deps_apps() -> dict[str, str]:
|
||||
"""Phase 2 Q2.3 dependency-resolver contract (refined operator-2026-05-28 SSO-dep plan §1):
|
||||
when a recipe declares `DEPS = [...]` in its `recipe_meta.py`, the orchestrator deploys each
|
||||
dep AFTER the generic tiers (between RESTORE and CUSTOM) and persists their per-run identity
|
||||
+ SSO creds to `$CCCI_DEPS_FILE`. Tests access the dep's per-run domain via this fixture.
|
||||
For full SSO creds (realm/client/secret/admin) use the `deps_creds` fixture instead.
|
||||
class _DepEntry(dict):
|
||||
"""One provisioned dep (full creds dict) with attribute sugar: `entry.domain`, `entry.realm`,
|
||||
`entry.client_secret`, ... — dict-style access works too (rcust P2d)."""
|
||||
|
||||
Returns `{dep_recipe: domain}` (str→str). Empty when no deps declared OR deps-not-ready."""
|
||||
def __getattr__(self, name):
|
||||
try:
|
||||
return self[name]
|
||||
except KeyError as e:
|
||||
raise AttributeError(name) from e
|
||||
|
||||
|
||||
@pytest.fixture(scope="session")
|
||||
def deps() -> dict[str, _DepEntry]:
|
||||
"""The recipe's provisioned deps (rcust P2d — consolidates the old `deps_apps`+`deps_creds`
|
||||
pair). When a recipe declares `DEPS = [...]` in its `recipe_meta.py`, the orchestrator
|
||||
provisions each dep BEFORE the single deploy and persists per-run identity + SSO creds to
|
||||
`$CCCI_DEPS_FILE`. `deps["keycloak"]` carries domain/realm/client_id/client_secret/user/
|
||||
password/email/admin_user/admin_password/discovery_url/token_url/... (`.domain` etc. work as
|
||||
attributes). Empty when no deps declared OR deps-not-ready — pair with
|
||||
`@pytest.mark.requires_deps` so the F2-11 skip-report keeps the green signal honest."""
|
||||
state = deps_mod.deps_as_dict(deps_mod.load_run_state())
|
||||
return {r: e["domain"] for r, e in state.items() if e.get("domain")}
|
||||
|
||||
|
||||
@pytest.fixture(scope="session")
|
||||
def deps_creds() -> dict[str, dict]:
|
||||
"""Full SSO-creds dict for each declared dep (operator-2026-05-28 SSO-dep plan §1).
|
||||
`deps_creds["keycloak"]` returns the entry written by setup_custom_tests with keys
|
||||
domain/realm/client_id/client_secret/user/password/email/admin_user/admin_password/
|
||||
discovery_url/token_url/.... Use this in `@pytest.mark.requires_deps` tests that need to
|
||||
authenticate via OIDC."""
|
||||
return deps_mod.deps_as_dict(deps_mod.load_run_state())
|
||||
return {r: _DepEntry(e) for r, e in state.items()}
|
||||
|
||||
|
||||
def pytest_collection_modifyitems(config, items):
|
||||
"""SSO-dep plan §4: tests marked `@pytest.mark.requires_deps` are skipped with reason
|
||||
`deps-not-ready: <captured-err>` when the orchestrator's setup_custom_tests step failed
|
||||
`deps-not-ready: <captured-err>` when the orchestrator's dep provisioning failed
|
||||
(orchestrator sets CCCI_DEPS_READY=0 in env). Non-deps custom tests are unaffected.
|
||||
|
||||
This is failure-isolation per plan §1 — generic tiers cannot break the SSO-marked tests'
|
||||
@ -113,40 +99,5 @@ def pytest_configure(config):
|
||||
"""Register the `requires_deps` marker so pytest doesn't warn about it."""
|
||||
config.addinivalue_line(
|
||||
"markers",
|
||||
"requires_deps: test requires DEPS-declared services + setup_custom_tests success.",
|
||||
"requires_deps: test requires DEPS-declared services + dep provisioning success.",
|
||||
)
|
||||
|
||||
|
||||
def _wait_healthy(domain, meta):
|
||||
lifecycle.wait_healthy(
|
||||
domain,
|
||||
ok_codes=tuple(meta.HEALTH_OK),
|
||||
path=meta.HEALTH_PATH,
|
||||
deploy_timeout=meta.DEPLOY_TIMEOUT,
|
||||
http_timeout=meta.HTTP_TIMEOUT,
|
||||
)
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def deployed(recipe, app_domain, meta, request):
|
||||
"""Function-scoped: deploy the current/$REF version healthy, guaranteed teardown after.
|
||||
Used by stages that start from current (install/backup)."""
|
||||
version = os.environ.get("VERSION") or None
|
||||
lifecycle.janitor()
|
||||
request.addfinalizer(lambda: lifecycle.teardown_app(app_domain))
|
||||
lifecycle.deploy_app(recipe, app_domain, version=version)
|
||||
_wait_healthy(app_domain, meta)
|
||||
return app_domain
|
||||
|
||||
|
||||
@pytest.fixture(scope="session")
|
||||
def deployed_app(recipe, app_domain, meta):
|
||||
"""Install stage: deploy the recipe and wait until healthy; tear down at session end."""
|
||||
version = os.environ.get("VERSION") or None
|
||||
lifecycle.janitor() # sweep orphans from crashed runs first
|
||||
try:
|
||||
lifecycle.deploy_app(recipe, app_domain, version=version, secrets=True)
|
||||
_wait_healthy(app_domain, meta)
|
||||
yield app_domain
|
||||
finally:
|
||||
lifecycle.teardown_app(app_domain)
|
||||
|
||||
Reference in New Issue
Block a user