Feature Specification: Effect octodns-zone
Created: 2026-10-07
Status: Implemented
Input: Reconstructed from the code of core/effects/octodns/ and core/setups/ at 14504f80d.
The octodns-zone effect publishes the DNS records of one group into one zone. The zone lives at an external DNS service or at the own named of the group. The effect deploys no nodes. The tf-incus setup builds it (tf-incus.md). The shared behavior of a standalone effect is in spec.md. The verbs and the state rules are in 004.
User Scenarios & Testing (mandatory)
User Story 1 - Publish the group zone (Priority: P1)
An operator deploys a group, runs a plan to see the record diff, and then runs a sync to push the records into the DNS service. The service then answers for every node and every served name.
Why this priority: Without the records, no user reaches a deployed service by name.
Independent Test: An integration test pushes a whole zone into an empty PowerDNS zone. An integration test pushes the group zone into the own named of the group with dynamic updates, and checks the transfer to the second nameserver.
Acceptance Scenarios:
- Given a rendered zone and a provider token, When the operator runs the plan, Then the effect prints the diff and changes nothing.
- Given the same inputs, When the operator runs the sync, Then the DNS service serves the records of the rendered zone.
- Given a provider secret that no source supplies, When the effect evaluates, Then the evaluation fails and names the secret.
User Story 2 - Render the zone from NetBox at run time (Priority: P1)
The group uses external IPAM, so every address is a placeholder at evaluation. The effect asks NetBox for the allocated addresses at run time and renders the zone from them.
Why this priority: Under external IPAM, a zone from the evaluation holds no real address, so a push from it is wrong.
Independent Test: An integration test renders from NetBox. A unit test of the setup checks when the zone comes from NetBox and when from the evaluation.
Acceptance Scenarios:
- Given a NetBox zone with a cluster name, When the sync runs, Then the zone holds one address record per NetBox address of that cluster, at a short TTL.
- Given a cluster name that NetBox does not hold, When the sync runs, Then the render fails and names the cluster.
User Story 3 - Share a zone that other parties also write (Priority: P2)
A third party holds the zone and writes records into it. With ownership on, the effect changes only the records that it wrote before. It does not delete or adopt the other records.
Why this priority: A push into a shared zone must not remove what a different party publishes.
Independent Test: An integration test pushes with ownership into zones that already hold records of a different owner.
Acceptance Scenarios:
- Given ownership and a record of a different owner in the zone, When the sync runs, Then that record stays and the effect names it.
- Given ownership and a rendered CNAME or a record of the same type at a name of a different owner, When the sync runs, Then the push fails on the collision.
- Given ownership and a plan that deletes more than a small part of the records, When the sync runs, Then the plan is refused.
User Story 4 - Publish a TXT value that a node minted (Priority: P3)
A node writes a public value to OpenBao KV, for example a key that a mail relay made. No evaluation can know that value. The effect reads it at run time and adds it as a TXT record.
Why this priority: Some records hold data that exists only after the node starts.
Independent Test: An integration test covers every way that the KV read fails.
Acceptance Scenarios:
- Given KV TXT labels and an OpenBao address, When KV holds the value, Then the zone holds the TXT record.
- Given the same, When one value is missing, Then the render writes the other records and the verb fails.
- Given missing values marked optional, When one value is missing, Then the effect skips that record and the verb succeeds.
User Story 5 - Publish the provider apex at deSEC (Priority: P3)
The apex of the provider is in the deSEC account of the operator lane. The effect publishes the group zone there with the manager token of that account. deSEC refuses a write with a TTL under the minimum TTL of the domain, and the rendered zone has shorter TTLs.
Why this priority: A provider that has no PowerDNS of its own can keep its apex at the same deSEC account as the zones of its operators.
Independent Test: A test with no VM runs the sync against a stub of the deSEC API.
Acceptance Scenarios:
- Given a zone with a TTL under the minimum TTL of the domain, When the sync runs, Then deSEC holds the record and its ownership marker at the minimum TTL.
- Given a record of a different owner in the deSEC domain, When the sync runs, Then that record stays.
- Given a sync that wrote the zone, When a second sync runs, Then it writes nothing.
- Given a token that deSEC refuses, When the sync runs, Then it fails and writes nothing.
Edge Cases
- A NetBox zone without a cluster name: the render looks up addresses in all clusters and prints a note.
- KV secrets without an OpenBao address, or a secret that two sources name: the evaluation fails.
- Withdrawals turned off: the plan drops every delete and prints them. The CI publish script uses this for its additive fallback.
- A plan that changes the apex
NSset: the effect accepts that change, and keeps the percentage guards for all other changes. - The verify cannot reach a nameserver over IPv6 and a different address of that nameserver answers: it sets the IPv6 failure aside.
Requirements (mandatory)
Functional Requirements
- FR-001: The catalog MUST offer
octodns-zonefor a group only when the DNS routing of the group is external or the group has DNS nodes. - FR-002: The effect MUST read its coordinates from the key set of the
tf-incusmethod. It MUST also declare its run-time switches: no withdrawals, and optional KV TXT values. - FR-003: The effect MUST offer the verbs validate, plan, sync and verify. Its run MUST be the sync. The validate MUST check the whole rendered configuration. The plan MUST change nothing, and the sync MUST apply.
- FR-004: The verify MUST ask every nameserver of the zone, without recursion, for every rendered record except
SOA, and MUST tell apart three results: all records served, a shortfall, and a failure to ask. - FR-005: The effect MUST take the zone from the evaluation, or from a run-time command. With a NetBox zone or KV TXT labels, the default command MUST render from NetBox and then add the KV TXT values.
- FR-006: The NetBox render MUST write the node records and the apex
NSat a short TTL, the other records at a long TTL, and a placeholderSOA. - FR-007: The effect MUST give each provider secret to OctoDNS through the environment, from a file at run time, and MUST NOT put it in the store.
- FR-008: The effect MUST read each KV secret from the OpenBao KV of its instance before OctoDNS runs.
- FR-009: The effect MUST support the providers
desec,hetzner,powerdnsandbind. Onbind, it MUST write with dynamic updates that a TSIG key signs. Ondesec, it MUST write to the deSEC service thatserver_urlnames (https://desec.iowhen not set), MUST apply the percentage thresholds, and MUST raise each TTL under the minimum TTL of the domain to that minimum. A provider name thatprovidersdoes not declare MUST fail at evaluation. - FR-010: The provider MUST allow a plan to update all records. Under ownership, it MUST refuse a plan that deletes more than a small part of the records.
- FR-011: Under ownership, the effect MUST NOT change, delete or adopt a record that it did not write, and MUST then honor the withdrawal switch. Without ownership, it MUST honor the withdrawal switch and then let the apex
NSchange. - FR-012: With an OpenBao address, the KV TXT step MUST process every label, and fail after that when one read failed.
- FR-013: The effect MUST remove its scratch directory on exit.
- FR-014: The zone MUST be a subzone: the records of the apex and its subdomains, with no
SOAand no zone TTL. The effect adds theSOAwhen it renders the zone file. An unknown record type MUST fail at evaluation. - FR-015: The configuration of each provider MUST be a set of option declarations, which type the configuration of the chosen provider. A value in place of a declaration MUST fail at evaluation.
- FR-016: A
service(scheme, host, port and token file) MUST set the configuration and the token secret of the chosen provider:server_urlandtokenondesec,tokenonhetzner, the host, port and scheme andapi_keyonpowerdns. On a provider that is not reached as a service, such asbind, aserviceMUST fail at evaluation.
Key Entities
- Rendered zone: A zone file for the domain, from the evaluation or from the run-time command.
- Provider: The DNS service that the sync writes to, with its secrets.
- Ownership marker: The records with which OctoDNS marks the records that this effect wrote.
- KV TXT value: A public value that a node wrote to OpenBao KV.
Success Criteria (mandatory)
Measurable Outcomes
- SC-001: After a sync, the verify reports all records served.
- SC-002: A second plan after a sync shows no change.
- SC-003: Under ownership, the count of records of other owners is the same before and after a sync.
- SC-004: No provider secret occurs in the store or in the rendered configuration.
- SC-005: The verbs read no environment key that the effect does not declare.
Assumptions
- The machine that runs the verb can reach the API of the DNS service.
- With a NetBox zone, the apply of the
tf-incusmethod already recorded the addresses in NetBox. - With KV TXT labels, the nodes already wrote their values to OpenBao KV.
- The version of OctoDNS keeps the percentage thresholds on the provider, and ignores thresholds on the zone.