From 810ac7352abe811c43db62eaeb1db5b643c5928b Mon Sep 17 00:00:00 2001 From: craig Date: Mon, 11 Mar 2024 14:50:34 +0000 Subject: [PATCH] add optional rootHost add validate call to publish --- .gitignore | 2 + api/v1alpha1/dnsrecord_types.go | 25 ++++++ api/v1alpha1/dnsrecord_types_test.go | 77 ++++++++++++++++++- api/v1alpha1/zz_generated.deepcopy.go | 5 ++ .../dns-operator.clusterserviceversion.yaml | 2 +- bundle/manifests/kuadrant.io_dnsrecords.yaml | 5 ++ config/crd/bases/kuadrant.io_dnsrecords.yaml | 5 ++ internal/controller/dnsrecord_controller.go | 4 +- 8 files changed, 121 insertions(+), 4 deletions(-) diff --git a/.gitignore b/.gitignore index 5959d984..3a59d1b3 100644 --- a/.gitignore +++ b/.gitignore @@ -5,6 +5,7 @@ *.dll *.so *.dylib +*.env bin/* Dockerfile.cross @@ -29,3 +30,4 @@ Dockerfile.cross tmp config/local-setup/**/*.env +local \ No newline at end of file diff --git a/api/v1alpha1/dnsrecord_types.go b/api/v1alpha1/dnsrecord_types.go index f36a88d5..8de27098 100644 --- a/api/v1alpha1/dnsrecord_types.go +++ b/api/v1alpha1/dnsrecord_types.go @@ -26,6 +26,11 @@ import ( // DNSRecordSpec defines the desired state of DNSRecord type DNSRecordSpec struct { + + // rootHost is the single root for all endpoints in a DNSRecord. + //If rootHost is set, it is expected all defined endpoints are children of or equal to this rootHost + // +optional + RootHost *string `json:"rootHost,omitempty"` // +kubebuilder:validation:Required // +required ManagedZoneRef *ManagedZoneReference `json:"managedZone,omitempty"` @@ -104,6 +109,12 @@ const ( // GetRootDomain returns the shortest domain that is shared across all spec.Endpoints dns names. // Validates that all endpoints share an equal root domain and returns an error if they don't. func (s *DNSRecord) GetRootDomain() (string, error) { + if err := s.Validate(); err != nil { + return "", err + } + if s.Spec.RootHost != nil { + return *s.Spec.RootHost, nil + } domain := "" dnsNames := []string{} for idx := range s.Spec.Endpoints { @@ -128,6 +139,20 @@ func (s *DNSRecord) GetRootDomain() (string, error) { return domain, nil } +func (s *DNSRecord) Validate() error { + if s.Spec.RootHost != nil { + if len(strings.Split(*s.Spec.RootHost, ".")) <= 1 { + return fmt.Errorf("invalid domain format no tld discovered") + } + for _, ep := range s.Spec.Endpoints { + if !strings.HasSuffix(ep.DNSName, *s.Spec.RootHost) { + return fmt.Errorf("invalid endpoint discovered %s all endpoints should be equal to or end with the rootHost %s", ep.DNSName, *s.Spec.RootHost) + } + } + } + return nil +} + func init() { SchemeBuilder.Register(&DNSRecord{}, &DNSRecordList{}) } diff --git a/api/v1alpha1/dnsrecord_types_test.go b/api/v1alpha1/dnsrecord_types_test.go index 4d0cacd1..e9f242ce 100644 --- a/api/v1alpha1/dnsrecord_types_test.go +++ b/api/v1alpha1/dnsrecord_types_test.go @@ -8,14 +8,20 @@ import ( ) func TestDNSRecord_GetRootDomain(t *testing.T) { + var ( + rootTestExample = "test.example.com" + example = "example.com" + ) tests := []struct { name string + rootHost *string dnsNames []string want string wantErr bool }{ { - name: "single endpoint", + name: "single endpoint", + rootHost: &rootTestExample, dnsNames: []string{ "test.example.com", }, @@ -40,12 +46,34 @@ func TestDNSRecord_GetRootDomain(t *testing.T) { wantErr: true, }, { - name: "multiple endpoints mismatching", + rootHost: &example, + name: "multiple endpoints", dnsNames: []string{ "foo.bar.test.example.com", "bar.test.example.com", "baz.example.com", }, + want: "example.com", + wantErr: false, + }, + { + rootHost: &example, + name: "multiple endpoints mismatching", + dnsNames: []string{ + "foo.bar.test.other.com", + "bar.test.example.com", + "baz.example.com", + }, + want: "", + wantErr: true, + }, + { + name: "multiple endpoints no rootHost", + dnsNames: []string{ + "foo.bar.test.other.com", + "bar.test.example.com", + "baz.example.com", + }, want: "", wantErr: true, }, @@ -65,6 +93,9 @@ func TestDNSRecord_GetRootDomain(t *testing.T) { Endpoints: []*endpoint.Endpoint{}, }, } + if tt.rootHost != nil { + s.Spec.RootHost = tt.rootHost + } for idx := range tt.dnsNames { s.Spec.Endpoints = append(s.Spec.Endpoints, &endpoint.Endpoint{DNSName: tt.dnsNames[idx]}) } @@ -79,3 +110,45 @@ func TestDNSRecord_GetRootDomain(t *testing.T) { }) } } + +func TestValidate(t *testing.T) { + tests := []struct { + name string + rootHost string + dnsNames []string + wantErr bool + }{ + { + name: "invalid domain", + rootHost: "example", + wantErr: true, + }, + { + name: "valid domain", + rootHost: "example.com", + dnsNames: []string{ + "example.com", + "a.b.example.com", + "b.a.example.com", + "a.example.com", + "b.example.com", + }, + wantErr: false, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + record := &DNSRecord{ + Spec: DNSRecordSpec{ + RootHost: &tt.rootHost, + }, + } + err := record.Validate() + if (err != nil) != tt.wantErr { + t.Errorf("Validate() error = %v, wantErr %v", err, tt.wantErr) + return + } + }) + } +} diff --git a/api/v1alpha1/zz_generated.deepcopy.go b/api/v1alpha1/zz_generated.deepcopy.go index 298cd8e3..82cba831 100644 --- a/api/v1alpha1/zz_generated.deepcopy.go +++ b/api/v1alpha1/zz_generated.deepcopy.go @@ -89,6 +89,11 @@ func (in *DNSRecordList) DeepCopyObject() runtime.Object { // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *DNSRecordSpec) DeepCopyInto(out *DNSRecordSpec) { *out = *in + if in.RootHost != nil { + in, out := &in.RootHost, &out.RootHost + *out = new(string) + **out = **in + } if in.ManagedZoneRef != nil { in, out := &in.ManagedZoneRef, &out.ManagedZoneRef *out = new(ManagedZoneReference) diff --git a/bundle/manifests/dns-operator.clusterserviceversion.yaml b/bundle/manifests/dns-operator.clusterserviceversion.yaml index cde091a2..fa685547 100644 --- a/bundle/manifests/dns-operator.clusterserviceversion.yaml +++ b/bundle/manifests/dns-operator.clusterserviceversion.yaml @@ -56,7 +56,7 @@ metadata: capabilities: Basic Install categories: Integration & Delivery containerImage: quay.io/kuadrant/dns-operator:latest - createdAt: "2024-03-11T11:01:25Z" + createdAt: "2024-03-11T14:53:47Z" description: A Kubernetes Operator to manage the lifecycle of DNS resources operators.operatorframework.io/builder: operator-sdk-v1.33.0 operators.operatorframework.io/project_layout: go.kubebuilder.io/v4 diff --git a/bundle/manifests/kuadrant.io_dnsrecords.yaml b/bundle/manifests/kuadrant.io_dnsrecords.yaml index eed45a53..658f1f9c 100644 --- a/bundle/manifests/kuadrant.io_dnsrecords.yaml +++ b/bundle/manifests/kuadrant.io_dnsrecords.yaml @@ -94,6 +94,11 @@ spec: required: - name type: object + rootHost: + description: "rootHost is the single root for all endpoints in a DNSRecord. + If rootHost is set, it is expected all defined endpoints are children + \tof or equal to this rootHost" + type: string type: object status: description: DNSRecordStatus defines the observed state of DNSRecord diff --git a/config/crd/bases/kuadrant.io_dnsrecords.yaml b/config/crd/bases/kuadrant.io_dnsrecords.yaml index ac503169..f9cd7e03 100644 --- a/config/crd/bases/kuadrant.io_dnsrecords.yaml +++ b/config/crd/bases/kuadrant.io_dnsrecords.yaml @@ -94,6 +94,11 @@ spec: required: - name type: object + rootHost: + description: "rootHost is the single root for all endpoints in a DNSRecord. + If rootHost is set, it is expected all defined endpoints are children + \tof or equal to this rootHost" + type: string type: object status: description: DNSRecordStatus defines the observed state of DNSRecord diff --git a/internal/controller/dnsrecord_controller.go b/internal/controller/dnsrecord_controller.go index 3a6fe412..42dd9aa0 100644 --- a/internal/controller/dnsrecord_controller.go +++ b/internal/controller/dnsrecord_controller.go @@ -175,7 +175,9 @@ func (r *DNSRecordReconciler) deleteRecord(ctx context.Context, dnsRecord *v1alp // DNSRecord (dnsRecord.Status.ParentManagedZone). func (r *DNSRecordReconciler) publishRecord(ctx context.Context, dnsRecord *v1alpha1.DNSRecord) error { logger := log.FromContext(ctx) - + if err := dnsRecord.Validate(); err != nil { + return fmt.Errorf("failed validation pre publish : %s", err) + } managedZone := &v1alpha1.ManagedZone{ ObjectMeta: metav1.ObjectMeta{ Name: dnsRecord.Spec.ManagedZoneRef.Name,