Repository navigation
Read one Domain on its proxy server with cloudfall domains observe (#118) - #142
Conversation
cloudfall domains observe <domain> runs the new read-only domains-observe.yml on the Domain's proxy server alone and reports the vhost file (present, sha256), the certificate's not_after, whether certbot.timer is enabled and active, and whether the vhost still has the configurationSha256 of the last receipt (receipt: null before one). Every server task is a stat or a command with changed_when: false; the reading comes back through a controller file, and the inventory is rendered in a temporary directory, so the project is left as it was. A missing vhost exits 0, an unknown Domain exits PRECONDITION (lifecycle_domain_missing) before the inventory render. domains apply and domains observe join the MCP project tools; domains_apply keeps its yes gate. Spec S-004 is built. Fixes #118 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
@romamo Decided without waiting: Why: S-004-14 says observe changes no file in the project, so its inventory can't go to Reply with a comment starting with |
|
@romamo Decided without waiting: Why: S-004-4, 14, 15, 16 ask for these; every existing command and tool answers as before. Reply with a comment starting with |
Summary
Implements the last open part of S-004 (
docs/specs/004-domains-apply.md): criteria S-004-4, 14, 15 and 16. With #116 and #117 merged, the spec is nowstatus: built, and its Issues and Verification sections are filled in.cloudfall domains observe <domain> [--deployments DIR]. It validates the project, refuses an unknown id withPRECONDITION(lifecycle_domain_missing) before the inventory render, loads<deployments>/<domain>.jsonwhen there is one (strict schema validation,CONFIG_INVALIDwhen malformed), then runs the newengine/ansible/playbooks/domains-observe.ymlwith--limit <proxy.server>status,action: "domains-observe",domain,server,vhost(path,present,sha256),receipt(configuration_sha256,deployed_at,matches, ornullwhen there is no receipt),certificate(path,present,not_afteras UTC RFC 3339), andcertbot_timer(enabled,active). Keys come out snake_case. A missing vhost or certificate is a reading and exits 0. An unreachable server exitsENGINE_STEP_FAILED, and so does a reading that breaks the engine contract (wrong domain or server, a non-boolean flag, a hash with no file, an unreadable date)domains-observe.ymlonly reads on the server:set_fact,assert, twostats (the vhost hashed with sha256 through links, as the site role hashes what it replaces),openssl x509 -noout -enddate, andsystemctl is-enabled|is-active certbot.timer. Each command haschanged_when: falseandcheck_mode: false, so the playbook reads the same under--check. Its one write is the reading itself, handed back to the controller atcloudfall_domains_observe_reading(delegate_to: localhost,become: false), asbackup.ymldoes with its receipt.gather_facts: falsetempfile.TemporaryDirectoryoutside the project. Sodomains observetakes no--inventory-filedomains.applyanddomains.observejoinPROJECT_COMMANDS, socloudfall mcp servelistsdomains_applyanddomains_observe.domains_applykeeps the CLI'syesgate (the sameFlag(confirm=True)asdeploy): withoutyesit answers the plan. The project instructions name both toolsdomains applyalready refused an unknown Domain (Add cloudfall domains apply (S-004-1, 3, 5..10) #117). Its test is renamedtest_s004_4_...domains observeread entry, the keysserver,vhost,certificateandcertbotTimer, and a fixed shape for an objectreceipt, sonullstaysnullinstead of{}._key_schemanow applies a fixed shape only where the key is an object, soreceiptstays a path string in every other command. Theplaybook runsummary namesdomains-observeas read-only besideinspectservice_evidence.load_deployment_receiptloads one receipt file.load_deployment_receiptsnow uses the same per-file builder, and its behaviour is unchangedsdk/README.mdandengine/README.mddescribe the command and the playbook. Changelog fragmentchangelog.d/118-domains-observe.mdFixes #118
Part of #87
Test plan
sdk/tests/test_domains_observe.py:domains-observe.ymlruns underansible-playbookagainst the rendered inventory of the examples, with every host on a local connection, the proxy server's files undertmp_path, and stand-inopenssl/systemctlscripts first onPATH. Onlyh2appears in the recap. The vhost hash, the certificate expiry and the timer state come back as read. Project and server files are unchanged by content and mtime. A missing vhost and certificate read aspresent: falsewith exit 0. A static check holds every server task tostat/set_fact/assertor a command withchanged_when: false, and the onecopyto the controller reading path. Throughcloudfall.cli.main, with a stand-in engine playbook, the payload comes out snake_case and the project tree is unchanged, because the inventory is rendered outside itmatches: truewhile the vhost is unchanged,falseonce it is edited or removed, andreceipt: nullwith no receipt. Through the CLI, the receipt object's keys are snake_casePRECONDITION/lifecycle_domain_missingand creates notmp/. At the lifecycle level, no step startsdomains-observe.ymlwith--limit h2and the two extra vars. Malformed readings exitlifecycle_execution_failed. A server address under.invalidexitsENGINE_STEP_FAILEDsdk/tests/test_domains_apply.py: S-004-4 forapply(renamed, plan and--yes)sdk/tests/test_agent_server.py: S-004-16 over the real stdio transport. Both tools are listed withdomainand no bound path properties,domains_observeisread_only_hint,domains_applyhasyes, and adomains_applycall withoutyesanswers the plan (dry_run)specs.py checkpasses, andspecs.py coverage --spec 004lists tests for every S-004 criterionuv run ruff check .,uv run mypy,uv run cloudfall config validate --project config/examples, and ansible-lint onengine/ansiblewithANSIBLE_CONFIG=engine/ansible/ansible.cfg(production profile, passed). Tests:test_app,test_commands,test_agent_server,test_lifecycle,test_domains_apply,test_domains_observe,test_domains_converge,test_service_evidence,test_operations_dashboard,test_dashboard_server,test_cutover,test_project,test_catalog,test_arguments,test_migrate, and enginetest_playbook,test_domains_one_domain,test_domains_certificate,test_domains_playbookall pass. The full suite is left to CIdomains-observe.ymlover SSH on a Debian proxy server, realopensslon a Let's Encryptfullchain.pem, the exit codes of realsystemctl is-enabled/is-activefor a missing unit, and reading/etc/letsencrypt/liveunderbecomePlan
observe_domainin lifecycle,domains observecommand and catalog entry, MCP tools, tests per criterion, spec built (see the spec's Behaviour)specs.py coverage --spec 004lists every S-004 criterion)🤖 Generated with Claude Code