From 17beb1a2d348bea74300ddbcdecca997d8f20601 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sun, 27 Jul 2025 23:46:29 -0700 Subject: [PATCH 01/10] chore(deps): bump renovatebot/github-action (#5691) Bumps the dev-dependencies group with 1 update: [renovatebot/github-action](https://github.com/renovatebot/github-action). Updates `renovatebot/github-action` from 43.0.4 to 43.0.5 - [Release notes](https://github.com/renovatebot/github-action/releases) - [Changelog](https://github.com/renovatebot/github-action/blob/main/CHANGELOG.md) - [Commits](https://github.com/renovatebot/github-action/compare/v43.0.4...v43.0.5) --- updated-dependencies: - dependency-name: renovatebot/github-action dependency-version: 43.0.5 dependency-type: direct:production update-type: version-update:semver-patch dependency-group: dev-dependencies ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/dependency-update.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/dependency-update.yaml b/.github/workflows/dependency-update.yaml index 53c128fab..e24f540f8 100644 --- a/.github/workflows/dependency-update.yaml +++ b/.github/workflows/dependency-update.yaml @@ -17,7 +17,7 @@ jobs: uses: actions/checkout@v4.2.2 # https://github.com/renovatebot/github-action - name: self-hosted renovate - uses: renovatebot/github-action@v43.0.4 + uses: renovatebot/github-action@v43.0.5 with: # https://docs.github.com/en/actions/security-for-github-actions/security-guides/automatic-token-authentication token: ${{ secrets.GITHUB_TOKEN }} From c736dd3385df0423aeec6b5a3357705173049b6b Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 28 Jul 2025 00:14:29 -0700 Subject: [PATCH 02/10] chore(deps): bump the dev-dependencies group across 1 directory with 5 updates (#5690) Bumps the dev-dependencies group with 5 updates in the / directory: | Package | From | To | | --- | --- | --- | | [github.com/cenkalti/backoff/v5](https://github.com/cenkalti/backoff) | `5.0.2` | `5.0.3` | | [github.com/civo/civogo](https://github.com/civo/civogo) | `0.6.1` | `0.6.2` | | [github.com/digitalocean/godo](https://github.com/digitalocean/godo) | `1.159.0` | `1.160.0` | | [go.etcd.io/etcd/api/v3](https://github.com/etcd-io/etcd) | `3.6.3` | `3.6.4` | | [go.etcd.io/etcd/client/v3](https://github.com/etcd-io/etcd) | `3.6.3` | `3.6.4` | Updates `github.com/cenkalti/backoff/v5` from 5.0.2 to 5.0.3 - [Changelog](https://github.com/cenkalti/backoff/blob/v5/CHANGELOG.md) - [Commits](https://github.com/cenkalti/backoff/compare/v5.0.2...v5.0.3) Updates `github.com/civo/civogo` from 0.6.1 to 0.6.2 - [Release notes](https://github.com/civo/civogo/releases) - [Changelog](https://github.com/civo/civogo/blob/master/changelog.yml) - [Commits](https://github.com/civo/civogo/compare/v0.6.1...v0.6.2) Updates `github.com/digitalocean/godo` from 1.159.0 to 1.160.0 - [Release notes](https://github.com/digitalocean/godo/releases) - [Changelog](https://github.com/digitalocean/godo/blob/main/CHANGELOG.md) - [Commits](https://github.com/digitalocean/godo/compare/v1.159.0...v1.160.0) Updates `go.etcd.io/etcd/api/v3` from 3.6.3 to 3.6.4 - [Release notes](https://github.com/etcd-io/etcd/releases) - [Commits](https://github.com/etcd-io/etcd/compare/v3.6.3...v3.6.4) Updates `go.etcd.io/etcd/client/v3` from 3.6.3 to 3.6.4 - [Release notes](https://github.com/etcd-io/etcd/releases) - [Commits](https://github.com/etcd-io/etcd/compare/v3.6.3...v3.6.4) --- updated-dependencies: - dependency-name: github.com/cenkalti/backoff/v5 dependency-version: 5.0.3 dependency-type: direct:production update-type: version-update:semver-patch dependency-group: dev-dependencies - dependency-name: github.com/civo/civogo dependency-version: 0.6.2 dependency-type: direct:production update-type: version-update:semver-patch dependency-group: dev-dependencies - dependency-name: github.com/digitalocean/godo dependency-version: 1.160.0 dependency-type: direct:production update-type: version-update:semver-minor dependency-group: dev-dependencies - dependency-name: go.etcd.io/etcd/api/v3 dependency-version: 3.6.4 dependency-type: direct:production update-type: version-update:semver-patch dependency-group: dev-dependencies - dependency-name: go.etcd.io/etcd/client/v3 dependency-version: 3.6.4 dependency-type: direct:production update-type: version-update:semver-patch dependency-group: dev-dependencies ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- go.mod | 12 ++++++------ go.sum | 24 ++++++++++++------------ 2 files changed, 18 insertions(+), 18 deletions(-) diff --git a/go.mod b/go.mod index c0d4e76d9..50708ad3f 100644 --- a/go.mod +++ b/go.mod @@ -22,14 +22,14 @@ require ( github.com/aws/aws-sdk-go-v2/service/servicediscovery v1.35.8 github.com/aws/aws-sdk-go-v2/service/sts v1.34.1 github.com/bodgit/tsig v1.2.2 - github.com/cenkalti/backoff/v5 v5.0.2 - github.com/civo/civogo v0.6.1 + github.com/cenkalti/backoff/v5 v5.0.3 + github.com/civo/civogo v0.6.2 github.com/cloudflare/cloudflare-go v0.115.0 github.com/cloudflare/cloudflare-go/v4 v4.6.0 github.com/cloudfoundry-community/go-cfclient v0.0.0-20190201205600-f136f9222381 github.com/datawire/ambassador v1.12.4 github.com/denverdino/aliyungo v0.0.0-20230411124812-ab98a9173ace - github.com/digitalocean/godo v1.159.0 + github.com/digitalocean/godo v1.160.0 github.com/dnsimple/dnsimple-go v1.7.0 github.com/exoscale/egoscale v0.102.3 github.com/ffledgling/pdns-go v0.0.0-20180219074714-524e7daccd99 @@ -55,8 +55,8 @@ require ( github.com/sirupsen/logrus v1.9.3 github.com/stretchr/testify v1.10.0 github.com/transip/gotransip/v6 v6.26.0 - go.etcd.io/etcd/api/v3 v3.6.3 - go.etcd.io/etcd/client/v3 v3.6.3 + go.etcd.io/etcd/api/v3 v3.6.4 + go.etcd.io/etcd/client/v3 v3.6.4 go.uber.org/ratelimit v0.3.1 golang.org/x/net v0.42.0 golang.org/x/oauth2 v0.30.0 @@ -170,7 +170,7 @@ require ( github.com/x448/float16 v0.8.4 // indirect github.com/xhit/go-str2duration/v2 v2.1.0 // indirect github.com/youmark/pkcs8 v0.0.0-20240726163527-a2c0da244d78 // indirect - go.etcd.io/etcd/client/pkg/v3 v3.6.3 // indirect + go.etcd.io/etcd/client/pkg/v3 v3.6.4 // indirect go.opentelemetry.io/auto/sdk v1.1.0 // indirect go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.61.0 // indirect go.opentelemetry.io/otel v1.36.0 // indirect diff --git a/go.sum b/go.sum index 206ac2dab..5c773b789 100644 --- a/go.sum +++ b/go.sum @@ -172,16 +172,16 @@ github.com/bugsnag/osext v0.0.0-20130617224835-0dd3f918b21b/go.mod h1:obH5gd0Bsq github.com/bugsnag/panicwrap v0.0.0-20151223152923-e2c28503fcd0/go.mod h1:D/8v3kj0zr8ZAKg1AQ6crr+5VwKN5eIywRkfhyM/+dE= github.com/casbin/casbin/v2 v2.1.2/go.mod h1:YcPU1XXisHhLzuxH9coDNf2FbKpjGlbCg3n9yuLkIJQ= github.com/cenkalti/backoff v2.2.1+incompatible/go.mod h1:90ReRw6GdpyfrHakVjL/QHaoyV4aDUVVkXQJJJ3NXXM= -github.com/cenkalti/backoff/v5 v5.0.2 h1:rIfFVxEf1QsI7E1ZHfp/B4DF/6QBAUhmgkxc0H7Zss8= -github.com/cenkalti/backoff/v5 v5.0.2/go.mod h1:rkhZdG3JZukswDf7f0cwqPNk4K0sa+F97BxZthm/crw= +github.com/cenkalti/backoff/v5 v5.0.3 h1:ZN+IMa753KfX5hd8vVaMixjnqRZ3y8CuJKRKj1xcsSM= +github.com/cenkalti/backoff/v5 v5.0.3/go.mod h1:rkhZdG3JZukswDf7f0cwqPNk4K0sa+F97BxZthm/crw= github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc= github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= 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/chai2010/gettext-go v0.0.0-20160711120539-c6fed771bfd5/go.mod h1:/iP1qXHoty45bqomnu2LM+VVyAEdWN+vtSHGlQgyxbw= -github.com/civo/civogo v0.6.1 h1:PFOh7rBU0vmj7LTDIv3z7l9uXG4SZyyzScCl3wyTFSc= -github.com/civo/civogo v0.6.1/go.mod h1:LaEbkszc+9nXSh4YNG0sYXFGYqdQFmXXzQg0gESs2hc= +github.com/civo/civogo v0.6.2 h1:tQegf+coNxIKhLjOo5bwAV04CPSk6ealSod55XHb7cw= +github.com/civo/civogo v0.6.2/go.mod h1:LaEbkszc+9nXSh4YNG0sYXFGYqdQFmXXzQg0gESs2hc= github.com/clbanning/x2j v0.0.0-20191024224557-825249438eec/go.mod h1:jMjuTZXRI4dUb/I5gc9Hdhagfvm9+RyrPryS/auMzxE= github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= github.com/cloudflare/cloudflare-go v0.115.0 h1:84/dxeeXweCc0PN5Cto44iTA8AkG1fyT11yPO5ZB7sM= @@ -252,8 +252,8 @@ github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZm github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f h1:lO4WD4F/rVNCu3HqELle0jiPLLBs70cWOduZpkS1E78= github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f/go.mod h1:cuUVRXasLTGF7a8hSLbxyZXjz+1KgoB3wDUb6vlszIc= github.com/dgryski/go-sip13 v0.0.0-20181026042036-e10d5fee7954/go.mod h1:vAd38F8PWV+bWy6jNmig1y/TA+kYO4g3RSRF0IAv0no= -github.com/digitalocean/godo v1.159.0 h1:GQLfVueriDHYpwLzDcbydHs6nBvQBO8/r8r9imPC434= -github.com/digitalocean/godo v1.159.0/go.mod h1:tYeiWY5ZXVpU48YaFv0M5irUFHXGorZpDNm7zzdWMzM= +github.com/digitalocean/godo v1.160.0 h1:3Wa6mOzv1m5DZQDANAk8u6v4DIUm5x2i4tZ7ke28lhs= +github.com/digitalocean/godo v1.160.0/go.mod h1:tYeiWY5ZXVpU48YaFv0M5irUFHXGorZpDNm7zzdWMzM= github.com/dnaeon/go-vcr v1.0.1/go.mod h1:aBB1+wY4s93YsC3HHjMBMrwTj2R9FHDzUr9KyGc8n1E= github.com/dnsimple/dnsimple-go v1.7.0 h1:JKu9xJtZ3SqOC+BuYgAWeab7+EEx0sz422vu8j611ZY= github.com/dnsimple/dnsimple-go v1.7.0/go.mod h1:EKpuihlWizqYafSnQHGCd/gyvy3HkEQJ7ODB4KdV8T8= @@ -1050,12 +1050,12 @@ github.com/ziutek/mymysql v1.5.4/go.mod h1:LMSpPZ6DbqWFxNCHW77HeMg9I646SAhApZ/wK go.etcd.io/bbolt v1.3.2/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU= go.etcd.io/bbolt v1.3.3/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU= go.etcd.io/etcd v0.0.0-20191023171146-3cf2f69b5738/go.mod h1:dnLIgRNXwCJa5e+c6mIZCrds/GIG4ncV9HhK5PX7jPg= -go.etcd.io/etcd/api/v3 v3.6.3 h1:4Lftl1e6VzBsj5HPhLu8GGybjeT5qg9mug70RxTHmQQ= -go.etcd.io/etcd/api/v3 v3.6.3/go.mod h1:eFhhvfR8Px1P6SEuLT600v+vrhdDTdcfMzmnxVXXSbk= -go.etcd.io/etcd/client/pkg/v3 v3.6.3 h1:1yE8p3PFZ+CWaVyTZk+6ngSyMK8TaG2589W3KGm22ao= -go.etcd.io/etcd/client/pkg/v3 v3.6.3/go.mod h1:sbdzr2cl3HzVmxNw//PH7aLGVtY4QySjQFuaCgcRFAI= -go.etcd.io/etcd/client/v3 v3.6.3 h1:yKpdrcVK6jTfr/VuVuH5VesaLmUi8PLI9eXHTy5kpTM= -go.etcd.io/etcd/client/v3 v3.6.3/go.mod h1:zDuGaiUvpECwqClZCUkHi6q2XSf2ejPbUB755QLXdL8= +go.etcd.io/etcd/api/v3 v3.6.4 h1:7F6N7toCKcV72QmoUKa23yYLiiljMrT4xCeBL9BmXdo= +go.etcd.io/etcd/api/v3 v3.6.4/go.mod h1:eFhhvfR8Px1P6SEuLT600v+vrhdDTdcfMzmnxVXXSbk= +go.etcd.io/etcd/client/pkg/v3 v3.6.4 h1:9HBYrjppeOfFjBjaMTRxT3R7xT0GLK8EJMVC4xg6ok0= +go.etcd.io/etcd/client/pkg/v3 v3.6.4/go.mod h1:sbdzr2cl3HzVmxNw//PH7aLGVtY4QySjQFuaCgcRFAI= +go.etcd.io/etcd/client/v3 v3.6.4 h1:YOMrCfMhRzY8NgtzUsHl8hC2EBSnuqbR3dh84Uryl7A= +go.etcd.io/etcd/client/v3 v3.6.4/go.mod h1:jaNNHCyg2FdALyKWnd7hxZXZxZANb0+KGY+YQaEMISo= go.mongodb.org/mongo-driver v1.0.3/go.mod h1:u7ryQJ+DOzQmeO7zB6MHyr8jkEQvC8vH7qLUO4lqsUM= go.mongodb.org/mongo-driver v1.1.1/go.mod h1:u7ryQJ+DOzQmeO7zB6MHyr8jkEQvC8vH7qLUO4lqsUM= go.mongodb.org/mongo-driver v1.1.2/go.mod h1:u7ryQJ+DOzQmeO7zB6MHyr8jkEQvC8vH7qLUO4lqsUM= From 22a0e08c65021306d30e46099a770143868ce3cb Mon Sep 17 00:00:00 2001 From: Ivan Ka <5395690+ivankatliarchuk@users.noreply.github.com> Date: Mon, 28 Jul 2025 09:04:29 +0100 Subject: [PATCH 03/10] chore(github): enchance issue-template for bug-report (#5692) * chore(template): added description to github template Signed-off-by: ivan katliarchuk * Update .github/ISSUE_TEMPLATE/---bug-report.md Co-authored-by: Michel Loiseleur <97035654+mloiseleur@users.noreply.github.com> --------- Signed-off-by: ivan katliarchuk Co-authored-by: Michel Loiseleur <97035654+mloiseleur@users.noreply.github.com> --- .github/ISSUE_TEMPLATE/---bug-report.md | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/.github/ISSUE_TEMPLATE/---bug-report.md b/.github/ISSUE_TEMPLATE/---bug-report.md index d9195b396..4ff5e91ae 100644 --- a/.github/ISSUE_TEMPLATE/---bug-report.md +++ b/.github/ISSUE_TEMPLATE/---bug-report.md @@ -7,8 +7,10 @@ assignees: '' --- - **What happened**: @@ -17,6 +19,10 @@ assignees: '' **How to reproduce it (as minimally and precisely as possible)**: + + **Anything else we need to know?**: **Environment**: From fe7940ce0acf16a5a3d40571e33959eb939604aa Mon Sep 17 00:00:00 2001 From: "Ahmed R. I." Date: Mon, 28 Jul 2025 10:48:29 +0200 Subject: [PATCH 04/10] docs(providers): add Myra Security DNS to the list (#5671) * docs: add Myra Security DNS to the list of providers * docs(providers): add minor docs improvements to docs/tutorials/myra.md --- README.md | 3 + docs/providers.md | 1 + docs/tutorials/myra.md | 215 +++++++++++++++++++++++++++++++++++++++++ 3 files changed, 219 insertions(+) create mode 100644 docs/tutorials/myra.md diff --git a/README.md b/README.md index 45ad8e06f..74c6c5cba 100644 --- a/README.md +++ b/README.md @@ -66,6 +66,7 @@ ExternalDNS allows you to keep selected zones (via `--domain-filter`) synchroniz - [Plural](https://www.plural.sh/) - [Pi-hole](https://pi-hole.net/) - [Alibaba Cloud DNS](https://www.alibabacloud.com/help/en/dns) +- [Myra Security DNS](https://www.myrasecurity.com/en/saasp/application-security/secure-dns/) ExternalDNS is, by default, aware of the records it is managing, therefore it can safely manage non-empty hosted zones. We strongly encourage you to set `--txt-owner-id` to a unique value that doesn't change for the lifetime of your cluster. @@ -104,6 +105,7 @@ from the usage of any externally developed webhook. | IONOS | https://github.com/ionos-cloud/external-dns-ionos-webhook | | Infoblox | https://github.com/AbsaOSS/external-dns-infoblox-webhook | | Mikrotik | https://github.com/mirceanton/external-dns-provider-mikrotik | +| Myra Security | https://github.com/Myra-Security-GmbH/external-dns-myrasec-webhook | | Netcup | https://github.com/mrueg/external-dns-netcup-webhook | | Netic | https://github.com/neticdk/external-dns-tidydns-webhook | | OpenStack Designate | https://github.com/inovex/external-dns-designate-webhook | @@ -203,6 +205,7 @@ The following tutorials are provided: - [IONOS Cloud](docs/tutorials/ionoscloud.md) - [Istio Gateway Source](docs/sources/istio.md) - [Linode](docs/tutorials/linode.md) +- [Myra Security](docs/tutorials/myra.md) - [NS1](docs/tutorials/ns1.md) - [NS Record Creation with CRD Source](docs/sources/ns-record.md) - [MX Record Creation with CRD Source](docs/sources/mx-record.md) diff --git a/docs/providers.md b/docs/providers.md index 228622939..0aafa2fdd 100644 --- a/docs/providers.md +++ b/docs/providers.md @@ -20,6 +20,7 @@ Provider supported configurations | Google GCP | n/a | yes | 300 | | InMemory | n/a | n/a | n/a | | Linode | n/a | n/a | n/a | +| Myra Security | n/a | yes | 300 | | NS1 | n/a | yes | 10 | | OCI | yes | yes | 300 | | OVH | n/a | yes | 0 | diff --git a/docs/tutorials/myra.md b/docs/tutorials/myra.md new file mode 100644 index 000000000..c25e16821 --- /dev/null +++ b/docs/tutorials/myra.md @@ -0,0 +1,215 @@ +# Myra ExternalDNS Webhook + +This guide provides quick instructions for setting up and testing the [Myra ExternalDNS Webhook](https://github.com/Myra-Security-GmbH/external-dns-myrasec-webhook) in a Kubernetes environment. + +## Prerequisites + +- Kubernetes cluster (v1.19+) +- `kubectl` configured to access your cluster +- Docker for building the container image +- MyraSec API credentials (API key and secret) +- Domain registered with MyraSec + +## Quick Installation + +### 1. Build and Push the Docker Image + +```bash +# From the project root +docker build -t myra-webhook:latest . + +# Tag the image for your container registry +docker tag myra-webhook:latest YOUR_REGISTRY/myra-webhook:latest + +# Push to your container registry +docker push YOUR_REGISTRY/myra-webhook:latest +``` + +> **Important**: The image must be pushed to a container registry accessible by your Kubernetes cluster. Update the image reference in the deployment YAML file to match your registry path. + +### 2. Configure API Credentials + +Create a secret with your MyraSec API credentials: + +```bash +kubectl create secret generic myra-webhook-secrets \ + --from-literal=myrasec-api-key=YOUR_API_KEY \ + --from-literal=myrasec-api-secret=YOUR_API_SECRET \ + --from-literal=domain-filter=YOUR_DOMAIN.com +``` + +Alternatively, apply the provided secret template after editing: + +```bash +# Edit the secret file first +vi deploy/myra-webhook-secrets.yaml + +# Then apply +kubectl apply -f deploy/myra-webhook-secrets.yaml +``` + +### 3. Deploy the Webhook and ExternalDNS + +```bash +# Apply the combined deployment +kubectl apply -f deploy/combined-deployment.yaml +``` + +This deploys: + +- ConfigMap with webhook configuration +- ServiceAccount, ClusterRole, and ClusterRoleBinding for RBAC +- Deployment with two containers: + - myra-webhook: The webhook provider implementation + - external-dns: The ExternalDNS controller using the webhook provider + +### 4. Verify Deployment + +```bash +# Check if pods are running +kubectl get pods -l app=myra-externaldns + +# Check logs for the webhook container +kubectl logs -l app=myra-externaldns -c myra-webhook + +# Check logs for the external-dns container +kubectl logs -l app=myra-externaldns -c external-dns +``` + +## Manual Testing with NGINX Demo + +### 1. Deploy the NGINX Demo Application + +```bash +# Edit the domain in the nginx-demo.yaml file to match your domain +vi deploy/nginx-demo.yaml + +# Most important part is to set the correct domain in the external-dns.alpha.kubernetes.io/hostname annotation +# Example: +# annotations: +# external-dns.alpha.kubernetes.io/enabled: "true" +# external-dns.alpha.kubernetes.io/hostname: "nginx-demo.dummydomainforkubes.de" +# external-dns.alpha.kubernetes.io/target: "9.2.3.4" + +# Apply the demo resources +kubectl apply -f deploy/nginx-demo.yaml +``` + +This creates: + +- NGINX Deployment +- Service for the deployment +- Ingress resource with ExternalDNS annotations + +### 2. Verify DNS Record Creation + +After deploying the demo application, ExternalDNS should automatically create DNS records in MyraSec: + +```bash +# Check external-dns logs to see record creation +kubectl logs -l app=myra-externaldns -c external-dns | grep "nginx-demo" + +# Verify the webhook logs +kubectl logs -l app=myra-externaldns -c myra-webhook | grep "Created DNS record" +``` + +You can also verify through the MyraSec dashboard that the records were created. + +### 3. Testing Record Deletion + +To test record deletion: + +```bash +# Delete the nginx-demo resources or remove annotation from ingress +kubectl delete -f deploy/nginx-demo.yaml + +# Delete the ingress resource or remove annotation from ingress +# If resource is still active, external dns might still see the record and manage it +kubectl delete ingress nginx-demo -n default + +# Check external-dns logs to see record deletion +kubectl logs -l app=myra-externaldns -c external-dns | grep "nginx-demo" | grep "delete" + +# Verify the webhook logs +kubectl logs -l app=myra-externaldns -c myra-webhook | grep "Deleted DNS record" +``` + +## Configuration Options + +The webhook can be configured through the ConfigMap: + +| Parameter | Description | Default | +|-----------|-------------|---------| +| `dry-run` | Run in dry-run mode without making actual changes | `"false"` | +| `environment` | Environment name (affects private IP handling) | `"prod"` | +| `log-level` | Logging level (debug, info, warn, error) | `"debug"` | +| `ttl` | Default TTL for DNS records | `"300"` | +| `webhook-listen-address` | Address and port for the webhook server | `":8080"` | + +## Troubleshooting + +### Common Issues + +1. **Webhook not receiving requests** + - Ensure the `webhook-provider-url` in the external-dns args is correct + - Check network connectivity between containers + +2. **DNS records not being created** + - Verify MyraSec API credentials are correct + - Check if the domain filter is properly configured + - Look for error messages in the webhook and external-dns logs + +3. **Permissions issues** + - Ensure the ServiceAccount has the correct RBAC permissions + +### Getting Help + +For more detailed logs: + +```bash +# Set log level to debug in the ConfigMap +kubectl edit configmap myra-externaldns-config +# Change log-level to "debug" + +# Restart the pods +kubectl rollout restart deployment myra-externaldns +``` + +## Environment Configuration + +The webhook supports different environment configurations through the `environment` setting in the ConfigMap: + +```yaml +apiVersion: v1 +kind: ConfigMap +metadata: + name: myra-externaldns-config +data: + environment: "prod" # Can be "prod", "staging", "dev", etc. +``` + +The environment setting affects how the webhook handles certain operations: + +| Environment | Behavior | +|-------------|----------| +| `prod`, `production`, `staging` | Strict mode: Skips private IP records, enforces stricter validation | +| `dev`, `development`, `test`, etc. | Development mode: Allows private IP records, more permissive validation | + +To modify the environment: + +```bash +# Edit the ConfigMap directly +kubectl edit configmap myra-externaldns-config + +# Or apply an updated YAML file +kubectl apply -f updated-config.yaml +``` + +## Advanced Configuration + +For production deployments, consider: + +1. Using a proper image registry instead of `latest` tag +2. Setting resource limits appropriate for your environment +3. Configuring horizontal pod autoscaling +4. Using Helm for deployment management From b741f3103c2e446dff13e46a82bfe7692d78c80d Mon Sep 17 00:00:00 2001 From: Ivan Ka <5395690+ivankatliarchuk@users.noreply.github.com> Date: Tue, 29 Jul 2025 11:10:02 +0100 Subject: [PATCH 05/10] chore(provider/aws): reduce if-nesting, dryRun (#5688) Signed-off-by: ivan katliarchuk --- provider/aws/aws.go | 81 +++++++++++++++++++++++---------------------- 1 file changed, 42 insertions(+), 39 deletions(-) diff --git a/provider/aws/aws.go b/provider/aws/aws.go index 1ddb35cce..873d860d8 100644 --- a/provider/aws/aws.go +++ b/provider/aws/aws.go @@ -734,56 +734,59 @@ func (p *AWSProvider) submitChanges(ctx context.Context, changes Route53Changes, log.Infof("Desired change: %s %s %s", c.Action, *c.ResourceRecordSet.Name, c.ResourceRecordSet.Type) } - if !p.dryRun { - params := &route53.ChangeResourceRecordSetsInput{ - HostedZoneId: aws.String(z), - ChangeBatch: &route53types.ChangeBatch{ - Changes: b.Route53Changes(), - }, - } + if p.dryRun { + log.Debug("Dry run mode, skipping change submission") + continue + } - successfulChanges := 0 + params := &route53.ChangeResourceRecordSetsInput{ + HostedZoneId: aws.String(z), + ChangeBatch: &route53types.ChangeBatch{ + Changes: b.Route53Changes(), + }, + } - client := p.clients[zones[z].profile] - if _, err := client.ChangeResourceRecordSets(ctx, params); err != nil { - log.Errorf("Failure in zone %s when submitting change batch: %v", *zones[z].zone.Name, err) + successfulChanges := 0 - changesByOwnership := groupChangesByNameAndOwnershipRelation(b) + client := p.clients[zones[z].profile] + if _, err := client.ChangeResourceRecordSets(ctx, params); err != nil { + log.Errorf("Failure in zone %s when submitting change batch: %v", *zones[z].zone.Name, err) - if len(changesByOwnership) > 1 { - log.Debug("Trying to submit change sets one-by-one instead") - for _, changes := range changesByOwnership { - if log.Logger.IsLevelEnabled(debugLevel) { - for _, c := range changes { - log.Debugf("Desired change: %s %s %s", c.Action, *c.ResourceRecordSet.Name, c.ResourceRecordSet.Type) - } - } - params.ChangeBatch = &route53types.ChangeBatch{ - Changes: changes.Route53Changes(), - } - if _, err := client.ChangeResourceRecordSets(ctx, params); err != nil { - failedUpdate = true - log.Errorf("Failed submitting change (error: %v), it will be retried in a separate change batch in the next iteration", err) - p.failedChangesQueue[z] = append(p.failedChangesQueue[z], changes...) - } else { - successfulChanges = successfulChanges + len(changes) + changesByOwnership := groupChangesByNameAndOwnershipRelation(b) + + if len(changesByOwnership) > 1 { + log.Debug("Trying to submit change sets one-by-one instead") + for _, changes := range changesByOwnership { + if log.Logger.IsLevelEnabled(debugLevel) { + for _, c := range changes { + log.Debugf("Desired change: %s %s %s", c.Action, *c.ResourceRecordSet.Name, c.ResourceRecordSet.Type) } } - } else { - failedUpdate = true + params.ChangeBatch = &route53types.ChangeBatch{ + Changes: changes.Route53Changes(), + } + if _, err := client.ChangeResourceRecordSets(ctx, params); err != nil { + failedUpdate = true + log.Errorf("Failed submitting change (error: %v), it will be retried in a separate change batch in the next iteration", err) + p.failedChangesQueue[z] = append(p.failedChangesQueue[z], changes...) + } else { + successfulChanges = successfulChanges + len(changes) + } } } else { - successfulChanges = len(b) + failedUpdate = true } + } else { + successfulChanges = len(b) + } - if successfulChanges > 0 { - // z is the R53 Hosted Zone ID already as aws.StringValue - log.Infof("%d record(s) were successfully updated", successfulChanges) - } + if successfulChanges > 0 { + // z is the R53 Hosted Zone ID already as aws.StringValue + log.Infof("%d record(s) were successfully updated", successfulChanges) + } - if i != len(batchCs)-1 { - time.Sleep(p.batchChangeInterval) - } + if i != len(batchCs)-1 { + time.Sleep(p.batchChangeInterval) } } From 23e12c1a093b3e951773b1bc337d60b4ab51e64c Mon Sep 17 00:00:00 2001 From: Andrew Hay <39sumer3939@gmail.com> Date: Tue, 29 Jul 2025 06:44:01 -0400 Subject: [PATCH 06/10] refactor(cloudflare): use lib v4 for zone services (#5654) * chore: update zone implementation to v4 * chore: update cloudflare zones to v4 * docs: newline around lists * docs: styling * style: remove trailing whitespace --- docs/tutorials/cloudflare.md | 33 +- provider/cloudflare/cloudflare.go | 68 ++- provider/cloudflare/cloudflare_test.go | 570 +++++++++++++++++++++++-- 3 files changed, 611 insertions(+), 60 deletions(-) diff --git a/docs/tutorials/cloudflare.md b/docs/tutorials/cloudflare.md index 1147e0e26..4afb55e24 100644 --- a/docs/tutorials/cloudflare.md +++ b/docs/tutorials/cloudflare.md @@ -4,6 +4,37 @@ This tutorial describes how to setup ExternalDNS for usage within a Kubernetes c Make sure to use **>=0.4.2** version of ExternalDNS for this tutorial. +## CloudFlare SDK Migration Status + +ExternalDNS is currently migrating from the legacy CloudFlare Go SDK v0 to the modern v4 SDK to improve performance, reliability, and access to newer CloudFlare features. The migration status is: + +**✅ Fully migrated to v4 SDK:** + +- Zone management (listing, filtering, pagination) +- Zone details retrieval (`GetZone`) +- Zone ID lookup by name (`ZoneIDByName`) +- Zone plan detection (fully v4 implementation) +- Regional services (data localization) + +**🔄 Still using legacy v0 SDK:** + +- DNS record management (create, update, delete records) +- Custom hostnames +- Proxied records + +This mixed approach ensures continued functionality while gradually modernizing the codebase. Users should not experience any breaking changes during this transition. + +### SDK Dependencies + +ExternalDNS currently uses: + +- **cloudflare-go v0.115.0+**: Legacy SDK for DNS records, custom hostnames, and proxied record features +- **cloudflare-go/v4 v4.6.0+**: Modern SDK for all zone management and regional services operations + +Zone management has been fully migrated to the v4 SDK, providing improved performance and reliability. + +Both SDKs are automatically managed as Go module dependencies and require no special configuration from users. + ## Creating a Cloudflare DNS zone We highly recommend to read this tutorial if you haven't used Cloudflare before: @@ -353,7 +384,7 @@ The custom hostname DNS must resolve to the Cloudflare DNS record (`external-dns Requires [Cloudflare for SaaS](https://developers.cloudflare.com/cloudflare-for-platforms/cloudflare-for-saas/) product and "SSL and Certificates" API permission. -Due to a limitation within the cloudflare-go v0 API, the custom hostname page size is fixed at 50. +**Note:** Due to using the legacy cloudflare-go v0 API for custom hostname management, the custom hostname page size is fixed at 50. This limitation will be addressed in a future migration to the v4 SDK. ## Using CRD source to manage DNS records in Cloudflare diff --git a/provider/cloudflare/cloudflare.go b/provider/cloudflare/cloudflare.go index 8914032cc..0b432b3f3 100644 --- a/provider/cloudflare/cloudflare.go +++ b/provider/cloudflare/cloudflare.go @@ -32,6 +32,7 @@ import ( cloudflarev4 "github.com/cloudflare/cloudflare-go/v4" "github.com/cloudflare/cloudflare-go/v4/addressing" "github.com/cloudflare/cloudflare-go/v4/option" + "github.com/cloudflare/cloudflare-go/v4/zones" log "github.com/sirupsen/logrus" "golang.org/x/net/publicsuffix" @@ -106,8 +107,8 @@ var recordTypeCustomHostnameSupported = map[string]bool{ // cloudFlareDNS is the subset of the CloudFlare API that we actually use. Add methods as required. Signatures must match exactly. type cloudFlareDNS interface { ZoneIDByName(zoneName string) (string, error) - ListZonesContext(ctx context.Context, opts ...cloudflare.ReqOption) (cloudflare.ZonesResponse, error) - ZoneDetails(ctx context.Context, zoneID string) (cloudflare.Zone, error) + ListZones(ctx context.Context, params zones.ZoneListParams) autoPager[zones.Zone] + GetZone(ctx context.Context, zoneID string) (*zones.Zone, error) ListDNSRecords(ctx context.Context, rc *cloudflare.ResourceContainer, rp cloudflare.ListDNSRecordsParams) ([]cloudflare.DNSRecord, *cloudflare.ResultInfo, error) CreateDNSRecord(ctx context.Context, rc *cloudflare.ResourceContainer, rp cloudflare.CreateDNSRecordParams) (cloudflare.DNSRecord, error) DeleteDNSRecord(ctx context.Context, rc *cloudflare.ResourceContainer, recordID string) error @@ -127,7 +128,23 @@ type zoneService struct { } func (z zoneService) ZoneIDByName(zoneName string) (string, error) { - return z.service.ZoneIDByName(zoneName) + // Use v4 API to find zone by name + params := zones.ZoneListParams{ + Name: cloudflarev4.F(zoneName), + } + + iter := z.serviceV4.Zones.ListAutoPaging(context.Background(), params) + for zone := range autoPagerIterator(iter) { + if zone.Name == zoneName { + return zone.ID, nil + } + } + + if err := iter.Err(); err != nil { + return "", fmt.Errorf("failed to list zones from CloudFlare API: %w", err) + } + + return "", fmt.Errorf("zone %q not found in CloudFlare account - verify the zone exists and API credentials have access to it", zoneName) } func (z zoneService) CreateDNSRecord(ctx context.Context, rc *cloudflare.ResourceContainer, rp cloudflare.CreateDNSRecordParams) (cloudflare.DNSRecord, error) { @@ -147,12 +164,12 @@ func (z zoneService) DeleteDNSRecord(ctx context.Context, rc *cloudflare.Resourc return z.service.DeleteDNSRecord(ctx, rc, recordID) } -func (z zoneService) ListZonesContext(ctx context.Context, opts ...cloudflare.ReqOption) (cloudflare.ZonesResponse, error) { - return z.service.ListZonesContext(ctx, opts...) +func (z zoneService) ListZones(ctx context.Context, params zones.ZoneListParams) autoPager[zones.Zone] { + return z.serviceV4.Zones.ListAutoPaging(ctx, params) } -func (z zoneService) ZoneDetails(ctx context.Context, zoneID string) (cloudflare.Zone, error) { - return z.service.ZoneDetails(ctx, zoneID) +func (z zoneService) GetZone(ctx context.Context, zoneID string) (*zones.Zone, error) { + return z.serviceV4.Zones.Get(ctx, zones.ZoneGetParams{ZoneID: cloudflarev4.F(zoneID)}) } func (z zoneService) CustomHostnames(ctx context.Context, zoneID string, page int, filter cloudflare.CustomHostname) ([]cloudflare.CustomHostname, cloudflare.ResultInfo, error) { @@ -167,6 +184,11 @@ func (z zoneService) CreateCustomHostname(ctx context.Context, zoneID string, ch return z.service.CreateCustomHostname(ctx, zoneID, ch) } +// listZonesV4Params returns the appropriate Zone List Params for v4 API +func listZonesV4Params() zones.ZoneListParams { + return zones.ZoneListParams{} +} + type DNSRecordsConfig struct { PerPage int Comment string @@ -202,13 +224,13 @@ func (p *CloudFlareProvider) ZoneHasPaidPlan(hostname string) bool { return false } - zoneDetails, err := p.Client.ZoneDetails(context.Background(), zoneID) + zoneDetails, err := p.Client.GetZone(context.Background(), zoneID) if err != nil { log.Errorf("Failed to get zone %s details %v", zone, err) return false } - return zoneDetails.Plan.IsSubscribed + return zoneDetails.Plan.IsSubscribed //nolint:staticcheck // SA1019: Plan.IsSubscribed is deprecated but no replacement available yet } // CloudFlareProvider is an implementation of Provider for CloudFlare DNS. @@ -343,8 +365,8 @@ func NewCloudFlareProvider( } // Zones returns the list of hosted zones. -func (p *CloudFlareProvider) Zones(ctx context.Context) ([]cloudflare.Zone, error) { - var result []cloudflare.Zone +func (p *CloudFlareProvider) Zones(ctx context.Context) ([]zones.Zone, error) { + var result []zones.Zone // if there is a zoneIDfilter configured // && if the filter isn't just a blank string (used in tests) @@ -352,34 +374,38 @@ func (p *CloudFlareProvider) Zones(ctx context.Context) ([]cloudflare.Zone, erro log.Debugln("zoneIDFilter configured. only looking up zone IDs defined") for _, zoneID := range p.zoneIDFilter.ZoneIDs { log.Debugf("looking up zone %q", zoneID) - detailResponse, err := p.Client.ZoneDetails(ctx, zoneID) + detailResponse, err := p.Client.GetZone(ctx, zoneID) if err != nil { log.Errorf("zone %q lookup failed, %v", zoneID, err) - return result, err + return result, convertCloudflareError(err) } log.WithFields(log.Fields{ "zoneName": detailResponse.Name, "zoneID": detailResponse.ID, }).Debugln("adding zone for consideration") - result = append(result, detailResponse) + result = append(result, *detailResponse) } return result, nil } log.Debugln("no zoneIDFilter configured, looking at all zones") - zonesResponse, err := p.Client.ListZonesContext(ctx) - if err != nil { - return nil, convertCloudflareError(err) - } - - for _, zone := range zonesResponse.Result { + params := listZonesV4Params() + iter := p.Client.ListZones(ctx, params) + for zone := range autoPagerIterator(iter) { if !p.domainFilter.Match(zone.Name) { log.Debugf("zone %q not in domain filter", zone.Name) continue } + log.WithFields(log.Fields{ + "zoneName": zone.Name, + "zoneID": zone.ID, + }).Debugln("adding zone for consideration") result = append(result, zone) } + if iter.Err() != nil { + return nil, convertCloudflareError(iter.Err()) + } return result, nil } @@ -722,7 +748,7 @@ func (p *CloudFlareProvider) AdjustEndpoints(endpoints []*endpoint.Endpoint) ([] } // changesByZone separates a multi-zone change into a single change per zone. -func (p *CloudFlareProvider) changesByZone(zones []cloudflare.Zone, changeSet []*cloudFlareChange) map[string][]*cloudFlareChange { +func (p *CloudFlareProvider) changesByZone(zones []zones.Zone, changeSet []*cloudFlareChange) map[string][]*cloudFlareChange { changes := make(map[string][]*cloudFlareChange) zoneNameIDMapper := provider.ZoneIDName{} diff --git a/provider/cloudflare/cloudflare_test.go b/provider/cloudflare/cloudflare_test.go index 79b670000..ccc3d005c 100644 --- a/provider/cloudflare/cloudflare_test.go +++ b/provider/cloudflare/cloudflare_test.go @@ -27,9 +27,12 @@ import ( "testing" "github.com/cloudflare/cloudflare-go" + "github.com/cloudflare/cloudflare-go/v4/zones" "github.com/maxatome/go-testdeep/td" log "github.com/sirupsen/logrus" "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + "sigs.k8s.io/external-dns/endpoint" "sigs.k8s.io/external-dns/internal/testutils" "sigs.k8s.io/external-dns/plan" @@ -52,15 +55,14 @@ type MockAction struct { } type mockCloudFlareClient struct { - Zones map[string]string - Records map[string]map[string]cloudflare.DNSRecord - Actions []MockAction - listZonesError error - zoneDetailsError error - listZonesContextError error - dnsRecordsError error - customHostnames map[string][]cloudflare.CustomHostname - regionalHostnames map[string][]regionalHostname + Zones map[string]string + Records map[string]map[string]cloudflare.DNSRecord + Actions []MockAction + listZonesError error // For v4 ListZones + getZoneError error // For v4 GetZone + dnsRecordsError error + customHostnames map[string][]cloudflare.CustomHostname + regionalHostnames map[string][]regionalHostname } var ExampleDomain = []cloudflare.DNSRecord{ @@ -335,54 +337,60 @@ func (m *mockCloudFlareClient) DeleteCustomHostname(ctx context.Context, zoneID } func (m *mockCloudFlareClient) ZoneIDByName(zoneName string) (string, error) { + // Simulate iterator error (line 144) + if m.listZonesError != nil { + return "", fmt.Errorf("failed to list zones from CloudFlare API: %w", m.listZonesError) + } + for id, name := range m.Zones { if name == zoneName { return id, nil } } - return "", errors.New("Unknown zone: " + zoneName) + // Use the improved error message (line 147) + return "", fmt.Errorf("zone %q not found in CloudFlare account - verify the zone exists and API credentials have access to it", zoneName) } -func (m *mockCloudFlareClient) ListZonesContext(ctx context.Context, opts ...cloudflare.ReqOption) (cloudflare.ZonesResponse, error) { - if m.listZonesContextError != nil { - return cloudflare.ZonesResponse{}, m.listZonesContextError +// V4 Zone methods +func (m *mockCloudFlareClient) ListZones(ctx context.Context, params zones.ZoneListParams) autoPager[zones.Zone] { + if m.listZonesError != nil { + return &mockAutoPager[zones.Zone]{ + err: m.listZonesError, + } } - result := []cloudflare.Zone{} + var results []zones.Zone - for zoneId, zoneName := range m.Zones { - result = append(result, cloudflare.Zone{ - ID: zoneId, + for id, zoneName := range m.Zones { + results = append(results, zones.Zone{ + ID: id, Name: zoneName, + Plan: zones.ZonePlan{IsSubscribed: strings.HasSuffix(zoneName, "bar.com")}, //nolint:SA1019 // Plan.IsSubscribed is deprecated but no replacement available yet }) } - return cloudflare.ZonesResponse{ - Result: result, - ResultInfo: cloudflare.ResultInfo{ - Page: 1, - TotalPages: 1, - }, - }, nil + return &mockAutoPager[zones.Zone]{ + items: results, + } } -func (m *mockCloudFlareClient) ZoneDetails(ctx context.Context, zoneID string) (cloudflare.Zone, error) { - if m.zoneDetailsError != nil { - return cloudflare.Zone{}, m.zoneDetailsError +func (m *mockCloudFlareClient) GetZone(ctx context.Context, zoneID string) (*zones.Zone, error) { + if m.getZoneError != nil { + return nil, m.getZoneError } for id, zoneName := range m.Zones { if zoneID == id { - return cloudflare.Zone{ + return &zones.Zone{ ID: zoneID, Name: zoneName, - Plan: cloudflare.ZonePlan{IsSubscribed: strings.HasSuffix(zoneName, "bar.com")}, + Plan: zones.ZonePlan{IsSubscribed: strings.HasSuffix(zoneName, "bar.com")}, //nolint:SA1019 // Plan.IsSubscribed is deprecated but no replacement available yet }, nil } } - return cloudflare.Zone{}, errors.New("Unknown zoneID: " + zoneID) + return nil, errors.New("Unknown zoneID: " + zoneID) } func getCustomHostnameIdxByID(chs []cloudflare.CustomHostname, customHostnameID string) int { @@ -841,7 +849,7 @@ func TestCloudflareZones(t *testing.T) { func TestCloudflareZonesFailed(t *testing.T) { client := NewMockCloudFlareClient() - client.zoneDetailsError = errors.New("zone lookup failed") + client.getZoneError = errors.New("zone lookup failed") provider := &CloudFlareProvider{ Client: client, @@ -877,7 +885,7 @@ func TestCloudFlareZonesWithIDFilter(t *testing.T) { func TestCloudflareListZonesRateLimited(t *testing.T) { // Create a mock client that returns a rate limit error client := NewMockCloudFlareClient() - client.listZonesContextError = &cloudflare.Error{ + client.listZonesError = &cloudflare.Error{ StatusCode: 429, ErrorCodes: []int{10000}, Type: cloudflare.ErrorTypeRateLimit, @@ -896,7 +904,7 @@ func TestCloudflareListZonesRateLimited(t *testing.T) { func TestCloudflareListZonesRateLimitedStringError(t *testing.T) { // Create a mock client that returns a rate limit error client := NewMockCloudFlareClient() - client.listZonesContextError = errors.New("exceeded available rate limit retries") + client.listZonesError = errors.New("exceeded available rate limit retries") p := &CloudFlareProvider{Client: client} // Call the Zones function @@ -909,7 +917,7 @@ func TestCloudflareListZonesRateLimitedStringError(t *testing.T) { func TestCloudflareListZoneInternalErrors(t *testing.T) { // Create a mock client that returns a internal server error client := NewMockCloudFlareClient() - client.listZonesContextError = &cloudflare.Error{ + client.listZonesError = &cloudflare.Error{ StatusCode: 500, ErrorCodes: []int{20000}, Type: cloudflare.ErrorTypeService, @@ -949,7 +957,7 @@ func TestCloudflareRecords(t *testing.T) { t.Errorf("expected to fail") } client.dnsRecordsError = nil - client.listZonesContextError = &cloudflare.Error{ + client.listZonesError = &cloudflare.Error{ StatusCode: 429, ErrorCodes: []int{10000}, Type: cloudflare.ErrorTypeRateLimit, @@ -960,7 +968,7 @@ func TestCloudflareRecords(t *testing.T) { t.Error("expected a rate limit error") } - client.listZonesContextError = &cloudflare.Error{ + client.listZonesError = &cloudflare.Error{ StatusCode: 500, ErrorCodes: []int{10000}, Type: cloudflare.ErrorTypeService, @@ -971,7 +979,7 @@ func TestCloudflareRecords(t *testing.T) { t.Error("expected a internal server error") } - client.listZonesContextError = errors.New("failed to list zones") + client.listZonesError = errors.New("failed to list zones") _, err = p.Records(ctx) if err == nil { t.Errorf("expected to fail") @@ -2584,7 +2592,7 @@ func TestZoneHasPaidPlan(t *testing.T) { assert.True(t, cfprovider.ZoneHasPaidPlan("subdomain.bar.com")) assert.False(t, cfprovider.ZoneHasPaidPlan("invaliddomain")) - client.zoneDetailsError = errors.New("zone lookup failed") + client.getZoneError = errors.New("zone lookup failed") cfproviderWithZoneError := &CloudFlareProvider{ Client: client, domainFilter: endpoint.NewDomainFilter([]string{"foo.com", "bar.com"}), @@ -2592,6 +2600,7 @@ func TestZoneHasPaidPlan(t *testing.T) { } assert.False(t, cfproviderWithZoneError.ZoneHasPaidPlan("subdomain.foo.com")) } + func TestCloudflareApplyChanges_AllErrorLogPaths(t *testing.T) { hook := testutils.LogsUnderTestWithLogLevel(log.ErrorLevel, t) @@ -2760,3 +2769,488 @@ func TestCloudFlareProvider_SupportedAdditionalRecordTypes(t *testing.T) { }) } } + +func TestCloudflareZoneChanges(t *testing.T) { + client := NewMockCloudFlareClient() + cfProvider := &CloudFlareProvider{ + Client: client, + domainFilter: endpoint.NewDomainFilter([]string{"foo.com", "bar.com"}), + zoneIDFilter: provider.NewZoneIDFilter([]string{""}), + } + + // Test zone listing and filtering + zones, err := cfProvider.Zones(context.Background()) + assert.NoError(t, err) + assert.Len(t, zones, 2) + + // Verify zone names + zoneNames := make([]string, len(zones)) + for i, zone := range zones { + zoneNames[i] = zone.Name + } + assert.Contains(t, zoneNames, "foo.com") + assert.Contains(t, zoneNames, "bar.com") + + // Test zone filtering with specific zone ID + providerWithZoneFilter := &CloudFlareProvider{ + Client: client, + domainFilter: endpoint.NewDomainFilter([]string{"foo.com", "bar.com"}), + zoneIDFilter: provider.NewZoneIDFilter([]string{"001"}), + } + + filteredZones, err := providerWithZoneFilter.Zones(context.Background()) + assert.NoError(t, err) + assert.Len(t, filteredZones, 1) + assert.Equal(t, "bar.com", filteredZones[0].Name) // zone 001 is bar.com + assert.Equal(t, "001", filteredZones[0].ID) + + // Test zone changes grouping + changes := []*cloudFlareChange{ + { + Action: cloudFlareCreate, + ResourceRecord: cloudflare.DNSRecord{Name: "test1.foo.com", Type: "A", Content: "1.2.3.4"}, + }, + { + Action: cloudFlareCreate, + ResourceRecord: cloudflare.DNSRecord{Name: "test2.foo.com", Type: "A", Content: "1.2.3.5"}, + }, + { + Action: cloudFlareCreate, + ResourceRecord: cloudflare.DNSRecord{Name: "test1.bar.com", Type: "A", Content: "1.2.3.6"}, + }, + } + + changesByZone := cfProvider.changesByZone(zones, changes) + assert.Len(t, changesByZone, 2) + assert.Len(t, changesByZone["001"], 1) // bar.com zone (test1.bar.com) + assert.Len(t, changesByZone["002"], 2) // foo.com zone (test1.foo.com, test2.foo.com) + + // Test paid plan detection + assert.False(t, cfProvider.ZoneHasPaidPlan("subdomain.foo.com")) // free plan + assert.True(t, cfProvider.ZoneHasPaidPlan("subdomain.bar.com")) // paid plan +} + +func TestCloudflareZoneErrors(t *testing.T) { + client := NewMockCloudFlareClient() + + // Test list zones error + client.listZonesError = errors.New("failed to list zones") + cfProvider := &CloudFlareProvider{ + Client: client, + } + + zones, err := cfProvider.Zones(context.Background()) + assert.Error(t, err) + assert.Contains(t, err.Error(), "failed to list zones") + assert.Nil(t, zones) + + // Test get zone error + client.listZonesError = nil + client.getZoneError = errors.New("failed to get zone") + + // This should still work for listing but fail when getting individual zones + zones, err = cfProvider.Zones(context.Background()) + assert.NoError(t, err) // List works, individual gets may fail internally + assert.NotNil(t, zones) +} + +func TestCloudflareZoneFiltering(t *testing.T) { + client := NewMockCloudFlareClient() + + // Test with domain filter only + cfProvider := &CloudFlareProvider{ + Client: client, + domainFilter: endpoint.NewDomainFilter([]string{"foo.com"}), + zoneIDFilter: provider.NewZoneIDFilter([]string{""}), + } + + zones, err := cfProvider.Zones(context.Background()) + assert.NoError(t, err) + assert.Len(t, zones, 1) + assert.Equal(t, "foo.com", zones[0].Name) + + // Test with zone ID filter + providerWithIDFilter := &CloudFlareProvider{ + Client: client, + domainFilter: endpoint.NewDomainFilter([]string{}), + zoneIDFilter: provider.NewZoneIDFilter([]string{"002"}), + } + + filteredZones, err := providerWithIDFilter.Zones(context.Background()) + assert.NoError(t, err) + assert.Len(t, filteredZones, 1) + assert.Equal(t, "foo.com", filteredZones[0].Name) // zone 002 is foo.com + assert.Equal(t, "002", filteredZones[0].ID) +} + +func TestCloudflareZonePlanDetection(t *testing.T) { + client := NewMockCloudFlareClient() + cfProvider := &CloudFlareProvider{ + Client: client, + domainFilter: endpoint.NewDomainFilter([]string{"foo.com", "bar.com"}), + zoneIDFilter: provider.NewZoneIDFilter([]string{""}), + } + + // Test free plan detection (foo.com) + assert.False(t, cfProvider.ZoneHasPaidPlan("foo.com")) + assert.False(t, cfProvider.ZoneHasPaidPlan("subdomain.foo.com")) + assert.False(t, cfProvider.ZoneHasPaidPlan("deep.subdomain.foo.com")) + + // Test paid plan detection (bar.com) + assert.True(t, cfProvider.ZoneHasPaidPlan("bar.com")) + assert.True(t, cfProvider.ZoneHasPaidPlan("subdomain.bar.com")) + assert.True(t, cfProvider.ZoneHasPaidPlan("deep.subdomain.bar.com")) + + // Test invalid domain + assert.False(t, cfProvider.ZoneHasPaidPlan("invalid.domain.com")) + + // Test with zone error + client.getZoneError = errors.New("zone lookup failed") + providerWithError := &CloudFlareProvider{ + Client: client, + domainFilter: endpoint.NewDomainFilter([]string{"foo.com", "bar.com"}), + zoneIDFilter: provider.NewZoneIDFilter([]string{""}), + } + assert.False(t, providerWithError.ZoneHasPaidPlan("subdomain.foo.com")) +} + +func TestCloudflareChangesByZone(t *testing.T) { + client := NewMockCloudFlareClient() + cfProvider := &CloudFlareProvider{ + Client: client, + domainFilter: endpoint.NewDomainFilter([]string{"foo.com", "bar.com"}), + zoneIDFilter: provider.NewZoneIDFilter([]string{""}), + } + + zones, err := cfProvider.Zones(context.Background()) + assert.NoError(t, err) + assert.Len(t, zones, 2) + + // Test empty changes + emptyChanges := []*cloudFlareChange{} + changesByZone := cfProvider.changesByZone(zones, emptyChanges) + assert.Len(t, changesByZone, 2) // Should return map with zones but empty slices + assert.Empty(t, changesByZone["001"]) // bar.com zone should have no changes + assert.Empty(t, changesByZone["002"]) // foo.com zone should have no changes + + // Test changes for different zones + changes := []*cloudFlareChange{ + { + Action: cloudFlareCreate, + ResourceRecord: cloudflare.DNSRecord{Name: "api.foo.com", Type: "A", Content: "1.2.3.4"}, + }, + { + Action: cloudFlareUpdate, + ResourceRecord: cloudflare.DNSRecord{Name: "www.foo.com", Type: "CNAME", Content: "foo.com"}, + }, + { + Action: cloudFlareCreate, + ResourceRecord: cloudflare.DNSRecord{Name: "mail.bar.com", Type: "MX", Content: "10 mail.bar.com"}, + }, + { + Action: cloudFlareDelete, + ResourceRecord: cloudflare.DNSRecord{Name: "old.bar.com", Type: "A", Content: "5.6.7.8"}, + }, + } + + changesByZone = cfProvider.changesByZone(zones, changes) + assert.Len(t, changesByZone, 2) + + // Verify bar.com zone changes (zone 001) + barChanges := changesByZone["001"] + assert.Len(t, barChanges, 2) + assert.Equal(t, "mail.bar.com", barChanges[0].ResourceRecord.Name) + assert.Equal(t, "old.bar.com", barChanges[1].ResourceRecord.Name) + + // Verify foo.com zone changes (zone 002) + fooChanges := changesByZone["002"] + assert.Len(t, fooChanges, 2) + assert.Equal(t, "api.foo.com", fooChanges[0].ResourceRecord.Name) + assert.Equal(t, "www.foo.com", fooChanges[1].ResourceRecord.Name) +} + +func TestConvertCloudflareError(t *testing.T) { + tests := []struct { + name string + inputError error + expectSoftError bool + description string + }{ + { + name: "Rate limit error via Error type", + inputError: &cloudflare.Error{StatusCode: 429, Type: cloudflare.ErrorTypeRateLimit}, + expectSoftError: true, + description: "CloudFlare API rate limit error should be converted to soft error", + }, + { + name: "Rate limit error via ClientRateLimited", + inputError: &cloudflare.Error{StatusCode: 429, ErrorCodes: []int{10000}, Type: cloudflare.ErrorTypeRateLimit}, // Complete rate limit error + expectSoftError: true, + description: "CloudFlare client rate limited error should be converted to soft error", + }, + { + name: "Server error 500", + inputError: &cloudflare.Error{StatusCode: 500}, + expectSoftError: true, + description: "Server error (500+) should be converted to soft error", + }, + { + name: "Server error 502", + inputError: &cloudflare.Error{StatusCode: 502}, + expectSoftError: true, + description: "Server error (502) should be converted to soft error", + }, + { + name: "Server error 503", + inputError: &cloudflare.Error{StatusCode: 503}, + expectSoftError: true, + description: "Server error (503) should be converted to soft error", + }, + { + name: "Rate limit string error", + inputError: errors.New("exceeded available rate limit retries"), + expectSoftError: true, + description: "String error containing rate limit message should be converted to soft error", + }, + { + name: "Rate limit string error mixed case", + inputError: errors.New("request failed: exceeded available rate limit retries for this operation"), + expectSoftError: true, + description: "String error containing rate limit message should be converted to soft error regardless of context", + }, + { + name: "Client error 400", + inputError: &cloudflare.Error{StatusCode: 400}, + expectSoftError: false, + description: "Client error (400) should not be converted to soft error", + }, + { + name: "Client error 401", + inputError: &cloudflare.Error{StatusCode: 401}, + expectSoftError: false, + description: "Client error (401) should not be converted to soft error", + }, + { + name: "Client error 404", + inputError: &cloudflare.Error{StatusCode: 404}, + expectSoftError: false, + description: "Client error (404) should not be converted to soft error", + }, + { + name: "Generic error", + inputError: errors.New("some generic error"), + expectSoftError: false, + description: "Generic error should not be converted to soft error", + }, + { + name: "Network error", + inputError: errors.New("connection refused"), + expectSoftError: false, + description: "Network error should not be converted to soft error", + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + result := convertCloudflareError(tt.inputError) + + if tt.expectSoftError { + assert.ErrorIs(t, result, provider.SoftError, + "Expected soft error for %s: %s", tt.name, tt.description) + + // Verify the original error message is preserved in the soft error + assert.Contains(t, result.Error(), tt.inputError.Error(), + "Original error message should be preserved") + } else { + assert.NotErrorIs(t, result, provider.SoftError, + "Expected non-soft error for %s: %s", tt.name, tt.description) + assert.Equal(t, tt.inputError, result, + "Non-soft errors should be returned unchanged") + } + }) + } +} + +func TestConvertCloudflareErrorInContext(t *testing.T) { + tests := []struct { + name string + setupMock func(*mockCloudFlareClient) + function func(*CloudFlareProvider) error + expectSoftError bool + description string + }{ + { + name: "Zones with GetZone rate limit error", + setupMock: func(client *mockCloudFlareClient) { + client.Zones = map[string]string{"zone1": "example.com"} + client.getZoneError = &cloudflare.Error{StatusCode: 429, Type: cloudflare.ErrorTypeRateLimit} + }, + function: func(p *CloudFlareProvider) error { + p.zoneIDFilter.ZoneIDs = []string{"zone1"} + _, err := p.Zones(context.Background()) + return err + }, + expectSoftError: true, + description: "Zones function should convert GetZone rate limit errors to soft errors", + }, + { + name: "Zones with GetZone server error", + setupMock: func(client *mockCloudFlareClient) { + client.Zones = map[string]string{"zone1": "example.com"} + client.getZoneError = &cloudflare.Error{StatusCode: 500} + }, + function: func(p *CloudFlareProvider) error { + p.zoneIDFilter.ZoneIDs = []string{"zone1"} + _, err := p.Zones(context.Background()) + return err + }, + expectSoftError: true, + description: "Zones function should convert GetZone server errors to soft errors", + }, + { + name: "Zones with GetZone client error", + setupMock: func(client *mockCloudFlareClient) { + client.Zones = map[string]string{"zone1": "example.com"} + client.getZoneError = &cloudflare.Error{StatusCode: 404} + }, + function: func(p *CloudFlareProvider) error { + p.zoneIDFilter.ZoneIDs = []string{"zone1"} + _, err := p.Zones(context.Background()) + return err + }, + expectSoftError: false, + description: "Zones function should not convert GetZone client errors to soft errors", + }, + { + name: "Zones with ListZones rate limit error", + setupMock: func(client *mockCloudFlareClient) { + client.listZonesError = errors.New("exceeded available rate limit retries") + }, + function: func(p *CloudFlareProvider) error { + _, err := p.Zones(context.Background()) + return err + }, + expectSoftError: true, + description: "Zones function should convert ListZones rate limit string errors to soft errors", + }, + { + name: "Zones with ListZones server error", + setupMock: func(client *mockCloudFlareClient) { + client.listZonesError = &cloudflare.Error{StatusCode: 503} + }, + function: func(p *CloudFlareProvider) error { + _, err := p.Zones(context.Background()) + return err + }, + expectSoftError: true, + description: "Zones function should convert ListZones server errors to soft errors", + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + client := NewMockCloudFlareClient() + tt.setupMock(client) + + p := &CloudFlareProvider{ + Client: client, + zoneIDFilter: provider.ZoneIDFilter{}, + } + + err := tt.function(p) + assert.Error(t, err, "Expected an error from %s", tt.name) + + if tt.expectSoftError { + assert.ErrorIs(t, err, provider.SoftError, + "Expected soft error for %s: %s", tt.name, tt.description) + } else { + assert.NotErrorIs(t, err, provider.SoftError, + "Expected non-soft error for %s: %s", tt.name, tt.description) + } + }) + } +} + +func TestCloudFlareZonesDomainFilter(t *testing.T) { + // Set required environment variables for CloudFlare provider + t.Setenv("CF_API_TOKEN", "test-token") + + client := NewMockCloudFlareClient() + + // Create a domain filter that only matches "bar.com" + // This should filter out "foo.com" and trigger the debug log + domainFilter := endpoint.NewDomainFilter([]string{"bar.com"}) + + p, err := NewCloudFlareProvider( + domainFilter, + provider.NewZoneIDFilter([]string{""}), // empty zone ID filter so it uses ListZones path + false, // proxied + false, // dry run + RegionalServicesConfig{}, + CustomHostnamesConfig{}, + DNSRecordsConfig{PerPage: 50}, + ) + require.NoError(t, err) + + // Replace the real client with our mock + p.Client = client + + // Capture debug logs to verify the filter log message + oldLevel := log.GetLevel() + log.SetLevel(log.DebugLevel) + defer log.SetLevel(oldLevel) + + // Use a custom formatter to capture log output + var logOutput strings.Builder + log.SetOutput(&logOutput) + defer log.SetOutput(os.Stderr) + + // Call Zones() which should trigger the domain filter logic + zones, err := p.Zones(context.Background()) + require.NoError(t, err) + + // Should only return the "bar.com" zone since "foo.com" is filtered out + assert.Len(t, zones, 1) + assert.Equal(t, "bar.com", zones[0].Name) + assert.Equal(t, "001", zones[0].ID) + + // Verify that the debug log was written for the filtered zone + logString := logOutput.String() + assert.Contains(t, logString, `zone \"foo.com\" not in domain filter`) + assert.Contains(t, logString, "no zoneIDFilter configured, looking at all zones") +} + +func TestZoneIDByNameIteratorError(t *testing.T) { + client := NewMockCloudFlareClient() + + // Set up an error that will be returned by the ListZones iterator (line 144) + client.listZonesError = fmt.Errorf("CloudFlare API connection timeout") + + // Call ZoneIDByName which should hit line 144 (iterator error handling) + zoneID, err := client.ZoneIDByName("example.com") + + // Should return empty zone ID and the wrapped iterator error + assert.Empty(t, zoneID) + assert.Error(t, err) + assert.Contains(t, err.Error(), "failed to list zones from CloudFlare API") + assert.Contains(t, err.Error(), "CloudFlare API connection timeout") +} + +func TestZoneIDByNameZoneNotFound(t *testing.T) { + client := NewMockCloudFlareClient() + + // Set up mock to return different zones but not the one we're looking for + client.Zones = map[string]string{ + "zone456": "different.com", + "zone789": "another.com", + } + + // Call ZoneIDByName for a zone that doesn't exist, should hit line 147 (zone not found) + zoneID, err := client.ZoneIDByName("nonexistent.com") + + // Should return empty zone ID and the improved error message + assert.Empty(t, zoneID) + assert.Error(t, err) + assert.Contains(t, err.Error(), `zone "nonexistent.com" not found in CloudFlare account`) + assert.Contains(t, err.Error(), "verify the zone exists and API credentials have access to it") +} From 0d1309c7fafa9436c8ba1754e37a6b32b8acd253 Mon Sep 17 00:00:00 2001 From: Ivan Ka <5395690+ivankatliarchuk@users.noreply.github.com> Date: Tue, 29 Jul 2025 11:44:09 +0100 Subject: [PATCH 07/10] fix(endpoint): domains handling with idna (#5685) * fix(idna): fix handling of domains Signed-off-by: ivan katliarchuk * fix(idna): fix handling of domains Signed-off-by: ivan katliarchuk * fix(idna): fix handling of domains Signed-off-by: ivan katliarchuk --------- Signed-off-by: ivan katliarchuk --- endpoint/domain_filter.go | 7 +++-- internal/idna/idna.go | 29 ++++++++++++++++++ internal/idna/idna_test.go | 59 +++++++++++++++++++++++++++++++++++++ plan/plan.go | 10 ++----- provider/zonefinder_test.go | 16 ++++++---- 5 files changed, 105 insertions(+), 16 deletions(-) create mode 100644 internal/idna/idna.go create mode 100644 internal/idna/idna_test.go diff --git a/endpoint/domain_filter.go b/endpoint/domain_filter.go index 47402bb76..8d8aad2dc 100644 --- a/endpoint/domain_filter.go +++ b/endpoint/domain_filter.go @@ -25,7 +25,8 @@ import ( "strings" log "github.com/sirupsen/logrus" - "golang.org/x/net/idna" + + "sigs.k8s.io/external-dns/internal/idna" ) type MatchAllDomainFilters []DomainFilterInterface @@ -247,9 +248,9 @@ func (df *DomainFilter) MatchParent(domain string) bool { } // normalizeDomain converts a domain to a canonical form, so that we can filter on it -// it: trim "." suffix, get Unicode version of domain complient with Section 5 of RFC 5891 +// it: trim "." suffix, get Unicode version of domain compliant with Section 5 of RFC 5891 func normalizeDomain(domain string) string { - s, err := idna.Lookup.ToUnicode(strings.TrimSuffix(domain, ".")) + s, err := idna.Profile.ToUnicode(strings.TrimSuffix(domain, ".")) if err != nil { log.Warnf(`Got error while parsing domain %s: %v`, domain, err) } diff --git a/internal/idna/idna.go b/internal/idna/idna.go new file mode 100644 index 000000000..9290e8a51 --- /dev/null +++ b/internal/idna/idna.go @@ -0,0 +1,29 @@ +/* +Copyright 2025 The Kubernetes Authors. + +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 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +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 idna + +import ( + "golang.org/x/net/idna" +) + +var ( + Profile = idna.New( + idna.MapForLookup(), + idna.Transitional(true), + idna.StrictDomainName(false), + ) +) diff --git a/internal/idna/idna_test.go b/internal/idna/idna_test.go new file mode 100644 index 000000000..f3ae93c44 --- /dev/null +++ b/internal/idna/idna_test.go @@ -0,0 +1,59 @@ +/* +Copyright 2025 The Kubernetes Authors. + +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 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +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 idna + +import ( + "strings" + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestProfileWithDefault(t *testing.T) { + tets := []struct { + input string + expected string + }{ + { + input: "*.GÖPHER.com", + expected: "*.göpher.com", + }, + { + input: "*._abrakadabra.com", + expected: "*._abrakadabra.com", + }, + { + input: "_abrakadabra.com", + expected: "_abrakadabra.com", + }, + { + input: "*.foo.kube.example.com", + expected: "*.foo.kube.example.com", + }, + { + input: "xn--bcher-kva.example.com", + expected: "bücher.example.com", + }, + } + for _, tt := range tets { + t.Run(strings.ToLower(tt.input), func(t *testing.T) { + result, err := Profile.ToUnicode(tt.input) + assert.NoError(t, err) + assert.Equal(t, tt.expected, result) + }) + } +} diff --git a/plan/plan.go b/plan/plan.go index 8061a5b3f..cf7db8858 100644 --- a/plan/plan.go +++ b/plan/plan.go @@ -23,9 +23,9 @@ import ( "github.com/google/go-cmp/cmp" log "github.com/sirupsen/logrus" - "golang.org/x/net/idna" "sigs.k8s.io/external-dns/endpoint" + "sigs.k8s.io/external-dns/internal/idna" ) // PropertyComparator is used in Plan for comparing the previous and current custom annotations. @@ -340,16 +340,10 @@ func filterRecordsForPlan(records []*endpoint.Endpoint, domainFilter endpoint.Ma return filtered } -var idnaProfile = idna.New( - idna.MapForLookup(), - idna.Transitional(true), - idna.StrictDomainName(false), -) - // normalizeDNSName converts a DNS name to a canonical form, so that we can use string equality // it: removes space, get ASCII version of dnsName complient with Section 5 of RFC 5891, ensures there is a trailing dot func normalizeDNSName(dnsName string) string { - s, err := idnaProfile.ToASCII(strings.TrimSpace(dnsName)) + s, err := idna.Profile.ToASCII(strings.TrimSpace(dnsName)) if err != nil { log.Warnf(`Got error while parsing DNSName %s: %v`, dnsName, err) } diff --git a/provider/zonefinder_test.go b/provider/zonefinder_test.go index dccfe5adb..4eddd36cb 100644 --- a/provider/zonefinder_test.go +++ b/provider/zonefinder_test.go @@ -32,14 +32,16 @@ func TestZoneIDName(t *testing.T) { z.Add("654321", "foo.qux.baz") z.Add("987654", "エイミー.みんな") z.Add("123123", "_metadata.example.com") + z.Add("1231231", "_foo._metadata.example.com") z.Add("456456", "_metadata.エイミー.みんな") assert.Equal(t, ZoneIDName{ - "123456": "qux.baz", - "654321": "foo.qux.baz", - "987654": "エイミー.みんな", - "123123": "_metadata.example.com", - "456456": "_metadata.エイミー.みんな", + "123456": "qux.baz", + "654321": "foo.qux.baz", + "987654": "エイミー.みんな", + "123123": "_metadata.example.com", + "1231231": "_foo._metadata.example.com", + "456456": "_metadata.エイミー.みんな", }, z) // simple entry in a domain @@ -77,6 +79,10 @@ func TestZoneIDName(t *testing.T) { assert.Equal(t, "エイミー.みんな", zoneName) assert.Equal(t, "987654", zoneID) + zoneID, zoneName = z.FindZone("_foo._metadata.example.com") + assert.Equal(t, "_foo._metadata.example.com", zoneName) + assert.Equal(t, "1231231", zoneID) + hook := testutils.LogsUnderTestWithLogLevel(log.WarnLevel, t) _, _ = z.FindZone("???") From 1b9d7cddc0f7c0a5a3b3bae0abcf0de57cd558cf Mon Sep 17 00:00:00 2001 From: Ivan Ka <5395690+ivankatliarchuk@users.noreply.github.com> Date: Wed, 30 Jul 2025 09:54:28 +0100 Subject: [PATCH 08/10] fix(source/service): disable pod and endpointSlices informers when they are not needed (#5646) * fix(source/service): disable pod and endpointSlicesInformer when not required Signed-off-by: ivan katliarchuk * fix(source/service): disable pod and endpointSlicesInformer when not required Signed-off-by: ivan katliarchuk * fix(source/service): disable pod and endpointSlices informers when they are not needed Signed-off-by: ivan katliarchuk * fix(source/service): disable pod and endpointSlices informers when they are not needed Signed-off-by: ivan katliarchuk --------- Signed-off-by: ivan katliarchuk --- source/informers/fake.go | 60 +++++++++++++ source/service.go | 93 ++++++++++--------- source/service_test.go | 189 ++++++++++++++++++++++++++++++++++----- 3 files changed, 273 insertions(+), 69 deletions(-) create mode 100644 source/informers/fake.go diff --git a/source/informers/fake.go b/source/informers/fake.go new file mode 100644 index 000000000..aed7b3aee --- /dev/null +++ b/source/informers/fake.go @@ -0,0 +1,60 @@ +/* +Copyright 2025 The Kubernetes Authors. +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 +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +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 informers + +import ( + "github.com/stretchr/testify/mock" + corev1lister "k8s.io/client-go/listers/core/v1" + discoveryv1lister "k8s.io/client-go/listers/discovery/v1" + "k8s.io/client-go/tools/cache" +) + +type FakeServiceInformer struct { + mock.Mock +} + +func (f *FakeServiceInformer) Informer() cache.SharedIndexInformer { + args := f.Called() + return args.Get(0).(cache.SharedIndexInformer) +} + +func (f *FakeServiceInformer) Lister() corev1lister.ServiceLister { + return corev1lister.NewServiceLister(f.Informer().GetIndexer()) +} + +type FakeEndpointSliceInformer struct { + mock.Mock +} + +func (f *FakeEndpointSliceInformer) Informer() cache.SharedIndexInformer { + args := f.Called() + return args.Get(0).(cache.SharedIndexInformer) +} + +func (f *FakeEndpointSliceInformer) Lister() discoveryv1lister.EndpointSliceLister { + return discoveryv1lister.NewEndpointSliceLister(f.Informer().GetIndexer()) +} + +type FakeNodeInformer struct { + mock.Mock +} + +func (f *FakeNodeInformer) Informer() cache.SharedIndexInformer { + args := f.Called() + return args.Get(0).(cache.SharedIndexInformer) +} + +func (f *FakeNodeInformer) Lister() corev1lister.NodeLister { + return corev1lister.NewNodeLister(f.Informer().GetIndexer()) +} diff --git a/source/service.go b/source/service.go index a339b192f..a2a8f8f7f 100644 --- a/source/service.go +++ b/source/service.go @@ -96,28 +96,9 @@ func NewServiceSource(ctx context.Context, kubeClient kubernetes.Interface, name // Set the resync period to 0 to prevent processing when nothing has changed informerFactory := kubeinformers.NewSharedInformerFactoryWithOptions(kubeClient, 0, kubeinformers.WithNamespace(namespace)) serviceInformer := informerFactory.Core().V1().Services() - endpointSlicesInformer := informerFactory.Discovery().V1().EndpointSlices() - podInformer := informerFactory.Core().V1().Pods() // Add default resource event handlers to properly initialize informer. - _, _ = serviceInformer.Informer().AddEventHandler( - cache.ResourceEventHandlerFuncs{ - AddFunc: func(obj interface{}) { - }, - }, - ) - _, _ = endpointSlicesInformer.Informer().AddEventHandler( - cache.ResourceEventHandlerFuncs{ - AddFunc: func(obj interface{}) { - }, - }, - ) - _, _ = podInformer.Informer().AddEventHandler( - cache.ResourceEventHandlerFuncs{ - AddFunc: func(obj interface{}) { - }, - }, - ) + _, _ = serviceInformer.Informer().AddEventHandler(informers.DefaultEventHandler()) // Transform the slice into a map so it will be way much easier and fast to filter later sTypesFilter, err := newServiceTypesFilter(serviceTypeFilter) @@ -125,30 +106,40 @@ func NewServiceSource(ctx context.Context, kubeClient kubernetes.Interface, name return nil, err } - var nodeInformer coreinformers.NodeInformer - if sTypesFilter.isNodeInformerRequired() { - nodeInformer = informerFactory.Core().V1().Nodes() - _, _ = nodeInformer.Informer().AddEventHandler(informers.DefaultEventHandler()) + var endpointSlicesInformer discoveryinformers.EndpointSliceInformer + var podInformer coreinformers.PodInformer + if sTypesFilter.isRequired(v1.ServiceTypeNodePort, v1.ServiceTypeClusterIP) { + endpointSlicesInformer = informerFactory.Discovery().V1().EndpointSlices() + podInformer = informerFactory.Core().V1().Pods() + + _, _ = endpointSlicesInformer.Informer().AddEventHandler(informers.DefaultEventHandler()) + _, _ = podInformer.Informer().AddEventHandler(informers.DefaultEventHandler()) + + // Add an indexer to the EndpointSlice informer to index by the service name label + err = endpointSlicesInformer.Informer().AddIndexers(cache.Indexers{ + serviceNameIndexKey: func(obj any) ([]string, error) { + endpointSlice, ok := obj.(*discoveryv1.EndpointSlice) + if !ok { + // This should never happen because the Informer should only contain EndpointSlice objects + return nil, fmt.Errorf("expected %T but got %T instead", endpointSlice, obj) + } + serviceName := endpointSlice.Labels[discoveryv1.LabelServiceName] + if serviceName == "" { + return nil, nil + } + key := types.NamespacedName{Namespace: endpointSlice.Namespace, Name: serviceName}.String() + return []string{key}, nil + }, + }) + if err != nil { + return nil, err + } } - // Add an indexer to the EndpointSlice informer to index by the service name label - err = endpointSlicesInformer.Informer().AddIndexers(cache.Indexers{ - serviceNameIndexKey: func(obj any) ([]string, error) { - endpointSlice, ok := obj.(*discoveryv1.EndpointSlice) - if !ok { - // This should never happen because the Informer should only contain EndpointSlice objects - return nil, fmt.Errorf("expected %T but got %T instead", endpointSlice, obj) - } - serviceName := endpointSlice.Labels[discoveryv1.LabelServiceName] - if serviceName == "" { - return nil, nil - } - key := types.NamespacedName{Namespace: endpointSlice.Namespace, Name: serviceName}.String() - return []string{key}, nil - }, - }) - if err != nil { - return nil, err + var nodeInformer coreinformers.NodeInformer + if sTypesFilter.isRequired(v1.ServiceTypeNodePort) { + nodeInformer = informerFactory.Core().V1().Nodes() + _, _ = nodeInformer.Informer().AddEventHandler(informers.DefaultEventHandler()) } informerFactory.Start(ctx.Done()) @@ -808,10 +799,10 @@ func (sc *serviceSource) AddEventHandler(_ context.Context, handler func()) { // Right now there is no way to remove event handler from informer, see: // https://github.com/kubernetes/kubernetes/issues/79610 _, _ = sc.serviceInformer.Informer().AddEventHandler(eventHandlerFunc(handler)) - if sc.listenEndpointEvents { + if sc.listenEndpointEvents && sc.serviceTypeFilter.isRequired(v1.ServiceTypeNodePort, v1.ServiceTypeClusterIP) { _, _ = sc.endpointSlicesInformer.Informer().AddEventHandler(eventHandlerFunc(handler)) } - if sc.serviceTypeFilter.isNodeInformerRequired() { + if sc.serviceTypeFilter.isRequired(v1.ServiceTypeNodePort) { _, _ = sc.nodeInformer.Informer().AddEventHandler(eventHandlerFunc(handler)) } } @@ -848,12 +839,18 @@ func (sc *serviceTypes) isProcessed(serviceType v1.ServiceType) bool { return !sc.enabled || sc.types[serviceType] } -func (sc *serviceTypes) isNodeInformerRequired() bool { - if !sc.enabled { +// isRequired returns true if service type filtering is disabled or if any of the provided service types are present in the filter. +// If no options are provided, it returns true. +func (sc *serviceTypes) isRequired(opts ...v1.ServiceType) bool { + if len(opts) == 0 || !sc.enabled { return true } - _, ok := sc.types[v1.ServiceTypeNodePort] - return ok + for _, opt := range opts { + if _, ok := sc.types[opt]; ok { + return true + } + } + return false } // conditionToBool converts an EndpointConditions condition to a bool value. diff --git a/source/service_test.go b/source/service_test.go index dd21331ef..07df803bd 100644 --- a/source/service_test.go +++ b/source/service_test.go @@ -37,6 +37,7 @@ import ( "k8s.io/apimachinery/pkg/labels" "k8s.io/apimachinery/pkg/util/intstr" "k8s.io/client-go/kubernetes/fake" + "sigs.k8s.io/external-dns/source/informers" "sigs.k8s.io/external-dns/endpoint" "sigs.k8s.io/external-dns/internal/testutils" @@ -251,7 +252,7 @@ func testServiceSourceEndpoints(t *testing.T) { }, externalIPs: []string{}, lbs: []string{"1.2.3.4"}, - serviceTypesFilter: []string{}, + serviceTypesFilter: []string{string(v1.ServiceTypeLoadBalancer)}, expected: []*endpoint.Endpoint{ {DNSName: "foo.example.org", RecordType: endpoint.RecordTypeA, Targets: endpoint.Targets{"1.2.3.4"}}, }, @@ -296,7 +297,7 @@ func testServiceSourceEndpoints(t *testing.T) { annotations: map[string]string{}, externalIPs: []string{}, lbs: []string{"1.2.3.4"}, - serviceTypesFilter: []string{}, + serviceTypesFilter: []string{string(v1.ServiceTypeLoadBalancer), string(v1.ServiceTypeNodePort)}, expected: []*endpoint.Endpoint{ {DNSName: "foo.fqdn.org", RecordType: endpoint.RecordTypeA, Targets: endpoint.Targets{"1.2.3.4"}}, {DNSName: "foo.fqdn.com", RecordType: endpoint.RecordTypeA, Targets: endpoint.Targets{"1.2.3.4"}}, @@ -2498,6 +2499,7 @@ func TestHeadlessServices(t *testing.T) { podsReady []bool publishNotReadyAddresses bool nodes []v1.Node + serviceTypesFilter []string expected []*endpoint.Endpoint expectError bool }{ @@ -2528,6 +2530,7 @@ func TestHeadlessServices(t *testing.T) { []bool{true, true}, false, []v1.Node{}, + []string{}, []*endpoint.Endpoint{ {DNSName: "foo-0.service.example.org", RecordType: endpoint.RecordTypeA, Targets: endpoint.Targets{"1.1.1.1"}}, {DNSName: "foo-1.service.example.org", RecordType: endpoint.RecordTypeA, Targets: endpoint.Targets{"1.1.1.2"}}, @@ -2562,6 +2565,7 @@ func TestHeadlessServices(t *testing.T) { []bool{true, true}, false, []v1.Node{}, + []string{string(v1.ServiceTypeClusterIP), string(v1.ServiceTypeLoadBalancer)}, []*endpoint.Endpoint{ {DNSName: "foo-0.service.example.org", RecordType: endpoint.RecordTypeAAAA, Targets: endpoint.Targets{"2001:db8::1"}}, {DNSName: "foo-1.service.example.org", RecordType: endpoint.RecordTypeAAAA, Targets: endpoint.Targets{"2001:db8::2"}}, @@ -2596,6 +2600,7 @@ func TestHeadlessServices(t *testing.T) { []bool{true, true}, false, []v1.Node{}, + []string{}, []*endpoint.Endpoint{}, false, }, @@ -2627,6 +2632,7 @@ func TestHeadlessServices(t *testing.T) { []bool{true, true}, false, []v1.Node{}, + []string{}, []*endpoint.Endpoint{ {DNSName: "foo-0.service.example.org", RecordType: endpoint.RecordTypeA, Targets: endpoint.Targets{"1.1.1.1"}, RecordTTL: endpoint.TTL(1)}, {DNSName: "foo-1.service.example.org", RecordType: endpoint.RecordTypeA, Targets: endpoint.Targets{"1.1.1.2"}, RecordTTL: endpoint.TTL(1)}, @@ -2662,6 +2668,7 @@ func TestHeadlessServices(t *testing.T) { []bool{true, true}, false, []v1.Node{}, + []string{}, []*endpoint.Endpoint{ {DNSName: "foo-0.service.example.org", RecordType: endpoint.RecordTypeAAAA, Targets: endpoint.Targets{"2001:db8::1"}, RecordTTL: endpoint.TTL(1)}, {DNSName: "foo-1.service.example.org", RecordType: endpoint.RecordTypeAAAA, Targets: endpoint.Targets{"2001:db8::2"}, RecordTTL: endpoint.TTL(1)}, @@ -2696,6 +2703,7 @@ func TestHeadlessServices(t *testing.T) { []bool{true, false}, false, []v1.Node{}, + []string{}, []*endpoint.Endpoint{ {DNSName: "foo-0.service.example.org", RecordType: endpoint.RecordTypeA, Targets: endpoint.Targets{"1.1.1.1"}}, {DNSName: "service.example.org", RecordType: endpoint.RecordTypeA, Targets: endpoint.Targets{"1.1.1.1"}}, @@ -2729,6 +2737,7 @@ func TestHeadlessServices(t *testing.T) { []bool{true, false}, true, []v1.Node{}, + []string{}, []*endpoint.Endpoint{ {DNSName: "foo-0.service.example.org", RecordType: endpoint.RecordTypeA, Targets: endpoint.Targets{"1.1.1.1"}}, {DNSName: "foo-1.service.example.org", RecordType: endpoint.RecordTypeA, Targets: endpoint.Targets{"1.1.1.2"}}, @@ -2763,6 +2772,7 @@ func TestHeadlessServices(t *testing.T) { []bool{true, true}, false, []v1.Node{}, + []string{}, []*endpoint.Endpoint{ {DNSName: "service.example.org", RecordType: endpoint.RecordTypeA, Targets: endpoint.Targets{"1.1.1.1", "1.1.1.2"}}, }, @@ -2795,6 +2805,7 @@ func TestHeadlessServices(t *testing.T) { []bool{true, true, true}, false, []v1.Node{}, + []string{}, []*endpoint.Endpoint{ {DNSName: "service.example.org", RecordType: endpoint.RecordTypeA, Targets: endpoint.Targets{"1.1.1.1", "1.1.1.2"}}, }, @@ -2827,6 +2838,7 @@ func TestHeadlessServices(t *testing.T) { []bool{true, true, true}, false, []v1.Node{}, + []string{}, []*endpoint.Endpoint{ {DNSName: "service.example.org", RecordType: endpoint.RecordTypeAAAA, Targets: endpoint.Targets{"2001:db8::1", "2001:db8::2"}}, }, @@ -2861,6 +2873,7 @@ func TestHeadlessServices(t *testing.T) { []bool{true, true, true}, false, []v1.Node{}, + []string{string(v1.ServiceTypeClusterIP)}, []*endpoint.Endpoint{ {DNSName: "service.example.org", RecordType: endpoint.RecordTypeA, Targets: endpoint.Targets{"1.2.3.4"}}, }, @@ -2895,6 +2908,7 @@ func TestHeadlessServices(t *testing.T) { []bool{true, true, true}, false, []v1.Node{}, + []string{}, []*endpoint.Endpoint{ {DNSName: "service.example.org", RecordType: endpoint.RecordTypeAAAA, Targets: endpoint.Targets{"2001:db8::4"}}, }, @@ -2939,6 +2953,7 @@ func TestHeadlessServices(t *testing.T) { }, }, }, + []string{}, []*endpoint.Endpoint{ {DNSName: "service.example.org", RecordType: endpoint.RecordTypeA, Targets: endpoint.Targets{"1.2.3.4"}}, }, @@ -2987,6 +3002,7 @@ func TestHeadlessServices(t *testing.T) { }, }, }, + []string{}, []*endpoint.Endpoint{ {DNSName: "service.example.org", RecordType: endpoint.RecordTypeAAAA, Targets: endpoint.Targets{"2001:db8::5"}}, }, @@ -3031,6 +3047,7 @@ func TestHeadlessServices(t *testing.T) { }, }, }, + []string{}, []*endpoint.Endpoint{ {DNSName: "service.example.org", RecordType: endpoint.RecordTypeAAAA, Targets: endpoint.Targets{"2001:db8::4"}}, }, @@ -3079,6 +3096,7 @@ func TestHeadlessServices(t *testing.T) { }, }, }, + []string{}, []*endpoint.Endpoint{ {DNSName: "service.example.org", RecordType: endpoint.RecordTypeA, Targets: endpoint.Targets{"1.2.3.4"}}, {DNSName: "service.example.org", RecordType: endpoint.RecordTypeAAAA, Targets: endpoint.Targets{"2001:db8::4"}}, @@ -3113,6 +3131,7 @@ func TestHeadlessServices(t *testing.T) { []bool{true, true, true}, false, []v1.Node{}, + []string{}, []*endpoint.Endpoint{ {DNSName: "service.example.org", RecordType: endpoint.RecordTypeA, Targets: endpoint.Targets{"1.2.3.4"}}, }, @@ -3146,6 +3165,7 @@ func TestHeadlessServices(t *testing.T) { []bool{true, true, true}, false, []v1.Node{}, + []string{}, []*endpoint.Endpoint{ {DNSName: "service.example.org", RecordType: endpoint.RecordTypeAAAA, Targets: endpoint.Targets{"2001:db8::4"}}, }, @@ -3242,7 +3262,7 @@ func TestHeadlessServices(t *testing.T) { true, false, false, - []string{}, + tc.serviceTypesFilter, tc.ignoreHostnameAnnotation, labels.Everything(), false, @@ -3994,7 +4014,6 @@ func TestHeadlessServicesHostIP(t *testing.T) { t.Run(tc.title, func(t *testing.T) { t.Parallel() - // Create a Kubernetes testing client kubernetes := fake.NewClientset() service := &v1.Service{ @@ -4134,7 +4153,7 @@ func TestExternalServices(t *testing.T) { }, "111.111.111.111", []string{}, - []string{}, + []string{string(v1.ServiceTypeNodePort), string(v1.ServiceTypeExternalName)}, []*endpoint.Endpoint{ {DNSName: "service.example.org", Targets: endpoint.Targets{"111.111.111.111"}, RecordType: endpoint.RecordTypeA}, }, @@ -4176,7 +4195,7 @@ func TestExternalServices(t *testing.T) { }, "remote.example.com", []string{}, - []string{}, + []string{string(v1.ServiceTypeExternalName)}, []*endpoint.Endpoint{ {DNSName: "service.example.org", Targets: endpoint.Targets{"remote.example.com"}, RecordType: endpoint.RecordTypeCNAME}, }, @@ -4371,6 +4390,8 @@ func TestNewServiceSourceInformersEnabled(t *testing.T) { assert.NotNil(t, svc.serviceTypeFilter) assert.False(t, svc.serviceTypeFilter.enabled) assert.NotNil(t, svc.nodeInformer) + assert.NotNil(t, svc.serviceInformer) + assert.NotNil(t, svc.endpointSlicesInformer) }, }, { @@ -4380,17 +4401,49 @@ func TestNewServiceSourceInformersEnabled(t *testing.T) { assert.NotNil(t, svc) assert.NotNil(t, svc.serviceTypeFilter) assert.True(t, svc.serviceTypeFilter.enabled) + assert.NotNil(t, svc.serviceInformer) assert.Nil(t, svc.nodeInformer) + assert.NotNil(t, svc.endpointSlicesInformer) + assert.NotNil(t, svc.podInformer) }, }, { - name: "serviceTypeFilter contains NodePort", - svcFilter: []string{string(v1.ServiceTypeNodePort)}, + name: "serviceTypeFilter contains NodePort and ExternalName", + svcFilter: []string{string(v1.ServiceTypeNodePort), string(v1.ServiceTypeExternalName)}, asserts: func(svc *serviceSource) { assert.NotNil(t, svc) assert.NotNil(t, svc.serviceTypeFilter) assert.True(t, svc.serviceTypeFilter.enabled) + assert.NotNil(t, svc.serviceInformer) assert.NotNil(t, svc.nodeInformer) + assert.NotNil(t, svc.endpointSlicesInformer) + assert.NotNil(t, svc.podInformer) + }, + }, + { + name: "serviceTypeFilter contains ExternalName", + svcFilter: []string{string(v1.ServiceTypeExternalName)}, + asserts: func(svc *serviceSource) { + assert.NotNil(t, svc) + assert.NotNil(t, svc.serviceTypeFilter) + assert.True(t, svc.serviceTypeFilter.enabled) + assert.NotNil(t, svc.serviceInformer) + assert.Nil(t, svc.nodeInformer) + assert.Nil(t, svc.endpointSlicesInformer) + assert.Nil(t, svc.podInformer) + }, + }, + { + name: "serviceTypeFilter contains LoadBalancer", + svcFilter: []string{string(v1.ServiceTypeLoadBalancer)}, + asserts: func(svc *serviceSource) { + assert.NotNil(t, svc) + assert.NotNil(t, svc.serviceTypeFilter) + assert.True(t, svc.serviceTypeFilter.enabled) + assert.NotNil(t, svc.serviceInformer) + assert.Nil(t, svc.nodeInformer) + assert.Nil(t, svc.endpointSlicesInformer) + assert.Nil(t, svc.podInformer) }, }, } @@ -4681,32 +4734,126 @@ func createTestServicesByType(namespace string, typeCounts map[v1.ServiceType]in func TestServiceTypes_isNodeInformerRequired(t *testing.T) { tests := []struct { - name string - filter []string - want bool + name string + filter []string + required []v1.ServiceType + want bool }{ { - name: "NodePort type present", - filter: []string{string(v1.ServiceTypeNodePort)}, - want: true, + name: "NodePort required and filter is empty", + filter: []string{}, + required: []v1.ServiceType{v1.ServiceTypeNodePort}, + want: true, }, { - name: "NodePort type absent, filter enabled", - filter: []string{string(v1.ServiceTypeLoadBalancer)}, - want: false, + name: "NodePort type present", + filter: []string{string(v1.ServiceTypeNodePort)}, + required: []v1.ServiceType{v1.ServiceTypeNodePort}, + want: true, }, { - name: "NodePort and other filters present", - filter: []string{string(v1.ServiceTypeLoadBalancer), string(v1.ServiceTypeNodePort)}, - want: true, + name: "NodePort type absent, filter enabled", + filter: []string{string(v1.ServiceTypeLoadBalancer)}, + required: []v1.ServiceType{v1.ServiceTypeNodePort}, + want: false, + }, + { + name: "NodePort and other filters present", + filter: []string{string(v1.ServiceTypeLoadBalancer), string(v1.ServiceTypeNodePort)}, + required: []v1.ServiceType{v1.ServiceTypeNodePort}, + want: true, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { filter, _ := newServiceTypesFilter(tt.filter) - got := filter.isNodeInformerRequired() + got := filter.isRequired(tt.required...) assert.Equal(t, tt.want, got) }) } } + +func TestServiceSource_AddEventHandler(t *testing.T) { + var fakeServiceInformer *informers.FakeServiceInformer + var fakeEdpInformer *informers.FakeEndpointSliceInformer + var fakeNodeInformer *informers.FakeNodeInformer + tests := []struct { + name string + filter []string + times int + asserts func(t *testing.T, s *serviceSource) + }{ + { + name: "AddEventHandler should trigger all event handlers when empty filter is provided", + filter: []string{}, + times: 3, + asserts: func(t *testing.T, s *serviceSource) { + fakeServiceInformer.AssertNumberOfCalls(t, "Informer", 1) + fakeEdpInformer.AssertNumberOfCalls(t, "Informer", 1) + fakeNodeInformer.AssertNumberOfCalls(t, "Informer", 1) + }, + }, + { + name: "AddEventHandler should trigger only service event handler", + filter: []string{string(v1.ServiceTypeExternalName), string(v1.ServiceTypeLoadBalancer)}, + times: 1, + asserts: func(t *testing.T, s *serviceSource) { + fakeServiceInformer.AssertNumberOfCalls(t, "Informer", 1) + fakeEdpInformer.AssertNumberOfCalls(t, "Informer", 0) + fakeNodeInformer.AssertNumberOfCalls(t, "Informer", 0) + }, + }, + { + name: "AddEventHandler should configure only service event handler", + filter: []string{string(v1.ServiceTypeExternalName), string(v1.ServiceTypeLoadBalancer), string(v1.ServiceTypeClusterIP)}, + times: 2, + asserts: func(t *testing.T, s *serviceSource) { + fakeServiceInformer.AssertNumberOfCalls(t, "Informer", 1) + fakeEdpInformer.AssertNumberOfCalls(t, "Informer", 1) + fakeNodeInformer.AssertNumberOfCalls(t, "Informer", 0) + }, + }, + { + name: "AddEventHandler should configure all service event handlers", + filter: []string{string(v1.ServiceTypeNodePort)}, + times: 3, + asserts: func(t *testing.T, s *serviceSource) { + fakeServiceInformer.AssertNumberOfCalls(t, "Informer", 1) + fakeEdpInformer.AssertNumberOfCalls(t, "Informer", 1) + fakeNodeInformer.AssertNumberOfCalls(t, "Informer", 1) + }, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + fakeServiceInformer = new(informers.FakeServiceInformer) + infSvc := testInformer{} + fakeServiceInformer.On("Informer").Return(&infSvc) + + fakeEdpInformer = new(informers.FakeEndpointSliceInformer) + infEdp := testInformer{} + fakeEdpInformer.On("Informer").Return(&infEdp) + + fakeNodeInformer = new(informers.FakeNodeInformer) + infNode := testInformer{} + fakeNodeInformer.On("Informer").Return(&infNode) + + filter, _ := newServiceTypesFilter(tt.filter) + + svcSource := &serviceSource{ + endpointSlicesInformer: fakeEdpInformer, + serviceInformer: fakeServiceInformer, + nodeInformer: fakeNodeInformer, + serviceTypeFilter: filter, + listenEndpointEvents: true, + } + + svcSource.AddEventHandler(t.Context(), func() {}) + + assert.Equal(t, tt.times, infSvc.times+infEdp.times+infNode.times) + + tt.asserts(t, svcSource) + }) + } +} From 1d6b2cde18e5e32af0d231def64c2a74473a9850 Mon Sep 17 00:00:00 2001 From: Ivan Ka <5395690+ivankatliarchuk@users.noreply.github.com> Date: Fri, 1 Aug 2025 08:03:15 +0100 Subject: [PATCH 09/10] chore(source): use types instead of strings (#5699) Signed-off-by: ivan katliarchuk --- source/store.go | 50 ++++++++++++++++++++++--------------------- source/store_test.go | 24 +++++++++++++-------- source/types/types.go | 46 +++++++++++++++++++++++++++++++++++++++ 3 files changed, 87 insertions(+), 33 deletions(-) create mode 100644 source/types/types.go diff --git a/source/store.go b/source/store.go index c77e89c3b..9dbc56872 100644 --- a/source/store.go +++ b/source/store.go @@ -37,6 +37,8 @@ import ( "k8s.io/client-go/tools/clientcmd" gateway "sigs.k8s.io/gateway-api/pkg/client/clientset/versioned" + "sigs.k8s.io/external-dns/source/types" + extdnshttp "sigs.k8s.io/external-dns/pkg/http" "sigs.k8s.io/external-dns/pkg/apis/externaldns" @@ -332,53 +334,53 @@ func ByNames(ctx context.Context, p ClientGenerator, names []string, cfg *Config // because they have simpler initialization requirements. func BuildWithConfig(ctx context.Context, source string, p ClientGenerator, cfg *Config) (Source, error) { switch source { - case "node": + case types.Node: return buildNodeSource(ctx, p, cfg) - case "service": + case types.Service: return buildServiceSource(ctx, p, cfg) - case "ingress": + case types.Ingress: return buildIngressSource(ctx, p, cfg) - case "pod": + case types.Pod: return buildPodSource(ctx, p, cfg) - case "gateway-httproute": + case types.GatewayHttpRoute: return NewGatewayHTTPRouteSource(p, cfg) - case "gateway-grpcroute": + case types.GatewayGrpcRoute: return NewGatewayGRPCRouteSource(p, cfg) - case "gateway-tlsroute": + case types.GatewayTlsRoute: return NewGatewayTLSRouteSource(p, cfg) - case "gateway-tcproute": + case types.GatewayTcpRoute: return NewGatewayTCPRouteSource(p, cfg) - case "gateway-udproute": + case types.GatewayUdpRoute: return NewGatewayUDPRouteSource(p, cfg) - case "istio-gateway": + case types.IstioGateway: return buildIstioGatewaySource(ctx, p, cfg) - case "istio-virtualservice": + case types.IstioVirtualService: return buildIstioVirtualServiceSource(ctx, p, cfg) - case "cloudfoundry": + case types.Cloudfoundry: return buildCloudFoundrySource(ctx, p, cfg) - case "ambassador-host": + case types.AmbassadorHost: return buildAmbassadorHostSource(ctx, p, cfg) - case "contour-httpproxy": + case types.ContourHTTPProxy: return buildContourHTTPProxySource(ctx, p, cfg) - case "gloo-proxy": + case types.GlooProxy: return buildGlooProxySource(ctx, p, cfg) - case "traefik-proxy": + case types.TraefikProxy: return buildTraefikProxySource(ctx, p, cfg) - case "openshift-route": + case types.OpenShiftRoute: return buildOpenShiftRouteSource(ctx, p, cfg) - case "fake": + case types.Fake: return NewFakeSource(cfg.FQDNTemplate) - case "connector": + case types.Connector: return NewConnectorSource(cfg.ConnectorServer) - case "crd": + case types.CRD: return buildCRDSource(ctx, p, cfg) - case "skipper-routegroup": + case types.SkipperRouteGroup: return buildSkipperRouteGroupSource(ctx, cfg) - case "kong-tcpingress": + case types.KongTCPIngress: return buildKongTCPIngressSource(ctx, p, cfg) - case "f5-virtualserver": + case types.F5VirtualServer: return buildF5VirtualServerSource(ctx, p, cfg) - case "f5-transportserver": + case types.F5TransportServer: return buildF5TransportServerSource(ctx, p, cfg) } return nil, ErrSourceNotFound diff --git a/source/store_test.go b/source/store_test.go index 3b9c38498..77e29fa50 100644 --- a/source/store_test.go +++ b/source/store_test.go @@ -21,7 +21,7 @@ import ( "errors" "testing" - cfclient "github.com/cloudfoundry-community/go-cfclient" + "github.com/cloudfoundry-community/go-cfclient" openshift "github.com/openshift/client-go/route/clientset/versioned" "github.com/stretchr/testify/mock" "github.com/stretchr/testify/suite" @@ -34,6 +34,7 @@ import ( fakeDynamic "k8s.io/client-go/dynamic/fake" "k8s.io/client-go/kubernetes" fakeKube "k8s.io/client-go/kubernetes/fake" + "sigs.k8s.io/external-dns/source/types" gateway "sigs.k8s.io/gateway-api/pkg/client/clientset/versioned" ) @@ -168,7 +169,10 @@ func (suite *ByNamesTestSuite) TestAllInitialized() { }: "IngressRouteUDPList", }), nil) - sources, err := ByNames(context.TODO(), mockClientGenerator, []string{"service", "ingress", "istio-gateway", "contour-httpproxy", "kong-tcpingress", "f5-virtualserver", "f5-transportserver", "traefik-proxy", "fake"}, &Config{}) + sources, err := ByNames(context.TODO(), mockClientGenerator, []string{ + types.Service, types.Ingress, types.IstioGateway, types.ContourHTTPProxy, + types.KongTCPIngress, types.F5VirtualServer, types.F5TransportServer, types.TraefikProxy, types.Fake, + }, &Config{}) suite.NoError(err, "should not generate errors") suite.Len(sources, 9, "should generate all nine sources") } @@ -177,7 +181,7 @@ func (suite *ByNamesTestSuite) TestOnlyFake() { mockClientGenerator := new(MockClientGenerator) mockClientGenerator.On("KubeClient").Return(fakeKube.NewSimpleClientset(), nil) - sources, err := ByNames(context.TODO(), mockClientGenerator, []string{"fake"}, &Config{}) + sources, err := ByNames(context.TODO(), mockClientGenerator, []string{types.Fake}, &Config{}) suite.NoError(err, "should not generate errors") suite.Len(sources, 1, "should generate fake source") suite.Nil(mockClientGenerator.kubeClient, "client should not be created") @@ -197,9 +201,9 @@ func (suite *ByNamesTestSuite) TestKubeClientFails() { mockClientGenerator.On("KubeClient").Return(nil, errors.New("foo")) sourcesDependentOnKubeClient := []string{ - "node", "service", "ingress", "pod", "istio-gateway", "istio-virtualservice", - "ambassador-host", "gloo-proxy", "traefik-proxy", "crd", "kong-tcpingress", - "f5-virtualserver", "f5-transportserver", + types.Node, types.Service, types.Ingress, types.Pod, types.IstioGateway, types.IstioVirtualService, + types.AmbassadorHost, types.GlooProxy, types.TraefikProxy, types.CRD, types.KongTCPIngress, + types.F5VirtualServer, types.F5TransportServer, } for _, source := range sourcesDependentOnKubeClient { @@ -214,7 +218,7 @@ func (suite *ByNamesTestSuite) TestIstioClientFails() { mockClientGenerator.On("IstioClient").Return(nil, errors.New("foo")) mockClientGenerator.On("DynamicKubernetesClient").Return(nil, errors.New("foo")) - sourcesDependentOnIstioClient := []string{"istio-gateway", "istio-virtualservice"} + sourcesDependentOnIstioClient := []string{types.IstioGateway, types.IstioVirtualService} for _, source := range sourcesDependentOnIstioClient { _, err := ByNames(context.TODO(), mockClientGenerator, []string{source}, &Config{}) @@ -228,8 +232,10 @@ func (suite *ByNamesTestSuite) TestDynamicKubernetesClientFails() { mockClientGenerator.On("IstioClient").Return(istiofake.NewSimpleClientset(), nil) mockClientGenerator.On("DynamicKubernetesClient").Return(nil, errors.New("foo")) - sourcesDependentOnDynamicKubernetesClient := []string{"ambassador-host", "contour-httpproxy", "gloo-proxy", "traefik-proxy", - "kong-tcpingress", "f5-virtualserver", "f5-transportserver"} + sourcesDependentOnDynamicKubernetesClient := []string{ + types.AmbassadorHost, types.ContourHTTPProxy, types.GlooProxy, types.TraefikProxy, + types.KongTCPIngress, types.F5VirtualServer, types.F5TransportServer, + } for _, source := range sourcesDependentOnDynamicKubernetesClient { _, err := ByNames(context.TODO(), mockClientGenerator, []string{source}, &Config{}) diff --git a/source/types/types.go b/source/types/types.go new file mode 100644 index 000000000..bf95491b3 --- /dev/null +++ b/source/types/types.go @@ -0,0 +1,46 @@ +/* +Copyright 2025 The Kubernetes Authors. + +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 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +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 types + +type Type = string + +const ( + Node Type = "node" + Service Type = "service" + Ingress Type = "ingress" + Pod Type = "pod" + GatewayHttpRoute Type = "gateway-httproute" + GatewayGrpcRoute Type = "gateway-grpcroute" + GatewayTlsRoute Type = "gateway-tlsroute" + GatewayTcpRoute Type = "gateway-tcproute" + GatewayUdpRoute Type = "gateway-udproute" + IstioGateway Type = "istio-gateway" + IstioVirtualService Type = "istio-virtualservice" + Cloudfoundry Type = "cloudfoundry" + AmbassadorHost Type = "ambassador-host" + ContourHTTPProxy Type = "contour-httpproxy" + GlooProxy Type = "gloo-proxy" + TraefikProxy Type = "traefik-proxy" + OpenShiftRoute Type = "openshift-route" + Fake Type = "fake" + Connector Type = "connector" + CRD Type = "crd" + SkipperRouteGroup Type = "skipper-routegroup" + KongTCPIngress Type = "kong-tcpingress" + F5VirtualServer Type = "f5-virtualserver" + F5TransportServer Type = "f5-transportserver" +) From 468fb66758aa55e7e5ef48670e9db205a7e1e6f5 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 1 Aug 2025 00:49:40 -0700 Subject: [PATCH 10/10] chore(deps): bump the dev-dependencies group across 1 directory with 17 updates (#5704) Bumps the dev-dependencies group with 14 updates in the / directory: | Package | From | To | | --- | --- | --- | | [github.com/Azure/azure-sdk-for-go/sdk/azcore](https://github.com/Azure/azure-sdk-for-go) | `1.18.1` | `1.18.2` | | [github.com/aws/aws-sdk-go-v2](https://github.com/aws/aws-sdk-go-v2) | `1.36.6` | `1.37.1` | | [github.com/aws/aws-sdk-go-v2/config](https://github.com/aws/aws-sdk-go-v2) | `1.29.18` | `1.30.2` | | [github.com/aws/aws-sdk-go-v2/feature/dynamodb/attributevalue](https://github.com/aws/aws-sdk-go-v2) | `1.19.6` | `1.20.1` | | [github.com/aws/aws-sdk-go-v2/service/route53](https://github.com/aws/aws-sdk-go-v2) | `1.53.1` | `1.54.1` | | [github.com/aws/aws-sdk-go-v2/service/servicediscovery](https://github.com/aws/aws-sdk-go-v2) | `1.35.8` | `1.36.1` | | [github.com/civo/civogo](https://github.com/civo/civogo) | `0.6.2` | `0.6.3` | | [github.com/digitalocean/godo](https://github.com/digitalocean/godo) | `1.160.0` | `1.161.0` | | [github.com/linode/linodego](https://github.com/linode/linodego) | `1.53.0` | `1.54.0` | | [github.com/oracle/oci-go-sdk/v65](https://github.com/oracle/oci-go-sdk) | `65.96.0` | `65.97.0` | | [github.com/prometheus/client_golang](https://github.com/prometheus/client_golang) | `1.22.0` | `1.23.0` | | [google.golang.org/api](https://github.com/googleapis/google-api-go-client) | `0.243.0` | `0.244.0` | | [istio.io/api](https://github.com/istio/api) | `1.26.2` | `1.26.3` | | [istio.io/client-go](https://github.com/istio/client-go) | `1.26.2` | `1.26.3` | Updates `github.com/Azure/azure-sdk-for-go/sdk/azcore` from 1.18.1 to 1.18.2 - [Release notes](https://github.com/Azure/azure-sdk-for-go/releases) - [Changelog](https://github.com/Azure/azure-sdk-for-go/blob/main/documentation/go-mgmt-sdk-release-guideline.md) - [Commits](https://github.com/Azure/azure-sdk-for-go/compare/sdk/azcore/v1.18.1...sdk/azcore/v1.18.2) Updates `github.com/aws/aws-sdk-go-v2` from 1.36.6 to 1.37.1 - [Release notes](https://github.com/aws/aws-sdk-go-v2/releases) - [Changelog](https://github.com/aws/aws-sdk-go-v2/blob/main/changelog-template.json) - [Commits](https://github.com/aws/aws-sdk-go-v2/compare/v1.36.6...v1.37.1) Updates `github.com/aws/aws-sdk-go-v2/config` from 1.29.18 to 1.30.2 - [Release notes](https://github.com/aws/aws-sdk-go-v2/releases) - [Changelog](https://github.com/aws/aws-sdk-go-v2/blob/main/changelog-template.json) - [Commits](https://github.com/aws/aws-sdk-go-v2/compare/config/v1.29.18...v1.30.2) Updates `github.com/aws/aws-sdk-go-v2/credentials` from 1.17.71 to 1.18.2 - [Release notes](https://github.com/aws/aws-sdk-go-v2/releases) - [Changelog](https://github.com/aws/aws-sdk-go-v2/blob/config/v1.18.2/CHANGELOG.md) - [Commits](https://github.com/aws/aws-sdk-go-v2/compare/credentials/v1.17.71...config/v1.18.2) Updates `github.com/aws/aws-sdk-go-v2/feature/dynamodb/attributevalue` from 1.19.6 to 1.20.1 - [Release notes](https://github.com/aws/aws-sdk-go-v2/releases) - [Changelog](https://github.com/aws/aws-sdk-go-v2/blob/main/changelog-template.json) - [Commits](https://github.com/aws/aws-sdk-go-v2/compare/service/m2/v1.19.6...v1.20.1) Updates `github.com/aws/aws-sdk-go-v2/service/dynamodb` from 1.44.1 to 1.45.1 - [Release notes](https://github.com/aws/aws-sdk-go-v2/releases) - [Changelog](https://github.com/aws/aws-sdk-go-v2/blob/main/changelog-template.json) - [Commits](https://github.com/aws/aws-sdk-go-v2/compare/service/ecr/v1.44.1...service/s3/v1.45.1) Updates `github.com/aws/aws-sdk-go-v2/service/route53` from 1.53.1 to 1.54.1 - [Release notes](https://github.com/aws/aws-sdk-go-v2/releases) - [Changelog](https://github.com/aws/aws-sdk-go-v2/blob/main/changelog-template.json) - [Commits](https://github.com/aws/aws-sdk-go-v2/compare/service/s3/v1.53.1...service/s3/v1.54.1) Updates `github.com/aws/aws-sdk-go-v2/service/servicediscovery` from 1.35.8 to 1.36.1 - [Release notes](https://github.com/aws/aws-sdk-go-v2/releases) - [Changelog](https://github.com/aws/aws-sdk-go-v2/blob/main/changelog-template.json) - [Commits](https://github.com/aws/aws-sdk-go-v2/compare/service/kms/v1.35.8...v1.36.1) Updates `github.com/aws/aws-sdk-go-v2/service/sts` from 1.34.1 to 1.35.1 - [Release notes](https://github.com/aws/aws-sdk-go-v2/releases) - [Changelog](https://github.com/aws/aws-sdk-go-v2/blob/main/changelog-template.json) - [Commits](https://github.com/aws/aws-sdk-go-v2/compare/service/s3/v1.34.1...service/iam/v1.35.1) Updates `github.com/civo/civogo` from 0.6.2 to 0.6.3 - [Release notes](https://github.com/civo/civogo/releases) - [Changelog](https://github.com/civo/civogo/blob/master/changelog.yml) - [Commits](https://github.com/civo/civogo/compare/v0.6.2...v0.6.3) Updates `github.com/digitalocean/godo` from 1.160.0 to 1.161.0 - [Release notes](https://github.com/digitalocean/godo/releases) - [Changelog](https://github.com/digitalocean/godo/blob/main/CHANGELOG.md) - [Commits](https://github.com/digitalocean/godo/compare/v1.160.0...v1.161.0) Updates `github.com/linode/linodego` from 1.53.0 to 1.54.0 - [Release notes](https://github.com/linode/linodego/releases) - [Commits](https://github.com/linode/linodego/compare/v1.53.0...v1.54.0) Updates `github.com/oracle/oci-go-sdk/v65` from 65.96.0 to 65.97.0 - [Release notes](https://github.com/oracle/oci-go-sdk/releases) - [Changelog](https://github.com/oracle/oci-go-sdk/blob/master/CHANGELOG.md) - [Commits](https://github.com/oracle/oci-go-sdk/compare/v65.96.0...v65.97.0) Updates `github.com/prometheus/client_golang` from 1.22.0 to 1.23.0 - [Release notes](https://github.com/prometheus/client_golang/releases) - [Changelog](https://github.com/prometheus/client_golang/blob/v1.23.0/CHANGELOG.md) - [Commits](https://github.com/prometheus/client_golang/compare/v1.22.0...v1.23.0) Updates `google.golang.org/api` from 0.243.0 to 0.244.0 - [Release notes](https://github.com/googleapis/google-api-go-client/releases) - [Changelog](https://github.com/googleapis/google-api-go-client/blob/main/CHANGES.md) - [Commits](https://github.com/googleapis/google-api-go-client/compare/v0.243.0...v0.244.0) Updates `istio.io/api` from 1.26.2 to 1.26.3 - [Commits](https://github.com/istio/api/compare/1.26.2...1.26.3) Updates `istio.io/client-go` from 1.26.2 to 1.26.3 - [Commits](https://github.com/istio/client-go/compare/1.26.2...1.26.3) --- updated-dependencies: - dependency-name: github.com/Azure/azure-sdk-for-go/sdk/azcore dependency-version: 1.18.2 dependency-type: direct:production update-type: version-update:semver-patch dependency-group: dev-dependencies - dependency-name: github.com/aws/aws-sdk-go-v2 dependency-version: 1.37.1 dependency-type: direct:production update-type: version-update:semver-minor dependency-group: dev-dependencies - dependency-name: github.com/aws/aws-sdk-go-v2/config dependency-version: 1.30.2 dependency-type: direct:production update-type: version-update:semver-minor dependency-group: dev-dependencies - dependency-name: github.com/aws/aws-sdk-go-v2/credentials dependency-version: 1.18.2 dependency-type: direct:production update-type: version-update:semver-minor dependency-group: dev-dependencies - dependency-name: github.com/aws/aws-sdk-go-v2/feature/dynamodb/attributevalue dependency-version: 1.20.1 dependency-type: direct:production update-type: version-update:semver-minor dependency-group: dev-dependencies - dependency-name: github.com/aws/aws-sdk-go-v2/service/dynamodb dependency-version: 1.45.1 dependency-type: direct:production update-type: version-update:semver-minor dependency-group: dev-dependencies - dependency-name: github.com/aws/aws-sdk-go-v2/service/route53 dependency-version: 1.54.1 dependency-type: direct:production update-type: version-update:semver-minor dependency-group: dev-dependencies - dependency-name: github.com/aws/aws-sdk-go-v2/service/servicediscovery dependency-version: 1.36.1 dependency-type: direct:production update-type: version-update:semver-minor dependency-group: dev-dependencies - dependency-name: github.com/aws/aws-sdk-go-v2/service/sts dependency-version: 1.35.1 dependency-type: direct:production update-type: version-update:semver-minor dependency-group: dev-dependencies - dependency-name: github.com/civo/civogo dependency-version: 0.6.3 dependency-type: direct:production update-type: version-update:semver-patch dependency-group: dev-dependencies - dependency-name: github.com/digitalocean/godo dependency-version: 1.161.0 dependency-type: direct:production update-type: version-update:semver-minor dependency-group: dev-dependencies - dependency-name: github.com/linode/linodego dependency-version: 1.54.0 dependency-type: direct:production update-type: version-update:semver-minor dependency-group: dev-dependencies - dependency-name: github.com/oracle/oci-go-sdk/v65 dependency-version: 65.97.0 dependency-type: direct:production update-type: version-update:semver-minor dependency-group: dev-dependencies - dependency-name: github.com/prometheus/client_golang dependency-version: 1.23.0 dependency-type: direct:production update-type: version-update:semver-minor dependency-group: dev-dependencies - dependency-name: google.golang.org/api dependency-version: 0.244.0 dependency-type: direct:production update-type: version-update:semver-minor dependency-group: dev-dependencies - dependency-name: istio.io/api dependency-version: 1.26.3 dependency-type: direct:production update-type: version-update:semver-patch dependency-group: dev-dependencies - dependency-name: istio.io/client-go dependency-version: 1.26.3 dependency-type: direct:production update-type: version-update:semver-patch dependency-group: dev-dependencies ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- go.mod | 62 ++++++++++++++--------------- go.sum | 124 ++++++++++++++++++++++++++++----------------------------- 2 files changed, 93 insertions(+), 93 deletions(-) diff --git a/go.mod b/go.mod index 50708ad3f..bec49d123 100644 --- a/go.mod +++ b/go.mod @@ -4,7 +4,7 @@ go 1.24.2 require ( cloud.google.com/go/compute/metadata v0.7.0 - github.com/Azure/azure-sdk-for-go/sdk/azcore v1.18.1 + github.com/Azure/azure-sdk-for-go/sdk/azcore v1.18.2 github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.10.1 github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/dns/armdns v1.2.0 github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/privatedns/armprivatedns v1.3.0 @@ -13,23 +13,23 @@ require ( github.com/akamai/AkamaiOPEN-edgegrid-golang v1.2.2 github.com/alecthomas/kingpin/v2 v2.4.0 github.com/aliyun/alibaba-cloud-sdk-go v1.63.107 - github.com/aws/aws-sdk-go-v2 v1.36.6 - github.com/aws/aws-sdk-go-v2/config v1.29.18 - github.com/aws/aws-sdk-go-v2/credentials v1.17.71 - github.com/aws/aws-sdk-go-v2/feature/dynamodb/attributevalue v1.19.6 - github.com/aws/aws-sdk-go-v2/service/dynamodb v1.44.1 - github.com/aws/aws-sdk-go-v2/service/route53 v1.53.1 - github.com/aws/aws-sdk-go-v2/service/servicediscovery v1.35.8 - github.com/aws/aws-sdk-go-v2/service/sts v1.34.1 + github.com/aws/aws-sdk-go-v2 v1.37.1 + github.com/aws/aws-sdk-go-v2/config v1.30.2 + github.com/aws/aws-sdk-go-v2/credentials v1.18.2 + github.com/aws/aws-sdk-go-v2/feature/dynamodb/attributevalue v1.20.1 + github.com/aws/aws-sdk-go-v2/service/dynamodb v1.45.1 + github.com/aws/aws-sdk-go-v2/service/route53 v1.54.1 + github.com/aws/aws-sdk-go-v2/service/servicediscovery v1.36.1 + github.com/aws/aws-sdk-go-v2/service/sts v1.35.1 github.com/bodgit/tsig v1.2.2 github.com/cenkalti/backoff/v5 v5.0.3 - github.com/civo/civogo v0.6.2 + github.com/civo/civogo v0.6.3 github.com/cloudflare/cloudflare-go v0.115.0 github.com/cloudflare/cloudflare-go/v4 v4.6.0 github.com/cloudfoundry-community/go-cfclient v0.0.0-20190201205600-f136f9222381 github.com/datawire/ambassador v1.12.4 github.com/denverdino/aliyungo v0.0.0-20230411124812-ab98a9173ace - github.com/digitalocean/godo v1.160.0 + github.com/digitalocean/godo v1.161.0 github.com/dnsimple/dnsimple-go v1.7.0 github.com/exoscale/egoscale v0.102.3 github.com/ffledgling/pdns-go v0.0.0-20180219074714-524e7daccd99 @@ -38,17 +38,17 @@ require ( github.com/goccy/go-yaml v1.18.0 github.com/google/go-cmp v0.7.0 github.com/google/uuid v1.6.0 - github.com/linode/linodego v1.53.0 + github.com/linode/linodego v1.54.0 github.com/maxatome/go-testdeep v1.14.0 github.com/miekg/dns v1.1.67 github.com/openshift/api v0.0.0-20230607130528-611114dca681 github.com/openshift/client-go v0.0.0-20230607134213-3cd0021bbee3 - github.com/oracle/oci-go-sdk/v65 v65.96.0 + github.com/oracle/oci-go-sdk/v65 v65.97.0 github.com/ovh/go-ovh v1.9.0 github.com/patrickmn/go-cache v2.1.0+incompatible github.com/pluralsh/gqlclient v1.12.2 github.com/projectcontour/contour v1.32.0 - github.com/prometheus/client_golang v1.22.0 + github.com/prometheus/client_golang v1.23.0 github.com/prometheus/client_model v0.6.2 github.com/prometheus/common v0.65.0 github.com/scaleway/scaleway-sdk-go v1.0.0-beta.34 @@ -63,10 +63,10 @@ require ( golang.org/x/sync v0.16.0 golang.org/x/text v0.27.0 golang.org/x/time v0.12.0 - google.golang.org/api v0.243.0 + google.golang.org/api v0.244.0 gopkg.in/ns1/ns1-go.v2 v2.14.4 - istio.io/api v1.26.2 - istio.io/client-go v1.26.2 + istio.io/api v1.26.3 + istio.io/client-go v1.26.3 k8s.io/api v0.33.3 k8s.io/apimachinery v0.33.3 k8s.io/client-go v0.33.3 @@ -80,22 +80,22 @@ require ( cloud.google.com/go/auth/oauth2adapt v0.2.8 // indirect code.cloudfoundry.org/gofileutils v0.0.0-20170111115228-4d0c80011a0f // indirect github.com/99designs/gqlgen v0.17.73 // indirect - github.com/Azure/azure-sdk-for-go/sdk/internal v1.11.1 // indirect + github.com/Azure/azure-sdk-for-go/sdk/internal v1.11.2 // indirect github.com/AzureAD/microsoft-authentication-library-for-go v1.4.2 // indirect github.com/Masterminds/semver v1.5.0 // indirect github.com/alecthomas/units v0.0.0-20211218093645-b94a6e3cc137 // indirect github.com/alexbrainman/sspi v0.0.0-20180613141037-e580b900e9f5 // indirect - github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.16.33 // indirect - github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.37 // indirect - github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.37 // indirect + github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.18.1 // indirect + github.com/aws/aws-sdk-go-v2/internal/configsources v1.4.1 // indirect + github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.7.1 // indirect github.com/aws/aws-sdk-go-v2/internal/ini v1.8.3 // indirect - github.com/aws/aws-sdk-go-v2/service/dynamodbstreams v1.26.1 // indirect - github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.12.4 // indirect - github.com/aws/aws-sdk-go-v2/service/internal/endpoint-discovery v1.10.18 // indirect - github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.12.18 // indirect - github.com/aws/aws-sdk-go-v2/service/sso v1.25.6 // indirect - github.com/aws/aws-sdk-go-v2/service/ssooidc v1.30.4 // indirect - github.com/aws/smithy-go v1.22.4 // indirect + github.com/aws/aws-sdk-go-v2/service/dynamodbstreams v1.27.1 // indirect + github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.13.0 // indirect + github.com/aws/aws-sdk-go-v2/service/internal/endpoint-discovery v1.11.1 // indirect + github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.13.1 // indirect + github.com/aws/aws-sdk-go-v2/service/sso v1.26.1 // indirect + github.com/aws/aws-sdk-go-v2/service/ssooidc v1.31.1 // indirect + github.com/aws/smithy-go v1.22.5 // indirect github.com/benbjohnson/clock v1.3.0 // indirect github.com/beorn7/perks v1.0.1 // indirect github.com/cespare/xxhash/v2 v2.3.0 // indirect @@ -154,7 +154,7 @@ require ( github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c // indirect github.com/pkg/errors v0.9.1 // indirect github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect - github.com/prometheus/procfs v0.15.1 // indirect + github.com/prometheus/procfs v0.16.1 // indirect github.com/rivo/uniseg v0.2.0 // indirect github.com/schollz/progressbar/v3 v3.8.6 // indirect github.com/shopspring/decimal v1.3.1 // indirect @@ -185,8 +185,8 @@ require ( golang.org/x/term v0.33.0 // indirect golang.org/x/tools v0.34.0 // indirect google.golang.org/genproto/googleapis/api v0.0.0-20250603155806-513f23925822 // indirect - google.golang.org/genproto/googleapis/rpc v0.0.0-20250715232539-7130f93afb79 // indirect - google.golang.org/grpc v1.73.0 // indirect + google.golang.org/genproto/googleapis/rpc v0.0.0-20250728155136-f173205681a0 // indirect + google.golang.org/grpc v1.74.2 // indirect google.golang.org/protobuf v1.36.6 // indirect gopkg.in/evanphx/json-patch.v4 v4.12.0 // indirect gopkg.in/inf.v0 v0.9.1 // indirect diff --git a/go.sum b/go.sum index 5c773b789..e6be1ad7f 100644 --- a/go.sum +++ b/go.sum @@ -16,14 +16,14 @@ github.com/99designs/gqlgen v0.17.73 h1:A3Ki+rHWqKbAOlg5fxiZBnz6OjW3nwupDHEG15gE github.com/99designs/gqlgen v0.17.73/go.mod h1:2RyGWjy2k7W9jxrs8MOQthXGkD3L3oGr0jXW3Pu8lGg= github.com/Azure/azure-sdk-for-go v16.2.1+incompatible h1:KnPIugL51v3N3WwvaSmZbxukD1WuWXOiE9fRdu32f2I= github.com/Azure/azure-sdk-for-go v16.2.1+incompatible/go.mod h1:9XXNKU+eRnpl9moKnB4QOLf1HestfXbmab5FXxiDBjc= -github.com/Azure/azure-sdk-for-go/sdk/azcore v1.18.1 h1:Wc1ml6QlJs2BHQ/9Bqu1jiyggbsSjramq2oUmp5WeIo= -github.com/Azure/azure-sdk-for-go/sdk/azcore v1.18.1/go.mod h1:Ot/6aikWnKWi4l9QB7qVSwa8iMphQNqkWALMoNT3rzM= +github.com/Azure/azure-sdk-for-go/sdk/azcore v1.18.2 h1:Hr5FTipp7SL07o2FvoVOX9HRiRH3CR3Mj8pxqCcdD5A= +github.com/Azure/azure-sdk-for-go/sdk/azcore v1.18.2/go.mod h1:QyVsSSN64v5TGltphKLQ2sQxe4OBQg0J1eKRcVBnfgE= github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.10.1 h1:B+blDbyVIG3WaikNxPnhPiJ1MThR03b3vKGtER95TP4= github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.10.1/go.mod h1:JdM5psgjfBf5fo2uWOZhflPWyDBZ/O/CNAH9CtsuZE4= github.com/Azure/azure-sdk-for-go/sdk/azidentity/cache v0.3.2 h1:yz1bePFlP5Vws5+8ez6T3HWXPmwOK7Yvq8QxDBD3SKY= github.com/Azure/azure-sdk-for-go/sdk/azidentity/cache v0.3.2/go.mod h1:Pa9ZNPuoNu/GztvBSKk9J1cDJW6vk/n0zLtV4mgd8N8= -github.com/Azure/azure-sdk-for-go/sdk/internal v1.11.1 h1:FPKJS1T+clwv+OLGt13a8UjqeRuh0O4SJ3lUriThc+4= -github.com/Azure/azure-sdk-for-go/sdk/internal v1.11.1/go.mod h1:j2chePtV91HrC22tGoRX3sGY42uF13WzmmV80/OdVAA= +github.com/Azure/azure-sdk-for-go/sdk/internal v1.11.2 h1:9iefClla7iYpfYWdzPCRDozdmndjTm8DXdpCzPajMgA= +github.com/Azure/azure-sdk-for-go/sdk/internal v1.11.2/go.mod h1:XtLgD3ZD34DAaVIIAyG3objl5DynM3CQ/vMcbBNJZGI= github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/dns/armdns v1.2.0 h1:lpOxwrQ919lCZoNCd69rVt8u1eLZuMORrGXqy8sNf3c= github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/dns/armdns v1.2.0/go.mod h1:fSvRkb8d26z9dbL40Uf/OO6Vo9iExtZK3D0ulRV+8M0= github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/internal/v3 v3.1.0 h1:2qsIIvxVT+uE6yrNldntJKlLRgxGbZ85kgtz5SNBhMw= @@ -114,44 +114,44 @@ github.com/aws/aws-lambda-go v1.13.3/go.mod h1:4UKl9IzQMoD+QF79YdCuzCwp8VbmG4VAQ github.com/aws/aws-sdk-go v1.15.11/go.mod h1:mFuSZ37Z9YOHbQEwBWztmVzqXrEkub65tZoCYDt7FT0= github.com/aws/aws-sdk-go v1.27.0/go.mod h1:KmX6BPdI08NWTb3/sm4ZGu5ShLoqVDhKgpiN924inxo= github.com/aws/aws-sdk-go-v2 v0.18.0/go.mod h1:JWVYvqSMppoMJC0x5wdwiImzgXTI9FuZwxzkQq9wy+g= -github.com/aws/aws-sdk-go-v2 v1.36.6 h1:zJqGjVbRdTPojeCGWn5IR5pbJwSQSBh5RWFTQcEQGdU= -github.com/aws/aws-sdk-go-v2 v1.36.6/go.mod h1:EYrzvCCN9CMUTa5+6lf6MM4tq3Zjp8UhSGR/cBsjai0= -github.com/aws/aws-sdk-go-v2/config v1.29.18 h1:x4T1GRPnqKV8HMJOMtNktbpQMl3bIsfx8KbqmveUO2I= -github.com/aws/aws-sdk-go-v2/config v1.29.18/go.mod h1:bvz8oXugIsH8K7HLhBv06vDqnFv3NsGDt2Znpk7zmOU= -github.com/aws/aws-sdk-go-v2/credentials v1.17.71 h1:r2w4mQWnrTMJjOyIsZtGp3R3XGY3nqHn8C26C2lQWgA= -github.com/aws/aws-sdk-go-v2/credentials v1.17.71/go.mod h1:E7VF3acIup4GB5ckzbKFrCK0vTvEQxOxgdq4U3vcMCY= -github.com/aws/aws-sdk-go-v2/feature/dynamodb/attributevalue v1.19.6 h1:gBfrCR6IwAhmx+oCf9i9FJo1+Cxx5f0In+PaYQbkqbU= -github.com/aws/aws-sdk-go-v2/feature/dynamodb/attributevalue v1.19.6/go.mod h1:zAO6MqUum/2yfE/Ig1LPPtzCBudQtrGBaz1gcNzgAoY= -github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.16.33 h1:D9ixiWSG4lyUBL2DDNK924Px9V/NBVpML90MHqyTADY= -github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.16.33/go.mod h1:caS/m4DI+cij2paz3rtProRBI4s/+TCiWoaWZuQ9010= -github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.37 h1:osMWfm/sC/L4tvEdQ65Gri5ZZDCUpuYJZbTTDrsn4I0= -github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.37/go.mod h1:ZV2/1fbjOPr4G4v38G3Ww5TBT4+hmsK45s/rxu1fGy0= -github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.37 h1:v+X21AvTb2wZ+ycg1gx+orkB/9U6L7AOp93R7qYxsxM= -github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.37/go.mod h1:G0uM1kyssELxmJ2VZEfG0q2npObR3BAkF3c1VsfVnfs= +github.com/aws/aws-sdk-go-v2 v1.37.1 h1:SMUxeNz3Z6nqGsXv0JuJXc8w5YMtrQMuIBmDx//bBDY= +github.com/aws/aws-sdk-go-v2 v1.37.1/go.mod h1:9Q0OoGQoboYIAJyslFyF1f5K1Ryddop8gqMhWx/n4Wg= +github.com/aws/aws-sdk-go-v2/config v1.30.2 h1:YE1BmSc4fFYqFgN1mN8uzrtc7R9x+7oSWeX8ckoltAw= +github.com/aws/aws-sdk-go-v2/config v1.30.2/go.mod h1:UNrLGZ6jfAVjgVJpkIxjLufRJqTXCVYOpkeVf83kwBo= +github.com/aws/aws-sdk-go-v2/credentials v1.18.2 h1:mfm0GKY/PHLhs7KO0sUaOtFnIQ15Qqxt+wXbO/5fIfs= +github.com/aws/aws-sdk-go-v2/credentials v1.18.2/go.mod h1:v0SdJX6ayPeZFQxgXUKw5RhLpAoZUuynxWDfh8+Eknc= +github.com/aws/aws-sdk-go-v2/feature/dynamodb/attributevalue v1.20.1 h1:1ToPL5M0nYwkIOTb9r+ION0ZZe9xemRe1mRMWMw5ihs= +github.com/aws/aws-sdk-go-v2/feature/dynamodb/attributevalue v1.20.1/go.mod h1:dDdNpGWZdj4AxADkfM1IG1IutBmSJM7zURhUNOVv/lE= +github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.18.1 h1:owmNBboeA0kHKDcdF8KiSXmrIuXZustfMGGytv6OMkM= +github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.18.1/go.mod h1:Bg1miN59SGxrZqlP8vJZSmXW+1N8Y1MjQDq1OfuNod8= +github.com/aws/aws-sdk-go-v2/internal/configsources v1.4.1 h1:ksZXBYv80EFTcgc8OJO48aQ8XDWXIQL7gGasPeCoTzI= +github.com/aws/aws-sdk-go-v2/internal/configsources v1.4.1/go.mod h1:HSksQyyJETVZS7uM54cir0IgxttTD+8aEoJMPGepHBI= +github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.7.1 h1:+dn/xF/05utS7tUhjIcndbuaPjfll2LhbH1cCDGLYUQ= +github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.7.1/go.mod h1:hyAGz30LHdm5KBZDI58MXx5lDVZ5CUfvfTZvMu4HCZo= github.com/aws/aws-sdk-go-v2/internal/ini v1.8.3 h1:bIqFDwgGXXN1Kpp99pDOdKMTTb5d2KyU5X/BZxjOkRo= github.com/aws/aws-sdk-go-v2/internal/ini v1.8.3/go.mod h1:H5O/EsxDWyU+LP/V8i5sm8cxoZgc2fdNR9bxlOFrQTo= -github.com/aws/aws-sdk-go-v2/service/dynamodb v1.44.1 h1:UoEWyfuQ/yNOuDENk5nn+AgNCH2Y5yzQEv6YbTyhIV8= -github.com/aws/aws-sdk-go-v2/service/dynamodb v1.44.1/go.mod h1:K1I47BjiTRX00pBxfJLYK80QFRcf6blev2wbjgC5Cyc= -github.com/aws/aws-sdk-go-v2/service/dynamodbstreams v1.26.1 h1:WD2RDt93+IgNvlxEKkx/b3BQrpw5G/YpDHvGXweO5wE= -github.com/aws/aws-sdk-go-v2/service/dynamodbstreams v1.26.1/go.mod h1:8ZWruWnVWtJwjSHEtMWFcI1W6L6PD6i+uKCJ9EiJBbE= -github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.12.4 h1:CXV68E2dNqhuynZJPB80bhPQwAKqBWVer887figW6Jc= -github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.12.4/go.mod h1:/xFi9KtvBXP97ppCz1TAEvU1Uf66qvid89rbem3wCzQ= -github.com/aws/aws-sdk-go-v2/service/internal/endpoint-discovery v1.10.18 h1:QnGWwpTiazs1Y74RwA8VUfAtKuJQbnQ98DBFnSywj0s= -github.com/aws/aws-sdk-go-v2/service/internal/endpoint-discovery v1.10.18/go.mod h1:gWOI6Vb0Bbmsi0Ejvtt3RkwKpdoa/SOYTVUlzqYPRLc= -github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.12.18 h1:vvbXsA2TVO80/KT7ZqCbx934dt6PY+vQ8hZpUZ/cpYg= -github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.12.18/go.mod h1:m2JJHledjBGNMsLOF1g9gbAxprzq3KjC8e4lxtn+eWg= -github.com/aws/aws-sdk-go-v2/service/route53 v1.53.1 h1:R3nSX1hguRy6MnknHiepSvqnnL8ansFwK2hidPesAYU= -github.com/aws/aws-sdk-go-v2/service/route53 v1.53.1/go.mod h1:fmSiB4OAghn85lQgk7XN9l9bpFg5Bm1v3HuaXKytPEw= -github.com/aws/aws-sdk-go-v2/service/servicediscovery v1.35.8 h1:PPQUm3zG6XzctspDTWC6vO3DvP/RZ+04RB11r98yb6E= -github.com/aws/aws-sdk-go-v2/service/servicediscovery v1.35.8/go.mod h1:C1n2zhotURaNj/BNgdPdhXh/i6V53rI3RmVEaNDakSM= -github.com/aws/aws-sdk-go-v2/service/sso v1.25.6 h1:rGtWqkQbPk7Bkwuv3NzpE/scwwL9sC1Ul3tn9x83DUI= -github.com/aws/aws-sdk-go-v2/service/sso v1.25.6/go.mod h1:u4ku9OLv4TO4bCPdxf4fA1upaMaJmP9ZijGk3AAOC6Q= -github.com/aws/aws-sdk-go-v2/service/ssooidc v1.30.4 h1:OV/pxyXh+eMA0TExHEC4jyWdumLxNbzz1P0zJoezkJc= -github.com/aws/aws-sdk-go-v2/service/ssooidc v1.30.4/go.mod h1:8Mm5VGYwtm+r305FfPSuc+aFkrypeylGYhFim6XEPoc= -github.com/aws/aws-sdk-go-v2/service/sts v1.34.1 h1:aUrLQwJfZtwv3/ZNG2xRtEen+NqI3iesuacjP51Mv1s= -github.com/aws/aws-sdk-go-v2/service/sts v1.34.1/go.mod h1:3wFBZKoWnX3r+Sm7in79i54fBmNfwhdNdQuscCw7QIk= -github.com/aws/smithy-go v1.22.4 h1:uqXzVZNuNexwc/xrh6Tb56u89WDlJY6HS+KC0S4QSjw= -github.com/aws/smithy-go v1.22.4/go.mod h1:t1ufH5HMublsJYulve2RKmHDC15xu1f26kHCp/HgceI= +github.com/aws/aws-sdk-go-v2/service/dynamodb v1.45.1 h1:gFD9BLrXox2Q5zxFwyD2OnGb40YYofQ/anaGxVP848Q= +github.com/aws/aws-sdk-go-v2/service/dynamodb v1.45.1/go.mod h1:J+qJkxNypYjDcwXldBH+ox2T7OshtP6LOq5VhU0v6hg= +github.com/aws/aws-sdk-go-v2/service/dynamodbstreams v1.27.1 h1:H4W48E0/zjiHLlL59/Y0DpaB+krXsuarjwrquCwMtT4= +github.com/aws/aws-sdk-go-v2/service/dynamodbstreams v1.27.1/go.mod h1:nGsqtVMMjTeFot6U+rLj+mpOcZybPoxyQPMKY4GHwQo= +github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.13.0 h1:6+lZi2JeGKtCraAj1rpoZfKqnQ9SptseRZioejfUOLM= +github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.13.0/go.mod h1:eb3gfbVIxIoGgJsi9pGne19dhCBpK6opTYpQqAmdy44= +github.com/aws/aws-sdk-go-v2/service/internal/endpoint-discovery v1.11.1 h1:/E4JUPMI8LRX2XpXsbmKN42l1lZPoLjGJ/Kun97pLc0= +github.com/aws/aws-sdk-go-v2/service/internal/endpoint-discovery v1.11.1/go.mod h1:qgbd/t8S8y5e87KPQ4kC0kyxZ0K6nC1QiDtFMoxlsOo= +github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.13.1 h1:ky79ysLMxhwk5rxJtS+ILd3Mc8kC5fhsLBrP27r6h4I= +github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.13.1/go.mod h1:+2MmkvFvPYM1vsozBWduoLJUi5maxFk5B7KJFECujhY= +github.com/aws/aws-sdk-go-v2/service/route53 v1.54.1 h1:DvwcqU6ec5NNCACSSEYKuTg9J3PDFFlngkwV0k7wvaI= +github.com/aws/aws-sdk-go-v2/service/route53 v1.54.1/go.mod h1:POH50FEbIpazXJUVj2hbpJT819o2UF547G+BJBM7HQM= +github.com/aws/aws-sdk-go-v2/service/servicediscovery v1.36.1 h1:EqupyVMtt84ZljchBzq+X+pPwuYhUT7dzfBoDqC0DB4= +github.com/aws/aws-sdk-go-v2/service/servicediscovery v1.36.1/go.mod h1:HrkmhW8FU7GObElHC6Lm3sosolbig21w00VOm77Vsss= +github.com/aws/aws-sdk-go-v2/service/sso v1.26.1 h1:uWaz3DoNK9MNhm7i6UGxqufwu3BEuJZm72WlpGwyVtY= +github.com/aws/aws-sdk-go-v2/service/sso v1.26.1/go.mod h1:ILpVNjL0BO+Z3Mm0SbEeUoYS9e0eJWV1BxNppp0fcb8= +github.com/aws/aws-sdk-go-v2/service/ssooidc v1.31.1 h1:XdG6/o1/ZDmn3wJU5SRAejHaWgKS4zHv0jBamuKuS2k= +github.com/aws/aws-sdk-go-v2/service/ssooidc v1.31.1/go.mod h1:oiotGTKadCOCl3vg/tYh4k45JlDF81Ka8rdumNhEnIQ= +github.com/aws/aws-sdk-go-v2/service/sts v1.35.1 h1:iF4Xxkc0H9c/K2dS0zZw3SCkj0Z7n6AMnUiiyoJND+I= +github.com/aws/aws-sdk-go-v2/service/sts v1.35.1/go.mod h1:0bxIatfN0aLq4mjoLDeBpOjOke68OsFlXPDFJ7V0MYw= +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/benbjohnson/clock v1.3.0 h1:ip6w0uFQkncKQ979AypyG0ER7mqUSBdKLOgAle/AT8A= github.com/benbjohnson/clock v1.3.0/go.mod h1:J11/hYXuz8f4ySSvYwY0FKfm+ezbsZBKZxNJlLklBHA= github.com/beorn7/perks v0.0.0-20160804104726-4c0e84591b9a/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q= @@ -180,8 +180,8 @@ github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XL 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/chai2010/gettext-go v0.0.0-20160711120539-c6fed771bfd5/go.mod h1:/iP1qXHoty45bqomnu2LM+VVyAEdWN+vtSHGlQgyxbw= -github.com/civo/civogo v0.6.2 h1:tQegf+coNxIKhLjOo5bwAV04CPSk6ealSod55XHb7cw= -github.com/civo/civogo v0.6.2/go.mod h1:LaEbkszc+9nXSh4YNG0sYXFGYqdQFmXXzQg0gESs2hc= +github.com/civo/civogo v0.6.3 h1:AgTJa2C8Q6q+vFfsaa41vVZBQXVOiUdb/JbWpNuDlc8= +github.com/civo/civogo v0.6.3/go.mod h1:LaEbkszc+9nXSh4YNG0sYXFGYqdQFmXXzQg0gESs2hc= github.com/clbanning/x2j v0.0.0-20191024224557-825249438eec/go.mod h1:jMjuTZXRI4dUb/I5gc9Hdhagfvm9+RyrPryS/auMzxE= github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= github.com/cloudflare/cloudflare-go v0.115.0 h1:84/dxeeXweCc0PN5Cto44iTA8AkG1fyT11yPO5ZB7sM= @@ -252,8 +252,8 @@ github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZm github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f h1:lO4WD4F/rVNCu3HqELle0jiPLLBs70cWOduZpkS1E78= github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f/go.mod h1:cuUVRXasLTGF7a8hSLbxyZXjz+1KgoB3wDUb6vlszIc= github.com/dgryski/go-sip13 v0.0.0-20181026042036-e10d5fee7954/go.mod h1:vAd38F8PWV+bWy6jNmig1y/TA+kYO4g3RSRF0IAv0no= -github.com/digitalocean/godo v1.160.0 h1:3Wa6mOzv1m5DZQDANAk8u6v4DIUm5x2i4tZ7ke28lhs= -github.com/digitalocean/godo v1.160.0/go.mod h1:tYeiWY5ZXVpU48YaFv0M5irUFHXGorZpDNm7zzdWMzM= +github.com/digitalocean/godo v1.161.0 h1:Q/3ImcotZp0GV9FY/dnLj9TmfOd+a7ZN/UNuhgDHI/Q= +github.com/digitalocean/godo v1.161.0/go.mod h1:tYeiWY5ZXVpU48YaFv0M5irUFHXGorZpDNm7zzdWMzM= github.com/dnaeon/go-vcr v1.0.1/go.mod h1:aBB1+wY4s93YsC3HHjMBMrwTj2R9FHDzUr9KyGc8n1E= github.com/dnsimple/dnsimple-go v1.7.0 h1:JKu9xJtZ3SqOC+BuYgAWeab7+EEx0sz422vu8j611ZY= github.com/dnsimple/dnsimple-go v1.7.0/go.mod h1:EKpuihlWizqYafSnQHGCd/gyvy3HkEQJ7ODB4KdV8T8= @@ -676,8 +676,8 @@ github.com/lib/pq v1.7.0/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o= github.com/liggitt/tabwriter v0.0.0-20181228230101-89fcab3d43de/go.mod h1:zAbeS9B/r2mtpb6U+EI2rYA5OAXxsYw6wTamcNW+zcE= github.com/lightstep/lightstep-tracer-common/golang/gogo v0.0.0-20190605223551-bc2310a04743/go.mod h1:qklhhLq1aX+mtWk9cPHPzaBjWImj5ULL6C7HFJtXQMM= github.com/lightstep/lightstep-tracer-go v0.18.1/go.mod h1:jlF1pusYV4pidLvZ+XD0UBX0ZE6WURAspgAczcDHrL4= -github.com/linode/linodego v1.53.0 h1:UWr7bUUVMtcfsuapC+6blm6+jJLPd7Tf9MZUpdOERnI= -github.com/linode/linodego v1.53.0/go.mod h1:bI949fZaVchjWyKIA08hNyvAcV6BAS+PM2op3p7PAWA= +github.com/linode/linodego v1.54.0 h1:29vTV5YjqjjwPxWLE8Qp1zgDDXM5ifAQ2T6azAYsj/w= +github.com/linode/linodego v1.54.0/go.mod h1:VHlFAbhj18634Cd7B7L5D723kFKFQMOxzIutSMcWsB4= github.com/lithammer/dedent v1.1.0/go.mod h1:jrXYCQtgg0nJiN+StA2KgR7w6CiQNv9Fd/Z9BP0jIOc= github.com/lyft/protoc-gen-star v0.4.10/go.mod h1:mE8fbna26u7aEA2QCVvvfBU/ZrPgocG1206xAFPcs94= github.com/lyft/protoc-gen-validate v0.0.13/go.mod h1:XbGvPuh87YZc5TdIa2/I4pLk0QoUACkjt2znoq26NVQ= @@ -819,8 +819,8 @@ github.com/openzipkin-contrib/zipkin-go-opentracing v0.4.5/go.mod h1:/wsWhb9smxS github.com/openzipkin/zipkin-go v0.1.6/go.mod h1:QgAqvLzwWbR/WpD4A3cGpPtJrZXNIiJc5AZX7/PBEpw= github.com/openzipkin/zipkin-go v0.2.1/go.mod h1:NaW6tEwdmWMaCDZzg8sh+IBNOxHMPnhQw8ySjnjRyN4= github.com/openzipkin/zipkin-go v0.2.2/go.mod h1:NaW6tEwdmWMaCDZzg8sh+IBNOxHMPnhQw8ySjnjRyN4= -github.com/oracle/oci-go-sdk/v65 v65.96.0 h1:ew0WavsB6N/I6etYCC160cD5qDXbek/1xZgujqTzork= -github.com/oracle/oci-go-sdk/v65 v65.96.0/go.mod h1:u6XRPsw9tPziBh76K7GrrRXPa8P8W3BQeqJ6ZZt9VLA= +github.com/oracle/oci-go-sdk/v65 v65.97.0 h1:QyOOg/qCIY6vBSD+GHUfEQaJk9Wbl8a6VESkXfZ0fYo= +github.com/oracle/oci-go-sdk/v65 v65.97.0/go.mod h1:u6XRPsw9tPziBh76K7GrrRXPa8P8W3BQeqJ6ZZt9VLA= github.com/ovh/go-ovh v1.9.0 h1:6K8VoL3BYjVV3In9tPJUdT7qMx9h0GExN9EXx1r2kKE= github.com/ovh/go-ovh v1.9.0/go.mod h1:cTVDnl94z4tl8pP1uZ/8jlVxntjSIf09bNcQ5TJSC7c= github.com/oxtoacart/bpool v0.0.0-20150712133111-4e1c5567d7c2 h1:CXwSGu/LYmbjEab5aMCs5usQRVBGThelUKBNnoSOuso= @@ -864,8 +864,8 @@ github.com/prometheus/client_golang v0.9.3/go.mod h1:/TN21ttK/J9q6uSwhBd54HahCDf github.com/prometheus/client_golang v1.0.0/go.mod h1:db9x61etRT2tGnBNRi70OPL5FsnadC4Ky3P0J6CfImo= github.com/prometheus/client_golang v1.3.0/go.mod h1:hJaj2vgQTGQmVCsAACORcieXFeDPbaTKGT+JTgUa3og= github.com/prometheus/client_golang v1.6.0/go.mod h1:ZLOG9ck3JLRdB5MgO8f+lLTe83AXG6ro35rLTxvnIl4= -github.com/prometheus/client_golang v1.22.0 h1:rb93p9lokFEsctTys46VnV1kLCDpVZ0a/Y92Vm0Zc6Q= -github.com/prometheus/client_golang v1.22.0/go.mod h1:R7ljNsLXhuQXYZYtw6GAE9AZg8Y7vEW5scdCXrWRXC0= +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_model v0.0.0-20171117100541-99fa1f4be8e5/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo= github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo= github.com/prometheus/client_model v0.0.0-20190115171406-56726106282f/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo= @@ -892,8 +892,8 @@ github.com/prometheus/procfs v0.0.2/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsT github.com/prometheus/procfs v0.0.5/go.mod h1:4A/X28fw3Fc593LaREMrKMqOKvUAntwMDaekg4FpcdQ= github.com/prometheus/procfs v0.0.8/go.mod h1:7Qr8sr6344vo1JqZ6HhLceV9o3AJ1Ff+GxbHq6oeK9A= github.com/prometheus/procfs v0.0.11/go.mod h1:lV6e/gmhEcM9IjHGsFOCxxuZ+z1YqCvr4OA4YeYWdaU= -github.com/prometheus/procfs v0.15.1 h1:YagwOFzUgYfKKHX6Dr+sHT7km/hxC76UB0learggepc= -github.com/prometheus/procfs v0.15.1/go.mod h1:fB45yRUv8NstnjriLhBQLuOUt+WW4BsoGhij/e3PBqk= +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/tsdb v0.7.1/go.mod h1:qhTCs0VvXwvX/y3TZrWD7rabWM+ijKTux40TwIPHuXU= github.com/rcrowley/go-metrics v0.0.0-20181016184325-3113b8401b8a/go.mod h1:bCqnVzQkZxMG4s8nGwiZ5l3QUCyqpo9Y+/ZMZ9VjZe4= github.com/redis/go-redis/v9 v9.8.0 h1:q3nRvjrlge/6UD7eTu/DSg2uYiU2mCL0G/uzBWqhicI= @@ -1350,8 +1350,8 @@ gonum.org/v1/plot v0.0.0-20190515093506-e2840ee46a6b/go.mod h1:Wt8AAjI+ypCyYX3nZ google.golang.org/api v0.0.0-20160322025152-9bf6e6e569ff/go.mod h1:4mhQ8q/RsB7i+udVvVy5NUi08OU8ZlA0gRVgrF7VFY0= google.golang.org/api v0.3.1/go.mod h1:6wY9I6uQWHQ8EM57III9mq/AjF+i8G65rmVagqKMtkk= google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE= -google.golang.org/api v0.243.0 h1:sw+ESIJ4BVnlJcWu9S+p2Z6Qq1PjG77T8IJ1xtp4jZQ= -google.golang.org/api v0.243.0/go.mod h1:GE4QtYfaybx1KmeHMdBnNnyLzBZCVihGBXAmJu/uUr8= +google.golang.org/api v0.244.0 h1:lpkP8wVibSKr++NCD36XzTk/IzeKJ3klj7vbj+XU5pE= +google.golang.org/api v0.244.0/go.mod h1:dMVhVcylamkirHdzEBAIQWUCgqY885ivNeZYd7VAVr8= google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= google.golang.org/appengine v1.2.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= @@ -1369,8 +1369,8 @@ google.golang.org/genproto v0.0.0-20250603155806-513f23925822 h1:rHWScKit0gvAPuO google.golang.org/genproto v0.0.0-20250603155806-513f23925822/go.mod h1:HubltRL7rMh0LfnQPkMH4NPDFEWp0jw3vixw7jEM53s= 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-20250715232539-7130f93afb79 h1:1ZwqphdOdWYXsUHgMpU/101nCtf/kSp9hOrcvFsnl10= -google.golang.org/genproto/googleapis/rpc v0.0.0-20250715232539-7130f93afb79/go.mod h1:qQ0YXyHHx3XkvlzUtpXDkS29lDSafHMZBAZDc03LQ3A= +google.golang.org/genproto/googleapis/rpc v0.0.0-20250728155136-f173205681a0 h1:MAKi5q709QWfnkkpNQ0M12hYJ1+e8qYVDyowc4U1XZM= +google.golang.org/genproto/googleapis/rpc v0.0.0-20250728155136-f173205681a0/go.mod h1:qQ0YXyHHx3XkvlzUtpXDkS29lDSafHMZBAZDc03LQ3A= google.golang.org/grpc v0.0.0-20160317175043-d3ddb4469d5a/go.mod h1:yo6s7OP7yaDglbqo1J04qKzAhqBH6lvTonzMVmEdcZw= google.golang.org/grpc v1.17.0/go.mod h1:6QZJwpn2B+Zp71q/5VxRsJ6NXXVCE5NRUHRo+f3cWCs= google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= @@ -1384,8 +1384,8 @@ google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQ google.golang.org/grpc v1.26.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= google.golang.org/grpc v1.29.1/go.mod h1:itym6AZVZYACWQqET3MqgPpjcuV5QH3BxFS3IjizoKk= -google.golang.org/grpc v1.73.0 h1:VIWSmpI2MegBtTuFt5/JWy2oXxtjJ/e89Z70ImfD2ok= -google.golang.org/grpc v1.73.0/go.mod h1:50sbHOUqWoCQGI8V2HQLJM0B+LMlIUjNSZmow7EVBQc= +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 v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM= @@ -1452,10 +1452,10 @@ honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWh honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg= -istio.io/api v1.26.2 h1:gLkGSB2nkqA/9u/tE/OMEv+U4Fhci2JZgIqjA0CxMak= -istio.io/api v1.26.2/go.mod h1:DTVGH6CLXj5W8FF9JUD3Tis78iRgT1WeuAnxfTz21Wg= -istio.io/client-go v1.26.2 h1:XWvQzBM69vB2xo1bzrp+CYbE8KlRAPotR4ls5Vv35EM= -istio.io/client-go v1.26.2/go.mod h1:eAImguSJPdaDiSSS2CEsywNHE8WWfqd3WfS18Rj8ynI= +istio.io/api v1.26.3 h1:/TiA7bJi24yBQSgpLy5vHhFkobf4DWS1L+CuUxNk4os= +istio.io/api v1.26.3/go.mod h1:DTVGH6CLXj5W8FF9JUD3Tis78iRgT1WeuAnxfTz21Wg= +istio.io/client-go v1.26.3 h1:ryF4+Nyz5wDO4mVCzXcm2W+fqbnekY88Z36hTcv5fnw= +istio.io/client-go v1.26.3/go.mod h1:u2p5L7UvjNswrrlHZ+QMlUOjERK2sXputywzyNhtTMg= k8s.io/api v0.18.0/go.mod h1:q2HRQkfDzHMBZL9l/y9rH63PkQl4vae0xRT+8prbrK8= k8s.io/api v0.18.2/go.mod h1:SJCWI7OLzhZSvbY7U8zwNl9UA4o1fizoug34OV/2r78= k8s.io/api v0.18.4/go.mod h1:lOIQAKYgai1+vz9J7YcDZwC26Z0zQewYOGWdyIPUUQ4=