From fda3120bd3055d3a7be3f29a33daf494759b4063 Mon Sep 17 00:00:00 2001 From: superflo22 Date: Fri, 11 Apr 2025 16:23:52 +0200 Subject: [PATCH] Name it and watch with annotations --- api/v1alpha1/zz_generated.deepcopy.go | 73 +++++++++++++++- ...ns.mayers.cloud_technitiumauthorities.yaml | 54 +++++++++++- .../dns.mayers.cloud_technitiumrecords.yaml | 27 +++++- config/rbac/role.yaml | 3 + .../externaldnswatcher_controller.go | 85 ++++++++++++++++++- 5 files changed, 230 insertions(+), 12 deletions(-) diff --git a/api/v1alpha1/zz_generated.deepcopy.go b/api/v1alpha1/zz_generated.deepcopy.go index b3a7019..3460742 100644 --- a/api/v1alpha1/zz_generated.deepcopy.go +++ b/api/v1alpha1/zz_generated.deepcopy.go @@ -24,12 +24,58 @@ import ( runtime "k8s.io/apimachinery/pkg/runtime" ) +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *AuthorityEndpoint) DeepCopyInto(out *AuthorityEndpoint) { + *out = *in + out.APIKeySecretRef = in.APIKeySecretRef +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new AuthorityEndpoint. +func (in *AuthorityEndpoint) DeepCopy() *AuthorityEndpoint { + if in == nil { + return nil + } + out := new(AuthorityEndpoint) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *AuthorityReference) DeepCopyInto(out *AuthorityReference) { + *out = *in +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new AuthorityReference. +func (in *AuthorityReference) DeepCopy() *AuthorityReference { + if in == nil { + return nil + } + out := new(AuthorityReference) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *SecretKeySelector) DeepCopyInto(out *SecretKeySelector) { + *out = *in +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new SecretKeySelector. +func (in *SecretKeySelector) DeepCopy() *SecretKeySelector { + if in == nil { + return nil + } + out := new(SecretKeySelector) + in.DeepCopyInto(out) + return out +} + // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *TechnitiumAuthority) DeepCopyInto(out *TechnitiumAuthority) { *out = *in out.TypeMeta = in.TypeMeta in.ObjectMeta.DeepCopyInto(&out.ObjectMeta) - out.Spec = in.Spec + in.Spec.DeepCopyInto(&out.Spec) out.Status = in.Status } @@ -86,6 +132,12 @@ func (in *TechnitiumAuthorityList) DeepCopyObject() runtime.Object { // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *TechnitiumAuthoritySpec) DeepCopyInto(out *TechnitiumAuthoritySpec) { *out = *in + out.Primary = in.Primary + if in.Secondaries != nil { + in, out := &in.Secondaries, &out.Secondaries + *out = make([]AuthorityEndpoint, len(*in)) + copy(*out, *in) + } } // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new TechnitiumAuthoritySpec. @@ -118,7 +170,7 @@ func (in *TechnitiumRecord) DeepCopyInto(out *TechnitiumRecord) { *out = *in out.TypeMeta = in.TypeMeta in.ObjectMeta.DeepCopyInto(&out.ObjectMeta) - out.Spec = in.Spec + in.Spec.DeepCopyInto(&out.Spec) out.Status = in.Status } @@ -175,6 +227,23 @@ func (in *TechnitiumRecordList) DeepCopyObject() runtime.Object { // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *TechnitiumRecordSpec) DeepCopyInto(out *TechnitiumRecordSpec) { *out = *in + out.AuthorityRef = in.AuthorityRef + if in.RecordData != nil { + in, out := &in.RecordData, &out.RecordData + *out = make(map[string][]string, len(*in)) + for key, val := range *in { + var outVal []string + if val == nil { + (*out)[key] = nil + } else { + inVal := (*in)[key] + in, out := &inVal, &outVal + *out = make([]string, len(*in)) + copy(*out, *in) + } + (*out)[key] = outVal + } + } } // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new TechnitiumRecordSpec. diff --git a/config/crd/bases/dns.mayers.cloud_technitiumauthorities.yaml b/config/crd/bases/dns.mayers.cloud_technitiumauthorities.yaml index ce36201..1f7df72 100644 --- a/config/crd/bases/dns.mayers.cloud_technitiumauthorities.yaml +++ b/config/crd/bases/dns.mayers.cloud_technitiumauthorities.yaml @@ -40,10 +40,58 @@ spec: spec: description: TechnitiumAuthoritySpec defines the desired state of TechnitiumAuthority properties: - foo: - description: Foo is an example field of TechnitiumAuthority. Edit - technitiumauthority_types.go to remove/update + dnsApp.config: type: string + primary: + description: AuthorityEndpoint defines the API endpoint and credentials + properties: + apiKeySecretRef: + description: SecretKeySelector defines the reference to a key + inside a Kubernetes Secret + properties: + key: + type: string + name: + type: string + required: + - key + - name + type: object + endpoint: + type: string + required: + - apiKeySecretRef + - endpoint + type: object + secondaries: + items: + description: AuthorityEndpoint defines the API endpoint and credentials + properties: + apiKeySecretRef: + description: SecretKeySelector defines the reference to a key + inside a Kubernetes Secret + properties: + key: + type: string + name: + type: string + required: + - key + - name + type: object + endpoint: + type: string + required: + - apiKeySecretRef + - endpoint + type: object + type: array + zone: + type: string + required: + - dnsApp.config + - primary + - zone type: object status: description: TechnitiumAuthorityStatus defines the observed state of TechnitiumAuthority diff --git a/config/crd/bases/dns.mayers.cloud_technitiumrecords.yaml b/config/crd/bases/dns.mayers.cloud_technitiumrecords.yaml index b2bb4ac..681d0ef 100644 --- a/config/crd/bases/dns.mayers.cloud_technitiumrecords.yaml +++ b/config/crd/bases/dns.mayers.cloud_technitiumrecords.yaml @@ -39,10 +39,31 @@ spec: spec: description: TechnitiumRecordSpec defines the desired state of TechnitiumRecord properties: - foo: - description: Foo is an example field of TechnitiumRecord. Edit technitiumrecord_types.go - to remove/update + authorityRef: + description: AuthorityReference references a TechnitiumAuthority + properties: + name: + type: string + required: + - name + type: object + classPath: type: string + name: + type: string + recordData: + additionalProperties: + items: + type: string + type: array + type: object + ttl: + type: integer + required: + - authorityRef + - classPath + - name + - ttl type: object status: description: TechnitiumRecordStatus defines the observed state of TechnitiumRecord diff --git a/config/rbac/role.yaml b/config/rbac/role.yaml index 6a4db3c..9c569a6 100644 --- a/config/rbac/role.yaml +++ b/config/rbac/role.yaml @@ -7,6 +7,7 @@ rules: - apiGroups: - dns.mayers.cloud resources: + - externaldnswatchers - technitiumauthorities - technitiumrecords verbs: @@ -20,6 +21,7 @@ rules: - apiGroups: - dns.mayers.cloud resources: + - externaldnswatchers/finalizers - technitiumauthorities/finalizers - technitiumrecords/finalizers verbs: @@ -27,6 +29,7 @@ rules: - apiGroups: - dns.mayers.cloud resources: + - externaldnswatchers/status - technitiumauthorities/status - technitiumrecords/status verbs: diff --git a/internal/controller/externaldnswatcher_controller.go b/internal/controller/externaldnswatcher_controller.go index 955e199..0d48df3 100644 --- a/internal/controller/externaldnswatcher_controller.go +++ b/internal/controller/externaldnswatcher_controller.go @@ -18,6 +18,10 @@ package controller import ( "context" + "fmt" + dnsv1alpha1 "git.mayers.cloud/superflo22/split-horizon-operator/api/v1alpha1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "sigs.k8s.io/controller-runtime/pkg/handler" gatewayv1 "sigs.k8s.io/gateway-api/apis/v1" "k8s.io/apimachinery/pkg/runtime" @@ -46,9 +50,75 @@ type ExternalDNSWatcherReconciler struct { // For more details, check Reconcile and its Result here: // - https://pkg.go.dev/sigs.k8s.io/controller-runtime@v0.19.0/pkg/reconcile func (r *ExternalDNSWatcherReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) { - _ = log.FromContext(ctx) + log := log.FromContext(ctx) - // TODO(user): your logic here + // Step 1: Fetch the Gateway + var gateway gatewayv1.Gateway + if err := r.Get(ctx, req.NamespacedName, &gateway); err != nil { + return ctrl.Result{}, client.IgnoreNotFound(err) + } + + // Step 2: Check annotation + annotations := gateway.GetAnnotations() + if annotations["dns.mayers.cloud/enabled"] != "true" { + return ctrl.Result{}, nil + } + + network := annotations["dns.mayers.cloud/network"] + authority := annotations["dns.mayers.cloud/authority"] + if network == "" || authority == "" { + log.Info("Missing required annotations") + return ctrl.Result{}, nil + } + + // Step 3: Get Gateway IP (if available) + var gatewayIPs []string + for _, addr := range gateway.Status.Addresses { + if addr.Type == nil || *addr.Type == gatewayv1.IPAddressType { + gatewayIPs = append(gatewayIPs, addr.Value) + } + } + + // Step 4: List HTTPRoutes that reference this Gateway + var routes gatewayv1.HTTPRouteList + if err := r.List(ctx, &routes, client.InNamespace(req.Namespace)); err != nil { + return ctrl.Result{}, err + } + + for _, route := range routes.Items { + for _, parent := range route.Spec.ParentRefs { + + // Check if the parent is a Gateway and matches the current gateway + if string(*parent.Kind) == "Gateway" && string(parent.Name) == gateway.Name { + // Step 5: Collect hostnames + for _, hostname := range route.Spec.Hostnames { + record := &dnsv1alpha1.TechnitiumRecord{ + ObjectMeta: metav1.ObjectMeta{ + Name: fmt.Sprintf("%s-%s", route.Name, string(hostname)), + Namespace: route.Namespace, + }, + Spec: dnsv1alpha1.TechnitiumRecordSpec{ + AuthorityRef: dnsv1alpha1.AuthorityReference{ + Name: authority, + }, + Name: string(hostname), + TTL: 300, + ClassPath: "SimpleAddress", + RecordData: map[string][]string{ + network: gatewayIPs, + }, + }, + } + + if err := r.Client.Patch(ctx, record, client.Apply, client.ForceOwnership, client.FieldOwner("external-dns-watcher")); err != nil { + log.Error(err, "Failed to apply TechnitiumRecord") + } else { + log.Info("Reconciled TechnitiumRecord", "name", record.Name) + } + } + } + } + } return ctrl.Result{}, nil } @@ -57,7 +127,14 @@ func (r *ExternalDNSWatcherReconciler) Reconcile(ctx context.Context, req ctrl.R func (r *ExternalDNSWatcherReconciler) SetupWithManager(mgr ctrl.Manager) error { return ctrl.NewControllerManagedBy(mgr). // Uncomment the following line adding a pointer to an instance of the controlled resource as an argument - For(&gatewayv1.HTTPRoute{}). - For(&gatewayv1.Gateway{}). + Named("external_dns_controller"). + Watches( + &gatewayv1.Gateway{}, + &handler.EnqueueRequestForObject{}, + ). + Watches( + &gatewayv1.HTTPRoute{}, + &handler.EnqueueRequestForObject{}, + ). Complete(r) }