feat(P67b): lifecycle tests default to plan-only; ACDL_LIFECYCLE_MODE flag overrides to full (REQ-134)
--- ci--- project: acdl phase: 67b milestone: v1.12 status: execute --- /ci--- The modules-lifecycle pipeline now defaults to plan-only (fast, no AWS mutation, no credentials, no cost) so it runs on every PR. A CI variable ACDL_LIFECYCLE_MODE (workflow_dispatch input 'lifecycle_mode', default 'plan') overrides to 'full' for the real apply->modify->destroy against live AWS. Scripts: run_lifecycle_test.sh / run_lifecycle_destroy.sh / run_l2_lifecycle_test.sh / run_l2_lifecycle_destroy.sh read the flag and dispatch to --plan-only (plan mode) or --apply/--destroy (full mode). Destroy is a no-op exit 0 in plan mode (nothing was applied). VPC-output injection is gated on full mode. Workflows: both .github + .gitea (byte-identical) expose lifecycle_mode as a workflow_dispatch input (choice: plan/full), pass it via env: ACDL_LIFECYCLE_MODE to every lifecycle step, skip ci-vpc-apply + ci-vpc-destroy + Read-CI-VPC-outputs in plan mode, and run the lifecycle + l2-lifecycle jobs with if: always() so they execute (plan-only) even when ci-vpc-apply is skipped. Contract + schema: pipelines/modules-lifecycle.yml gains default_mode: plan; the schema accepts default_mode (enum plan|full) and a richer workflow_dispatch inputs shape. Tests: 14 new tests in test_lifecycle_mode_flag.py (script dispatch) + 10 new tests in TestModulesLifecyclePipeline (workflow flag wiring, byte-identity, plan-mode skips). Updated test_platform_vpc_destroy to reflect the plan-mode skip. 516 tests pass; smoke-tested plan mode on the s3 module (--plan-only green, no AWS apply).
This commit is contained in:
@@ -581,10 +581,14 @@ class TestModulesLifecyclePipeline:
|
||||
assert any("Modify" in n for n in step_names), "Missing modify step"
|
||||
assert any("Destroy" in n for n in step_names), "Missing destroy step"
|
||||
|
||||
def test_platform_vpc_destroy_always_runs(self):
|
||||
def test_platform_vpc_destroy_runs_in_full_mode(self):
|
||||
wf = _load_workflow(".gitea/workflows/modules-lifecycle.yml")
|
||||
destroy_job = wf["jobs"]["ci-vpc-destroy"]
|
||||
assert destroy_job.get("if") == "always()", "ci-vpc-destroy must always run (cleanup)"
|
||||
# ci-vpc-destroy must always run in full mode (cleanup), but is
|
||||
# skipped in plan mode (REQ-134: nothing is applied).
|
||||
cond = destroy_job.get("if", "")
|
||||
assert "always()" in cond, "ci-vpc-destroy must run in full mode even if lifecycle fails"
|
||||
assert "plan" in cond, "ci-vpc-destroy must be skipped in plan mode (REQ-134)"
|
||||
|
||||
def test_l2_lifecycle_job_exists(self):
|
||||
wf = _load_workflow(".gitea/workflows/modules-lifecycle.yml")
|
||||
@@ -613,4 +617,67 @@ class TestModulesLifecyclePipeline:
|
||||
|
||||
def test_contract_matrix_lists_l2_modules(self):
|
||||
contract = _load_yaml("pipelines/modules-lifecycle.yml")
|
||||
assert set(contract["matrix"]["l2_modules"]) == {"static-assets", "microservice"}
|
||||
assert set(contract["matrix"]["l2_modules"]) == {"static-assets", "microservice"}
|
||||
|
||||
# --- REQ-134: lifecycle mode flag (plan-only default, full override) ---
|
||||
|
||||
def test_contract_declares_plan_as_default_mode(self):
|
||||
"""The pipeline contract declares default_mode: plan (REQ-134)."""
|
||||
contract = _load_yaml("pipelines/modules-lifecycle.yml")
|
||||
assert contract.get("default_mode") == "plan", \
|
||||
"default_mode must be 'plan' (fast, no AWS mutation, the default on every PR)"
|
||||
|
||||
def test_schema_accepts_default_mode_field(self):
|
||||
"""The schema accepts the default_mode field with plan/full enum."""
|
||||
schema = json.load(open(ROOT / "schemas/modules-lifecycle-pipeline.schema.json"))
|
||||
props = schema["properties"]
|
||||
assert "default_mode" in props
|
||||
assert set(props["default_mode"]["enum"]) == {"plan", "full"}
|
||||
|
||||
def test_workflow_has_lifecycle_mode_dispatch_input(self):
|
||||
"""workflow_dispatch exposes a lifecycle_mode input defaulting to plan."""
|
||||
wf = _load_workflow(".gitea/workflows/modules-lifecycle.yml")
|
||||
wd = wf["on"]["workflow_dispatch"]
|
||||
assert isinstance(wd, dict), "workflow_dispatch must declare inputs"
|
||||
inputs = wd.get("inputs", {})
|
||||
assert "lifecycle_mode" in inputs
|
||||
assert inputs["lifecycle_mode"].get("default") == "plan"
|
||||
assert inputs["lifecycle_mode"].get("type") == "choice"
|
||||
assert set(inputs["lifecycle_mode"].get("options", [])) == {"plan", "full"}
|
||||
|
||||
def test_lifecycle_job_passes_mode_env_to_steps(self):
|
||||
"""The lifecycle job sets ACDL_LIFECYCLE_MODE env so scripts dispatch
|
||||
to plan-only by default, full on override."""
|
||||
wf = _load_workflow(".gitea/workflows/modules-lifecycle.yml")
|
||||
env = wf["jobs"]["lifecycle"].get("env", {})
|
||||
assert "ACDL_LIFECYCLE_MODE" in env
|
||||
# The expression must resolve to 'plan' when no input/var is set.
|
||||
assert "plan" in env["ACDL_LIFECYCLE_MODE"]
|
||||
|
||||
def test_l2_lifecycle_job_passes_mode_env_to_steps(self):
|
||||
"""The L2 lifecycle job also sets ACDL_LIFECYCLE_MODE env."""
|
||||
wf = _load_workflow(".gitea/workflows/modules-lifecycle.yml")
|
||||
env = wf["jobs"]["l2-lifecycle"].get("env", {})
|
||||
assert "ACDL_LIFECYCLE_MODE" in env
|
||||
assert "plan" in env["ACDL_LIFECYCLE_MODE"]
|
||||
|
||||
def test_ci_vpc_apply_skipped_in_plan_mode(self):
|
||||
"""The CI VPC apply job is skipped in plan mode (nothing is applied)."""
|
||||
wf = _load_workflow(".gitea/workflows/modules-lifecycle.yml")
|
||||
cond = wf["jobs"]["ci-vpc-apply"].get("if", "")
|
||||
assert "plan" in cond, "ci-vpc-apply must be skipped in plan mode (REQ-134)"
|
||||
|
||||
def test_lifecycle_job_runs_even_if_vpc_apply_skipped(self):
|
||||
"""The lifecycle job uses `if: always()` so it still runs (plan-only)
|
||||
even when ci-vpc-apply is skipped in plan mode."""
|
||||
wf = _load_workflow(".gitea/workflows/modules-lifecycle.yml")
|
||||
assert wf["jobs"]["lifecycle"].get("if") == "always()"
|
||||
assert wf["jobs"]["l2-lifecycle"].get("if") == "always()"
|
||||
|
||||
def test_read_ci_vpc_outputs_skipped_in_plan_mode(self):
|
||||
"""The 'Read CI VPC outputs' step is skipped in plan mode (no VPC)."""
|
||||
wf = _load_workflow(".gitea/workflows/modules-lifecycle.yml")
|
||||
steps = wf["jobs"]["lifecycle"]["steps"]
|
||||
read_step = next(s for s in steps if s.get("name") == "Read CI VPC outputs")
|
||||
cond = read_step.get("if", "")
|
||||
assert "full" in cond, "Read CI VPC outputs step must be skipped in plan mode (REQ-134)"
|
||||
Reference in New Issue
Block a user