Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
95 changes: 95 additions & 0 deletions api/v1alpha/httpproxy_types.go
Original file line number Diff line number Diff line change
Expand Up @@ -444,8 +444,103 @@ type HostnameStatus struct {
// +listMapKey=type
// +optional
Conditions []metav1.Condition `json:"conditions,omitempty"`

// DNSRecords lists every DNS record this hostname depends on, who publishes
// it, and whether it is in place. A record is Present only once it takes
// effect on the Internet. Records the user publishes stay listed while the
// hostname needs them, so this list alone says what is left to do.
//
// +listType=atomic
// +kubebuilder:validation:MaxItems=16
// +optional
DNSRecords []HostnameDNSRecord `json:"dnsRecords,omitempty"`
}

// HostnameDNSRecord is one DNS record a hostname depends on.
type HostnameDNSRecord struct {
// Name is the fully qualified name of the record, without a trailing dot.
//
// +kubebuilder:validation:Required
// +kubebuilder:validation:MaxLength=253
Name string `json:"name"`

// Type is the DNS record type. ALIAS stands for a CNAME at a zone apex,
// which DNS providers offer as ALIAS, ANAME or CNAME flattening.
//
// +kubebuilder:validation:Required
// +kubebuilder:validation:Enum=CNAME;ALIAS;TXT
Type string `json:"type"`

// Content is the value the record must hold.
//
// +kubebuilder:validation:Required
// +kubebuilder:validation:MaxLength=512
Content string `json:"content"`

// Purpose says what the record is for.
//
// +kubebuilder:validation:Required
Purpose HostnameDNSRecordPurpose `json:"purpose"`

// ManagedBy says who publishes the record: the user at their DNS provider,
// or the platform in a Datum DNS zone that serves the domain.
//
// +kubebuilder:validation:Required
ManagedBy HostnameDNSRecordManager `json:"managedBy"`

// State says whether the record is in place.
//
// +kubebuilder:validation:Required
State HostnameDNSRecordState `json:"state"`
}

// HostnameDNSRecordPurpose says what a DNS record is for.
//
// +kubebuilder:validation:Enum=Routing;Certificate;Ownership
type HostnameDNSRecordPurpose string

const (
// HostnameDNSRecordPurposeRouting points the hostname at the platform.
HostnameDNSRecordPurposeRouting HostnameDNSRecordPurpose = "Routing"

// HostnameDNSRecordPurposeCertificate delegates the ACME DNS challenge for
// the hostname to the platform, so certificates issue before traffic moves.
HostnameDNSRecordPurposeCertificate HostnameDNSRecordPurpose = "Certificate"

// HostnameDNSRecordPurposeOwnership proves ownership of the hostname's
// domain. It is listed until the domain is verified.
HostnameDNSRecordPurposeOwnership HostnameDNSRecordPurpose = "Ownership"
)

// HostnameDNSRecordManager says who publishes a DNS record.
//
// +kubebuilder:validation:Enum=User;Platform
type HostnameDNSRecordManager string

const (
// HostnameDNSRecordManagedByUser means the user publishes the record at
// their DNS provider.
HostnameDNSRecordManagedByUser HostnameDNSRecordManager = "User"

// HostnameDNSRecordManagedByPlatform means the platform publishes the record
// in a Datum DNS zone that serves the domain.
HostnameDNSRecordManagedByPlatform HostnameDNSRecordManager = "Platform"
)

// HostnameDNSRecordState says whether a DNS record is in place.
//
// +kubebuilder:validation:Enum=Present;Missing
type HostnameDNSRecordState string

const (
// HostnameDNSRecordPresent means the record takes effect on the Internet.
HostnameDNSRecordPresent HostnameDNSRecordState = "Present"

// HostnameDNSRecordMissing means the record is absent, wrong, or held in a
// zone that does not serve the domain.
HostnameDNSRecordMissing HostnameDNSRecordState = "Missing"
)

