Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add external-dns(v0.14.0) as a dependency #43

Merged
merged 3 commits into from
Mar 11, 2024
Merged
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
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -27,3 +27,5 @@ Dockerfile.cross

# Temporary files and directories
tmp

config/local-setup/**/*.env
8 changes: 5 additions & 3 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -149,7 +149,9 @@ local-setup: $(KIND) ## Setup local development kind cluster and dependencies
$(MAKE) kind-create-cluster

.PHONY: local-deploy
local-deploy: docker-build kind-load-image deploy ## Deploy the dns operator into local kind cluster from the current code
local-deploy: docker-build kind-load-image ## Deploy the dns operator into local kind cluster from the current code
$(KUBECTL) config use-context kind-$(KIND_CLUSTER_NAME)
$(MAKE) deploy

##@ Build

Expand All @@ -159,13 +161,13 @@ build: manifests generate fmt vet ## Build manager binary.

.PHONY: run
run: manifests generate fmt vet ## Run a controller from your host.
go run ./cmd/main.go
go run ./cmd/main.go --zap-log-level=3

# If you wish built the manager image targeting other platforms you can use the --platform flag.
# (i.e. docker build --platform linux/arm64 ). However, you must enable docker buildKit for it.
# More info: https://docs.docker.com/develop/develop-images/build_enhancements/
.PHONY: docker-build
docker-build: test ## Build docker image with the manager.
docker-build: ## Build docker image with the manager.
$(CONTAINER_TOOL) build -t ${IMG} .

.PHONY: docker-push
Expand Down
116 changes: 31 additions & 85 deletions api/v1alpha1/dnsrecord_types.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,101 +18,20 @@ package v1alpha1

import (
"fmt"
"strings"

metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
externaldns "sigs.k8s.io/external-dns/endpoint"
)

// ProviderSpecificProperty holds the name and value of a configuration which is specific to individual DNS providers
type ProviderSpecificProperty struct {
Name string `json:"name,omitempty"`
Value string `json:"value,omitempty"`
}

// Targets is a representation of a list of targets for an endpoint.
type Targets []string

// TTL is a structure defining the TTL of a DNS record
type TTL int64

// Labels store metadata related to the endpoint
// it is then stored in a persistent storage via serialization
type Labels map[string]string

// ProviderSpecific holds configuration which is specific to individual DNS providers
type ProviderSpecific []ProviderSpecificProperty

// Endpoint is a high-level way of a connection between a service and an IP
type Endpoint struct {
// The hostname of the DNS record
DNSName string `json:"dnsName,omitempty"`
// The targets the DNS record points to
Targets Targets `json:"targets,omitempty"`
// RecordType type of record, e.g. CNAME, A, SRV, TXT etc
RecordType string `json:"recordType,omitempty"`
// Identifier to distinguish multiple records with the same name and type (e.g. Route53 records with routing policies other than 'simple')
SetIdentifier string `json:"setIdentifier,omitempty"`
// TTL for the record
RecordTTL TTL `json:"recordTTL,omitempty"`
// Labels stores labels defined for the Endpoint
// +optional
Labels Labels `json:"labels,omitempty"`
// ProviderSpecific stores provider specific config
// +optional
ProviderSpecific ProviderSpecific `json:"providerSpecific,omitempty"`
}

// SetID returns an id that should be unique across a set of endpoints
func (e *Endpoint) SetID() string {
return e.DNSName + e.SetIdentifier
}

// WithSetIdentifier applies the given set identifier to the endpoint.
func (e *Endpoint) WithSetIdentifier(setIdentifier string) *Endpoint {
e.SetIdentifier = setIdentifier
return e
}

// GetProviderSpecificProperty returns a ProviderSpecificProperty if the property exists.
func (e *Endpoint) GetProviderSpecificProperty(key string) (ProviderSpecificProperty, bool) {
for _, providerSpecific := range e.ProviderSpecific {
if providerSpecific.Name == key {
return providerSpecific, true
}
}
return ProviderSpecificProperty{}, false
}

// SetProviderSpecific sets a provider specific key/value pair.
func (e *Endpoint) SetProviderSpecific(name, value string) {
if e.ProviderSpecific == nil {
e.ProviderSpecific = ProviderSpecific{}
}

for i, pair := range e.ProviderSpecific {
if pair.Name == name {
e.ProviderSpecific[i].Value = value
return
}
}

e.ProviderSpecific = append(e.ProviderSpecific, ProviderSpecificProperty{
Name: name,
Value: value,
})
}

