Skip to content

Accept a command health check in the component schema (#161) - #170

Merged
shipmill-romamo[bot] merged 1 commit into
mainfrom
feat/161-command-health-check-schema
Oct 30, 2026
Merged

shipmill-romamo[bot] merged 1 commit into
mainfrom
feat/161-command-health-check-schema

Conversation

@shipmill-romamo

Copy link
Copy Markdown
Contributor

Plan

  • Serves: G-3
  • Files: config/schemas/v1/component.schema.json, sdk/src/cloudfall/domain.py, sdk/src/cloudfall/inventory.py, sdk/src/cloudfall/lifecycle.py, engine/ansible/roles/cloudfall_deploy/meta/argument_specs.yml, engine/ansible/roles/cloudfall_deploy/tasks/{main,restart,rollback,health}.yml, sdk/tests/test_command_health_check.py, engine/tests/test_deploy_role.py, engine/tests/test_ansible_inventory.py, changelog.d/161-command-health-check-schema.md
  • Order: failing tests first (config validate accepts/refuses the command check, the inventory parses and renders it, deploy/restart/rollback plans name it, the engine refuses it until Gate a deploy on the command health check and receipt it (S-009-2..9, 11, 12) #164); then the schema oneOf branch, HealthCheckType.COMMAND and its value objects, the parser, the preview effects, the engine refusal; then the changelog fragment
  • Proof: S-009-1, S-009-13 (test_s009_1_*, test_s009_13_*)

Summary

Adds the config contract of spec 009's command health check; the engine running it is #164.

  • config/schemas/v1/component.schema.json: a third healthCheck oneOf branch, type: command with a required command (non-empty list of non-empty strings, NUL refused as CommandArgv refuses it), timeoutSeconds (1 to 300) and attempts (1 to 20), additionalProperties: false. The http and none branches are unchanged
  • sdk/src/cloudfall/domain.py: HealthCheckType.COMMAND, and the value objects HealthCheckTimeout (1..300) and HealthCheckAttempts (1..20)
  • sdk/src/cloudfall/inventory.py: ComponentCommandHealthCheck (CommandArgv, HealthCheckTimeout, HealthCheckAttempts), an optional ComponentHealthCheck.command required exactly for type: command, its parser and as_dict (so the rendered Ansible inventory carries it as declared)
  • sdk/src/cloudfall/lifecycle.py: deploy, restart and rollback without --yes add "; its command health check <command> runs on each server after its restart" to wouldRun. The plan JSON gains no field
  • Engine, carry-through only: the cloudfall_deploy argument spec accepts type: command and its command key; main.yml, restart.yml, rollback.yml and health.yml start with a task that fails for a command check ("Component X declares a command health check, which this engine does not run yet (Gate a deploy on the command health check and receipt it (S-009-2..9, 11, 12) #164); nothing was changed"), so until Gate a deploy on the command health check and receipt it (S-009-2..9, 11, 12) #164 lands no deploy, restart or rollback of such a component passes an unrun gate. cloudfall health reports it unhealthy with that message. Gate a deploy on the command health check and receipt it (S-009-2..9, 11, 12) #164 replaces these tasks with the check itself

Test plan

  • New sdk/tests/test_command_health_check.py: S-009-1 (accepts, bounds, each malformed case exits CONFIG_INVALID/schema_validation_failed, value objects, Q-3 shapes of http/none unchanged) and S-009-13 (the three plans exit 0, name the check and its command, run no ansible-playbook, and carry the same keys as for http/none)
  • engine/tests/test_ansible_inventory.py: a command check renders as declared; engine/tests/test_deploy_role.py: each entry point refuses a command check first and only a command check, and the argument spec accepts all three types
  • Ran: uv run ruff check ., uv run mypy, uv run cloudfall config validate --project config/examples, ansible-lint engine/ansible, and pytest on the touched and neighbouring files (test_command_health_check, test_validation, test_inventory, test_lifecycle, test_release_command_plan, test_ansible_inventory, test_deploy_role, test_release_command, test_deploy_templates, test_artifact_build_steps, test_catalog, test_ansible_reader): all green. The full suite is left to CI

Fixes #161
Part of #152

🤖 Generated with Claude Code

A component can declare healthCheck type: command (command argv,
timeoutSeconds 1-300, attempts 1-20, no other key). The SDK parses it into
value objects and the deploy, restart and rollback plans name the check and
its command in wouldRun; the plan JSON gains no field (S-009-1, S-009-13).

The engine does not run the check yet (#164), so the deploy role's entry
points refuse a command check on their first task instead of passing an
unrun gate.

Fixes #161

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@shipmill-romamo
shipmill-romamo Bot merged commit a4f86d7 into main Oct 30, 2026
1 check passed
@shipmill-romamo
shipmill-romamo Bot deleted the feat/161-command-health-check-schema branch October 30, 2026 14:18
shipmill-romamo Bot added a commit that referenced this pull request Oct 30, 2026
…11) (#171)

* Accept a command health check in the component schema (#170)

A component can declare healthCheck type: command (command argv,
timeoutSeconds 1-300, attempts 1-20, no other key). The SDK parses it into
value objects and the deploy, restart and rollback plans name the check and
its command in wouldRun; the plan JSON gains no field (S-009-1, S-009-13).

The engine does not run the check yet (#164), so the deploy role's entry
points refuse a command check on their first task instead of passing an
unrun gate.

Fixes #161

Co-authored-by: shipmill-romamo[bot] <338557544+shipmill-romamo[bot]@users.noreply.github.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>

* Refuse a bad --source-schema with PRECONDITION and add migrate --data-schema (S-007-12, S-007-13) (#169)

* Refuse a bad --source-schema with PRECONDITION and add migrate --data-schema

A schema name must match ^[a-z_][a-z0-9_]*$ and be at most 63
characters, and may be named once; a malformed or repeated name now
exits PRECONDITION from the command before any playbook, as spec 007
says, where it was a usage error and a repeat was silently dropped.
cloudfall migrate takes --data-schema DATABASE=NAME beside --data, so
the data step copies only the named schemas as data copy
--source-schema does; one for a database with no --data is refused
before any step (S-007-12, S-007-13).

Fixes #126

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* Describe a resumed migrate step by this run's options

A pending step of a persisted plan kept the description it was saved with,
so a data step saved with --data-schema crm=public and resumed without it
reported "schema public" while it copied every schema (#126, S-007-13).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

---------

Co-authored-by: shipmill-romamo[bot] <338557544+shipmill-romamo[bot]@users.noreply.github.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>

* Validate Domain routes (S-003-1, S-003-8..12) (#168)

* Validate Domain routes (S-003-1, S-003-8..12)

A Domain can now declare spec.routes: proxy routes (path + upstream) and
static routes (path + static component, directory, spaFallback). The schema
holds each route's shape and the release-relative directory and fallback;
config validate checks the route source, path syntax, reserved and duplicate
paths, and that a static component exists and is deployed on the proxy
server. The inventory parses routes into value objects and serializes them
only when present, so a Domain with only proxy.upstream is unchanged.
Rendering routes comes with #162.

Fixes #159
Part of #90

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* Refuse a trailing newline in a static route's directory or spaFallback

The releasePath schema pattern ends with $, which Python's re.search lets
match before one trailing newline, so config validate accepted
directory: "web\n" that ReleaseRelativePath then refused with a ValueError
when the inventory loaded. Refuse control characters in the schema, as
component.schema.json's controlCharacterFree does.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

---------

Co-authored-by: shipmill-romamo[bot] <338557544+shipmill-romamo[bot]@users.noreply.github.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>

* Refuse a data copy the target can't hold before dumping it

A preflight on the target host, after the empty-target check and
before pg_dump, reads the source and target catalogs and names every
schema the source lacks, extension and policy role the target lacks,
and auth. reference, taking every name only as a psql variable. Any
finding refuses the copy with nothing dumped: data copy and migrate's
data step exit PRECONDITION with data_copy_preflight_refused and the
four lists, read whole from a findings file under --data-migrations.
pg_dump takes the needed extensions with named schemas, and pg_restore
runs in one transaction, so a failed restore leaves no table and no
receipt. Spec 007 is built (S-007-6 to S-007-11).

Fixes #125

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

---------

Co-authored-by: shipmill-romamo[bot] <338557544+shipmill-romamo[bot]@users.noreply.github.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Validate a command health check and name it in plans (S-009-1, S-009-13)

0 participants