Repository navigation
Gate a deploy on the command health check and receipt it (S-009-2..9, 11, 12) - #180
Conversation
… 11, 12) A component's healthCheck of type: command now runs in the deploy role's health gate, after the switch and the restart and inside the block whose rescue restores the previous release. It runs in a transient systemd unit cloudfall-health-<component> as the application user, in <installRoot>/current, with the unit's EnvironmentFile=- line and RuntimeMaxSec= set to timeoutSeconds, up to attempts times 3 seconds apart. The run task is no_log and the unit has no --pipe, so the command's output stays in the server's journal; only the exit code and systemd-run's timeout line are read. A failed check rolls back (or stops a first release), writes the release receipt with healthCheckPassed: false and the check's result, and fails the deploy naming the check and its exit code or timeout. The receipt tasks move to release_receipt.yml so the pass path and the rescue write the same receipt. The receipt schema gains healthCheck (no other key) and accepts healthCheckPassed: false only with a failed command check; receipts written before still validate. http and none gates and receipts are unchanged. restart, rollback and health keep refusing a command check until #166. Fixes #164 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
@romamo Decided without waiting: the release receipt schema refuses a little more than S-009-11 lists (a passing Why: S-009-11 asks the schema to refuse contradictory receipts and accept every receipt the deploy writes today; these rules refuse only contradictions, and the review confirmed every receipt written today still validates. Reply with a comment starting with |
Summary
Runs a component's
healthCheckoftype: command(schema from #170) in thecloudfall_deployrole's health gate and records it in the release receipt, per spec S-009 (docs/specs/009-command-health-check.md).tasks/command_health_check.yml(new) runs the check afterSwitch the current release symlinkandRestart the component service, inside "Activate the release behind the health gate", in a transient unitcloudfall-health-<component>(systemd-run --wait --collect --service-type=exec,--uid/--gidof the application user,--working-directory=<installRoot>/current,EnvironmentFile=-<installRoot>/shared/env/<component>.env,RuntimeMaxSec=<timeoutSeconds>,TimeoutStopSec=10,NoNewPrivileges,PrivateTmp). The argv is a list: a relative first element becomes<installRoot>/current/<element>, an absolute one is used as is, and the command is never passed through a shell or templatedattemptsruns, 3 seconds apart (retries/delay/until); theuntilends the loop itself on the last attempt, so the result (attempts,exitCodeornull,timedOutfrom systemd-run'sFinished with result: timeoutline) is always recorded before a separate task fails the gate--pipe, and the run task isno_log: true. Onlyrcand the timeout line are readhealthCheckPassed: falseand fails with a message naming the check, its exit code or timeout, the attempts and the release it was rolled back totasks/release_receipt.yml, included from the pass path and from the rescue, so both write the same receipt; a command component's receipt carrieshealthCheck,httpandnonereceipts do notrelease-receipt.schema.json:healthCheckPassedis a boolean; new optionalhealthCheck(type,command,attempts,exitCode,timedOut,additionalProperties: false);falserequires ahealthCheckwhoseexitCodeis not 0restart.yml,rollback.ymlandhealth.ymlkeep refusing a command check, now pointing at Run the command health check in restart, rollback and health (S-009-10) #166;main.ymlno longer refuses itPlan
engine/ansible/roles/cloudfall_deploy/tasks/command_health_check.yml(new),engine/ansible/roles/cloudfall_deploy/tasks/release_receipt.yml(new, the receipt tasks moved out ofmain.ymlso the pass path and the rescue write the same receipt),engine/ansible/roles/cloudfall_deploy/tasks/main.yml,defaults/main.yml(a comment),restart.yml,rollback.yml,health.yml(their refusal now points at Run the command health check in restart, rollback and health (S-009-10) #166),config/schemas/v1/release-receipt.schema.json,engine/tests/test_command_health_gate.py(new),engine/tests/test_deploy_role.py,engine/tests/test_release_command.py,engine/README.md,config/README.md,docs/ci-deploy-guide.md,changelog.d/164-command-health-gate.mdcommand_health_check.ymlrun for real underansible-playbookwith a stand-insystemd-run(S-009-2, 3, 7, 8, 9), then the gate block, its rescue andrelease_receipt.yml(S-009-4, 5, 6, 12), then the docs; behaviour as in the spec's "How it runs", "Output stays on the server", "Failure" and "Receipt"engine/tests/test_command_health_gate.pywith one test per criterion S-009-2, S-009-3, S-009-4, S-009-5, S-009-6, S-009-7, S-009-8, S-009-9, S-009-11, S-009-12; the existingtest_deploy_role.pyandtest_release_command.pystill pass forhttpandnoneChoices worth a look
healthCheckmust haveexitCode: 0andtimedOut: false, and insidehealthChecktimedOut: truerequiresexitCode: null(andfalsean integer). Every receipt written today has nohealthCheck, so it is unaffectedrelease_receipt.ymlis a task file the spec's "Touches" list doesn't name; it keeps one receipt template for the pass path and the rescuecommand_health_check.ymlis included frommain.ymlonly. The spec also includes it fromhealth_gate.yml; that is Run the command health check in restart, rollback and health (S-009-10) #166's (S-009-10)Test plan
engine/tests/test_command_health_gate.py(new, 39 tests):command_health_check.ymlandrelease_receipt.ymlrun for real underansible-playbookagainst localhost with a stand-insystemd-runthat, unlike the real one, passes the command's output on. The S-009-9 marker test runsansible-playbook -vvvv; withno_log: falseit fails, showing the marker in the output. The gate's order, its rescue and the failure message are replayed with Ansible's templating, astest_deploy_role.pydoesuv run pytest engine/tests sdk/tests/test_resources.py sdk/tests/test_command_health_check.py: 318 passed, 1 skippeduv run ruff check .,uv run mypy,ANSIBLE_CONFIG=engine/ansible/ansible.cfg uv run ansible-lint engine/ansible(as CI runs it): cleansystemd-run, so its exit code andFinished with result: timeoutline on a timeout are taken from the release command's proven behaviour (S-005), not seen hereFixes #164
🤖 Generated with Claude Code