Skip to content

Apply one Domain on its proxy server with cloudfall domains apply (#117) - #137

Merged
shipmill-romamo[bot] merged 3 commits into
mainfrom
feat/117-domains-apply
Oct 27, 2026
Merged

shipmill-romamo[bot] merged 3 commits into
mainfrom
feat/117-domains-apply

Conversation

@shipmill-romamo

@shipmill-romamo shipmill-romamo Bot commented Oct 27, 2026 •

Copy link
Copy Markdown
Contributor

Summary

Implements the domains apply part of S-004 (docs/specs/004-domains-apply.md): criteria S-004-1, 3, 5, 6, 7, 8, 9 and 10. domains observe and the MCP tools (S-004-4, 14, 15, 16) stay with #118, so the spec is not marked built.

  • New domains group with cloudfall domains apply <domain> [--issue-certificate] [--deployments DIR] [--yes], modeled on deploy: the project is validated first (CONFIG_INVALID), an unknown id exits PRECONDITION (lifecycle_domain_missing), and both refusals come before the inventory render
  • With --yes it runs domains.yml with --limit <proxy.server> and cloudfall_domains_domain_id, the one-Domain run Converge one Domain in domains.yml and record the previous vhost hash (S-004-2, 11, 12, 13, 17) #116 added, and answers status: "ok", action: "domains-apply", domain, servers, receipt and effect: "updated". A run that writes no receipt is refused (lifecycle_receipt_missing), as deploy does. Without --yes it answers the plan (status: "plan", would_run, instruction, effect: "would_update") after running every controller-side check, the DNS check included
  • --issue-certificate refuses tls.mode: disabled (PRECONDITION, domains_tls_disabled) before anything else. On a dns-only Domain it then requires the primary name and every alias to resolve to at least one address of the proxy server (PRECONDITION, domains_dns_unverified, naming each failing name and what it resolved to), then passes cloudfall_domains_issue_certificates: true. A proxied Domain skips the check (dns_check: "skipped-proxied"); a checked one answers "verified"; without the flag dns_check is null
  • The resolver is a NameResolver protocol passed into preview_domain_apply / apply_domain (SystemNameResolver on the CLI, getaddrinfo), so tests answer DNS offline without monkeypatching. lifecycle.proxy_addresses computes the proxy address set the way migrate's dns-verify does (server address or what its hostname resolves to, plus network.ipv4); migrate itself is unchanged and keeps its own code
  • Catalog: domains apply is a servers command gated by --yes; output keys domain and dnsCheck are added (additive). sdk/README.md names the command

Fixes #117

Test plan

  • sdk/tests/test_domains_apply.py:
    • S-004-1: apply_domain runs the real domains.yml under ansible-playbook against the rendered inventory of the examples (servers h1, h2; crm-site proxied on h2), every host on a local connection and cloudfall_nginx_site swapped through ANSIBLE_ROLES_PATH for a recorder. The play recap lists only h2, every task that ran belongs to domains.yml or the stub, the stub saw cloudfall_nginx_site_issue_certificate as asked, and the receipt is written. A static check holds domains.yml to the one include_role of cloudfall_nginx_site, with no roles/import_playbook, and the role to no dependencies or nested role includes
    • S-004-3, 5, 7, 8 through cloudfall.cli.main: plan output, CONFIG_INVALID with and without --yes, proxied --issue-certificate plan, domains_tls_disabled with and without --yes; none renders the inventory or writes a receipt. An unknown id exits lifecycle_domain_missing too
    • S-004-6, 7, 8, 9 at the lifecycle level with a stand-in resolver and a recording runner: an alias elsewhere and a name resolving to nothing are refused naming them (plan and --yes), with no step run; proxied asks no resolver; TLS disabled is refused before DNS; the certificate run passes --limit h2 and the extra vars and answers dnsCheck: "verified" and the receipt
  • engine/tests/test_domains_certificate.py: Templar replays of the role's when conditions (issuance runs on a fresh Domain and is skipped once fullchain.pem exists; certbot.timer is enabled either way), and the role's receipt tasks run for real twice on localhost: the second receipt's configurationSha256 equals its previousConfigurationSha256 (S-004-10)
  • sdk/tests/test_commands.py: domains apply joins the --yes-gated set
  • sdk/tests/test_migrate.py: pins migrate's dns-verify addresses as on main: an unresolvable server hostname (.invalid) adds no address and raises nothing, and a scoped link-local address (fe80::1%eth0) is kept as written
  • Ran locally: uv run ruff check ., uv run mypy, uv run cloudfall config validate --project config/examples, and the tests of the touched and neighbouring files (test_domains_apply, test_commands, test_migrate, test_domains_converge, test_lifecycle, test_project, test_app, test_agent_server, test_catalog, test_arguments, test_domains_certificate, test_domains_one_domain: 153 passed). No Ansible file changed, so ansible-lint was not needed. The full suite is left to CI
  • Not run live: a real Let's Encrypt issuance, fullchain.pem present and certbot.timer active on a Debian host (S-004-9), and the second run's actual skip of Issue the declared certificate against a real certificate (S-004-10); the real SystemNameResolver against public DNS

Plan

  • Serves: quality bar
  • Files: sdk/src/cloudfall/lifecycle.py, sdk/src/cloudfall/app.py, sdk/src/cloudfall/commands.py, sdk/README.md, changelog.d/117-domains-apply.md, tests
  • Order: resolver protocol and proxy addresses, preview and apply in lifecycle, domains apply command and catalog entry, tests per criterion (see the spec's Behaviour)
  • Proof: S-004-1, S-004-3, S-004-5, S-004-6, S-004-7, S-004-8, S-004-9, S-004-10 (specs.py coverage --spec 004; S-004-4, 14, 15, 16 stay with Add cloudfall domains observe and the domains MCP tools (S-004-4, 14, 15, 16) #118)

🤖 Generated with Claude Code

@shipmill-romamo shipmill-romamo Bot added the decided An agent decided at notify; the maintainer may veto label Oct 27, 2026
@shipmill-romamo

Copy link
Copy Markdown
Contributor Author

@romamo Decided without waiting: cloudfall domains apply is a new command with the output keys domain and dns_check (snake_case like every key), the context code domains_tls_disabled for S-004-8 (which names none), and a --yes run that writes no receipt is refused with lifecycle_receipt_missing, as deploy does; cloudfall migrate is unchanged.

Why: S-004-1, 3, 5..10 ask for this command; every existing command behaves as before.
Not taken: an unnamed PRECONDITION for S-004-8, and accepting a run that left no receipt (a run the record can't show).

Reply with a comment starting with veto before 2026-10-28 10:10 UTC to reverse it, adding what you want instead if you like. A later veto still counts and gets a follow-up.

shipmill-romamo Bot and others added 3 commits October 27, 2026 12:17
cloudfall domains apply <domain> [--issue-certificate] [--yes] runs
domains.yml limited to the Domain's proxy server and to that Domain, so
no baseline, firewall or service task runs. Without --yes it answers a
plan after every controller-side check. --issue-certificate refuses
tls.mode: disabled, and checks on the controller that the primary name
and every alias resolve to the proxy server (domains_dns_unverified);
a proxied Domain skips the check. The resolver is a protocol passed to
the lifecycle calls, so tests answer DNS offline. migrate's dns-verify
shares the proxy address computation.

Fixes #117

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
migrate goes back to its own address computation: any OSError from the
server hostname lookup counts as no address, and a scoped link-local
address is kept as written. Tests pin both.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The server schema accepts any IPv6 spelling (2001:DB8::10), but IpAddress
requires canonical notation, so --issue-certificate crashed with a
ValueError instead of running the DNS check. Canonicalise the parsed
address before building the expected set.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@shipmill-romamo
shipmill-romamo Bot force-pushed the feat/117-domains-apply branch from ad995db to 51c8193 Compare October 27, 2026 10:18
@shipmill-romamo
shipmill-romamo Bot merged commit 8ac1c34 into main Oct 27, 2026
1 check passed
@shipmill-romamo
shipmill-romamo Bot deleted the feat/117-domains-apply branch October 27, 2026 10:26
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

decided An agent decided at notify; the maintainer may veto

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Add cloudfall domains apply (S-004-1, 3, 5..10)

0 participants