func (e *Endpoint) String() string {
return fmt.Sprintf("%s %d IN %s %s %s %s", e.DNSName, e.RecordTTL, e.RecordType, e.SetIdentifier, e.Targets, e.ProviderSpecific)
}

// DNSRecordSpec defines the desired state of DNSRecord
type DNSRecordSpec struct {
// +kubebuilder:validation:Required
// +required
ManagedZoneRef *ManagedZoneReference `json:"managedZone,omitempty"`
// +kubebuilder:validation:MinItems=1
// +optional
Endpoints []*Endpoint `json:"endpoints,omitempty"`
Endpoints []*externaldns.Endpoint `json:"endpoints,omitempty"`
}

// DNSRecordStatus defines the observed state of DNSRecord
Expand Down Expand Up @@ -140,7 +59,7 @@ type DNSRecordStatus struct {
//
// Note: This will not be required if/when we switch to using external-dns since when
// running with a "sync" policy it will clean up unused records automatically.
Endpoints []*Endpoint `json:"endpoints,omitempty"`
Endpoints []*externaldns.Endpoint `json:"endpoints,omitempty"`
}

//+kubebuilder:object:root=true
Expand Down Expand Up @@ -182,6 +101,33 @@ const (
DefaultGeo string = "default"
)

// 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) {
mikenairn marked this conversation as resolved.
Show resolved Hide resolved
domain := ""
dnsNames := []string{}
for idx := range s.Spec.Endpoints {
dnsNames = append(dnsNames, s.Spec.Endpoints[idx].DNSName)
}
for idx := range dnsNames {
if domain == "" || len(domain) > len(dnsNames[idx]) {
domain = dnsNames[idx]
}
}

if domain == "" {
return "", fmt.Errorf("unable to determine root domain from %v", dnsNames)
}

for idx := range dnsNames {
if !strings.HasSuffix(dnsNames[idx], domain) {
return "", fmt.Errorf("inconsitent domains, got %s, expected suffix %s", dnsNames[idx], domain)
}
}

return domain, nil
}
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We might want to replace this by adding a new rootDomain field to the dnsrecord spec, but this will work for now.


func init() {
SchemeBuilder.Register(&DNSRecord{}, &DNSRecordList{})
}
81 changes: 81 additions & 0 deletions api/v1alpha1/dnsrecord_types_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
package v1alpha1

import (
"testing"

metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"sigs.k8s.io/external-dns/endpoint"
)

func TestDNSRecord_GetRootDomain(t *testing.T) {
tests := []struct {
name string
dnsNames []string
want string
wantErr bool
}{
{
name: "single endpoint",
dnsNames: []string{
"test.example.com",
},
want: "test.example.com",
wantErr: false,
},
{
name: "multiple endpoints matching",
dnsNames: []string{
"bar.baz.test.example.com",
"bar.test.example.com",
"test.example.com",
"foo.bar.baz.test.example.com",
},
want: "test.example.com",
wantErr: false,
},
{
name: "no endpoints",
dnsNames: []string{},
want: "",
wantErr: true,
},
{
name: "multiple endpoints mismatching",
dnsNames: []string{
"foo.bar.test.example.com",
"bar.test.example.com",
"baz.example.com",
},
want: "",
wantErr: true,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
s := &DNSRecord{
TypeMeta: metav1.TypeMeta{
Kind: "DNSRecord",
APIVersion: GroupVersion.String(),
},
ObjectMeta: metav1.ObjectMeta{
Name: "testRecord",
Namespace: "testNS",
},
Spec: DNSRecordSpec{
Endpoints: []*endpoint.Endpoint{},
},
}
for idx := range tt.dnsNames {
s.Spec.Endpoints = append(s.Spec.Endpoints, &endpoint.Endpoint{DNSName: tt.dnsNames[idx]})
}
got, err := s.GetRootDomain()
if (err != nil) != tt.wantErr {
t.Errorf("GetRootDomain() error = %v, wantErr %v", err, tt.wantErr)
return
}
if got != tt.want {
t.Errorf("GetRootDomain() got = %v, want %v", got, tt.want)
}
})
}
}
Loading
Loading