From 2ca9055f0d19b84533dbdf9041f74501de8c8f2e Mon Sep 17 00:00:00 2001 From: Jan Jansen Date: Thu, 4 Dec 2025 08:58:09 +0100 Subject: [PATCH 1/3] update to v0.20 and prepare for 0.21 Signed-off-by: Jan Jansen --- Dockerfile | 2 +- README.md | 4 + coredns.go | 5 ++ coredns_test.go | 3 + go.mod | 88 ++++++++++----------- go.sum | 204 +++++++++++++++++++++++++----------------------- 6 files changed, 164 insertions(+), 142 deletions(-) diff --git a/Dockerfile b/Dockerfile index 40a2ff5..378e6f3 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,4 +1,4 @@ -FROM golang:1.24-bookworm AS builder +FROM golang:1.25-bookworm AS builder COPY . /code/external-dns-coredns-webhook WORKDIR /code/external-dns-coredns-webhook diff --git a/README.md b/README.md index c99c46e..8267819 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,9 @@ # ExternalDNS Plugin CoreDNS Webhook +## Deprecated + +With the release of version 0.21 or newer, every function is now included inside the in-tree provider. + ## Commandline ``` diff --git a/coredns.go b/coredns.go index 25a82de..2aeb5f4 100644 --- a/coredns.go +++ b/coredns.go @@ -94,6 +94,9 @@ type Service struct { // ManagedBy is used to prevent service to be added by different external-dns (only used by external-dns) ManagedBy string `json:"managedby,omitempty"` + + // OwnedBy is used to prevent service to be added by different external-dns (only used by external-dns) + OwnedBy string `json:"ownedby,omitempty"` } type etcdClient struct { @@ -130,6 +133,7 @@ func (c etcdClient) GetServices(ctx context.Context, prefix string) ([]*Service, Text: svc.Text, Key: string(n.Key), ManagedBy: svc.ManagedBy, + OwnedBy: svc.OwnedBy, } if _, ok := bx[b]; ok { // skip the service if already added to service list. @@ -162,6 +166,7 @@ func (c etcdClient) SaveService(ctx context.Context, service *Service) error { if c.managedBy != "" { service.ManagedBy = c.managedBy + service.OwnedBy = c.managedBy } if ownedBy, err := c.IsOwnedBy(ctx, service.Key); err != nil { return err diff --git a/coredns_test.go b/coredns_test.go index 14cb146..a34e312 100644 --- a/coredns_test.go +++ b/coredns_test.go @@ -944,6 +944,7 @@ func TestSaveService(t *testing.T) { Text: "hello", Key: "/prefix/1", ManagedBy: "managed-by", + OwnedBy: "managed-by", }, }, { @@ -966,6 +967,7 @@ func TestSaveService(t *testing.T) { Text: "hello", Key: "/prefix/1", ManagedBy: "managed-by", + OwnedBy: "managed-by", }, }, { @@ -989,6 +991,7 @@ func TestSaveService(t *testing.T) { Text: "hello", Key: "/prefix/1", ManagedBy: "managed-by", + OwnedBy: "managed-by", }, }, { diff --git a/go.mod b/go.mod index 05e3d7a..015ed1b 100644 --- a/go.mod +++ b/go.mod @@ -1,86 +1,86 @@ module github.com/GDATASoftwareAG/external-dns-coredns-webhook -go 1.24.2 - -toolchain go1.24.5 +go 1.25 require ( github.com/alecthomas/kingpin v2.2.6+incompatible github.com/sirupsen/logrus v1.9.3 github.com/stretchr/testify v1.11.1 - go.etcd.io/etcd/api/v3 v3.6.5 - go.etcd.io/etcd/client/v3 v3.6.5 - sigs.k8s.io/external-dns v0.19.0 + go.etcd.io/etcd/api/v3 v3.6.6 + go.etcd.io/etcd/client/v3 v3.6.6 + sigs.k8s.io/external-dns v0.20.0 ) require ( github.com/alecthomas/kingpin/v2 v2.4.0 // indirect github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751 // indirect - github.com/alecthomas/units v0.0.0-20240626203959-61d1e3462e30 // indirect - github.com/aws/aws-sdk-go-v2/service/route53 v1.56.2 // indirect - github.com/aws/smithy-go v1.22.5 // indirect + github.com/alecthomas/units v0.0.0-20240927000941-0f3dac36c52b // indirect + github.com/aws/aws-sdk-go-v2/service/route53 v1.59.5 // indirect + github.com/aws/smithy-go v1.23.2 // indirect github.com/beorn7/perks v1.0.1 // indirect github.com/cespare/xxhash/v2 v2.3.0 // indirect github.com/coreos/go-semver v0.3.1 // indirect github.com/coreos/go-systemd/v22 v22.5.0 // indirect github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect - github.com/emicklei/go-restful/v3 v3.12.1 // indirect + github.com/emicklei/go-restful/v3 v3.13.0 // indirect github.com/evanphx/json-patch/v5 v5.9.11 // indirect - github.com/fxamacker/cbor/v2 v2.7.0 // indirect + github.com/fxamacker/cbor/v2 v2.9.0 // indirect github.com/go-logr/logr v1.4.3 // indirect - github.com/go-openapi/jsonpointer v0.21.0 // indirect + github.com/go-openapi/jsonpointer v0.21.2 // indirect github.com/go-openapi/jsonreference v0.21.0 // indirect - github.com/go-openapi/swag v0.23.0 // indirect + github.com/go-openapi/swag v0.23.1 // indirect github.com/gogo/protobuf v1.3.2 // indirect github.com/golang/protobuf v1.5.4 // indirect - github.com/google/gnostic-models v0.6.9 // indirect + github.com/google/gnostic-models v0.7.0 // indirect github.com/google/go-cmp v0.7.0 // indirect github.com/google/uuid v1.6.0 // indirect github.com/grpc-ecosystem/grpc-gateway/v2 v2.26.3 // indirect + github.com/inconshreveable/mousetrap v1.1.0 // indirect github.com/josharian/intern v1.0.0 // indirect github.com/json-iterator/go v1.1.12 // indirect github.com/mailru/easyjson v0.9.0 // indirect github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect - github.com/modern-go/reflect2 v1.0.2 // indirect + github.com/modern-go/reflect2 v1.0.3-0.20250322232337-35a7c28c31ee // indirect github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect - github.com/onsi/ginkgo/v2 v2.23.4 // indirect - github.com/onsi/gomega v1.37.0 // indirect - github.com/pkg/errors v0.9.1 // indirect + github.com/onsi/ginkgo/v2 v2.25.3 // indirect + github.com/onsi/gomega v1.38.2 // indirect github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect - github.com/prometheus/client_golang v1.23.0 // indirect + github.com/prometheus/client_golang v1.23.2 // indirect github.com/prometheus/client_model v0.6.2 // indirect - github.com/prometheus/common v0.65.0 // indirect - github.com/prometheus/procfs v0.16.1 // indirect - github.com/spf13/pflag v1.0.7 // indirect + github.com/prometheus/common v0.67.2 // indirect + github.com/prometheus/procfs v0.17.0 // indirect + github.com/spf13/cobra v1.10.1 // indirect + github.com/spf13/pflag v1.0.9 // indirect github.com/stretchr/objx v0.5.2 // indirect github.com/x448/float16 v0.8.4 // indirect github.com/xhit/go-str2duration/v2 v2.1.0 // indirect - go.etcd.io/etcd/client/pkg/v3 v3.6.5 // indirect + go.etcd.io/etcd/client/pkg/v3 v3.6.6 // indirect go.uber.org/multierr v1.11.0 // indirect go.uber.org/zap v1.27.0 // indirect - golang.org/x/net v0.43.0 // indirect - golang.org/x/oauth2 v0.30.0 // indirect - golang.org/x/sys v0.35.0 // indirect - golang.org/x/term v0.34.0 // indirect - golang.org/x/text v0.28.0 // indirect - golang.org/x/time v0.12.0 // indirect - google.golang.org/genproto/googleapis/api v0.0.0-20250603155806-513f23925822 // indirect - google.golang.org/genproto/googleapis/rpc v0.0.0-20250818200422-3122310a409c // indirect - google.golang.org/grpc v1.74.2 // indirect - google.golang.org/protobuf v1.36.7 // indirect - gopkg.in/evanphx/json-patch.v4 v4.12.0 // indirect + go.yaml.in/yaml/v2 v2.4.3 // indirect + go.yaml.in/yaml/v3 v3.0.4 // indirect + golang.org/x/net v0.47.0 // indirect + golang.org/x/oauth2 v0.33.0 // indirect + golang.org/x/sys v0.38.0 // indirect + golang.org/x/term v0.37.0 // indirect + golang.org/x/text v0.31.0 // indirect + golang.org/x/time v0.14.0 // indirect + google.golang.org/genproto/googleapis/api v0.0.0-20250811230008-5f3141c8851a // indirect + google.golang.org/genproto/googleapis/rpc v0.0.0-20251103181224-f26f9409b101 // indirect + google.golang.org/grpc v1.76.0 // indirect + google.golang.org/protobuf v1.36.10 // indirect + gopkg.in/evanphx/json-patch.v4 v4.13.0 // indirect gopkg.in/inf.v0 v0.9.1 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect - k8s.io/api v0.33.4 // indirect - k8s.io/apiextensions-apiserver v0.33.3 // indirect - k8s.io/apimachinery v0.33.4 // indirect - k8s.io/client-go v0.33.4 // indirect + k8s.io/api v0.34.2 // indirect + k8s.io/apimachinery v0.34.2 // indirect + k8s.io/client-go v0.34.2 // indirect k8s.io/klog/v2 v2.130.1 // indirect - k8s.io/kube-openapi v0.0.0-20250318190949-c8a335a9a2ff // indirect - k8s.io/utils v0.0.0-20241210054802-24370beab758 // indirect - sigs.k8s.io/controller-runtime v0.21.0 // indirect - sigs.k8s.io/json v0.0.0-20241014173422-cfa47c3a1cc8 // indirect + k8s.io/kube-openapi v0.0.0-20250814151709-d7b6acb124c3 // indirect + k8s.io/utils v0.0.0-20250820121507-0af2bda4dd1d // indirect + sigs.k8s.io/controller-runtime v0.22.4 // indirect + sigs.k8s.io/json v0.0.0-20250730193827-2d320260d730 // indirect sigs.k8s.io/randfill v1.0.0 // indirect - sigs.k8s.io/structured-merge-diff/v4 v4.7.0 // indirect - sigs.k8s.io/yaml v1.4.0 // indirect + sigs.k8s.io/structured-merge-diff/v6 v6.3.0 // indirect + sigs.k8s.io/yaml v1.6.0 // indirect ) diff --git a/go.sum b/go.sum index 5777948..ce8a5b5 100644 --- a/go.sum +++ b/go.sum @@ -1,47 +1,49 @@ +github.com/Masterminds/semver v1.5.0 h1:H65muMkzWKEuNDnfl9d70GUjFniHKHRbFPGBuZ3QEww= +github.com/Masterminds/semver/v3 v3.4.0 h1:Zog+i5UMtVoCU8oKka5P7i9q9HgrJeGzI9SA1Xbatp0= +github.com/Masterminds/semver/v3 v3.4.0/go.mod h1:4V+yj/TJE1HU9XfppCwVMZq3I84lprf4nC11bSS5beM= github.com/alecthomas/kingpin v2.2.6+incompatible h1:5svnBTFgJjZvGKyYBtMB0+m5wvrbUHiqye8wRJMlnYI= github.com/alecthomas/kingpin v2.2.6+incompatible/go.mod h1:59OFYbFVLKQKq+mqrL6Rw5bR0c3ACQaawgXx0QYndlE= github.com/alecthomas/kingpin/v2 v2.4.0 h1:f48lwail6p8zpO1bC4TxtqACaGqHYA22qkHjHpqDjYY= github.com/alecthomas/kingpin/v2 v2.4.0/go.mod h1:0gyi0zQnjuFk8xrkNKamJoyUo382HRL7ATRpFZCw6tE= github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751 h1:JYp7IbQjafoB+tBA3gMyHYHrpOtNuDiK/uB5uXxq5wM= github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= -github.com/alecthomas/units v0.0.0-20240626203959-61d1e3462e30 h1:t3eaIm0rUkzbrIewtiFmMK5RXHej2XnoXNhxVsAYUfg= -github.com/alecthomas/units v0.0.0-20240626203959-61d1e3462e30/go.mod h1:fvzegU4vN3H1qMT+8wDmzjAcDONcgo2/SZ/TyfdUOFs= -github.com/aws/aws-sdk-go-v2/service/route53 v1.56.2 h1:6QKyfbweIsjt1kvE8rw+LeDxmCt1uvI8ywRe2cYOpQo= -github.com/aws/aws-sdk-go-v2/service/route53 v1.56.2/go.mod h1:Ro0zSeA7hRAhX04QgnUAc8MvvQO74wg/S15wzA/mxgo= -github.com/aws/smithy-go v1.22.5 h1:P9ATCXPMb2mPjYBgueqJNCA5S9UfktsW0tTxi+a7eqw= -github.com/aws/smithy-go v1.22.5/go.mod h1:t1ufH5HMublsJYulve2RKmHDC15xu1f26kHCp/HgceI= +github.com/alecthomas/units v0.0.0-20240927000941-0f3dac36c52b h1:mimo19zliBX/vSQ6PWWSL9lK8qwHozUj03+zLoEB8O0= +github.com/alecthomas/units v0.0.0-20240927000941-0f3dac36c52b/go.mod h1:fvzegU4vN3H1qMT+8wDmzjAcDONcgo2/SZ/TyfdUOFs= +github.com/aws/aws-sdk-go-v2/service/route53 v1.59.5 h1:4Uy8lhrh4E9jS/MtmzjuEuvX7zOZTbNuPe+zkvtvRRU= +github.com/aws/aws-sdk-go-v2/service/route53 v1.59.5/go.mod h1:TUbfYOisWZWyT2qjmlMh93ERw1Ry8G4q/yT2Q8TsDag= +github.com/aws/smithy-go v1.23.2 h1:Crv0eatJUQhaManss33hS5r40CG3ZFH+21XSkqMrIUM= +github.com/aws/smithy-go v1.23.2/go.mod h1:LEj2LM3rBRQJxPZTB4KuzZkaZYnZPnvgIhb4pu07mx0= github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM= github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw= -github.com/blang/semver/v4 v4.0.0 h1:1PFHFE6yCCTv8C1TeyNNarDzntLi7wMI5i/pzqYIsAM= -github.com/blang/semver/v4 v4.0.0/go.mod h1:IbckMUScFkM3pff0VJDNKRiT6TG/YpiHIM2yvyW5YoQ= github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs= github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= github.com/coreos/go-semver v0.3.1 h1:yi21YpKnrx1gt5R+la8n5WgS0kCrsPp33dmEyHReZr4= github.com/coreos/go-semver v0.3.1/go.mod h1:irMmmIw/7yzSRPWryHsK7EYSg09caPQL03VsM8rvUec= github.com/coreos/go-systemd/v22 v22.5.0 h1:RrqgGjYQKalulkV8NGVIfkXQf6YYmOyiJKk8iXXhfZs= github.com/coreos/go-systemd/v22 v22.5.0/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc= +github.com/cpuguy83/go-md2man/v2 v2.0.6/go.mod h1:oOW0eioCTA6cOiMLiUPZOpcVxMig6NIQQ7OS05n1F4g= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM= github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= -github.com/emicklei/go-restful/v3 v3.12.1 h1:PJMDIM/ak7btuL8Ex0iYET9hxM3CI2sjZtzpL63nKAU= -github.com/emicklei/go-restful/v3 v3.12.1/go.mod h1:6n3XBCmQQb25CM2LCACGz8ukIrRry+4bhvbpWn3mrbc= +github.com/emicklei/go-restful/v3 v3.13.0 h1:C4Bl2xDndpU6nJ4bc1jXd+uTmYPVUwkD6bFY/oTyCes= +github.com/emicklei/go-restful/v3 v3.13.0/go.mod h1:6n3XBCmQQb25CM2LCACGz8ukIrRry+4bhvbpWn3mrbc= github.com/evanphx/json-patch/v5 v5.9.11 h1:/8HVnzMq13/3x9TPvjG08wUGqBTmZBsCWzjTM0wiaDU= github.com/evanphx/json-patch/v5 v5.9.11/go.mod h1:3j+LviiESTElxA4p3EMKAB9HXj3/XEtnUf6OZxqIQTM= -github.com/fxamacker/cbor/v2 v2.7.0 h1:iM5WgngdRBanHcxugY4JySA0nk1wZorNOpTgCMedv5E= -github.com/fxamacker/cbor/v2 v2.7.0/go.mod h1:pxXPTn3joSm21Gbwsv0w9OSA2y1HFR9qXEeXQVeNoDQ= +github.com/fxamacker/cbor/v2 v2.9.0 h1:NpKPmjDBgUfBms6tr6JZkTHtfFGcMKsw3eGcmD/sapM= +github.com/fxamacker/cbor/v2 v2.9.0/go.mod h1:vM4b+DJCtHn+zz7h3FFp/hDAI9WNWCsZj23V5ytsSxQ= github.com/go-logr/logr v1.4.3 h1:CjnDlHq8ikf6E492q6eKboGOC0T8CDaOvkHCIg8idEI= github.com/go-logr/logr v1.4.3/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= github.com/go-logr/zapr v1.3.0 h1:XGdV8XW8zdwFiwOA2Dryh1gj2KRQyOOoNmBy4EplIcQ= github.com/go-logr/zapr v1.3.0/go.mod h1:YKepepNBd1u/oyhd/yQmtjVXmm9uML4IXUgMOwR8/Gg= -github.com/go-openapi/jsonpointer v0.21.0 h1:YgdVicSA9vH5RiHs9TZW5oyafXZFc6+2Vc1rr/O9oNQ= -github.com/go-openapi/jsonpointer v0.21.0/go.mod h1:IUyH9l/+uyhIYQ/PXVA41Rexl+kOkAPDdXEYns6fzUY= +github.com/go-openapi/jsonpointer v0.21.2 h1:AqQaNADVwq/VnkCmQg6ogE+M3FOsKTytwges0JdwVuA= +github.com/go-openapi/jsonpointer v0.21.2/go.mod h1:50I1STOfbY1ycR8jGz8DaMeLCdXiI6aDteEdRNNzpdk= github.com/go-openapi/jsonreference v0.21.0 h1:Rs+Y7hSXT83Jacb7kFyjn4ijOuVGSvOdF2+tg1TRrwQ= github.com/go-openapi/jsonreference v0.21.0/go.mod h1:LmZmgsrTkVg9LG4EaHeY8cBDslNPMo06cago5JNLkm4= -github.com/go-openapi/swag v0.23.0 h1:vsEVJDUo2hPJ2tu0/Xc+4noaxyEffXNIs3cOULZ+GrE= -github.com/go-openapi/swag v0.23.0/go.mod h1:esZ8ITTYEsH1V2trKHjAN8Ai7xHb8RV+YSZ577vPjgQ= +github.com/go-openapi/swag v0.23.1 h1:lpsStH0n2ittzTnbaSloVZLuB5+fvSY/+hnagBjSNZU= +github.com/go-openapi/swag v0.23.1/go.mod h1:STZs8TbRvEQQKUA+JZNAm3EWlgaOBGpyFDqQnDHMef0= github.com/go-task/slim-sprig/v3 v3.0.0 h1:sUs3vkvUymDpBKi3qH1YSqBQk9+9D/8M2mN1vB6EwHI= github.com/go-task/slim-sprig/v3 v3.0.0/go.mod h1:W848ghGpv3Qj3dhTPRyJypKRiqCdHZiAzKg9hl15HA8= github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= @@ -49,9 +51,8 @@ github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q= github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek= github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps= -github.com/google/gnostic-models v0.6.9 h1:MU/8wDLif2qCXZmzncUQ/BOfxWfthHi63KqpoNbWqVw= -github.com/google/gnostic-models v0.6.9/go.mod h1:CiWsm0s6BSQd1hRn8/QmxqB6BesYcbSZxsz9b0KuDBw= -github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= +github.com/google/gnostic-models v0.7.0 h1:qwTtogB15McXDaNqTZdzPJRHvaVJlAl+HVQnLmJEJxo= +github.com/google/gnostic-models v0.7.0/go.mod h1:whL5G0m6dmc5cPxKc5bdKdEN3UjI7OUGxBlw57miDrQ= github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8= github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU= github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= @@ -61,6 +62,8 @@ github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/grpc-ecosystem/grpc-gateway/v2 v2.26.3 h1:5ZPtiqj0JL5oKWmcsq4VMaAW5ukBEgSGXEN89zeH1Jo= github.com/grpc-ecosystem/grpc-gateway/v2 v2.26.3/go.mod h1:ndYquD05frm2vACXE1nsccT4oJzjhw2arTS2cpUD1PI= +github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8= +github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw= github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY= github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y= github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM= @@ -76,33 +79,35 @@ github.com/mailru/easyjson v0.9.0/go.mod h1:1+xMtQp2MRNVL/V1bOzuP3aP8VNwRW55fQUt github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg= github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= -github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M= github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= +github.com/modern-go/reflect2 v1.0.3-0.20250322232337-35a7c28c31ee h1:W5t00kpgFdJifH4BDsTlE89Zl93FEloxaWZfGcifgq8= +github.com/modern-go/reflect2 v1.0.3-0.20250322232337-35a7c28c31ee/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq1c1nUAm88MOHcQC9l5mIlSMApZMrHA= github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ= -github.com/onsi/ginkgo/v2 v2.23.4 h1:ktYTpKJAVZnDT4VjxSbiBenUjmlL/5QkBEocaWXiQus= -github.com/onsi/ginkgo/v2 v2.23.4/go.mod h1:Bt66ApGPBFzHyR+JO10Zbt0Gsp4uWxu5mIOTusL46e8= -github.com/onsi/gomega v1.37.0 h1:CdEG8g0S133B4OswTDC/5XPSzE1OeP29QOioj2PID2Y= -github.com/onsi/gomega v1.37.0/go.mod h1:8D9+Txp43QWKhM24yyOBEdpkzN8FvJyAwecBgsU4KU0= -github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= -github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/onsi/ginkgo/v2 v2.25.3 h1:Ty8+Yi/ayDAGtk4XxmmfUy4GabvM+MegeB4cDLRi6nw= +github.com/onsi/ginkgo/v2 v2.25.3/go.mod h1:43uiyQC4Ed2tkOzLsEYm7hnrb7UJTWHYNsuy3bG/snE= +github.com/onsi/gomega v1.38.2 h1:eZCjf2xjZAqe+LeWvKb5weQ+NcPwX84kqJ0cZNxok2A= +github.com/onsi/gomega v1.38.2/go.mod h1:W2MJcYxRGV63b418Ai34Ud0hEdTVXq9NW9+Sx6uXf3k= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U= github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= -github.com/prometheus/client_golang v1.23.0 h1:ust4zpdl9r4trLY/gSjlm07PuiBq2ynaXXlptpfy8Uc= -github.com/prometheus/client_golang v1.23.0/go.mod h1:i/o0R9ByOnHX0McrTMTyhYvKE4haaf2mW08I+jGAjEE= +github.com/prometheus/client_golang v1.23.2 h1:Je96obch5RDVy3FDMndoUsjAhG5Edi49h0RJWRi/o0o= +github.com/prometheus/client_golang v1.23.2/go.mod h1:Tb1a6LWHB3/SPIzCoaDXI4I8UHKeFTEQ1YCr+0Gyqmg= github.com/prometheus/client_model v0.6.2 h1:oBsgwpGs7iVziMvrGhE53c/GrLUsZdHnqNwqPLxwZyk= github.com/prometheus/client_model v0.6.2/go.mod h1:y3m2F6Gdpfy6Ut/GBsUqTWZqCUvMVzSfMLjcu6wAwpE= -github.com/prometheus/common v0.65.0 h1:QDwzd+G1twt//Kwj/Ww6E9FQq1iVMmODnILtW1t2VzE= -github.com/prometheus/common v0.65.0/go.mod h1:0gZns+BLRQ3V6NdaerOhMbwwRbNh9hkGINtQAsP5GS8= -github.com/prometheus/procfs v0.16.1 h1:hZ15bTNuirocR6u0JZ6BAHHmwS1p8B4P6MRqxtzMyRg= -github.com/prometheus/procfs v0.16.1/go.mod h1:teAbpZRB1iIAJYREa1LsoWUXykVXA1KlTmWl8x/U+Is= +github.com/prometheus/common v0.67.2 h1:PcBAckGFTIHt2+L3I33uNRTlKTplNzFctXcWhPyAEN8= +github.com/prometheus/common v0.67.2/go.mod h1:63W3KZb1JOKgcjlIr64WW/LvFGAqKPj0atm+knVGEko= +github.com/prometheus/procfs v0.17.0 h1:FuLQ+05u4ZI+SS/w9+BWEM2TXiHKsUQ9TADiRH7DuK0= +github.com/prometheus/procfs v0.17.0/go.mod h1:oPQLaDAMRbA+u8H5Pbfq+dl3VDAvHxMUOVhe0wYB2zw= github.com/rogpeppe/go-internal v1.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0tI/otEQ= github.com/rogpeppe/go-internal v1.14.1/go.mod h1:MaRKkUm5W0goXpeCfT7UZI6fk/L7L7so1lCWt35ZSgc= +github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ= github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= -github.com/spf13/pflag v1.0.7 h1:vN6T9TfwStFPFM5XzjsvmzZkLuaLX+HS+0SeFLRgU6M= -github.com/spf13/pflag v1.0.7/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= +github.com/spf13/cobra v1.10.1 h1:lJeBwCfmrnXthfAupyUTzJ/J4Nc1RsHC/mSRU2dll/s= +github.com/spf13/cobra v1.10.1/go.mod h1:7SmJGaTHFVBY0jW4NXGluQoLvhqFQM+6XSKD+P4XaB0= +github.com/spf13/pflag v1.0.9 h1:9exaQaMOCwffKiiiYk6/BndUBv+iRViNW+4lEMi0PvY= +github.com/spf13/pflag v1.0.9/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= @@ -122,24 +127,24 @@ github.com/xhit/go-str2duration/v2 v2.1.0 h1:lxklc02Drh6ynqX+DdPyp5pCKLUQpRT8bp8 github.com/xhit/go-str2duration/v2 v2.1.0/go.mod h1:ohY8p+0f07DiV6Em5LKB0s2YpLtXVyJfNt1+BlmyAsU= github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= -go.etcd.io/etcd/api/v3 v3.6.5 h1:pMMc42276sgR1j1raO/Qv3QI9Af/AuyQUW6CBAWuntA= -go.etcd.io/etcd/api/v3 v3.6.5/go.mod h1:ob0/oWA/UQQlT1BmaEkWQzI0sJ1M0Et0mMpaABxguOQ= -go.etcd.io/etcd/client/pkg/v3 v3.6.5 h1:Duz9fAzIZFhYWgRjp/FgNq2gO1jId9Yae/rLn3RrBP8= -go.etcd.io/etcd/client/pkg/v3 v3.6.5/go.mod h1:8Wx3eGRPiy0qOFMZT/hfvdos+DjEaPxdIDiCDUv/FQk= -go.etcd.io/etcd/client/v3 v3.6.5 h1:yRwZNFBx/35VKHTcLDeO7XVLbCBFbPi+XV4OC3QJf2U= -go.etcd.io/etcd/client/v3 v3.6.5/go.mod h1:ZqwG/7TAFZ0BJ0jXRPoJjKQJtbFo/9NIY8uoFFKcCyo= +go.etcd.io/etcd/api/v3 v3.6.6 h1:mcaMp3+7JawWv69p6QShYWS8cIWUOl32bFLb6qf8pOQ= +go.etcd.io/etcd/api/v3 v3.6.6/go.mod h1:f/om26iXl2wSkcTA1zGQv8reJRSLVdoEBsi4JdfMrx4= +go.etcd.io/etcd/client/pkg/v3 v3.6.6 h1:uoqgzSOv2H9KlIF5O1Lsd8sW+eMLuV6wzE3q5GJGQNs= +go.etcd.io/etcd/client/pkg/v3 v3.6.6/go.mod h1:YngfUVmvsvOJ2rRgStIyHsKtOt9SZI2aBJrZiWJhCbI= +go.etcd.io/etcd/client/v3 v3.6.6 h1:G5z1wMf5B9SNexoxOHUGBaULurOZPIgGPsW6CN492ec= +go.etcd.io/etcd/client/v3 v3.6.6/go.mod h1:36Qv6baQ07znPR3+n7t+Rk5VHEzVYPvFfGmfF4wBHV8= go.opentelemetry.io/auto/sdk v1.1.0 h1:cH53jehLUN6UFLY71z+NDOiNJqDdPRaXzTel0sJySYA= go.opentelemetry.io/auto/sdk v1.1.0/go.mod h1:3wSPjt5PWp2RhlCcmmOial7AvC4DQqZb7a7wCow3W8A= -go.opentelemetry.io/otel v1.36.0 h1:UumtzIklRBY6cI/lllNZlALOF5nNIzJVb16APdvgTXg= -go.opentelemetry.io/otel v1.36.0/go.mod h1:/TcFMXYjyRNh8khOAO9ybYkqaDBb/70aVwkNML4pP8E= -go.opentelemetry.io/otel/metric v1.36.0 h1:MoWPKVhQvJ+eeXWHFBOPoBOi20jh6Iq2CcCREuTYufE= -go.opentelemetry.io/otel/metric v1.36.0/go.mod h1:zC7Ks+yeyJt4xig9DEw9kuUFe5C3zLbVjV2PzT6qzbs= -go.opentelemetry.io/otel/sdk v1.36.0 h1:b6SYIuLRs88ztox4EyrvRti80uXIFy+Sqzoh9kFULbs= -go.opentelemetry.io/otel/sdk v1.36.0/go.mod h1:+lC+mTgD+MUWfjJubi2vvXWcVxyr9rmlshZni72pXeY= -go.opentelemetry.io/otel/sdk/metric v1.36.0 h1:r0ntwwGosWGaa0CrSt8cuNuTcccMXERFwHX4dThiPis= -go.opentelemetry.io/otel/sdk/metric v1.36.0/go.mod h1:qTNOhFDfKRwX0yXOqJYegL5WRaW376QbB7P4Pb0qva4= -go.opentelemetry.io/otel/trace v1.36.0 h1:ahxWNuqZjpdiFAyrIoQ4GIiAIhxAunQR6MUoKrsNd4w= -go.opentelemetry.io/otel/trace v1.36.0/go.mod h1:gQ+OnDZzrybY4k4seLzPAWNwVBBVlF2szhehOBB/tGA= +go.opentelemetry.io/otel v1.37.0 h1:9zhNfelUvx0KBfu/gb+ZgeAfAgtWrfHJZcAqFC228wQ= +go.opentelemetry.io/otel v1.37.0/go.mod h1:ehE/umFRLnuLa/vSccNq9oS1ErUlkkK71gMcN34UG8I= +go.opentelemetry.io/otel/metric v1.37.0 h1:mvwbQS5m0tbmqML4NqK+e3aDiO02vsf/WgbsdpcPoZE= +go.opentelemetry.io/otel/metric v1.37.0/go.mod h1:04wGrZurHYKOc+RKeye86GwKiTb9FKm1WHtO+4EVr2E= +go.opentelemetry.io/otel/sdk v1.37.0 h1:ItB0QUqnjesGRvNcmAcU0LyvkVyGJ2xftD29bWdDvKI= +go.opentelemetry.io/otel/sdk v1.37.0/go.mod h1:VredYzxUvuo2q3WRcDnKDjbdvmO0sCzOvVAiY+yUkAg= +go.opentelemetry.io/otel/sdk/metric v1.37.0 h1:90lI228XrB9jCMuSdA0673aubgRobVZFhbjxHHspCPc= +go.opentelemetry.io/otel/sdk/metric v1.37.0/go.mod h1:cNen4ZWfiD37l5NhS+Keb5RXVWZWpRE+9WyVCpbo5ps= +go.opentelemetry.io/otel/trace v1.37.0 h1:HLdcFNbRQBE2imdSEgm/kwqmQj1Or1l/7bW6mxVK7z4= +go.opentelemetry.io/otel/trace v1.37.0/go.mod h1:TlgrlQ+PtQO5XFerSPUYG0JSgGyryXewPGyayAWSBS0= go.uber.org/automaxprocs v1.6.0 h1:O3y2/QNTOdbF+e/dpXNNW7Rx2hZ4sTIPyybbxyNqTUs= go.uber.org/automaxprocs v1.6.0/go.mod h1:ifeIMSnPZuznNm6jmdzmU3/bfk01Fe2fotchwEFJ8r8= go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto= @@ -148,6 +153,10 @@ go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0= go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y= go.uber.org/zap v1.27.0 h1:aJMhYGrd5QSmlpLMr2MftRKl7t8J8PTZPA732ud/XR8= go.uber.org/zap v1.27.0/go.mod h1:GB2qFLM7cTU87MWRP2mPIjqfIDnGu+VIO4V/SdhGo2E= +go.yaml.in/yaml/v2 v2.4.3 h1:6gvOSjQoTB3vt1l+CU+tSyi/HOjfOjRLJ4YwYZGwRO0= +go.yaml.in/yaml/v2 v2.4.3/go.mod h1:zSxWcmIDjOzPXpjlTTbAsKokqkDNAVtZO0WOMiT90s8= +go.yaml.in/yaml/v3 v3.0.4 h1:tfq32ie2Jv2UxXFdLJdh3jXuOzWiL1fo0bu/FbuKpbc= +go.yaml.in/yaml/v3 v3.0.4/go.mod h1:DhzuOOF2ATzADvBadXxruRBLzYTpT36CKvDb3+aBEFg= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= @@ -157,10 +166,10 @@ golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= -golang.org/x/net v0.43.0 h1:lat02VYK2j4aLzMzecihNvTlJNQUq316m2Mr9rnM6YE= -golang.org/x/net v0.43.0/go.mod h1:vhO1fvI4dGsIjh73sWfUVjj3N7CA9WkKJNQm2svM6Jg= -golang.org/x/oauth2 v0.30.0 h1:dnDm7JmhM45NNpd8FDDeLhK6FwqbOf4MLCM9zb1BOHI= -golang.org/x/oauth2 v0.30.0/go.mod h1:B++QgG3ZKulg6sRPGD/mqlHQs5rB3Ml9erfeDY7xKlU= +golang.org/x/net v0.47.0 h1:Mx+4dIFzqraBXUugkia1OOvlD6LemFo1ALMHjrXDOhY= +golang.org/x/net v0.47.0/go.mod h1:/jNxtkgq5yWUGYkaZGqo27cfGZ1c5Nen03aYrrKpVRU= +golang.org/x/oauth2 v0.33.0 h1:4Q+qn+E5z8gPRJfmRy7C2gGG3T4jIprK6aSYgTXGRpo= +golang.org/x/oauth2 v0.33.0/go.mod h1:lzm5WQJQwKZ3nwavOZ3IS5Aulzxi68dUSgRHujetwEA= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= @@ -168,68 +177,69 @@ golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5h golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.35.0 h1:vz1N37gP5bs89s7He8XuIYXpyY0+QlsKmzipCbUtyxI= -golang.org/x/sys v0.35.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k= -golang.org/x/term v0.34.0 h1:O/2T7POpk0ZZ7MAzMeWFSg6S5IpWd/RXDlM9hgM3DR4= -golang.org/x/term v0.34.0/go.mod h1:5jC53AEywhIVebHgPVeg0mj8OD3VO9OzclacVrqpaAw= +golang.org/x/sys v0.38.0 h1:3yZWxaJjBmCWXqhN1qh02AkOnCQ1poK6oF+a7xWL6Gc= +golang.org/x/sys v0.38.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks= +golang.org/x/term v0.37.0 h1:8EGAD0qCmHYZg6J17DvsMy9/wJ7/D/4pV/wfnld5lTU= +golang.org/x/term v0.37.0/go.mod h1:5pB4lxRNYYVZuTLmy8oR2BH8dflOR+IbTYFD8fi3254= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= -golang.org/x/text v0.28.0 h1:rhazDwis8INMIwQ4tpjLDzUhx6RlXqZNPEM0huQojng= -golang.org/x/text v0.28.0/go.mod h1:U8nCwOR8jO/marOQ0QbDiOngZVEBB7MAiitBuMjXiNU= -golang.org/x/time v0.12.0 h1:ScB/8o8olJvc+CQPWrK3fPZNfh7qgwCrY0zJmoEQLSE= -golang.org/x/time v0.12.0/go.mod h1:CDIdPxbZBQxdj6cxyCIdrNogrJKMJ7pr37NYpMcMDSg= +golang.org/x/text v0.31.0 h1:aC8ghyu4JhP8VojJ2lEHBnochRno1sgL6nEi9WGFGMM= +golang.org/x/text v0.31.0/go.mod h1:tKRAlv61yKIjGGHX/4tP1LTbc13YSec1pxVEWXzfoeM= +golang.org/x/time v0.14.0 h1:MRx4UaLrDotUKUdCIqzPC48t1Y9hANFKIRpNx+Te8PI= +golang.org/x/time v0.14.0/go.mod h1:eL/Oa2bBBK0TkX57Fyni+NgnyQQN4LitPmob2Hjnqw4= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= -golang.org/x/tools v0.35.0 h1:mBffYraMEf7aa0sB+NuKnuCy8qI/9Bughn8dC2Gu5r0= -golang.org/x/tools v0.35.0/go.mod h1:NKdj5HkL/73byiZSJjqJgKn3ep7KjFkBOkR/Hps3VPw= +golang.org/x/tools v0.38.0 h1:Hx2Xv8hISq8Lm16jvBZ2VQf+RLmbd7wVUsALibYI/IQ= +golang.org/x/tools v0.38.0/go.mod h1:yEsQ/d/YK8cjh0L6rZlY8tgtlKiBNTL14pGDJPJpYQs= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -google.golang.org/genproto/googleapis/api v0.0.0-20250603155806-513f23925822 h1:oWVWY3NzT7KJppx2UKhKmzPq4SRe0LdCijVRwvGeikY= -google.golang.org/genproto/googleapis/api v0.0.0-20250603155806-513f23925822/go.mod h1:h3c4v36UTKzUiuaOKQ6gr3S+0hovBtUrXzTG/i3+XEc= -google.golang.org/genproto/googleapis/rpc v0.0.0-20250818200422-3122310a409c h1:qXWI/sQtv5UKboZ/zUk7h+mrf/lXORyI+n9DKDAusdg= -google.golang.org/genproto/googleapis/rpc v0.0.0-20250818200422-3122310a409c/go.mod h1:gw1tLEfykwDz2ET4a12jcXt4couGAm7IwsVaTy0Sflo= -google.golang.org/grpc v1.74.2 h1:WoosgB65DlWVC9FqI82dGsZhWFNBSLjQ84bjROOpMu4= -google.golang.org/grpc v1.74.2/go.mod h1:CtQ+BGjaAIXHs/5YS3i473GqwBBa1zGQNevxdeBEXrM= -google.golang.org/protobuf v1.36.7 h1:IgrO7UwFQGJdRNXH/sQux4R1Dj1WAKcLElzeeRaXV2A= -google.golang.org/protobuf v1.36.7/go.mod h1:jduwjTPXsFjZGTmRluh+L6NjiWu7pchiJ2/5YcXBHnY= +gonum.org/v1/gonum v0.16.0 h1:5+ul4Swaf3ESvrOnidPp4GZbzf0mxVQpDCYUQE7OJfk= +gonum.org/v1/gonum v0.16.0/go.mod h1:fef3am4MQ93R2HHpKnLk4/Tbh/s0+wqD5nfa6Pnwy4E= +google.golang.org/genproto/googleapis/api v0.0.0-20250811230008-5f3141c8851a h1:DMCgtIAIQGZqJXMVzJF4MV8BlWoJh2ZuFiRdAleyr58= +google.golang.org/genproto/googleapis/api v0.0.0-20250811230008-5f3141c8851a/go.mod h1:y2yVLIE/CSMCPXaHnSKXxu1spLPnglFLegmgdY23uuE= +google.golang.org/genproto/googleapis/rpc v0.0.0-20251103181224-f26f9409b101 h1:tRPGkdGHuewF4UisLzzHHr1spKw92qLM98nIzxbC0wY= +google.golang.org/genproto/googleapis/rpc v0.0.0-20251103181224-f26f9409b101/go.mod h1:7i2o+ce6H/6BluujYR+kqX3GKH+dChPTQU19wjRPiGk= +google.golang.org/grpc v1.76.0 h1:UnVkv1+uMLYXoIz6o7chp59WfQUYA2ex/BXQ9rHZu7A= +google.golang.org/grpc v1.76.0/go.mod h1:Ju12QI8M6iQJtbcsV+awF5a4hfJMLi4X0JLo94ULZ6c= +google.golang.org/protobuf v1.36.10 h1:AYd7cD/uASjIL6Q9LiTjz8JLcrh/88q5UObnmY3aOOE= +google.golang.org/protobuf v1.36.10/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= -gopkg.in/evanphx/json-patch.v4 v4.12.0 h1:n6jtcsulIzXPJaxegRbvFNNrZDjbij7ny3gmSPG+6V4= -gopkg.in/evanphx/json-patch.v4 v4.12.0/go.mod h1:p8EYWUEYMpynmqDbY58zCKCFZw8pRWMG4EsWvDvM72M= +gopkg.in/evanphx/json-patch.v4 v4.13.0 h1:czT3CmqEaQ1aanPc5SdlgQrrEIb8w/wwCvWWnfEbYzo= +gopkg.in/evanphx/json-patch.v4 v4.13.0/go.mod h1:p8EYWUEYMpynmqDbY58zCKCFZw8pRWMG4EsWvDvM72M= gopkg.in/inf.v0 v0.9.1 h1:73M5CoZyi3ZLMOyDlQh031Cx6N9NDJ2Vvfl76EDAgDc= gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= -k8s.io/api v0.33.4 h1:oTzrFVNPXBjMu0IlpA2eDDIU49jsuEorGHB4cvKupkk= -k8s.io/api v0.33.4/go.mod h1:VHQZ4cuxQ9sCUMESJV5+Fe8bGnqAARZ08tSTdHWfeAc= -k8s.io/apiextensions-apiserver v0.33.3 h1:qmOcAHN6DjfD0v9kxL5udB27SRP6SG/MTopmge3MwEs= -k8s.io/apiextensions-apiserver v0.33.3/go.mod h1:oROuctgo27mUsyp9+Obahos6CWcMISSAPzQ77CAQGz8= -k8s.io/apimachinery v0.33.4 h1:SOf/JW33TP0eppJMkIgQ+L6atlDiP/090oaX0y9pd9s= -k8s.io/apimachinery v0.33.4/go.mod h1:BHW0YOu7n22fFv/JkYOEfkUYNRN0fj0BlvMFWA7b+SM= -k8s.io/client-go v0.33.4 h1:TNH+CSu8EmXfitntjUPwaKVPN0AYMbc9F1bBS8/ABpw= -k8s.io/client-go v0.33.4/go.mod h1:LsA0+hBG2DPwovjd931L/AoaezMPX9CmBgyVyBZmbCY= +k8s.io/api v0.34.2 h1:fsSUNZhV+bnL6Aqrp6O7lMTy6o5x2C4XLjnh//8SLYY= +k8s.io/api v0.34.2/go.mod h1:MMBPaWlED2a8w4RSeanD76f7opUoypY8TFYkSM+3XHw= +k8s.io/apiextensions-apiserver v0.34.1 h1:NNPBva8FNAPt1iSVwIE0FsdrVriRXMsaWFMqJbII2CI= +k8s.io/apiextensions-apiserver v0.34.1/go.mod h1:hP9Rld3zF5Ay2Of3BeEpLAToP+l4s5UlxiHfqRaRcMc= +k8s.io/apimachinery v0.34.2 h1:zQ12Uk3eMHPxrsbUJgNF8bTauTVR2WgqJsTmwTE/NW4= +k8s.io/apimachinery v0.34.2/go.mod h1:/GwIlEcWuTX9zKIg2mbw0LRFIsXwrfoVxn+ef0X13lw= +k8s.io/client-go v0.34.2 h1:Co6XiknN+uUZqiddlfAjT68184/37PS4QAzYvQvDR8M= +k8s.io/client-go v0.34.2/go.mod h1:2VYDl1XXJsdcAxw7BenFslRQX28Dxz91U9MWKjX97fE= k8s.io/klog/v2 v2.130.1 h1:n9Xl7H1Xvksem4KFG4PYbdQCQxqc/tTUyrgXaOhHSzk= k8s.io/klog/v2 v2.130.1/go.mod h1:3Jpz1GvMt720eyJH1ckRHK1EDfpxISzJ7I9OYgaDtPE= -k8s.io/kube-openapi v0.0.0-20250318190949-c8a335a9a2ff h1:/usPimJzUKKu+m+TE36gUyGcf03XZEP0ZIKgKj35LS4= -k8s.io/kube-openapi v0.0.0-20250318190949-c8a335a9a2ff/go.mod h1:5jIi+8yX4RIb8wk3XwBo5Pq2ccx4FP10ohkbSKCZoK8= -k8s.io/utils v0.0.0-20241210054802-24370beab758 h1:sdbE21q2nlQtFh65saZY+rRM6x6aJJI8IUa1AmH/qa0= -k8s.io/utils v0.0.0-20241210054802-24370beab758/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0= -sigs.k8s.io/controller-runtime v0.21.0 h1:CYfjpEuicjUecRk+KAeyYh+ouUBn4llGyDYytIGcJS8= -sigs.k8s.io/controller-runtime v0.21.0/go.mod h1:OSg14+F65eWqIu4DceX7k/+QRAbTTvxeQSNSOQpukWM= -sigs.k8s.io/external-dns v0.19.0 h1:fyH3AN/WmOrbuAVYgtHPK4UhkNy9DR3gXMU65yQxuzQ= -sigs.k8s.io/external-dns v0.19.0/go.mod h1:hW+Ce4C1mjxNaGuuPf2BZDohBAs4C1JtCAUZK02R7hM= -sigs.k8s.io/json v0.0.0-20241014173422-cfa47c3a1cc8 h1:gBQPwqORJ8d8/YNZWEjoZs7npUVDpVXUUOFfW6CgAqE= -sigs.k8s.io/json v0.0.0-20241014173422-cfa47c3a1cc8/go.mod h1:mdzfpAEoE6DHQEN0uh9ZbOCuHbLK5wOm7dK4ctXE9Tg= -sigs.k8s.io/randfill v0.0.0-20250304075658-069ef1bbf016/go.mod h1:XeLlZ/jmk4i1HRopwe7/aU3H5n1zNUcX6TM94b3QxOY= +k8s.io/kube-openapi v0.0.0-20250814151709-d7b6acb124c3 h1:liMHz39T5dJO1aOKHLvwaCjDbf07wVh6yaUlTpunnkE= +k8s.io/kube-openapi v0.0.0-20250814151709-d7b6acb124c3/go.mod h1:UZ2yyWbFTpuhSbFhv24aGNOdoRdJZgsIObGBUaYVsts= +k8s.io/utils v0.0.0-20250820121507-0af2bda4dd1d h1:wAhiDyZ4Tdtt7e46e9M5ZSAJ/MnPGPs+Ki1gHw4w1R0= +k8s.io/utils v0.0.0-20250820121507-0af2bda4dd1d/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0= +sigs.k8s.io/controller-runtime v0.22.4 h1:GEjV7KV3TY8e+tJ2LCTxUTanW4z/FmNB7l327UfMq9A= +sigs.k8s.io/controller-runtime v0.22.4/go.mod h1:+QX1XUpTXN4mLoblf4tqr5CQcyHPAki2HLXqQMY6vh8= +sigs.k8s.io/external-dns v0.20.0 h1:rJ4Q5c32NStvI8J+u2nyM4bcKxZG4g1NLPL0p994U9M= +sigs.k8s.io/external-dns v0.20.0/go.mod h1:ccNJqr47BJYN75U9WBgeAdEznyrV7VuIlS3TeO0iqSY= +sigs.k8s.io/json v0.0.0-20250730193827-2d320260d730 h1:IpInykpT6ceI+QxKBbEflcR5EXP7sU1kvOlxwZh5txg= +sigs.k8s.io/json v0.0.0-20250730193827-2d320260d730/go.mod h1:mdzfpAEoE6DHQEN0uh9ZbOCuHbLK5wOm7dK4ctXE9Tg= sigs.k8s.io/randfill v1.0.0 h1:JfjMILfT8A6RbawdsK2JXGBR5AQVfd+9TbzrlneTyrU= sigs.k8s.io/randfill v1.0.0/go.mod h1:XeLlZ/jmk4i1HRopwe7/aU3H5n1zNUcX6TM94b3QxOY= -sigs.k8s.io/structured-merge-diff/v4 v4.7.0 h1:qPeWmscJcXP0snki5IYF79Z8xrl8ETFxgMd7wez1XkI= -sigs.k8s.io/structured-merge-diff/v4 v4.7.0/go.mod h1:dDy58f92j70zLsuZVuUX5Wp9vtxXpaZnkPGWeqDfCps= -sigs.k8s.io/yaml v1.4.0 h1:Mk1wCc2gy/F0THH0TAp1QYyJNzRm2KCLy3o5ASXVI5E= -sigs.k8s.io/yaml v1.4.0/go.mod h1:Ejl7/uTz7PSA4eKMyQCUTnhZYNmLIl+5c2lQPGR2BPY= +sigs.k8s.io/structured-merge-diff/v6 v6.3.0 h1:jTijUJbW353oVOd9oTlifJqOGEkUw2jB/fXCbTiQEco= +sigs.k8s.io/structured-merge-diff/v6 v6.3.0/go.mod h1:M3W8sfWvn2HhQDIbGWj3S099YozAsymCo/wrT5ohRUE= +sigs.k8s.io/yaml v1.6.0 h1:G8fkbMSAFqgEFgh4b1wmtzDnioxFCUgTZhlbj5P9QYs= +sigs.k8s.io/yaml v1.6.0/go.mod h1:796bPqUfzR/0jLAl6XjHl3Ck7MiyVv8dbTdyT3/pMf4= From f306b22ad6a7efd4258ce10215033c959a5895a7 Mon Sep 17 00:00:00 2001 From: Jan Jansen Date: Wed, 17 Dec 2025 09:53:12 +0100 Subject: [PATCH 2/3] switch to ownedby --- coredns.go | 141 +++++----- coredns_test.go | 716 +++++++++++++++++++++++++++++------------------- main.go | 12 +- 3 files changed, 506 insertions(+), 363 deletions(-) diff --git a/coredns.go b/coredns.go index 2aeb5f4..5af328b 100644 --- a/coredns.go +++ b/coredns.go @@ -28,6 +28,7 @@ import ( "time" log "github.com/sirupsen/logrus" + "go.etcd.io/etcd/api/v3/mvccpb" etcdcv3 "go.etcd.io/etcd/client/v3" "sigs.k8s.io/external-dns/pkg/tlsutils" @@ -92,22 +93,19 @@ type Service struct { // Etcd key where we found this service and ignored from json un-/marshaling Key string `json:"-"` - // ManagedBy is used to prevent service to be added by different external-dns (only used by external-dns) - ManagedBy string `json:"managedby,omitempty"` - // OwnedBy is used to prevent service to be added by different external-dns (only used by external-dns) OwnedBy string `json:"ownedby,omitempty"` } type etcdClient struct { - client *etcdcv3.Client - managedBy string - ignoreEmptyManagedBy bool + client *etcdcv3.Client + ownerID string + strictlyOwned bool } var _ coreDNSClient = etcdClient{} -// GetServices return all Service records stored in etcd stored anywhere under the given key (recursively) +// GetServices GetService return all Service records stored in etcd stored anywhere under the given key (recursively) func (c etcdClient) GetServices(ctx context.Context, prefix string) ([]*Service, error) { ctx, cancel := context.WithTimeout(ctx, etcdTimeout) defer cancel() @@ -121,32 +119,26 @@ func (c etcdClient) GetServices(ctx context.Context, prefix string) ([]*Service, var svcs []*Service bx := make(map[Service]bool) for _, n := range r.Kvs { - svc := new(Service) - if err := json.Unmarshal(n.Value, svc); err != nil { - return nil, fmt.Errorf("%s: %w", n.Key, err) + svc, err := c.unmarshalService(n) + if err != nil { + return nil, err + } + if c.strictlyOwned && svc.OwnedBy != c.ownerID { + continue } b := Service{ - Host: svc.Host, - Port: svc.Port, - Priority: svc.Priority, - Weight: svc.Weight, - Text: svc.Text, - Key: string(n.Key), - ManagedBy: svc.ManagedBy, - OwnedBy: svc.OwnedBy, + Host: svc.Host, + Port: svc.Port, + Priority: svc.Priority, + Weight: svc.Weight, + Text: svc.Text, + Key: string(n.Key), } if _, ok := bx[b]; ok { // skip the service if already added to service list. // the same service might be found in multiple etcd nodes. continue } - if c.managedBy != "" { - if c.ignoreEmptyManagedBy && b.ManagedBy != c.managedBy { - continue - } else if !c.ignoreEmptyManagedBy && b.ManagedBy != "" && b.ManagedBy != c.managedBy { - continue - } - } bx[b] = true svc.Key = string(n.Key) @@ -155,7 +147,6 @@ func (c etcdClient) GetServices(ctx context.Context, prefix string) ([]*Service, } svcs = append(svcs, svc) } - return svcs, nil } @@ -164,14 +155,23 @@ func (c etcdClient) SaveService(ctx context.Context, service *Service) error { ctx, cancel := context.WithTimeout(ctx, etcdTimeout) defer cancel() - if c.managedBy != "" { - service.ManagedBy = c.managedBy - service.OwnedBy = c.managedBy - } - if ownedBy, err := c.IsOwnedBy(ctx, service.Key); err != nil { - return err - } else if !ownedBy { - return fmt.Errorf("key %q is not owned by this service", service.Key) + // check only for empty OwnedBy + if c.strictlyOwned && service.OwnedBy != c.ownerID { + r, err := c.client.Get(ctx, service.Key) + if err != nil { + return fmt.Errorf("etcd get %q: %w", service.Key, err) + } + // Key missing -> treat as owned (safe to create) + if r != nil && len(r.Kvs) != 0 { + svc, err := c.unmarshalService(r.Kvs[0]) + if err != nil { + return fmt.Errorf("failed to unmarshal value for key %q: %w", service.Key, err) + } + if svc.OwnedBy != c.ownerID { + return fmt.Errorf("key %q is not owned by this provider", service.Key) + } + } + service.OwnedBy = c.ownerID } value, err := json.Marshal(&service) @@ -185,54 +185,43 @@ func (c etcdClient) SaveService(ctx context.Context, service *Service) error { return nil } -func (c etcdClient) IsOwnedBy(ctx context.Context, key string) (bool, error) { +// DeleteService deletes service record from etcd +func (c etcdClient) DeleteService(ctx context.Context, key string) error { ctx, cancel := context.WithTimeout(ctx, etcdTimeout) defer cancel() - if c.managedBy == "" { - return true, nil - } - - r, err := c.client.Get(ctx, key) - if err != nil { - return false, err - } - if r == nil { - return true, nil - } else if len(r.Kvs) > 1 { - return false, fmt.Errorf("found multiple keys with the same key this service") - } else if len(r.Kvs) == 0 { - return true, nil - } - for _, n := range r.Kvs { - svc := new(Service) - if err := json.Unmarshal(n.Value, svc); err != nil { - return false, fmt.Errorf("%s: %w", n.Key, err) + if c.strictlyOwned { + rs, err := c.client.Get(ctx, key, etcdcv3.WithPrefix()) + if err != nil { + return err } + for _, r := range rs.Kvs { + svc, err := c.unmarshalService(r) + if err != nil { + return err + } + if svc.OwnedBy != c.ownerID { + continue + } - if !c.ignoreEmptyManagedBy && svc.ManagedBy == "" { - return true, nil - } - if svc.ManagedBy == c.managedBy { - return true, nil + _, err = c.client.Delete(ctx, string(r.Key)) + if err != nil { + return err + } } + return err + } else { + _, err := c.client.Delete(ctx, key, etcdcv3.WithPrefix()) + return err } - return false, nil } -// DeleteService deletes service record from etcd -func (c etcdClient) DeleteService(ctx context.Context, key string) error { - ctx, cancel := context.WithTimeout(ctx, etcdTimeout) - defer cancel() - - if owned, err := c.IsOwnedBy(ctx, key); err != nil { - return err - } else if !owned { - return fmt.Errorf("key %q is not owned by this service", key) +func (c etcdClient) unmarshalService(n *mvccpb.KeyValue) (*Service, error) { + svc := new(Service) + if err := json.Unmarshal(n.Value, svc); err != nil { + return nil, fmt.Errorf("failed to unmarshal %q: %w", n.Key, err) } - - _, err := c.client.Delete(ctx, key, etcdcv3.WithPrefix()) - return err + return svc, nil } // builds etcd client config depending on connection scheme and TLS parameters @@ -265,7 +254,7 @@ func getETCDConfig() (*etcdcv3.Config, error) { } // the newETCDClient is an etcd client constructor -func newETCDClient(managedBy string, ignoreEmptyManagedBy bool) (coreDNSClient, error) { +func newETCDClient(ownerID string, strictlyOwned bool) (coreDNSClient, error) { cfg, err := getETCDConfig() if err != nil { return nil, err @@ -274,12 +263,12 @@ func newETCDClient(managedBy string, ignoreEmptyManagedBy bool) (coreDNSClient, if err != nil { return nil, err } - return etcdClient{c, managedBy, ignoreEmptyManagedBy}, nil + return etcdClient{c, ownerID, strictlyOwned}, nil } // NewCoreDNSProvider is a CoreDNS provider constructor -func NewCoreDNSProvider(config CoreDNSConfig, managedBy string, ignoreEmptyManagedBy, dryRun bool) (provider.Provider, error) { - client, err := newETCDClient(managedBy, ignoreEmptyManagedBy) +func NewCoreDNSProvider(config CoreDNSConfig, ownerID string, strictlyOwned, dryRun bool) (provider.Provider, error) { + client, err := newETCDClient(ownerID, strictlyOwned) if err != nil { return nil, err } diff --git a/coredns_test.go b/coredns_test.go index a34e312..2677308 100644 --- a/coredns_test.go +++ b/coredns_test.go @@ -5,7 +5,7 @@ Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at - http://www.apache.org/licenses/LICENSE-2.0 + http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, @@ -13,6 +13,7 @@ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ + package main import ( @@ -23,17 +24,17 @@ import ( "strings" "testing" + "github.com/GDATASoftwareAG/external-dns-coredns-webhook/internal/testutils" log "github.com/sirupsen/logrus" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/mock" - "github.com/stretchr/testify/require" "go.etcd.io/etcd/api/v3/mvccpb" etcdcv3 "go.etcd.io/etcd/client/v3" - "github.com/GDATASoftwareAG/external-dns-coredns-webhook/internal/testutils" - "sigs.k8s.io/external-dns/endpoint" "sigs.k8s.io/external-dns/plan" + + "github.com/stretchr/testify/require" ) const defaultCoreDNSPrefix = "/skydns/" @@ -74,18 +75,28 @@ func (m *MockEtcdKV) Put(ctx context.Context, key, input string, _ ...etcdcv3.Op return args.Get(0).(*etcdcv3.PutResponse), args.Error(1) } -func (m *MockEtcdKV) Get(ctx context.Context, key string, _ ...etcdcv3.OpOption) (*etcdcv3.GetResponse, error) { - args := m.Called(ctx, key) - return args.Get(0).(*etcdcv3.GetResponse), args.Error(1) +func (m *MockEtcdKV) Get(ctx context.Context, key string, opts ...etcdcv3.OpOption) (*etcdcv3.GetResponse, error) { + if len(opts) == 0 { + args := m.Called(ctx, key) + return args.Get(0).(*etcdcv3.GetResponse), args.Error(1) + } else { + args := m.Called(ctx, key, opts[0]) + return args.Get(0).(*etcdcv3.GetResponse), args.Error(1) + } } func (m *MockEtcdKV) Delete(ctx context.Context, key string, opts ...etcdcv3.OpOption) (*etcdcv3.DeleteResponse, error) { - args := m.Called(ctx, key, opts[0]) - return args.Get(0).(*etcdcv3.DeleteResponse), args.Error(1) + if len(opts) == 0 { + args := m.Called(ctx, key) + return args.Get(0).(*etcdcv3.DeleteResponse), args.Error(1) + } else { + args := m.Called(ctx, key, opts[0]) + return args.Get(0).(*etcdcv3.DeleteResponse), args.Error(1) + } } func TestETCDConfig(t *testing.T) { - tests := []struct { + var tests = []struct { name string input map[string]string want *etcdcv3.Config @@ -419,55 +430,6 @@ func TestCoreDNSApplyChanges(t *testing.T) { validateServices(client.services, expectedServices4, t, 4) } -func TestApplyChangesAWithGroupServiceTranslation(t *testing.T) { - client := fakeETCDClient{ - map[string]Service{}, - } - coredns := coreDNSProvider{ - client: client, - CoreDNSConfig: CoreDNSConfig{ - coreDNSPrefix: defaultCoreDNSPrefix, - }, - } - - changes1 := &plan.Changes{ - Create: []*endpoint.Endpoint{ - endpoint.NewEndpoint("domain1.local", endpoint.RecordTypeA, "5.5.5.5").WithProviderSpecific(providerSpecificGroup, "test1"), - endpoint.NewEndpoint("domain2.local", endpoint.RecordTypeA, "5.5.5.6").WithProviderSpecific(providerSpecificGroup, "test1"), - endpoint.NewEndpoint("domain3.local", endpoint.RecordTypeA, "5.5.5.7").WithProviderSpecific(providerSpecificGroup, "test2"), - }, - } - coredns.ApplyChanges(context.Background(), changes1) - - expectedServices1 := map[string][]*Service{ - "/skydns/local/domain1": {{Host: "5.5.5.5", Group: "test1"}}, - "/skydns/local/domain2": {{Host: "5.5.5.6", Group: "test1"}}, - "/skydns/local/domain3": {{Host: "5.5.5.7", Group: "test2"}}, - } - validateServices(client.services, expectedServices1, t, 1) -} - -func TestRecordsAWithGroupServiceTranslation(t *testing.T) { - client := fakeETCDClient{ - map[string]Service{ - "/skydns/local/domain1": {Host: "5.5.5.5", Group: "test1"}, - }, - } - coredns := coreDNSProvider{ - client: client, - CoreDNSConfig: CoreDNSConfig{ - coreDNSPrefix: defaultCoreDNSPrefix, - }, - } - endpoints, err := coredns.Records(context.Background()) - require.NoError(t, err) - if prop, ok := endpoints[0].GetProviderSpecificProperty(providerSpecificGroup); !ok { - t.Error("go no Group name") - } else if prop != "test1" { - t.Errorf("got unexpected Group name: %s != %s", prop, "test1") - } -} - func TestCoreDNSApplyChanges_DomainDoNotMatch(t *testing.T) { client := fakeETCDClient{ map[string]Service{}, @@ -522,7 +484,7 @@ func validateServices(services map[string]Service, expectedServices map[string][ } found := false for i, expectedServiceEntry := range expectedServiceEntries { - if value.Host == expectedServiceEntry.Host && value.Text == expectedServiceEntry.Text { + if value.Host == expectedServiceEntry.Host && value.Text == expectedServiceEntry.Text && value.Group == expectedServiceEntry.Group { expectedServiceEntries = append(expectedServiceEntries[:i], expectedServiceEntries[i+1:]...) found = true break @@ -556,14 +518,15 @@ func TestGetServices_Success(t *testing.T) { value, err := json.Marshal(svc) require.NoError(t, err) mockKV := new(MockEtcdKV) - mockKV.On("Get", mock.Anything, "/prefix").Return(&etcdcv3.GetResponse{ - Kvs: []*mvccpb.KeyValue{ - { - Key: []byte("/prefix/1"), - Value: value, + mockKV.On("Get", mock.Anything, "/prefix", mock.AnythingOfType("clientv3.OpOption")). + Return(&etcdcv3.GetResponse{ + Kvs: []*mvccpb.KeyValue{ + { + Key: []byte("/prefix/1"), + Value: value, + }, }, - }, - }, nil) + }, nil) c := etcdClient{ client: &etcdcv3.Client{ @@ -589,18 +552,19 @@ func TestGetServices_Duplicate(t *testing.T) { value, err := json.Marshal(svc) require.NoError(t, err) - mockKV.On("Get", mock.Anything, "/prefix").Return(&etcdcv3.GetResponse{ - Kvs: []*mvccpb.KeyValue{ - { - Key: []byte("/prefix/1"), - Value: value, - }, - { - Key: []byte("/prefix/1"), - Value: value, + mockKV.On("Get", mock.Anything, "/prefix", mock.AnythingOfType("clientv3.OpOption")). + Return(&etcdcv3.GetResponse{ + Kvs: []*mvccpb.KeyValue{ + { + Key: []byte("/prefix/1"), + Value: value, + }, + { + Key: []byte("/prefix/1"), + Value: value, + }, }, - }, - }, nil) + }, nil) result, err := c.GetServices(context.Background(), "/prefix") assert.NoError(t, err) @@ -622,18 +586,19 @@ func TestGetServices_Multiple(t *testing.T) { value2, err := json.Marshal(svc2) require.NoError(t, err) - mockKV.On("Get", mock.Anything, "/prefix").Return(&etcdcv3.GetResponse{ - Kvs: []*mvccpb.KeyValue{ - { - Key: []byte("/prefix/1"), - Value: value, - }, - { - Key: []byte("/prefix/2"), - Value: value2, + mockKV.On("Get", mock.Anything, "/prefix", mock.AnythingOfType("clientv3.OpOption")). + Return(&etcdcv3.GetResponse{ + Kvs: []*mvccpb.KeyValue{ + { + Key: []byte("/prefix/1"), + Value: value, + }, + { + Key: []byte("/prefix/2"), + Value: value2, + }, }, - }, - }, nil) + }, nil) result, err := c.GetServices(context.Background(), "/prefix") assert.NoError(t, err) @@ -641,91 +606,91 @@ func TestGetServices_Multiple(t *testing.T) { assert.Equal(t, priority, result[1].Priority) } -func TestGetServices_FilterOutOtherServicesWithDifferentManager(t *testing.T) { +func TestGetServices_FilterOutOtherServicesOwnerIDSetButNothingChanged(t *testing.T) { mockKV := new(MockEtcdKV) c := etcdClient{ client: &etcdcv3.Client{ KV: mockKV, }, - managedBy: "managed-by", - ignoreEmptyManagedBy: false, + ownerID: "owner", + strictlyOwned: false, } - svc := Service{Host: "example.com", Port: 80, Priority: 1, Weight: 10, Text: "hello", ManagedBy: "managed-by"} + svc := Service{Host: "example.com", Port: 80, Priority: 1, Weight: 10, Text: "hello", OwnedBy: "owner"} value, err := json.Marshal(svc) require.NoError(t, err) - svc2 := Service{Host: "example.com", Port: 80, Priority: 0, Weight: 10, Text: "hello", ManagedBy: ""} + svc2 := Service{Host: "example.com", Port: 80, Priority: 0, Weight: 10, Text: "hello", OwnedBy: ""} value2, err := json.Marshal(svc2) require.NoError(t, err) - svc3 := Service{Host: "example.com", Port: 80, Priority: 0, Weight: 10, Text: "hello", ManagedBy: "managed-by-someone-else"} + svc3 := Service{Host: "example.com", Port: 80, Priority: 0, Weight: 10, Text: "hello", OwnedBy: "managed-by-someone-else"} value3, err := json.Marshal(svc3) require.NoError(t, err) - mockKV.On("Get", mock.Anything, "/prefix").Return(&etcdcv3.GetResponse{ - Kvs: []*mvccpb.KeyValue{ - { - Key: []byte("/prefix/1"), - Value: value, - }, - { - Key: []byte("/prefix/2"), - Value: value2, - }, - { - Key: []byte("/prefix/3"), - Value: value3, + mockKV.On("Get", mock.Anything, "/prefix", mock.AnythingOfType("clientv3.OpOption")). + Return(&etcdcv3.GetResponse{ + Kvs: []*mvccpb.KeyValue{ + { + Key: []byte("/prefix/1"), + Value: value, + }, + { + Key: []byte("/prefix/2"), + Value: value2, + }, + { + Key: []byte("/prefix/3"), + Value: value3, + }, }, - }, - }, nil) + }, nil) result, err := c.GetServices(context.Background(), "/prefix") assert.NoError(t, err) - assert.Len(t, result, 2) - assert.Equal(t, "managed-by", result[0].ManagedBy) - assert.Equal(t, "", result[1].ManagedBy) + assert.Len(t, result, 3) } -func TestGetServices_FilterOutOtherServicesWithDifferentManagerAndIgnoreEmpty(t *testing.T) { +func TestGetServices_FilterOutOtherServicesWithStrictlyOwned(t *testing.T) { mockKV := new(MockEtcdKV) c := etcdClient{ client: &etcdcv3.Client{ KV: mockKV, }, - managedBy: "managed-by", - ignoreEmptyManagedBy: true, + ownerID: "owned-by", + strictlyOwned: true, } - svc := Service{Host: "example.com", Port: 80, Priority: 1, Weight: 10, Text: "hello", ManagedBy: "managed-by"} + svc := Service{Host: "example.com", Port: 80, Priority: 1, Weight: 10, Text: "hello", OwnedBy: "owned-by"} value, err := json.Marshal(svc) require.NoError(t, err) - svc2 := Service{Host: "example.com", Port: 80, Priority: 0, Weight: 10, Text: "hello", ManagedBy: ""} + svc2 := Service{Host: "example.com", Port: 80, Priority: 0, Weight: 10, Text: "hello", OwnedBy: ""} value2, err := json.Marshal(svc2) require.NoError(t, err) - svc3 := Service{Host: "example.com", Port: 80, Priority: 0, Weight: 10, Text: "hello", ManagedBy: "managed-by-someone-else"} + svc3 := Service{Host: "example.com", Port: 80, Priority: 0, Weight: 10, Text: "hello", OwnedBy: "owned-by-someone-else"} value3, err := json.Marshal(svc3) require.NoError(t, err) - mockKV.On("Get", mock.Anything, "/prefix").Return(&etcdcv3.GetResponse{ - Kvs: []*mvccpb.KeyValue{ - { - Key: []byte("/prefix/1"), - Value: value, - }, - { - Key: []byte("/prefix/2"), - Value: value2, - }, - { - Key: []byte("/prefix/3"), - Value: value3, + mockKV.On("Get", mock.Anything, "/prefix", mock.AnythingOfType("clientv3.OpOption")). + Return(&etcdcv3.GetResponse{ + Kvs: []*mvccpb.KeyValue{ + { + Key: []byte("/prefix/1"), + Value: value, + }, + { + Key: []byte("/prefix/2"), + Value: value2, + }, + { + Key: []byte("/prefix/3"), + Value: value3, + }, }, - }, - }, nil) + }, nil) result, err := c.GetServices(context.Background(), "/prefix") assert.NoError(t, err) assert.Len(t, result, 1) - assert.Equal(t, "managed-by", result[0].ManagedBy) + assert.Equal(t, "owned-by", result[0].OwnedBy) } func TestGetServices_UnmarshalError(t *testing.T) { @@ -736,18 +701,19 @@ func TestGetServices_UnmarshalError(t *testing.T) { }, } - mockKV.On("Get", mock.Anything, "/prefix").Return(&etcdcv3.GetResponse{ - Kvs: []*mvccpb.KeyValue{ - { - Key: []byte("/prefix/1"), - Value: []byte("invalid-json"), - }, - { - Key: []byte("/prefix/1"), - Value: []byte("invalid-json"), + mockKV.On("Get", mock.Anything, "/prefix", mock.AnythingOfType("clientv3.OpOption")). + Return(&etcdcv3.GetResponse{ + Kvs: []*mvccpb.KeyValue{ + { + Key: []byte("/prefix/1"), + Value: []byte("invalid-json"), + }, + { + Key: []byte("/prefix/1"), + Value: []byte("invalid-json"), + }, }, - }, - }, nil) + }, nil) _, err := c.GetServices(context.Background(), "/prefix") assert.Error(t, err) @@ -762,7 +728,8 @@ func TestGetServices_GetError(t *testing.T) { }, } - mockKV.On("Get", mock.Anything, "/prefix").Return(&etcdcv3.GetResponse{}, errors.New("etcd failure")) + mockKV.On("Get", mock.Anything, "/prefix", mock.AnythingOfType("clientv3.OpOption")). + Return(&etcdcv3.GetResponse{}, errors.New("etcd failure")) _, err := c.GetServices(context.Background(), "/prefix") assert.Error(t, err) @@ -771,124 +738,177 @@ func TestGetServices_GetError(t *testing.T) { func TestDeleteService(t *testing.T) { tests := []struct { - name string - managedBy string - key string - service *Service - exists bool - mockErr error - wantErr bool - preventDeleteCall bool + name string + key string + mockErr error + wantErr bool }{ { - name: "successful deletion", - key: "/skydns/local/test", - exists: true, - service: &Service{ + name: "successful deletion", + key: "/skydns/local/test", + }, + { + name: "etcd error", + key: "/skydns/local/test", + mockErr: errors.New("etcd failure"), + wantErr: true, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + mockKV := new(MockEtcdKV) + mockKV.On("Delete", mock.Anything, mock.Anything, mock.AnythingOfType("clientv3.OpOption")). + Return(&etcdcv3.DeleteResponse{}, tt.mockErr) + + c := etcdClient{ + client: &etcdcv3.Client{ + KV: mockKV, + }, + } + + err := c.DeleteService(context.Background(), tt.key) + + if tt.wantErr { + require.Error(t, err) + assert.Equal(t, tt.mockErr, err) + } else { + require.NoError(t, err) + } + mockKV.AssertExpectations(t) + }) + } +} + +func TestDeleteServiceWithStrictlyOwned(t *testing.T) { + tests := []struct { + name string + ownerID string + key string + existingServices []Service + deletedKeys []string + }{ + { + name: "successful deletion with owned by (same) with strictly owned", + key: "/skydns/local/test", + ownerID: "owned-by", + existingServices: []Service{{ Host: "example.com", Port: 80, Priority: 1, Weight: 10, Text: "hello", Key: "/skydns/local/test", - }, + OwnedBy: "owned-by", + }}, + deletedKeys: []string{"/skydns/local/test"}, }, { - name: "successful deletion with managed by (no one)", - key: "/skydns/local/test", - managedBy: "managed-by", - exists: true, - service: &Service{ + name: "prevent deletion with owned by (no one) with strictly owned", + key: "/skydns/local/test", + ownerID: "owned-by", + existingServices: []Service{{ Host: "example.com", Port: 80, Priority: 1, Weight: 10, Text: "hello", Key: "/skydns/local/test", - }, - }, - { - name: "successful deletion with managed by (same)", - key: "/skydns/local/test", - managedBy: "managed-by", - exists: true, - service: &Service{ - Host: "example.com", - Port: 80, - Priority: 1, - Weight: 10, - Text: "hello", - Key: "/skydns/local/test", - ManagedBy: "managed-by", - }, + }}, + deletedKeys: []string{}, }, { - name: "prevent deletion with managed by (other)", - key: "/skydns/local/test", - managedBy: "managed-by", - exists: true, - wantErr: true, - preventDeleteCall: true, - mockErr: errors.New("key \"/skydns/local/test\" is not owned by this service"), - service: &Service{ - Host: "example.com", - Port: 80, - Priority: 1, - Weight: 10, - Text: "hello", - Key: "/skydns/local/test", - ManagedBy: "managed-by-other", - }, + name: "prevent deletion with owned by (other) with strictly owned", + key: "/skydns/local/test", + ownerID: "owned-by", + existingServices: []Service{{ + Host: "example.com", + Port: 80, + Priority: 1, + Weight: 10, + Text: "hello", + Key: "/skydns/local/test", + OwnedBy: "owned-by-other", + }}, + deletedKeys: []string{}, }, { - name: "etcd error", + name: "successful partial deletion with owned by (same) with strictly owned", key: "/skydns/local/test", - mockErr: errors.New("etcd failure"), - wantErr: true, + ownerID: "owned-by", + existingServices: []Service{ + { + Host: "example.com", + Port: 80, + Priority: 1, + Weight: 10, + Text: "hello", + Key: "/skydns/local/test/1", + OwnedBy: "owned-by", + }, + { + Host: "example.com", + Port: 80, + Priority: 1, + Weight: 10, + Text: "hello", + Key: "/skydns/local/test/2", + }, + { + Host: "example.com", + Port: 80, + Priority: 1, + Weight: 10, + Text: "hello", + Key: "/skydns/local/test/3", + OwnedBy: "owned-by-other", + }, + { + Host: "example.com", + Port: 80, + Priority: 1, + Weight: 10, + Text: "hello", + Key: "/skydns/local/test/4", + OwnedBy: "owned-by", + }, + }, + deletedKeys: []string{"/skydns/local/test/1", "/skydns/local/test/4"}, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { mockKV := new(MockEtcdKV) - if !tt.preventDeleteCall { - mockKV.On("Delete", mock.Anything, mock.Anything, mock.AnythingOfType("clientv3.OpOption")). - Return(&etcdcv3.DeleteResponse{}, tt.mockErr) + for _, key := range tt.deletedKeys { + mockKV.On("Delete", mock.Anything, key). + Return(&etcdcv3.DeleteResponse{}, nil) } - actualValue, err := json.Marshal(&tt.service) - require.NoError(t, err) - if tt.managedBy != "" { - if tt.exists { - mockKV.On("Get", mock.Anything, tt.service.Key).Return(&etcdcv3.GetResponse{ - Kvs: []*mvccpb.KeyValue{ - { - Key: []byte(tt.service.Key), - Value: actualValue, - }, - }, - }, nil) - } else { - mockKV.On("Get", mock.Anything, tt.service.Key).Return(&etcdcv3.GetResponse{ - Kvs: []*mvccpb.KeyValue{}, - }, nil) - } + kvs := []*mvccpb.KeyValue{} + for _, service := range tt.existingServices { + actualValue, err := json.Marshal(&service) + require.NoError(t, err) + kvs = append(kvs, &mvccpb.KeyValue{ + Key: []byte(service.Key), + Value: actualValue, + }) } + mockKV.On("Get", mock.Anything, tt.key, mock.AnythingOfType("clientv3.OpOption")).Return(&etcdcv3.GetResponse{ + Kvs: kvs, + }, nil) + c := etcdClient{ client: &etcdcv3.Client{ KV: mockKV, }, - managedBy: tt.managedBy, + ownerID: tt.ownerID, + strictlyOwned: true, } - err = c.DeleteService(context.Background(), tt.key) + err := c.DeleteService(context.Background(), tt.key) - if tt.wantErr { - require.Error(t, err) - assert.Equal(t, tt.mockErr, err) - } else { - require.NoError(t, err) - } + require.NoError(t, err) mockKV.AssertExpectations(t) }) } @@ -897,10 +917,12 @@ func TestDeleteService(t *testing.T) { func TestSaveService(t *testing.T) { type testCase struct { name string - managedBy string + ownerID string + strictlyOwned bool service *Service expectedService *Service exists bool + ignoreGetCall bool mockPutErr error wantErr bool } @@ -925,9 +947,30 @@ func TestSaveService(t *testing.T) { }, }, { - name: "success with managed by (take over ownership)", - managedBy: "managed-by", - exists: true, + name: "success with 'owned-by' without strictly owned", + ownerID: "owned-by", + exists: true, + service: &Service{ + Host: "example.com", + Port: 80, + Priority: 1, + Weight: 10, + Text: "hello", + Key: "/prefix/1", + }, + expectedService: &Service{ + Host: "example.com", + Port: 80, + Priority: 1, + Weight: 10, + Text: "hello", + Key: "/prefix/1", + }, + }, + { + name: "success with 'owned-by' (creation) without strictly owned", + ownerID: "owned-by", + exists: false, service: &Service{ Host: "example.com", Port: 80, @@ -937,20 +980,18 @@ func TestSaveService(t *testing.T) { Key: "/prefix/1", }, expectedService: &Service{ - Host: "example.com", - Port: 80, - Priority: 1, - Weight: 10, - Text: "hello", - Key: "/prefix/1", - ManagedBy: "managed-by", - OwnedBy: "managed-by", + Host: "example.com", + Port: 80, + Priority: 1, + Weight: 10, + Text: "hello", + Key: "/prefix/1", }, }, { - name: "success with managed by (creation)", - managedBy: "managed-by", - exists: false, + name: "success with 'owned-by' (update) without strictly owned (owner not changed)", + ownerID: "owned-by", + exists: true, service: &Service{ Host: "example.com", Port: 80, @@ -958,54 +999,117 @@ func TestSaveService(t *testing.T) { Weight: 10, Text: "hello", Key: "/prefix/1", + OwnedBy: "owned-by", }, expectedService: &Service{ - Host: "example.com", - Port: 80, - Priority: 1, - Weight: 10, - Text: "hello", - Key: "/prefix/1", - ManagedBy: "managed-by", - OwnedBy: "managed-by", + Host: "example.com", + Port: 80, + Priority: 1, + Weight: 10, + Text: "hello", + Key: "/prefix/1", + OwnedBy: "owned-by", }, }, { - name: "success with managed by (update)", - managedBy: "managed-by", - exists: false, + name: "success with different 'owned-by' without strictly owned", + ownerID: "owned-by", + exists: true, service: &Service{ - Host: "example.com", - Port: 80, - Priority: 1, - Weight: 10, - Text: "hello", - Key: "/prefix/1", - ManagedBy: "managed-by", + Host: "example.com", + Port: 80, + Priority: 1, + Weight: 10, + Text: "hello", + Key: "/prefix/1", + OwnedBy: "other-owned-by", }, expectedService: &Service{ - Host: "example.com", - Port: 80, - Priority: 1, - Weight: 10, - Text: "hello", - Key: "/prefix/1", - ManagedBy: "managed-by", - OwnedBy: "managed-by", + Host: "example.com", + Port: 80, + Priority: 1, + Weight: 10, + Text: "hello", + Key: "/prefix/1", + OwnedBy: "other-owned-by", }, }, { - name: "fail saving due to managed by someone else", - managedBy: "managed-by", - exists: true, + name: "failed with 'owned-by' is empty with strictly owned", + ownerID: "owned-by", + strictlyOwned: true, + exists: true, service: &Service{ - Host: "example.com", - Port: 80, - Priority: 1, - Weight: 10, - Text: "hello", - Key: "/prefix/1", - ManagedBy: "other-managed-by", + Host: "example.com", + Port: 80, + Priority: 1, + Weight: 10, + Text: "hello", + Key: "/prefix/1", + }, + wantErr: true, + }, + { + name: "success with 'owned-by' (creation) with strictly owned", + ownerID: "owned-by", + strictlyOwned: true, + exists: false, + service: &Service{ + Host: "example.com", + Port: 80, + Priority: 1, + Weight: 10, + Text: "hello", + Key: "/prefix/1", + }, + expectedService: &Service{ + Host: "example.com", + Port: 80, + Priority: 1, + Weight: 10, + Text: "hello", + Key: "/prefix/1", + OwnedBy: "owned-by", + }, + }, + { + name: "success with 'owned-by' (update) with strictly owned (owner not changed)", + ownerID: "owned-by", + strictlyOwned: true, + exists: true, + ignoreGetCall: true, + service: &Service{ + Host: "example.com", + Port: 80, + Priority: 1, + Weight: 10, + Text: "hello", + Key: "/prefix/1", + OwnedBy: "owned-by", + }, + expectedService: &Service{ + Host: "example.com", + Port: 80, + Priority: 1, + Weight: 10, + Text: "hello", + Key: "/prefix/1", + OwnedBy: "owned-by", + }, + }, + { + name: "failed with different 'owned-by' with strictly owned", + ownerID: "owned-by", + strictlyOwned: true, + exists: true, + service: &Service{ + Host: "example.com", + Port: 80, + Priority: 1, + Weight: 10, + Text: "hello", + Key: "/prefix/1", + OwnedBy: "other-owned-by", }, wantErr: true, }, @@ -1035,7 +1139,7 @@ func TestSaveService(t *testing.T) { } actualValue, err := json.Marshal(&tt.service) require.NoError(t, err) - if tt.managedBy != "" { + if tt.strictlyOwned && !tt.ignoreGetCall { if tt.exists { mockKV.On("Get", mock.Anything, tt.service.Key).Return(&etcdcv3.GetResponse{ Kvs: []*mvccpb.KeyValue{ @@ -1056,7 +1160,8 @@ func TestSaveService(t *testing.T) { client: &etcdcv3.Client{ KV: mockKV, }, - managedBy: tt.managedBy, + ownerID: tt.ownerID, + strictlyOwned: tt.strictlyOwned, } err = c.SaveService(context.Background(), tt.service) @@ -1218,3 +1323,52 @@ func TestCoreDNSProvider_updateTXTRecords_ClearsExtraText(t *testing.T) { assert.Equal(t, "txt-value", services[0].Text) assert.Empty(t, services[1].Text) } + +func TestApplyChangesAWithGroupServiceTranslation(t *testing.T) { + client := fakeETCDClient{ + map[string]Service{}, + } + coredns := coreDNSProvider{ + client: client, + CoreDNSConfig: CoreDNSConfig{ + coreDNSPrefix: defaultCoreDNSPrefix, + }, + } + + changes1 := &plan.Changes{ + Create: []*endpoint.Endpoint{ + endpoint.NewEndpoint("domain1.local", endpoint.RecordTypeA, "5.5.5.5").WithProviderSpecific(providerSpecificGroup, "test1"), + endpoint.NewEndpoint("domain2.local", endpoint.RecordTypeA, "5.5.5.6").WithProviderSpecific(providerSpecificGroup, "test1"), + endpoint.NewEndpoint("domain3.local", endpoint.RecordTypeA, "5.5.5.7").WithProviderSpecific(providerSpecificGroup, "test2"), + }, + } + coredns.ApplyChanges(context.Background(), changes1) + + expectedServices1 := map[string][]*Service{ + "/skydns/local/domain1": {{Host: "5.5.5.5", Group: "test1"}}, + "/skydns/local/domain2": {{Host: "5.5.5.6", Group: "test1"}}, + "/skydns/local/domain3": {{Host: "5.5.5.7", Group: "test2"}}, + } + validateServices(client.services, expectedServices1, t, 1) +} + +func TestRecordsAWithGroupServiceTranslation(t *testing.T) { + client := fakeETCDClient{ + map[string]Service{ + "/skydns/local/domain1": {Host: "5.5.5.5", Group: "test1"}, + }, + } + coredns := coreDNSProvider{ + client: client, + CoreDNSConfig: CoreDNSConfig{ + coreDNSPrefix: defaultCoreDNSPrefix, + }, + } + endpoints, err := coredns.Records(context.Background()) + require.NoError(t, err) + if prop, ok := endpoints[0].GetProviderSpecificProperty(providerSpecificGroup); !ok { + t.Error("go no Group name") + } else if prop != "test1" { + t.Errorf("got unexpected Group name: %s != %s", prop, "test1") + } +} diff --git a/main.go b/main.go index 66e00f9..313fbdf 100644 --- a/main.go +++ b/main.go @@ -40,8 +40,8 @@ type Config struct { webhookProviderReadTimeout time.Duration webhookProviderWriteTimeout time.Duration webhookProviderPort string - managedBy string - ignoreEmptyManagedBy bool + ownerID string + strictlyOwned bool } // allLogLevelsAsStrings returns all logrus levels as a list of strings @@ -74,9 +74,9 @@ func (cfg *Config) ParseFlags(args []string) error { app.Flag("prefix", "Specify the prefix name"). Default("/skydns/").StringVar(&cfg.coreDNSPrefix) app.Flag("managed-by", "Only allow checking of services created by the same manager (default: \"\")"). - Default("").StringVar(&cfg.managedBy) - app.Flag("ignore-empty-managed-by", "If the 'managed-by' field is set, this prevents the takeover of services without a 'managed-by' value (default: disabled)"). - BoolVar(&cfg.ignoreEmptyManagedBy) + Default("").StringVar(&cfg.ownerID) + app.Flag("strictly-owned", "When using the CoreDNS provider, store and filter strictly by txt-owner-id using an extra field inside of the etcd service (default: false)"). + BoolVar(&cfg.strictlyOwned) _, err := app.Parse(args) if err != nil { @@ -112,7 +112,7 @@ func main() { log.SetLevel(ll) // instantiate the dns provider - dnsProvider, err := NewCoreDNSProvider(cfg.CoreDNSConfig, cfg.managedBy, cfg.ignoreEmptyManagedBy, cfg.dryRun) + dnsProvider, err := NewCoreDNSProvider(cfg.CoreDNSConfig, cfg.ownerID, cfg.strictlyOwned, cfg.dryRun) if err != nil { log.Fatalf("listen failed error: %v", err) } From ea6e7bbe6c6319fee65194485bf0639ff76b0608 Mon Sep 17 00:00:00 2001 From: Jan Jansen Date: Mon, 12 Jan 2026 09:28:31 +0100 Subject: [PATCH 3/3] Merge https://github.com/kubernetes-sigs/external-dns/pull/6032 into webhook Signed-off-by: Jan Jansen --- coredns.go | 54 +++++++++------- coredns_test.go | 165 ++++++++++++++++++++++++++++++------------------ main.go | 5 ++ 3 files changed, 141 insertions(+), 83 deletions(-) diff --git a/coredns.go b/coredns.go index 5af328b..4f0ed7f 100644 --- a/coredns.go +++ b/coredns.go @@ -30,6 +30,7 @@ import ( log "github.com/sirupsen/logrus" "go.etcd.io/etcd/api/v3/mvccpb" etcdcv3 "go.etcd.io/etcd/client/v3" + "sigs.k8s.io/external-dns/pkg/tlsutils" "sigs.k8s.io/external-dns/endpoint" @@ -37,16 +38,13 @@ import ( "sigs.k8s.io/external-dns/provider" ) -func init() { - rand.Seed(time.Now().UnixNano()) -} - const ( priority = 10 // default priority when nothing is set etcdTimeout = 5 * time.Second - randomPrefixLabel = "prefix" - providerSpecificGroup = "webhook/coredns-group" + randomPrefixLabel = "prefix" + providerSpecificGroup = "webhook/coredns-group" + providerSpecificGroup2 = "coredns/group" ) type CoreDNSConfig struct { @@ -66,6 +64,7 @@ type coreDNSProvider struct { client coreDNSClient dryRun bool CoreDNSConfig + strictlyOwned bool } // Service represents CoreDNS etcd record @@ -93,13 +92,13 @@ type Service struct { // Etcd key where we found this service and ignored from json un-/marshaling Key string `json:"-"` - // OwnedBy is used to prevent service to be added by different external-dns (only used by external-dns) - OwnedBy string `json:"ownedby,omitempty"` + // Owner is used to prevent service to be added by different external-dns (only used by external-dns) + Owner string `json:"owner,omitempty"` } type etcdClient struct { client *etcdcv3.Client - ownerID string + owner string strictlyOwned bool } @@ -123,7 +122,7 @@ func (c etcdClient) GetServices(ctx context.Context, prefix string) ([]*Service, if err != nil { return nil, err } - if c.strictlyOwned && svc.OwnedBy != c.ownerID { + if c.strictlyOwned && svc.Owner != c.owner { continue } b := Service{ @@ -156,7 +155,7 @@ func (c etcdClient) SaveService(ctx context.Context, service *Service) error { defer cancel() // check only for empty OwnedBy - if c.strictlyOwned && service.OwnedBy != c.ownerID { + if c.strictlyOwned && service.Owner != c.owner { r, err := c.client.Get(ctx, service.Key) if err != nil { return fmt.Errorf("etcd get %q: %w", service.Key, err) @@ -167,11 +166,11 @@ func (c etcdClient) SaveService(ctx context.Context, service *Service) error { if err != nil { return fmt.Errorf("failed to unmarshal value for key %q: %w", service.Key, err) } - if svc.OwnedBy != c.ownerID { + if svc.Owner != c.owner { return fmt.Errorf("key %q is not owned by this provider", service.Key) } } - service.OwnedBy = c.ownerID + service.Owner = c.owner } value, err := json.Marshal(&service) @@ -200,7 +199,7 @@ func (c etcdClient) DeleteService(ctx context.Context, key string) error { if err != nil { return err } - if svc.OwnedBy != c.ownerID { + if svc.Owner != c.owner { continue } @@ -234,9 +233,10 @@ func getETCDConfig() (*etcdcv3.Config, error) { firstURL := strings.ToLower(etcdURLs[0]) etcdUsername := os.Getenv("ETCD_USERNAME") etcdPassword := os.Getenv("ETCD_PASSWORD") - if strings.HasPrefix(firstURL, "http://") { + switch { + case strings.HasPrefix(firstURL, "http://"): return &etcdcv3.Config{Endpoints: etcdURLs, Username: etcdUsername, Password: etcdPassword}, nil - } else if strings.HasPrefix(firstURL, "https://") { + case strings.HasPrefix(firstURL, "https://"): tlsConfig, err := tlsutils.CreateTLSConfig("ETCD") if err != nil { return nil, err @@ -248,13 +248,13 @@ func getETCDConfig() (*etcdcv3.Config, error) { Username: etcdUsername, Password: etcdPassword, }, nil - } else { + default: return nil, errors.New("etcd URLs must start with either http:// or https://") } } // the newETCDClient is an etcd client constructor -func newETCDClient(ownerID string, strictlyOwned bool) (coreDNSClient, error) { +func newETCDClient(owner string, strictlyOwned bool) (coreDNSClient, error) { cfg, err := getETCDConfig() if err != nil { return nil, err @@ -263,12 +263,12 @@ func newETCDClient(ownerID string, strictlyOwned bool) (coreDNSClient, error) { if err != nil { return nil, err } - return etcdClient{c, ownerID, strictlyOwned}, nil + return etcdClient{c, owner, strictlyOwned}, nil } // NewCoreDNSProvider is a CoreDNS provider constructor -func NewCoreDNSProvider(config CoreDNSConfig, ownerID string, strictlyOwned, dryRun bool) (provider.Provider, error) { - client, err := newETCDClient(ownerID, strictlyOwned) +func NewCoreDNSProvider(config CoreDNSConfig, owner string, strictlyOwned, dryRun bool) (provider.Provider, error) { + client, err := newETCDClient(owner, strictlyOwned) if err != nil { return nil, err } @@ -277,6 +277,7 @@ func NewCoreDNSProvider(config CoreDNSConfig, ownerID string, strictlyOwned, dry client: client, dryRun: dryRun, CoreDNSConfig: config, + strictlyOwned: strictlyOwned, }, nil } @@ -333,9 +334,13 @@ func (p coreDNSProvider) Records(ctx context.Context) ([]*endpoint.Endpoint, err ) if service.Group != "" { ep.WithProviderSpecific(providerSpecificGroup, service.Group) + ep.WithProviderSpecific(providerSpecificGroup2, service.Group) } log.Debugf("Creating new ep (%s) with new service host (%s)", ep, service.Host) } + if p.strictlyOwned { + ep.Labels[endpoint.OwnerLabelKey] = service.Owner + } ep.Labels["originalText"] = service.Text ep.Labels[randomPrefixLabel] = prefix ep.Labels[service.Host] = prefix @@ -347,6 +352,9 @@ func (p coreDNSProvider) Records(ctx context.Context) ([]*endpoint.Endpoint, err endpoint.RecordTypeTXT, service.Text, ) + if p.strictlyOwned { + ep.Labels[endpoint.OwnerLabelKey] = service.Owner + } ep.Labels[randomPrefixLabel] = prefix result = append(result, ep) } @@ -420,11 +428,13 @@ func (p coreDNSProvider) createServicesForEndpoint(ctx context.Context, dnsName prefix = fmt.Sprintf("%08x", rand.Int31()) log.Infof("Generating new prefix: (%s)", prefix) } - group := "" if prop, ok := ep.GetProviderSpecificProperty(providerSpecificGroup); ok { group = prop } + if prop, ok := ep.GetProviderSpecificProperty(providerSpecificGroup2); ok { + group = prop + } service := Service{ Host: target, Text: ep.Labels["originalText"], diff --git a/coredns_test.go b/coredns_test.go index 2677308..ef09a9d 100644 --- a/coredns_test.go +++ b/coredns_test.go @@ -24,13 +24,14 @@ import ( "strings" "testing" - "github.com/GDATASoftwareAG/external-dns-coredns-webhook/internal/testutils" log "github.com/sirupsen/logrus" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/mock" "go.etcd.io/etcd/api/v3/mvccpb" etcdcv3 "go.etcd.io/etcd/client/v3" + "github.com/GDATASoftwareAG/external-dns-coredns-webhook/internal/testutils" + "sigs.k8s.io/external-dns/endpoint" "sigs.k8s.io/external-dns/plan" @@ -96,7 +97,7 @@ func (m *MockEtcdKV) Delete(ctx context.Context, key string, opts ...etcdcv3.OpO } func TestETCDConfig(t *testing.T) { - var tests = []struct { + tests := []struct { name string input map[string]string want *etcdcv3.Config @@ -606,23 +607,23 @@ func TestGetServices_Multiple(t *testing.T) { assert.Equal(t, priority, result[1].Priority) } -func TestGetServices_FilterOutOtherServicesOwnerIDSetButNothingChanged(t *testing.T) { +func TestGetServices_FilterOutOtherServicesOwnerSetButNothingChanged(t *testing.T) { mockKV := new(MockEtcdKV) c := etcdClient{ client: &etcdcv3.Client{ KV: mockKV, }, - ownerID: "owner", + owner: "owner", strictlyOwned: false, } - svc := Service{Host: "example.com", Port: 80, Priority: 1, Weight: 10, Text: "hello", OwnedBy: "owner"} + svc := Service{Host: "example.com", Port: 80, Priority: 1, Weight: 10, Text: "hello", Owner: "owner"} value, err := json.Marshal(svc) require.NoError(t, err) - svc2 := Service{Host: "example.com", Port: 80, Priority: 0, Weight: 10, Text: "hello", OwnedBy: ""} + svc2 := Service{Host: "example.com", Port: 80, Priority: 0, Weight: 10, Text: "hello", Owner: ""} value2, err := json.Marshal(svc2) require.NoError(t, err) - svc3 := Service{Host: "example.com", Port: 80, Priority: 0, Weight: 10, Text: "hello", OwnedBy: "managed-by-someone-else"} + svc3 := Service{Host: "example.com", Port: 80, Priority: 0, Weight: 10, Text: "hello", Owner: "different-owner"} value3, err := json.Marshal(svc3) require.NoError(t, err) @@ -655,17 +656,17 @@ func TestGetServices_FilterOutOtherServicesWithStrictlyOwned(t *testing.T) { client: &etcdcv3.Client{ KV: mockKV, }, - ownerID: "owned-by", + owner: "owner", strictlyOwned: true, } - svc := Service{Host: "example.com", Port: 80, Priority: 1, Weight: 10, Text: "hello", OwnedBy: "owned-by"} + svc := Service{Host: "example.com", Port: 80, Priority: 1, Weight: 10, Text: "hello", Owner: "owner"} value, err := json.Marshal(svc) require.NoError(t, err) - svc2 := Service{Host: "example.com", Port: 80, Priority: 0, Weight: 10, Text: "hello", OwnedBy: ""} + svc2 := Service{Host: "example.com", Port: 80, Priority: 0, Weight: 10, Text: "hello", Owner: ""} value2, err := json.Marshal(svc2) require.NoError(t, err) - svc3 := Service{Host: "example.com", Port: 80, Priority: 0, Weight: 10, Text: "hello", OwnedBy: "owned-by-someone-else"} + svc3 := Service{Host: "example.com", Port: 80, Priority: 0, Weight: 10, Text: "hello", Owner: "different-owner"} value3, err := json.Marshal(svc3) require.NoError(t, err) @@ -690,7 +691,7 @@ func TestGetServices_FilterOutOtherServicesWithStrictlyOwned(t *testing.T) { result, err := c.GetServices(context.Background(), "/prefix") assert.NoError(t, err) assert.Len(t, result, 1) - assert.Equal(t, "owned-by", result[0].OwnedBy) + assert.Equal(t, "owner", result[0].Owner) } func TestGetServices_UnmarshalError(t *testing.T) { @@ -783,15 +784,15 @@ func TestDeleteService(t *testing.T) { func TestDeleteServiceWithStrictlyOwned(t *testing.T) { tests := []struct { name string - ownerID string + owner string key string existingServices []Service deletedKeys []string }{ { - name: "successful deletion with owned by (same) with strictly owned", - key: "/skydns/local/test", - ownerID: "owned-by", + name: "successful deletion with the same owner with strictly owned", + key: "/skydns/local/test", + owner: "owner", existingServices: []Service{{ Host: "example.com", Port: 80, @@ -799,14 +800,14 @@ func TestDeleteServiceWithStrictlyOwned(t *testing.T) { Weight: 10, Text: "hello", Key: "/skydns/local/test", - OwnedBy: "owned-by", + Owner: "owner", }}, deletedKeys: []string{"/skydns/local/test"}, }, { - name: "prevent deletion with owned by (no one) with strictly owned", - key: "/skydns/local/test", - ownerID: "owned-by", + name: "prevent deletion of a service without an owner with strictly owned", + key: "/skydns/local/test", + owner: "owner", existingServices: []Service{{ Host: "example.com", Port: 80, @@ -818,9 +819,9 @@ func TestDeleteServiceWithStrictlyOwned(t *testing.T) { deletedKeys: []string{}, }, { - name: "prevent deletion with owned by (other) with strictly owned", - key: "/skydns/local/test", - ownerID: "owned-by", + name: "prevent deletion with different owner with strictly owned", + key: "/skydns/local/test", + owner: "owner", existingServices: []Service{{ Host: "example.com", Port: 80, @@ -828,14 +829,14 @@ func TestDeleteServiceWithStrictlyOwned(t *testing.T) { Weight: 10, Text: "hello", Key: "/skydns/local/test", - OwnedBy: "owned-by-other", + Owner: "other-owner", }}, deletedKeys: []string{}, }, { - name: "successful partial deletion with owned by (same) with strictly owned", - key: "/skydns/local/test", - ownerID: "owned-by", + name: "successful partial deletion for same owners with strictly owned", + key: "/skydns/local/test", + owner: "owner", existingServices: []Service{ { Host: "example.com", @@ -844,7 +845,7 @@ func TestDeleteServiceWithStrictlyOwned(t *testing.T) { Weight: 10, Text: "hello", Key: "/skydns/local/test/1", - OwnedBy: "owned-by", + Owner: "owner", }, { Host: "example.com", @@ -861,7 +862,7 @@ func TestDeleteServiceWithStrictlyOwned(t *testing.T) { Weight: 10, Text: "hello", Key: "/skydns/local/test/3", - OwnedBy: "owned-by-other", + Owner: "different-owner", }, { Host: "example.com", @@ -870,7 +871,7 @@ func TestDeleteServiceWithStrictlyOwned(t *testing.T) { Weight: 10, Text: "hello", Key: "/skydns/local/test/4", - OwnedBy: "owned-by", + Owner: "owner", }, }, deletedKeys: []string{"/skydns/local/test/1", "/skydns/local/test/4"}, @@ -902,7 +903,7 @@ func TestDeleteServiceWithStrictlyOwned(t *testing.T) { client: &etcdcv3.Client{ KV: mockKV, }, - ownerID: tt.ownerID, + owner: tt.owner, strictlyOwned: true, } @@ -917,7 +918,7 @@ func TestDeleteServiceWithStrictlyOwned(t *testing.T) { func TestSaveService(t *testing.T) { type testCase struct { name string - ownerID string + owner string strictlyOwned bool service *Service expectedService *Service @@ -947,9 +948,9 @@ func TestSaveService(t *testing.T) { }, }, { - name: "success with 'owned-by' without strictly owned", - ownerID: "owned-by", - exists: true, + name: "success with 'owner' without strictly owned", + owner: "owner", + exists: true, service: &Service{ Host: "example.com", Port: 80, @@ -968,9 +969,9 @@ func TestSaveService(t *testing.T) { }, }, { - name: "success with 'owned-by' (creation) without strictly owned", - ownerID: "owned-by", - exists: false, + name: "success with 'owner' (creation) without strictly owned", + owner: "owner", + exists: false, service: &Service{ Host: "example.com", Port: 80, @@ -989,9 +990,9 @@ func TestSaveService(t *testing.T) { }, }, { - name: "success with 'owned-by' (update) without strictly owned (owner not changed)", - ownerID: "owned-by", - exists: true, + name: "success with 'owner' (update) without strictly owned (owner not changed)", + owner: "owner", + exists: true, service: &Service{ Host: "example.com", Port: 80, @@ -999,7 +1000,7 @@ func TestSaveService(t *testing.T) { Weight: 10, Text: "hello", Key: "/prefix/1", - OwnedBy: "owned-by", + Owner: "owner", }, expectedService: &Service{ Host: "example.com", @@ -1008,13 +1009,13 @@ func TestSaveService(t *testing.T) { Weight: 10, Text: "hello", Key: "/prefix/1", - OwnedBy: "owned-by", + Owner: "owner", }, }, { - name: "success with different 'owned-by' without strictly owned", - ownerID: "owned-by", - exists: true, + name: "success with different 'owner' without strictly owned", + owner: "owner", + exists: true, service: &Service{ Host: "example.com", Port: 80, @@ -1022,7 +1023,7 @@ func TestSaveService(t *testing.T) { Weight: 10, Text: "hello", Key: "/prefix/1", - OwnedBy: "other-owned-by", + Owner: "other-owner", }, expectedService: &Service{ Host: "example.com", @@ -1031,12 +1032,12 @@ func TestSaveService(t *testing.T) { Weight: 10, Text: "hello", Key: "/prefix/1", - OwnedBy: "other-owned-by", + Owner: "other-owner", }, }, { - name: "failed with 'owned-by' is empty with strictly owned", - ownerID: "owned-by", + name: "failed with 'owner' is empty with strictly owned", + owner: "owner", strictlyOwned: true, exists: true, service: &Service{ @@ -1050,8 +1051,8 @@ func TestSaveService(t *testing.T) { wantErr: true, }, { - name: "success with 'owned-by' (creation) with strictly owned", - ownerID: "owned-by", + name: "success with 'owner' (creation) with strictly owned", + owner: "owner", strictlyOwned: true, exists: false, service: &Service{ @@ -1069,12 +1070,12 @@ func TestSaveService(t *testing.T) { Weight: 10, Text: "hello", Key: "/prefix/1", - OwnedBy: "owned-by", + Owner: "owner", }, }, { - name: "success with 'owned-by' (update) with strictly owned (owner not changed)", - ownerID: "owned-by", + name: "success with 'owner' (update) with strictly owned (owner not changed)", + owner: "owner", strictlyOwned: true, exists: true, ignoreGetCall: true, @@ -1085,7 +1086,7 @@ func TestSaveService(t *testing.T) { Weight: 10, Text: "hello", Key: "/prefix/1", - OwnedBy: "owned-by", + Owner: "owner", }, expectedService: &Service{ Host: "example.com", @@ -1094,12 +1095,12 @@ func TestSaveService(t *testing.T) { Weight: 10, Text: "hello", Key: "/prefix/1", - OwnedBy: "owned-by", + Owner: "owner", }, }, { - name: "failed with different 'owned-by' with strictly owned", - ownerID: "owned-by", + name: "failed with different 'owner' with strictly owned", + owner: "owner", strictlyOwned: true, exists: true, service: &Service{ @@ -1109,7 +1110,7 @@ func TestSaveService(t *testing.T) { Weight: 10, Text: "hello", Key: "/prefix/1", - OwnedBy: "other-owned-by", + Owner: "other-owner", }, wantErr: true, }, @@ -1160,7 +1161,7 @@ func TestSaveService(t *testing.T) { client: &etcdcv3.Client{ KV: mockKV, }, - ownerID: tt.ownerID, + owner: tt.owner, strictlyOwned: tt.strictlyOwned, } @@ -1372,3 +1373,45 @@ func TestRecordsAWithGroupServiceTranslation(t *testing.T) { t.Errorf("got unexpected Group name: %s != %s", prop, "test1") } } + +func TestRecordsIncludeLabelOwnerWithStrictlyOwned(t *testing.T) { + client := fakeETCDClient{ + map[string]Service{ + "/skydns/local/domain1": {Host: "5.5.5.5", Group: "test1", Owner: "owner"}, + "/skydns/com/example": {Text: "bla", Owner: "owner"}, + }, + } + coredns := coreDNSProvider{ + client: client, + CoreDNSConfig: CoreDNSConfig{ + coreDNSPrefix: defaultCoreDNSPrefix, + }, + strictlyOwned: true, + } + endpoints, err := coredns.Records(context.Background()) + require.NoError(t, err) + for _, ep := range endpoints { + assert.Equal(t, "owner", ep.Labels[endpoint.OwnerLabelKey]) + } +} + +func TestRecordsIncludeOwnerASLabelWithoutStrictlyOwned(t *testing.T) { + client := fakeETCDClient{ + map[string]Service{ + "/skydns/local/domain1": {Host: "5.5.5.5", Group: "test1", Owner: "owner"}, + "/skydns/com/example": {Text: "bla", Owner: "owner"}, + }, + } + coredns := coreDNSProvider{ + client: client, + CoreDNSConfig: CoreDNSConfig{ + coreDNSPrefix: defaultCoreDNSPrefix, + }, + strictlyOwned: false, + } + endpoints, err := coredns.Records(context.Background()) + require.NoError(t, err) + for _, ep := range endpoints { + assert.Empty(t, ep.Labels[endpoint.OwnerLabelKey]) + } +} diff --git a/main.go b/main.go index 313fbdf..2d3e0b1 100644 --- a/main.go +++ b/main.go @@ -17,6 +17,7 @@ limitations under the License. package main import ( + "math/rand" "os" "os/signal" "syscall" @@ -29,6 +30,10 @@ import ( "sigs.k8s.io/external-dns/endpoint" ) +func init() { + rand.Seed(time.Now().UnixNano()) +} + // Version is the current version of the app, generated at build time var Version = "unknown"