You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
Commit 87aa1dc
Browse filesBrowse the repository at this point in the historyBrowse files
feat(route53): adopt an existing hosted zone by name (Zone.spec.adoption.byName) (#14)
Route 53 Zone adoption becomes a oneOf: {hostedZoneId: Z...} (unchanged) or
{byName: true}. By-name resolution lists same-name hosted zones, keeps those
whose private/public type matches the ZoneClass zoneType, requires exactly
one match, and records its ID in status through the existing adoption
patch; later reconciles and deletion use the recorded ID and never re-list.
Zero matches -> Programmed=False ExternalResourceNotFound; several ->
ExternalResourceMismatch with the count; both fields set -> Accepted=False
InvalidAdoption. No implicit adoption without spec.adoption; record-set
adoption, Cloudflare and VPC-association behaviour unchanged.
Motivation: appthrust/platform hosting-substrate-design.md S2 — the hosting
bundle renders Zone hcp for hcp.<installation>.internal under a Private
class; the hosted zone ID lives only in Terraform state and the installation
spec is immutable, so adoption by name removes the need to thread the ID.
Copy file name to clipboardExpand all lines: app/operator/config/provider/route53_extensions.yaml
+10-2Lines changed: 10 additions & 2 deletions
Original file line number
Diff line number
Diff line change
@@ -60,14 +60,22 @@ spec:
60
60
adoption:
61
61
openAPIV3Schema:
62
62
type: object
63
-
required:
64
-
- hostedZoneId
63
+
oneOf:
64
+
- required:
65
+
- hostedZoneId
66
+
- required:
67
+
- byName
65
68
additionalProperties: false
66
69
properties:
67
70
hostedZoneId:
68
71
type: string
69
72
description: Route 53 hosted zone ID of the existing hosted zone to adopt. The zone must be public or private as declared by the ZoneClass zoneType.
70
73
pattern: '^Z[A-Z0-9]+$'
74
+
byName:
75
+
type: boolean
76
+
description: Adopt exactly one existing hosted zone matching domainName and the ZoneClass zoneType. The resolved hosted zone ID is recorded in status and reused without further name lookups.
Copy file name to clipboardExpand all lines: deploy/charts/dns-api/templates/provider-route53.yaml
+10-2Lines changed: 10 additions & 2 deletions
Original file line number
Diff line number
Diff line change
@@ -61,14 +61,22 @@ spec:
61
61
adoption:
62
62
openAPIV3Schema:
63
63
type: object
64
-
required:
65
-
- hostedZoneId
64
+
oneOf:
65
+
- required:
66
+
- hostedZoneId
67
+
- required:
68
+
- byName
66
69
additionalProperties: false
67
70
properties:
68
71
hostedZoneId:
69
72
type: string
70
73
description: Route 53 hosted zone ID of the existing hosted zone to adopt. The zone must be public or private as declared by the ZoneClass zoneType.
71
74
pattern: '^Z[A-Z0-9]+$'
75
+
byName:
76
+
type: boolean
77
+
description: Adopt exactly one existing hosted zone matching domainName and the ZoneClass zoneType. The resolved hosted zone ID is recorded in status and reused without further name lookups.
Copy file name to clipboardExpand all lines: docs/design/route53-provider.md
+19-5Lines changed: 19 additions & 5 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -134,7 +134,7 @@ AWS credentials themselves are not stored in `ZoneClass.spec.parameters`. `ZoneC
134
134
135
135
### Private hosted zones
136
136
137
-
With `zoneType: Private`, dns-api adopts an existing private hosted zone through `Zone.spec.adoption.hostedZoneId` and manages its RecordSets. dns-api never creates or deletes private hosted zones and never manages their VPC associations. Create the private hosted zone and configure its VPC associations outside dns-api before adoption.
137
+
With `zoneType: Private`, dns-api adopts an existing private hosted zone through `Zone.spec.adoption.hostedZoneId` or `Zone.spec.adoption.byName: true` and manages its RecordSets. dns-api never creates or deletes private hosted zones and never manages their VPC associations. Create the private hosted zone and configure its VPC associations outside dns-api before adoption.
138
138
139
139
The ZoneClass accepts `zoneCreationPolicy` only when absent or `Deny`, and `zoneDeletionPolicy` only when absent or `Retain`. Otherwise it sets `Accepted=False`, reason `InvalidParameters`, with `zoneType Private requires zoneCreationPolicy Deny` or `zoneType Private requires zoneDeletionPolicy Retain`. `sameNameZonePolicy` and `tags` are allowed, but apply only to hosted zone creation and do not cause private zone creation or tagging during adoption.
140
140
@@ -166,31 +166,45 @@ When `Zone.spec.adoption` is absent, the controller creates public hosted zones
166
166
167
167
Route 53 controller stores `CreateHostedZone` `CallerReference` only as provider-internal state in `ZoneUnit.status.provider.state`. It is not stored in `ZoneUnit.spec` and is not public `status.provider.data`. The controller may reuse a cached caller reference while status is present, but the caller reference is not the recovery source of truth.
168
168
169
-
If Route 53 status is lost after a hosted zone was created, recovery uses explicit adoption through `Zone.spec.adoption.hostedZoneId` / `ZoneUnit.spec.zone.adoption.hostedZoneId`. If status and adoption are both absent, the controller does not automatically adopt a same-name hosted zone. It reports `Programmed=False`, reason `ProviderConflict`, when provider state is ambiguous.
169
+
If Route 53 status is lost after a hosted zone was created, recovery uses explicit adoption through `Zone.spec.adoption` / `ZoneUnit.spec.zone.adoption`, either by hosted zone ID or by name. If status and adoption are both absent, the controller does not automatically adopt a same-name hosted zone. It reports `Programmed=False`, reason `ProviderConflict`, when provider state is ambiguous.
170
170
171
171
Temporary Route 53 ZoneUnit controller pseudocode is not part of the design source of truth. Hosted zone resolution order, finalizers, pending changes, Route 53 API errors, batch limits, and requeue behavior are defined in this document.
172
172
173
173
Route 53 controller re-observes hosted zones and child record sets even in stable state. If provider state differs from `ZoneUnit.spec`, it treats the difference as drift and self-heals owned items. Re-observation intervals, change check intervals, and temporary error retry intervals are controller configuration, not `Zone.spec` or `RecordSet.spec`.
174
174
175
175
Route 53 controller also enqueues a `ZoneUnit` when `ZoneUnit.metadata.annotations["dns.appthrust.io/reconcile-request"]` changes. This requests re-observation of the hosted zone and child record sets without waiting for periodic re-observation. It follows the same validation, pending change, batch serialization, and drift self-healing rules. Updating the annotation alone must not set `Programmed=False` or send no-op provider API changes when external state already matches `ZoneUnit.spec`.
176
176
177
-
Route 53 `Zone.spec.adoption` shape:
177
+
Route 53 `Zone.spec.adoption` accepts exactly one of these shapes:
178
178
179
179
```yaml
180
180
spec:
181
181
adoption:
182
182
hostedZoneId: Z10219583PPQMV0U1KGN2
183
183
```
184
184
185
-
`hostedZoneId`is a Route 53 hosted zone ID in `Z...` form. `/hostedzone/Z...` and hosted zone ARNs are not accepted. This shape is validated by Route 53 Provider version `zone.schemas.adoption`.
185
+
```yaml
186
+
spec:
187
+
domainName: hcp.example.internal
188
+
adoption:
189
+
byName: true
190
+
```
191
+
192
+
`hostedZoneId`is a Route 53 hosted zone ID in `Z...` form. `/hostedzone/Z...` and hosted zone ARNs are not accepted. Alternatively, `byName` must be the boolean `true`; `false`, an empty adoption object, or both fields together are invalid. These mutually exclusive shapes are validated by the `oneOf` in Route 53 Provider version `zone.schemas.adoption`. Invalid adoption sets `Accepted=False`, reason `InvalidAdoption`.
193
+
194
+
By-name adoption avoids passing an externally managed hosted zone ID (for example, an ID held only in Terraform state) through immutable installation configuration. It is explicit opt-in, not implicit same-name recovery.
186
195
187
196
Route 53 `Provider` schema descriptions for `zone.schemas.adoption`:
188
197
189
198
| Field | `description` |
190
199
| --- | --- |
191
200
| `hostedZoneId` | `Route 53 hosted zone ID of the existing hosted zone to adopt. The zone must be public or private as declared by the ZoneClass zoneType.` |
201
+
| `byName` | `Adopt exactly one existing hosted zone matching domainName and the ZoneClass zoneType. The resolved hosted zone ID is recorded in status and reused without further name lookups.` |
202
+
203
+
For `byName: true` without a recorded hosted zone ID, the controller calls `ListHostedZonesByName` with `domainName` and retains only exact same-name zones after trailing-dot normalization whose public/private type matches the ZoneClass `zoneType` (`Public` by default). **By-name adoption requires exactly one same-name zone of the class's type.** A public and a private zone with the same name are not ambiguous when only one matches the class. Zero matches set `Programmed=False`, reason `ExternalResourceNotFound`. Multiple matches set `Programmed=False`, reason `ExternalResourceMismatch`; the message reports the matching count, never the candidate IDs.
204
+
205
+
For either adoption mode, the Route 53 controller gets the selected hosted zone, verifies its ID, verifies that the hosted zone name matches `ZoneUnit.spec.zone.domainName` after trailing-dot normalization, and verifies that its public/private type matches the ZoneClass `zoneType`. If it matches, the controller manages its RecordSets and stores the resolved ID in `ZoneUnit.status.zone.provider.data.hostedZoneID` and the observed type in `zoneType`, using the same status patch for both modes. If the hosted zone does not exist, set `Programmed=False`, reason `ExternalResourceNotFound`. If the ID, type, or name differs, set `Programmed=False`, reason `ExternalResourceMismatch`. A Public class adopting a private zone by ID keeps the message `Route 53 hosted zone is private; only public hosted zones are supported`. A Private class adopting a public zone by ID reports `Route 53 hosted zone is public; ZoneClass zoneType is Private`.
192
206
193
-
When adoption is specified, the Route 53 controller gets the hosted zone, verifies that the hosted zone name matches `ZoneUnit.spec.zone.domainName`, and verifies that its public/private type matches the ZoneClass `zoneType`. If it matches, the controller manages its RecordSets and stores `hostedZoneId` in `ZoneUnit.status.zone.provider.data.hostedZoneID` and the observed type in `zoneType`. If the hosted zone does not exist, set `Programmed=False`, reason `ExternalResourceNotFound`. If the type or name differs, set `Programmed=False`, reason `ExternalResourceMismatch`. A Public class adopting a private zone keeps the message `Route 53 hosted zone is private; only public hosted zones are supported`. A Private class adopting a public zone reports `Route 53 hosted zone is public; ZoneClass zoneType is Private`.
207
+
Once recorded, the status hosted zone ID is authoritative for subsequent reconciles and deletion. By-name adoption does not list again or switch to another same-name zone, even if the recorded zone disappears; that case reports `ExternalResourceNotFound`. An explicit `hostedZoneId` differing from recorded status still sets `Accepted=False`, reason `ManagedResourceMismatch`. Deletion never resolves a previously unresolved by-name adoption: without a recorded ID there is no managed hosted zone to delete. Private zones are always retained; public zones follow the class deletion policy.
return ctrl.Result{}, r.setProgrammed(ctx, &zone, metav1.ConditionFalse, "ExternalResourceMismatch", "adopted Route 53 hosted zone ID does not match spec.adoption.hostedZoneId")
137
+
ifhostedZone.ID!=selection.ID {
138
+
message:="adopted Route 53 hosted zone ID does not match spec.adoption.hostedZoneId"
139
+
ifadoption.ByName {
140
+
message="adopted Route 53 hosted zone ID does not match resolved adoption target"
0 commit comments