// HTTPProxyStatus defines the observed state of HTTPProxy.
type HTTPProxyStatus struct {
// Addresses lists the network addresses that have been bound to the
Expand Down
20 changes: 20 additions & 0 deletions api/v1alpha/zz_generated.deepcopy.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

60 changes: 60 additions & 0 deletions config/crd/bases/networking.datumapis.com_httpproxies.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -3705,6 +3705,66 @@ spec:
x-kubernetes-list-map-keys:
- type
x-kubernetes-list-type: map
dnsRecords:
description: |-
DNSRecords lists every DNS record this hostname depends on, who publishes
it, and whether it is in place. A record is Present only once it takes
effect on the Internet. Records the user publishes stay listed while the
hostname needs them, so this list alone says what is left to do.
items:
description: HostnameDNSRecord is one DNS record a hostname
depends on.
properties:
content:
description: Content is the value the record must hold.
maxLength: 512
type: string
managedBy:
description: |-
ManagedBy says who publishes the record: the user at their DNS provider,
or the platform in a Datum DNS zone that serves the domain.
enum:
- User
- Platform
type: string
name:
description: Name is the fully qualified name of the record,
without a trailing dot.
maxLength: 253
type: string
purpose:
description: Purpose says what the record is for.
enum:
- Routing
- Certificate
- Ownership
type: string
state:
description: State says whether the record is in place.
enum:
- Present
- Missing
type: string
type:
description: |-
Type is the DNS record type. ALIAS stands for a CNAME at a zone apex,
which DNS providers offer as ALIAS, ANAME or CNAME flattening.
enum:
- CNAME
- ALIAS
- TXT
type: string
required:
- content
- managedBy
- name
- purpose
- state
- type
type: object
maxItems: 16
type: array
x-kubernetes-list-type: atomic
hostname:
description: |-
Hostname is the fully qualified domain name being tracked.
Expand Down
82 changes: 82 additions & 0 deletions docs/api/httpproxies.md
Original file line number Diff line number Diff line change
Expand Up @@ -4725,6 +4725,16 @@ Must be a valid RFC 1123 hostname without a trailing dot.<br/>
Standard condition types include Verified and DNSRecordProgrammed.<br/>
</td>
<td>false</td>
</tr><tr>
<td><b><a href="#httpproxystatushostnamestatusesindexdnsrecordsindex">dnsRecords</a></b></td>
<td>[]object</td>
<td>
DNSRecords lists every DNS record this hostname depends on, who publishes
it, and whether it is in place. A record is Present only once it takes
effect on the Internet. Records the user publishes stay listed while the
hostname needs them, so this list alone says what is left to do.<br/>
</td>
<td>false</td>
</tr></tbody>
</table>

Expand Down Expand Up @@ -4804,3 +4814,75 @@ with respect to the current state of the instance.<br/>
<td>false</td>
</tr></tbody>
</table>


### HTTPProxy.status.hostnameStatuses[index].dnsRecords[index]
<sup><sup>[↩ Parent](#httpproxystatushostnamestatusesindex)</sup></sup>



HostnameDNSRecord is one DNS record a hostname depends on.

<table>
<thead>
<tr>
<th>Name</th>
<th>Type</th>
<th>Description</th>
<th>Required</th>
</tr>
</thead>
<tbody><tr>
<td><b>content</b></td>
<td>string</td>
<td>
Content is the value the record must hold.<br/>
</td>
<td>true</td>
</tr><tr>
<td><b>managedBy</b></td>
<td>enum</td>
<td>
ManagedBy says who publishes the record: the user at their DNS provider,
or the platform in a Datum DNS zone that serves the domain.<br/>
<br/>
<i>Enum</i>: User, Platform<br/>
</td>
<td>true</td>
</tr><tr>
<td><b>name</b></td>
<td>string</td>
<td>
Name is the fully qualified name of the record, without a trailing dot.<br/>
</td>
<td>true</td>
</tr><tr>
<td><b>purpose</b></td>
<td>enum</td>
<td>
Purpose says what the record is for.<br/>
<br/>
<i>Enum</i>: Routing, Certificate, Ownership<br/>
</td>
<td>true</td>
</tr><tr>
<td><b>state</b></td>
<td>enum</td>
<td>
State says whether the record is in place.<br/>
<br/>
<i>Enum</i>: Present, Missing<br/>
</td>
<td>true</td>
</tr><tr>
<td><b>type</b></td>
<td>enum</td>
<td>
Type is the DNS record type. ALIAS stands for a CNAME at a zone apex,
which DNS providers offer as ALIAS, ANAME or CNAME flattening.<br/>
<br/>
<i>Enum</i>: CNAME, ALIAS, TXT<br/>
</td>
<td>true</td>
</tr></tbody>
</table>
21 changes: 16 additions & 5 deletions internal/controller/httpproxy_controller.go
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,8 @@ type HTTPProxyReconciler struct {
Config config.NetworkServicesOperator

DownstreamCluster cluster.Cluster

routing *routingObserver
}

type desiredHTTPProxyResources struct {
Expand Down Expand Up @@ -407,7 +409,9 @@ func (r *HTTPProxyReconciler) Reconcile(ctx context.Context, req mcreconcile.Req

applyPartialProgramming(ctx, desiredResources.partialProgramming, programmedCondition)

r.reconcileHTTPProxyHostnameStatus(ctx, cl.GetClient(), gateway, httpProxyCopy, string(req.ClusterName))
if recheck := r.reconcileHTTPProxyHostnameStatus(ctx, cl.GetClient(), gateway, httpProxyCopy, string(req.ClusterName)); recheck {
return ctrl.Result{RequeueAfter: routingRecheckInterval}, nil
}

return ctrl.Result{}, nil
}
Expand Down Expand Up @@ -571,21 +575,21 @@ func (r *HTTPProxyReconciler) reconcileHTTPProxyHostnameStatus(
gateway *gatewayv1.Gateway,
httpProxyCopy *networkingv1alpha.HTTPProxy,
clusterName string,
) {
) (recheckRouting bool) {
logger := log.FromContext(ctx)

gatewayAcceptedCondition := apimeta.FindStatusCondition(gateway.Status.Conditions, string(gatewayv1.GatewayConditionAccepted))
if gatewayAcceptedCondition == nil {
// Should never happen due to defaulting, but just in case
logger.Info("accepted condition not found on gateway")
return
return false
} else if gatewayAcceptedCondition.ObservedGeneration != gateway.Generation {
logger.Info(
"observed generation on accepted condition does not match generation on gateway, delaying processing",
"gateway_generation", gateway.Generation,
"condition_generation", gatewayAcceptedCondition.ObservedGeneration,
)
return
return false
}
logger.Info("updating hostname status")

Expand Down Expand Up @@ -682,16 +686,22 @@ func (r *HTTPProxyReconciler) reconcileHTTPProxyHostnameStatus(
availabilityStatuses := buildAvailabilityStatuses(acceptedHostnames, inUseHostnames, httpProxyCopy.Generation)
dnsStatuses := r.buildDNSStatuses(ctx, cl, gateway, httpProxyCopy.Generation)
certificateStatuses := r.buildCertificateStatuses(ctx, cl, clusterName, gateway, httpProxyCopy)
dnsRecordStatuses, recheckRouting := r.buildDNSRecordStatuses(ctx, cl, gateway, httpProxyCopy)
previousHostnameStatuses := httpProxyCopy.Status.HostnameStatuses
httpProxyCopy.Status.HostnameStatuses = mergeHostnameStatuses(availabilityStatuses, dnsStatuses, certificateStatuses)
httpProxyCopy.Status.HostnameStatuses = mergeHostnameStatuses(availabilityStatuses, dnsStatuses, certificateStatuses, dnsRecordStatuses)
preserveHostnameConditionTransitions(httpProxyCopy.Status.HostnameStatuses, previousHostnameStatuses)

r.setCertificatesReadyCondition(httpProxyCopy, certificateStatuses, gateway)

return recheckRouting
}

// SetupWithManager sets up the controller with the Manager.
func (r *HTTPProxyReconciler) SetupWithManager(mgr mcmanager.Manager) error {
r.mgr = mgr
if r.routing == nil {
r.routing = newRoutingObserver()
}

builder := mcbuilder.ControllerManagedBy(mgr).
For(&networkingv1alpha.HTTPProxy{}).
Expand Down Expand Up @@ -1744,6 +1754,7 @@ func mergeHostnameStatuses(statusSets ...[]networkingv1alpha.HostnameStatus) []n
for _, c := range hs.Conditions {
apimeta.SetStatusCondition(&existing.Conditions, c)
}
existing.DNSRecords = append(existing.DNSRecords, hs.DNSRecords...)
} else {
copy := hs
byHostname[hs.Hostname] = &copy
Expand Down
Loading
Loading