Repository: kubernetes-sigs/external-dns

Issue:
Unnecessary access to v1.Pod lead to timeout.
<!-- Please use this template while reporting a bug and provide as much info as possible. Not doing so may result in your bug not being addressed in a timely manner. Thanks!

-->

**What happened**:

my config:
```
        --source=service
        --service-type-filter=LoadBalancer
```

my k8s cluster has too many pods, and apiserver can NOT return in 60 seconds.

It will error at:
```
time="2024-12-16T12:43:02Z" level=info msg="Created Kubernetes client https://10.0.0.1:443"
time="2024-12-16T12:44:02Z" level=fatal msg="failed to sync *v1.Pod: context deadline exceeded"
```

**What you expected to happen**:

Because I specifiy `source=service` and `service-type-filter=LoadBalancer`, So it should NEVER accecss api to `v1.Pod`. 

But actually, `NewServiceSource` call `waitForCacheSync` which will get all pods regardless of `service-type-filter`.

https://github.com/kubernetes-sigs/external-dns/blob/2a45cc85a4981f8a843468eca49ae3fe36af3462/source/service.go#L67-L113

So it became timeout because of hard code time.

https://github.com/kubernetes-sigs/external-dns/blob/2a45cc85a4981f8a843468eca49ae3fe36af3462/source/source.go#L356

**How to reproduce it (as minimally and precisely as possible)**:

**Anything else we need to know?**:

**Environment**:
- External-DNS version (use `external-dns --version`): v0.14.2
- DNS provider: rfc2136
- Others:


** Releated issues**:
- https://github.com/kubernetes-sigs/external-dns/issues/3173
- https://github.com/kubernetes-sigs/external-dns/issues/3169



Discussion:
@qin-nz: I try delete this line (and releated lines). There is no timeout anymore.
https://github.com/kubernetes-sigs/external-dns/blob/2a45cc85a4981f8a843468eca49ae3fe36af3462/source/service.go#L78@dmarkhas: I've seen that this can happen due to permissions:
https://github.com/kubernetes-sigs/external-dns/issues/4960

Make sure the account running external-dns is allowed to list pods.@qin-nz: @dmarkhas 
1. Yes, I am sure external-dns is allowed to list pods. Because when I delete thousands of pod. List pod can return in 50 seconds.
2. When use `--service-type-filter=LoadBalancer`, list pod is unnecessary. So the code should NOT list pods.@ivankatliarchuk: /help@k8s-ci-robot: @ivankatliarchuk: 
	This request has been marked as needing help from a contributor.

### Guidelines
Please ensure that the issue body includes answers to the following questions:
- Why are we solving this issue?
- To address this issue, are there any code changes? If there are code changes, what needs to be done in the code and what places can the assignee treat as reference points?
- Does this issue have zero to low barrier of entry?
- How can the assignee reach out to you for help?


For more details on the requirements of such an issue, please see [here](https://git.k8s.io/community/contributors/guide/help-wanted.md) and ensure that they are met.

If this request no longer meets these requirements, the label can be removed
by commenting with the `/remove-help` command.


<details>

In response to [this](https://github.com/kubernetes-sigs/external-dns/issues/4955):

>/help


Instructions for interacting with me using PR comments are available [here](https://git.k8s.io/community/contributors/guide/pull-requests.md).  If you have questions or suggestions related to my behavior, please file an issue against the [kubernetes-sigs/prow](https://github.com/kubernetes-sigs/prow/issues/new?title=Prow%20issue:) repository.
</details>@ivankatliarchuk: /assign

Solution patch:
diff --git a/.github/ISSUE_TEMPLATE/---bug-report.md b/.github/ISSUE_TEMPLATE/---bug-report.md
index d9195b39..4ff5e91a 100644
--- a/.github/ISSUE_TEMPLATE/---bug-report.md
+++ b/.github/ISSUE_TEMPLATE/---bug-report.md
@@ -7,8 +7,10 @@ assignees: ''
 
 ---
 
-<!-- Please use this template while reporting a bug and provide as much info as possible. Not doing so may result in your bug not being addressed in a timely manner. Thanks!
+<!--
+Please use this template while reporting a bug and provide as much info as possible. Not doing so may result in your bug not being addressed in a timely manner. Thanks!
 
+Make sure to validate the behavior against latest release https://github.com/kubernetes-sigs/external-dns/releases as we don't support past versions.
 -->
 
 **What happened**:
@@ -17,6 +19,10 @@ assignees: ''
 
 **How to reproduce it (as minimally and precisely as possible)**:
 
+<!--
+Please provide as much detail as possible, including Kubernetes manifests with spec.status, ExternalDNS arguments, and logs. A bug that cannot be reproduced won't be fixed.
+-->
+
 **Anything else we need to know?**:
 
 **Environment**:
diff --git a/.github/workflows/dependency-update.yaml b/.github/workflows/dependency-update.yaml
index 6ab818a8..e24f540f 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.3
+        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 }}
diff --git a/.github/workflows/lint.yaml b/.github/workflows/lint.yaml
index c04b5e89..c89e3db8 100644
--- a/.github/workflows/lint.yaml
+++ b/.github/workflows/lint.yaml
@@ -52,7 +52,7 @@ jobs:
 
       # Run Spectral
     - name: Lint OpenAPI spec
-      uses: stoplightio/spectral-action@577bade2d6e0eeb50528c94182a5588bf961ae8f # v0.8.12
+      uses: stoplightio/spectral-action@6416fd018ae38e60136775066eb3e98172143141 # v0.8.13
       with:
         file_glob: 'api/*.yaml'
 
diff --git a/README.md b/README.md
index 45ad8e06..74c6c5cb 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/charts/external-dns/CHANGELOG.md b/charts/external-dns/CHANGELOG.md
index e30a440f..b0e88965 100644
--- a/charts/external-dns/CHANGELOG.md
+++ b/charts/external-dns/CHANGELOG.md
@@ -18,16 +18,16 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
 
 ## [UNRELEASED]
 
-### Fixed
-
-- Fixed the lack of schema support for `create-only` dns policy in helm values ([#5627](https://github.com/kubernetes-sigs/external-dns/pull/5627)) _@coltonhughes_
+## [v1.18.0] - 2025-07-14
 
 ### Changed
 
 - Update RBAC for `Service` source to support `EndpointSlices`. ([#5493](https://github.com/kubernetes-sigs/external-dns/pull/5493)) _@vflaux_
+- Update _ExternalDNS_ OCI image version to [v0.18.0](https://github.com/kubernetes-sigs/external-dns/releases/tag/v0.18.0). ([#5633](https://github.com/kubernetes-sigs/external-dns/pull/5633)) _@elafarge_
 
 ### Fixed
 
+- Fixed the lack of schema support for `create-only` dns policy in helm values ([#5627](https://github.com/kubernetes-sigs/external-dns/pull/5627)) _@coltonhughes_
 - Fixed the type of `.extraContainers` from `object` to `list` (array). ([#5564](https://github.com/kubernetes-sigs/external-dns/pull/5564)) _@svengreb_
 
 ## [v1.17.0] - 2025-06-04
@@ -279,6 +279,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
 RELEASE LINKS
 -->
 [UNRELEASED]: https://github.com/kubernetes-sigs/external-dns/tree/master/charts/external-dns
+[v1.18.0]: https://github.com/kubernetes-sigs/external-dns/releases/tag/external-dns-helm-chart-1.18.0
 [v1.17.0]: https://github.com/kubernetes-sigs/external-dns/releases/tag/external-dns-helm-chart-1.17.0
 [v1.16.1]: https://github.com/kubernetes-sigs/external-dns/releases/tag/external-dns-helm-chart-1.16.1
 [v1.16.0]: https://github.com/kubernetes-sigs/external-dns/releases/tag/external-dns-helm-chart-1.16.0
diff --git a/charts/external-dns/Chart.yaml b/charts/external-dns/Chart.yaml
index 89445c3e..c1e7c6ce 100644
--- a/charts/external-dns/Chart.yaml
+++ b/charts/external-dns/Chart.yaml
@@ -2,8 +2,8 @@ apiVersion: v2
 name: external-dns
 description: ExternalDNS synchronizes exposed Kubernetes Services and Ingresses with DNS providers.
 type: application
-version: 1.17.0
-appVersion: 0.17.0
+version: 1.18.0
+appVersion: 0.18.0
 keywords:
   - kubernetes
   - k8s
diff --git a/charts/external-dns/README.md b/charts/external-dns/README.md
index f67502ae..33286d91 100644
--- a/charts/external-dns/README.md
+++ b/charts/external-dns/README.md
@@ -1,6 +1,6 @@
 # external-dns
 
-![Version: 1.17.0](https://img.shields.io/badge/Version-1.17.0-informational?style=flat-square) ![Type: application](https://img.shields.io/badge/Type-application-informational?style=flat-square) ![AppVersion: 0.17.0](https://img.shields.io/badge/AppVersion-0.17.0-informational?style=flat-square)
+![Version: 1.18.0](https://img.shields.io/badge/Version-1.18.0-informational?style=flat-square) ![Type: application](https://img.shields.io/badge/Type-application-informational?style=flat-square) ![AppVersion: 0.18.0](https://img.shields.io/badge/AppVersion-0.18.0-informational?style=flat-square)
 
 ExternalDNS synchronizes exposed Kubernetes Services and Ingresses with DNS providers.
 
@@ -27,7 +27,7 @@ helm repo add external-dns https://kubernetes-sigs.github.io/external-dns/
 After you've installed the repo you can install the chart.
 
 ```shell
-helm upgrade --install external-dns external-dns/external-dns --version 1.17.0
+helm upgrade --install external-dns external-dns/external-dns --version 1.18.0
 ```
 
 ## Providers
diff --git a/controller/controller.go b/controller/controller.go
index 06562da5..220da28c 100644
--- a/controller/controller.go
+++ b/controller/controller.go
@@ -37,7 +37,6 @@ import (
 var (
 	registryErrorsTotal = metrics.NewCounterWithOpts(
 		prometheus.CounterOpts{
-			Namespace: "external_dns",
 			Subsystem: "registry",
 			Name:      "errors_total",
 			Help:      "Number of Registry errors.",
@@ -45,7 +44,6 @@ var (
 	)
 	sourceErrorsTotal = metrics.NewCounterWithOpts(
 		prometheus.CounterOpts{
-			Namespace: "external_dns",
 			Subsystem: "source",
 			Name:      "errors_total",
 			Help:      "Number of Source errors.",
@@ -53,7 +51,6 @@ var (
 	)
 	sourceEndpointsTotal = metrics.NewGaugeWithOpts(
 		prometheus.GaugeOpts{
-			Namespace: "external_dns",
 			Subsystem: "source",
 			Name:      "endpoints_total",
 			Help:      "Number of Endpoints in all sources",
@@ -61,7 +58,6 @@ var (
 	)
 	registryEndpointsTotal = metrics.NewGaugeWithOpts(
 		prometheus.GaugeOpts{
-			Namespace: "external_dns",
 			Subsystem: "registry",
 			Name:      "endpoints_total",
 			Help:      "Number of Endpoints in the registry",
@@ -69,7 +65,6 @@ var (
 	)
 	lastSyncTimestamp = metrics.NewGaugeWithOpts(
 		prometheus.GaugeOpts{
-			Namespace: "external_dns",
 			Subsystem: "controller",
 			Name:      "last_sync_timestamp_seconds",
 			Help:      "Timestamp of last successful sync with the DNS provider",
@@ -77,7 +72,6 @@ var (
 	)
 	lastReconcileTimestamp = metrics.NewGaugeWithOpts(
 		prometheus.GaugeOpts{
-			Namespace: "external_dns",
 			Subsystem: "controller",
 			Name:      "last_reconcile_timestamp_seconds",
 			Help:      "Timestamp of last attempted sync with the DNS provider",
@@ -85,7 +79,6 @@ var (
 	)
 	controllerNoChangesTotal = metrics.NewCounterWithOpts(
 		prometheus.CounterOpts{
-			Namespace: "external_dns",
 			Subsystem: "controller",
 			Name:      "no_op_runs_total",
 			Help:      "Number of reconcile loops ending up with no changes on the DNS provider side.",
@@ -108,7 +101,6 @@ var (
 
 	registryRecords = metrics.NewGaugedVectorOpts(
 		prometheus.GaugeOpts{
-			Namespace: "external_dns",
 			Subsystem: "registry",
 			Name:      "records",
 			Help:      "Number of registry records partitioned by label name (vector).",
@@ -118,7 +110,6 @@ var (
 
 	sourceRecords = metrics.NewGaugedVectorOpts(
 		prometheus.GaugeOpts{
-			Namespace: "external_dns",
 			Subsystem: "source",
 			Name:      "records",
 			Help:      "Number of source records partitioned by label name (vector).",
@@ -128,7 +119,6 @@ var (
 
 	verifiedRecords = metrics.NewGaugedVectorOpts(
 		prometheus.GaugeOpts{
-			Namespace: "external_dns",
 			Subsystem: "controller",
 			Name:      "verified_records",
 			Help:      "Number of DNS records that exists both in source and registry (vector).",
@@ -138,7 +128,6 @@ var (
 
 	consecutiveSoftErrors = metrics.NewGaugeWithOpts(
 		prometheus.GaugeOpts{
-			Namespace: "external_dns",
 			Subsystem: "controller",
 			Name:      "consecutive_soft_errors",
 			Help:      "Number of consecutive soft errors in reconciliation loop.",
diff --git a/docs/contributing/dev-guide.md b/docs/contributing/dev-guide.md
index a8863225..8a04c7f6 100644
--- a/docs/contributing/dev-guide.md
+++ b/docs/contributing/dev-guide.md
@@ -70,11 +70,11 @@ import (
 )
 
 func TestMe(t *testing.T) {
-  hook := testutils.LogsUnderTestWithLogLeve(log.WarnLevel, t)
+  hook := testutils.LogsUnderTestWithLogLevel(log.WarnLevel, t)
   ... function under tests ...
   testutils.TestHelperLogContains("example warning message", hook, t)
   // provide negative assertion
-  testuitls.TestHelperLogNotContains("this message should not be shown", hook, t)
+  testutils.TestHelperLogNotContains("this message should not be shown", hook, t)
 }
 ```
 
diff --git a/docs/flags.md b/docs/flags.md
index 68f6307c..59f92888 100644
--- a/docs/flags.md
+++ b/docs/flags.md
@@ -66,7 +66,7 @@
 | `--google-zone-visibility=` | When using the Google provider, filter for zones with this visibility (optional, options: public, private) |
 | `--alibaba-cloud-config-file="/etc/kubernetes/alibaba-cloud.json"` | When using the Alibaba Cloud provider, specify the Alibaba Cloud configuration file (required when --provider=alibabacloud) |
 | `--alibaba-cloud-zone-type=` | When using the Alibaba Cloud provider, filter for zones of this type (optional, options: public, private) |
-| `--aws-zone-type=` | When using the AWS provider, filter for zones of this type (optional, options: public, private) |
+| `--aws-zone-type=` | When using the AWS provider, filter for zones of this type (optional, default: any, options: public, private) |
 | `--aws-zone-tags=` | When using the AWS provider, filter for zones with these tags |
 | `--aws-profile=` | When using the AWS provider, name of the profile to use |
 | `--aws-assume-role=""` | When using the AWS API, assume this IAM role. Useful for hosted zones in another AWS account. Specify the full ARN, e.g. `arn:aws:iam::123455567:role/external-dns` (optional) |
diff --git a/docs/monitoring/metrics.md b/docs/monitoring/metrics.md
index c80d3933..3da6d52a 100644
--- a/docs/monitoring/metrics.md
+++ b/docs/monitoring/metrics.md
@@ -20,6 +20,7 @@ curl https://localhost:7979/metrics
 
 | Name                             | Metric Type | Subsystem   |  Help                                                 |
 |:---------------------------------|:------------|:------------|:------------------------------------------------------|
+| build_info | Gauge |  | A metric with a constant '1' value labeled with 'version' and 'revision' of external_dns and the 'go_version', 'os' and the 'arch' used the build. |
 | consecutive_soft_errors | Gauge | controller | Number of consecutive soft errors in reconciliation loop. |
 | last_reconcile_timestamp_seconds | Gauge | controller | Timestamp of last attempted sync with the DNS provider |
 | last_sync_timestamp_seconds | Gauge | controller | Timestamp of last successful sync with the DNS provider |
diff --git a/docs/providers.md b/docs/providers.md
index 22862293..0aafa2fd 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/registry/txt.md b/docs/registry/txt.md
index e3ae66d6..d538eb28 100644
--- a/docs/registry/txt.md
+++ b/docs/registry/txt.md
@@ -118,19 +118,19 @@ Note that the key used for encryption should be a secure key and properly manage
 Python
 
 ```python
-python -c 'import os,base64; print(base64.urlsafe_b64encode(os.urandom(32)).decode())'
+python -c 'import os,base64; print(base64.standard_b64encode(os.urandom(32)).decode())'
 ```
 
 Bash
 
 ```shell
-dd if=/dev/urandom bs=32 count=1 2>/dev/null | base64 | tr -d -- '\n' | tr -- '+/' '-_'; echo
+dd if=/dev/urandom bs=32 count=1 2>/dev/null | base64; echo
 ```
 
 OpenSSL
 
 ```shell
-openssl rand -base64 32 | tr -- '+/' '-_'
+openssl rand -base64 32
 ```
 
 PowerShell
@@ -138,7 +138,7 @@ PowerShell
 ```powershell
 # Add System.Web assembly to session, just in case
 Add-Type -AssemblyName System.Web
-[Convert]::ToBase64String([System.Text.Encoding]::UTF8.GetBytes([System.Web.Security.Membership]::GeneratePassword(32,4))).Replace("+","-").Replace("/","_")
+[Convert]::ToBase64String([System.Text.Encoding]::UTF8.GetBytes([System.Web.Security.Membership]::GeneratePassword(32,4)))
 ```
 
 Terraform
@@ -146,7 +146,6 @@ Terraform
 ```hcl
 resource "random_password" "txt_key" {
   length           = 32
-  override_special = "-_"
 }
 ```
 
diff --git a/docs/tutorials/cloudflare.md b/docs/tutorials/cloudflare.md
index 1147e0e2..4afb55e2 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/docs/tutorials/myra.md b/docs/tutorials/myra.md
new file mode 100644
index 00000000..c25e1682
--- /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
diff --git a/endpoint/domain_filter.go b/endpoint/domain_filter.go
index 47402bb7..8d8aad2d 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/go.mod b/go.mod
index f2f93575..50708ad3 100644
--- a/go.mod
+++ b/go.mod
@@ -8,28 +8,28 @@ require (
 	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
-	github.com/F5Networks/k8s-bigip-ctlr/v2 v2.20.0
+	github.com/F5Networks/k8s-bigip-ctlr/v2 v2.20.1
 	github.com/Yamashou/gqlgenc v0.33.0
 	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.5
-	github.com/aws/aws-sdk-go-v2/config v1.29.17
-	github.com/aws/aws-sdk-go-v2/credentials v1.17.70
-	github.com/aws/aws-sdk-go-v2/feature/dynamodb/attributevalue v1.19.4
-	github.com/aws/aws-sdk-go-v2/service/dynamodb v1.44.0
-	github.com/aws/aws-sdk-go-v2/service/route53 v1.53.0
-	github.com/aws/aws-sdk-go-v2/service/servicediscovery v1.35.7
-	github.com/aws/aws-sdk-go-v2/service/sts v1.34.0
+	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/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.157.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
@@ -38,45 +38,45 @@ 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/linki/instrumented_http v0.3.0
 	github.com/linode/linodego v1.53.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.95.1
+	github.com/oracle/oci-go-sdk/v65 v65.96.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_model v0.6.2
+	github.com/prometheus/common v0.65.0
 	github.com/scaleway/scaleway-sdk-go v1.0.0-beta.34
 	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.2
-	go.etcd.io/etcd/client/v3 v3.6.2
+	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
 	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.241.0
+	google.golang.org/api v0.243.0
 	gopkg.in/ns1/ns1-go.v2 v2.14.4
 	istio.io/api v1.26.2
 	istio.io/client-go v1.26.2
-	k8s.io/api v0.33.2
-	k8s.io/apimachinery v0.33.2
-	k8s.io/client-go v0.33.2
+	k8s.io/api v0.33.3
+	k8s.io/apimachinery v0.33.3
+	k8s.io/client-go v0.33.3
 	k8s.io/klog/v2 v2.130.1
 	sigs.k8s.io/controller-runtime v0.21.0
 	sigs.k8s.io/gateway-api v1.3.0
 )
 
 require (
-	cloud.google.com/go/auth v0.16.2 // indirect
+	cloud.google.com/go/auth v0.16.3 // indirect
 	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
@@ -85,16 +85,16 @@ require (
 	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.32 // indirect
-	github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.36 // indirect
-	github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.36 // 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/internal/ini v1.8.3 // indirect
-	github.com/aws/aws-sdk-go-v2/service/dynamodbstreams v1.25.6 // 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.17 // indirect
-	github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.12.17 // indirect
-	github.com/aws/aws-sdk-go-v2/service/sso v1.25.5 // indirect
-	github.com/aws/aws-sdk-go-v2/service/ssooidc v1.30.3 // 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/benbjohnson/clock v1.3.0 // indirect
 	github.com/beorn7/perks v1.0.1 // indirect
@@ -121,7 +121,7 @@ require (
 	github.com/google/go-querystring v1.1.0 // indirect
 	github.com/google/s2a-go v0.1.9 // indirect
 	github.com/googleapis/enterprise-certificate-proxy v0.3.6 // indirect
-	github.com/googleapis/gax-go/v2 v2.14.2 // indirect
+	github.com/googleapis/gax-go/v2 v2.15.0 // indirect
 	github.com/gopherjs/gopherjs v1.17.2 // indirect
 	github.com/grpc-ecosystem/grpc-gateway/v2 v2.26.3 // indirect
 	github.com/hashicorp/errwrap v1.1.0 // indirect
@@ -154,7 +154,6 @@ 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/common v0.64.0 // indirect
 	github.com/prometheus/procfs v0.15.1 // indirect
 	github.com/rivo/uniseg v0.2.0 // indirect
 	github.com/schollz/progressbar/v3 v3.8.6 // indirect
@@ -171,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.2 // 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
@@ -185,8 +184,8 @@ require (
 	golang.org/x/sys v0.34.0 // indirect
 	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-20250505200425-f936aa4a68b2 // indirect
-	google.golang.org/genproto/googleapis/rpc v0.0.0-20250603155806-513f23925822 // 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/protobuf v1.36.6 // indirect
 	gopkg.in/evanphx/json-patch.v4 v4.12.0 // indirect
diff --git a/go.sum b/go.sum
index 702b715a..5c773b78 100644
--- a/go.sum
+++ b/go.sum
@@ -2,8 +2,8 @@ bazil.org/fuse v0.0.0-20160811212531-371fbbdaa898/go.mod h1:Xbm+BRKSBEpa4q4hTSxo
 cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
 cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
 cloud.google.com/go v0.38.0/go.mod h1:990N+gfupTy94rShfmMCWGDn0LpTmnzTp2qbd1dvSRU=
-cloud.google.com/go/auth v0.16.2 h1:QvBAGFPLrDeoiNjyfVunhQ10HKNYuOwZ5noee0M5df4=
-cloud.google.com/go/auth v0.16.2/go.mod h1:sRBas2Y1fB1vZTdurouM0AzuYQBMZinrUYL8EufhtEA=
+cloud.google.com/go/auth v0.16.3 h1:kabzoQ9/bobUmnseYnBO6qQG7q4a/CffFRlJSxv2wCc=
+cloud.google.com/go/auth v0.16.3/go.mod h1:NucRGjaXfzP1ltpcQ7On/VTZ0H4kWB5Jy+Y9Dnm76fA=
 cloud.google.com/go/auth/oauth2adapt v0.2.8 h1:keo8NaayQZ6wimpNSmW5OPc283g65QNIiLpZnkHRbnc=
 cloud.google.com/go/auth/oauth2adapt v0.2.8/go.mod h1:XQ9y31RkqZCcwJWNSx2Xvric3RrU88hAYYbjDWYDL+c=
 cloud.google.com/go/compute/metadata v0.7.0 h1:PBWF+iiAerVNe8UCHxdOt6eHLVc3ydFeOCw78U8ytSU=
@@ -48,8 +48,8 @@ github.com/AzureAD/microsoft-authentication-library-for-go v1.4.2/go.mod h1:wP83
 github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
 github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo=
 github.com/DATA-DOG/go-sqlmock v1.4.1/go.mod h1:f/Ixk793poVmq4qj/V1dPUg2JEAKC73Q5eFN3EC/SaM=
-github.com/F5Networks/k8s-bigip-ctlr/v2 v2.20.0 h1:WqsoU+5aA9kDypiBzWbLSkESQUA3NDLNvkjTFzipX3I=
-github.com/F5Networks/k8s-bigip-ctlr/v2 v2.20.0/go.mod h1:/lGdCgv0e1qrS4ithe2qTU6q23IT8kqZhMlFBQmuNi0=
+github.com/F5Networks/k8s-bigip-ctlr/v2 v2.20.1 h1:O4a7qJCbH2bQPzsk7NNIm9/2orkYEH7g4Uerdp0gzps=
+github.com/F5Networks/k8s-bigip-ctlr/v2 v2.20.1/go.mod h1:/lGdCgv0e1qrS4ithe2qTU6q23IT8kqZhMlFBQmuNi0=
 github.com/HdrHistogram/hdrhistogram-go v1.1.2/go.mod h1:yDgFjdqOqDEKOvasDdhWNXYg9BVp4O+o5f6V/ehm6Oo=
 github.com/Knetic/govaluate v3.0.1-0.20171022003610-9aa49832a739+incompatible/go.mod h1:r7JcOSlj0wfOMncg0iLm8Leh48TZaKVeNIfJntJ2wa0=
 github.com/MakeNowJust/heredoc v0.0.0-20170808103936-bb23615498cd/go.mod h1:64YHyfSL2R96J44Nlwm39UHepQbyR5q10x7iYa1ks2E=
@@ -114,42 +114,42 @@ 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.5 h1:0OF9RiEMEdDdZEMqF9MRjevyxAQcf6gY+E7vwBILFj0=
-github.com/aws/aws-sdk-go-v2 v1.36.5/go.mod h1:EYrzvCCN9CMUTa5+6lf6MM4tq3Zjp8UhSGR/cBsjai0=
-github.com/aws/aws-sdk-go-v2/config v1.29.17 h1:jSuiQ5jEe4SAMH6lLRMY9OVC+TqJLP5655pBGjmnjr0=
-github.com/aws/aws-sdk-go-v2/config v1.29.17/go.mod h1:9P4wwACpbeXs9Pm9w1QTh6BwWwJjwYvJ1iCt5QbCXh8=
-github.com/aws/aws-sdk-go-v2/credentials v1.17.70 h1:ONnH5CM16RTXRkS8Z1qg7/s2eDOhHhaXVd72mmyv4/0=
-github.com/aws/aws-sdk-go-v2/credentials v1.17.70/go.mod h1:M+lWhhmomVGgtuPOhO85u4pEa3SmssPTdcYpP/5J/xc=
-github.com/aws/aws-sdk-go-v2/feature/dynamodb/attributevalue v1.19.4 h1:jKR2jpZqpmBSAVX7xxdOi1E3Z0E9WizMIlxlGI3Hh9o=
-github.com/aws/aws-sdk-go-v2/feature/dynamodb/attributevalue v1.19.4/go.mod h1:ATyfcCpSMZuB/rnpFcVbiqrTiFzdwcTXeVbgEk6iXbY=
-github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.16.32 h1:KAXP9JSHO1vKGCr5f4O6WmlVKLFFXgWYAGoJosorxzU=
-github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.16.32/go.mod h1:h4Sg6FQdexC1yYG9RDnOvLbW1a/P986++/Y/a+GyEM8=
-github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.36 h1:SsytQyTMHMDPspp+spo7XwXTP44aJZZAC7fBV2C5+5s=
-github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.36/go.mod h1:Q1lnJArKRXkenyog6+Y+zr7WDpk4e6XlR6gs20bbeNo=
-github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.36 h1:i2vNHQiXUvKhs3quBR6aqlgJaiaexz/aNvdCktW/kAM=
-github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.36/go.mod h1:UdyGa7Q91id/sdyHPwth+043HhmP6yP9MBHgbZM0xo8=
+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/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.0 h1:A99gjqZDbdhjtjJVZrmVzVKO2+p3MSg35bDWtbMQVxw=
-github.com/aws/aws-sdk-go-v2/service/dynamodb v1.44.0/go.mod h1:mWB0GE1bqcVSvpW7OtFA0sKuHk52+IqtnsYU2jUfYAs=
-github.com/aws/aws-sdk-go-v2/service/dynamodbstreams v1.25.6 h1:QHaS/SHXfyNycuu4GiWb+AfW5T3bput6X5E3Ai/Q31M=
-github.com/aws/aws-sdk-go-v2/service/dynamodbstreams v1.25.6/go.mod h1:He/RikglWUczbkV+fkdpcV/3GdL/rTRNVy7VaUiezMo=
+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.17 h1:x187MqiHwBGjMGAed8Y8K1VGuCtFvQvXb24r+bwmSdo=
-github.com/aws/aws-sdk-go-v2/service/internal/endpoint-discovery v1.10.17/go.mod h1:mC9qMbA6e1pwEq6X3zDGtZRXMG2YaElJkbJlMVHLs5I=
-github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.12.17 h1:t0E6FzREdtCsiLIoLCWsYliNsRBgyGD/MCK571qk4MI=
-github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.12.17/go.mod h1:ygpklyoaypuyDvOM5ujWGrYWpAK3h7ugnmKCU/76Ys4=
-github.com/aws/aws-sdk-go-v2/service/route53 v1.53.0 h1:UglIEyurCqfzZkjNdYAuXUGFu/FNWMKP5eorzggvXe8=
-github.com/aws/aws-sdk-go-v2/service/route53 v1.53.0/go.mod h1:wi1naoiPnCQG3cyjsivwPON1ZmQt/EJGxFqXzubBTAw=
-github.com/aws/aws-sdk-go-v2/service/servicediscovery v1.35.7 h1:1eaP4/444jrv04HhJdwTHtgnyxWgxwdLjSYBGq+oMB4=
-github.com/aws/aws-sdk-go-v2/service/servicediscovery v1.35.7/go.mod h1:czoZQabc2chvmV/ak4oGSNR9CbcUw2bef3tatmwtoIA=
-github.com/aws/aws-sdk-go-v2/service/sso v1.25.5 h1:AIRJ3lfb2w/1/8wOOSqYb9fUKGwQbtysJ2H1MofRUPg=
-github.com/aws/aws-sdk-go-v2/service/sso v1.25.5/go.mod h1:b7SiVprpU+iGazDUqvRSLf5XmCdn+JtT1on7uNL6Ipc=
-github.com/aws/aws-sdk-go-v2/service/ssooidc v1.30.3 h1:BpOxT3yhLwSJ77qIY3DoHAQjZsc4HEGfMCE4NGy3uFg=
-github.com/aws/aws-sdk-go-v2/service/ssooidc v1.30.3/go.mod h1:vq/GQR1gOFLquZMSrxUK/cpvKCNVYibNyJ1m7JrU88E=
-github.com/aws/aws-sdk-go-v2/service/sts v1.34.0 h1:NFOJ/NXEGV4Rq//71Hs1jC/NvPs1ezajK+yQmkwnPV0=
-github.com/aws/aws-sdk-go-v2/service/sts v1.34.0/go.mod h1:7ph2tGpfQvwzgistp2+zga9f+bCjlQJPkPUmMgDSD7w=
+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/benbjohnson/clock v1.3.0 h1:ip6w0uFQkncKQ979AypyG0ER7mqUSBdKLOgAle/AT8A=
@@ -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.157.0 h1:ReELaS6FxXNf8gryUiVH0wmyUmZN8/NCmBX4gXd3F0o=
-github.com/digitalocean/godo v1.157.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=
@@ -503,8 +503,8 @@ github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+
 github.com/googleapis/enterprise-certificate-proxy v0.3.6 h1:GW/XbdyBFQ8Qe+YAmFU9uHLo7OnF5tL52HFAgMmyrf4=
 github.com/googleapis/enterprise-certificate-proxy v0.3.6/go.mod h1:MkHOF77EYAE7qfSuSS9PU6g4Nt4e11cnsDUowfwewLA=
 github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg=
-github.com/googleapis/gax-go/v2 v2.14.2 h1:eBLnkZ9635krYIPD+ag1USrOAI0Nr0QYF3+/3GqO0k0=
-github.com/googleapis/gax-go/v2 v2.14.2/go.mod h1:ON64QhlJkhVtSqp4v1uaK92VyZ2gmvDQsweuyLV+8+w=
+github.com/googleapis/gax-go/v2 v2.15.0 h1:SyjDc1mGgZU5LncH8gimWo9lW1DtIfPibOG81vgd/bo=
+github.com/googleapis/gax-go/v2 v2.15.0/go.mod h1:zVVkkxAQHa1RQpg9z2AUCMnKhi0Qld9rcmyfL1OZhoc=
 github.com/googleapis/gnostic v0.0.0-20170729233727-0c5108395e2d/go.mod h1:sJBsCZ4ayReDTBIg8b9dl28c5xFWyhBTVRp3pOg5EKY=
 github.com/googleapis/gnostic v0.1.0/go.mod h1:sJBsCZ4ayReDTBIg8b9dl28c5xFWyhBTVRp3pOg5EKY=
 github.com/googleapis/gnostic v0.3.1/go.mod h1:on+2t9HRStVgn95RSsFWFz+6Q0Snyqv1awfrALZdbtU=
@@ -676,8 +676,6 @@ 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/linki/instrumented_http v0.3.0 h1:dsN92+mXpfZtjJraartcQ99jnuw7fqsnPDjr85ma2dA=
-github.com/linki/instrumented_http v0.3.0/go.mod h1:pjYbItoegfuVi2GUOMhEqzvm/SJKuEL3H0tc8QRLRFk=
 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/lithammer/dedent v1.1.0/go.mod h1:jrXYCQtgg0nJiN+StA2KgR7w6CiQNv9Fd/Z9BP0jIOc=
@@ -821,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.95.1 h1:KCYeX+c+A0ezLVgXKpEcgXNukn6wBkD9oWoSV2FlaUs=
-github.com/oracle/oci-go-sdk/v65 v65.95.1/go.mod h1:u6XRPsw9tPziBh76K7GrrRXPa8P8W3BQeqJ6ZZt9VLA=
+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/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=
@@ -884,8 +882,8 @@ github.com/prometheus/common v0.4.0/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y8
 github.com/prometheus/common v0.4.1/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4=
 github.com/prometheus/common v0.7.0/go.mod h1:DjGbpBbp5NYNiECxcL/VnbXCCaQpKd3tt26CguLLsqA=
 github.com/prometheus/common v0.9.1/go.mod h1:yhUN8i9wzaXS3w1O07YhxHEBxD+W35wd8bs7vj7HSQ4=
-github.com/prometheus/common v0.64.0 h1:pdZeA+g617P7oGv1CzdTzyeShxAGrTBsolKNOLQPGO4=
-github.com/prometheus/common v0.64.0/go.mod h1:0gZns+BLRQ3V6NdaerOhMbwwRbNh9hkGINtQAsP5GS8=
+github.com/prometheus/common v0.65.0 h1:QDwzd+G1twt//Kwj/Ww6E9FQq1iVMmODnILtW1t2VzE=
+github.com/prometheus/common v0.65.0/go.mod h1:0gZns+BLRQ3V6NdaerOhMbwwRbNh9hkGINtQAsP5GS8=
 github.com/prometheus/procfs v0.0.0-20180125133057-cb4147076ac7/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk=
 github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk=
 github.com/prometheus/procfs v0.0.0-20190117184657-bf6a532e95b1/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk=
@@ -1052,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.2 h1:25aCkIMjUmiiOtnBIp6PhNj4KdcURuBak0hU2P1fgRc=
-go.etcd.io/etcd/api/v3 v3.6.2/go.mod h1:eFhhvfR8Px1P6SEuLT600v+vrhdDTdcfMzmnxVXXSbk=
-go.etcd.io/etcd/client/pkg/v3 v3.6.2 h1:zw+HRghi/G8fKpgKdOcEKpnBTE4OO39T6MegA0RopVU=
-go.etcd.io/etcd/client/pkg/v3 v3.6.2/go.mod h1:sbdzr2cl3HzVmxNw//PH7aLGVtY4QySjQFuaCgcRFAI=
-go.etcd.io/etcd/client/v3 v3.6.2 h1:RgmcLJxkpHqpFvgKNwAQHX3K+wsSARMXKgjmUSpoSKQ=
-go.etcd.io/etcd/client/v3 v3.6.2/go.mod h1:PL7e5QMKzjybn0FosgiWvCUDzvdChpo5UgGR4Sk4Gzc=
+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=
@@ -1352,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.241.0 h1:QKwqWQlkc6O895LchPEDUSYr22Xp3NCxpQRiWTB6avE=
-google.golang.org/api v0.241.0/go.mod h1:cOVEm2TpdAGHL2z+UwyS+kmlGr3bVWQQ6sYEqkKje50=
+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/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=
@@ -1367,12 +1365,12 @@ google.golang.org/genproto v0.0.0-20190425155659-357c62f0e4bb/go.mod h1:VzzqZJRn
 google.golang.org/genproto v0.0.0-20190530194941-fb225487d101/go.mod h1:z3L6/3dTEVtUr6QSP8miRzeRqwQOioJ9I66odjN4I7s=
 google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc=
 google.golang.org/genproto v0.0.0-20200115191322-ca5a22157cba/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
-google.golang.org/genproto v0.0.0-20250505200425-f936aa4a68b2 h1:1tXaIXCracvtsRxSBsYDiSBN0cuJvM7QYW+MrpIRY78=
-google.golang.org/genproto v0.0.0-20250505200425-f936aa4a68b2/go.mod h1:49MsLSx0oWMOZqcpB3uL8ZOkAh1+TndpJ8ONoCBWiZk=
-google.golang.org/genproto/googleapis/api v0.0.0-20250505200425-f936aa4a68b2 h1:vPV0tzlsK6EzEDHNNH5sa7Hs9bd7iXR7B1tSiPepkV0=
-google.golang.org/genproto/googleapis/api v0.0.0-20250505200425-f936aa4a68b2/go.mod h1:pKLAc5OolXC3ViWGI62vvC0n10CpwAtRcTNCFwTKBEw=
-google.golang.org/genproto/googleapis/rpc v0.0.0-20250603155806-513f23925822 h1:fc6jSaCT0vBduLYZHYrBBNY4dsWuvgyff9noRNDdBeE=
-google.golang.org/genproto/googleapis/rpc v0.0.0-20250603155806-513f23925822/go.mod h1:qQ0YXyHHx3XkvlzUtpXDkS29lDSafHMZBAZDc03LQ3A=
+google.golang.org/genproto v0.0.0-20250603155806-513f23925822 h1:rHWScKit0gvAPuOnu87KpaYtjK5zBMLcULh7gxkCXu4=
+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/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=
@@ -1461,16 +1459,16 @@ istio.io/client-go v1.26.2/go.mod h1:eAImguSJPdaDiSSS2CEsywNHE8WWfqd3WfS18Rj8ynI
 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=
-k8s.io/api v0.33.2 h1:YgwIS5jKfA+BZg//OQhkJNIfie/kmRsO0BmNaVSimvY=
-k8s.io/api v0.33.2/go.mod h1:fhrbphQJSM2cXzCWgqU29xLDuks4mu7ti9vveEnpSXs=
+k8s.io/api v0.33.3 h1:SRd5t//hhkI1buzxb288fy2xvjubstenEKL9K51KBI8=
+k8s.io/api v0.33.3/go.mod h1:01Y/iLUjNBM3TAvypct7DIj0M0NIZc+PzAHCIo0CYGE=
 k8s.io/apiextensions-apiserver v0.18.0/go.mod h1:18Cwn1Xws4xnWQNC00FLq1E350b9lUF+aOdIWDOZxgo=
 k8s.io/apiextensions-apiserver v0.18.2/go.mod h1:q3faSnRGmYimiocj6cHQ1I3WpLqmDgJFlKL37fC4ZvY=
 k8s.io/apiextensions-apiserver v0.18.4/go.mod h1:NYeyeYq4SIpFlPxSAB6jHPIdvu3hL0pc36wuRChybio=
 k8s.io/apimachinery v0.18.0/go.mod h1:9SnR/e11v5IbyPCGbvJViimtJ0SwHG4nfZFjU77ftcA=
 k8s.io/apimachinery v0.18.2/go.mod h1:9SnR/e11v5IbyPCGbvJViimtJ0SwHG4nfZFjU77ftcA=
 k8s.io/apimachinery v0.18.4/go.mod h1:OaXp26zu/5J7p0f92ASynJa1pZo06YlV9fG7BoWbCko=
-k8s.io/apimachinery v0.33.2 h1:IHFVhqg59mb8PJWTLi8m1mAoepkUNYmptHsV+Z1m5jY=
-k8s.io/apimachinery v0.33.2/go.mod h1:BHW0YOu7n22fFv/JkYOEfkUYNRN0fj0BlvMFWA7b+SM=
+k8s.io/apimachinery v0.33.3 h1:4ZSrmNa0c/ZpZJhAgRdcsFcZOw1PQU1bALVQ0B3I5LA=
+k8s.io/apimachinery v0.33.3/go.mod h1:BHW0YOu7n22fFv/JkYOEfkUYNRN0fj0BlvMFWA7b+SM=
 k8s.io/apiserver v0.18.0/go.mod h1:3S2O6FeBBd6XTo0njUrLxiqk8GNy6wWOftjhJcXYnjw=
 k8s.io/apiserver v0.18.2/go.mod h1:Xbh066NqrZO8cbsoenCwyDJ1OSi8Ag8I2lezeHxzwzw=
 k8s.io/apiserver v0.18.4/go.mod h1:q+zoFct5ABNnYkGIaGQ3bcbUNdmPyOCoEBcg51LChY8=
@@ -1479,8 +1477,8 @@ k8s.io/cli-runtime v0.18.4/go.mod h1:9/hS/Cuf7NVzWR5F/5tyS6xsnclxoPLVtwhnkJG1Y4g
 k8s.io/client-go v0.18.0/go.mod h1:uQSYDYs4WhVZ9i6AIoEZuwUggLVEF64HOD37boKAtF8=
 k8s.io/client-go v0.18.2/go.mod h1:Xcm5wVGXX9HAA2JJ2sSBUn3tCJ+4SVlCbl2MNNv+CIU=
 k8s.io/client-go v0.18.4/go.mod h1:f5sXwL4yAZRkAtzOxRWUhA/N8XzGCb+nPZI8PfobZ9g=
-k8s.io/client-go v0.33.2 h1:z8CIcc0P581x/J1ZYf4CNzRKxRvQAwoAolYPbtQes+E=
-k8s.io/client-go v0.33.2/go.mod h1:9mCgT4wROvL948w6f6ArJNb7yQd7QsvqavDeZHvNmHo=
+k8s.io/client-go v0.33.3 h1:M5AfDnKfYmVJif92ngN532gFqakcGi6RvaOF16efrpA=
+k8s.io/client-go v0.33.3/go.mod h1:luqKBQggEf3shbxHY4uVENAxrDISLOarxpTKMiUuujg=
 k8s.io/code-generator v0.18.0/go.mod h1:+UHX5rSbxmR8kzS+FAv7um6dtYrZokQvjHpDSYRVkTc=
 k8s.io/code-generator v0.18.2/go.mod h1:+UHX5rSbxmR8kzS+FAv7um6dtYrZokQvjHpDSYRVkTc=
 k8s.io/code-generator v0.18.4/go.mod h1:TgNEVx9hCyPGpdtCWA34olQYLkh3ok9ar7XfSsr8b6c=
diff --git a/internal/idna/idna.go b/internal/idna/idna.go
new file mode 100644
index 00000000..9290e8a5
--- /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/pkg/apis/externaldns/types.go b/pkg/apis/externaldns/types.go
index ea809d3a..c4df4e35 100644
--- a/pkg/apis/externaldns/types.go
+++ b/pkg/apis/externaldns/types.go
@@ -504,7 +504,7 @@ func App(cfg *Config) *kingpin.Application {
 	app.Flag("google-zone-visibility", "When using the Google provider, filter for zones with this visibility (optional, options: public, private)").Default(defaultConfig.GoogleZoneVisibility).EnumVar(&cfg.GoogleZoneVisibility, "", "public", "private")
 	app.Flag("alibaba-cloud-config-file", "When using the Alibaba Cloud provider, specify the Alibaba Cloud configuration file (required when --provider=alibabacloud)").Default(defaultConfig.AlibabaCloudConfigFile).StringVar(&cfg.AlibabaCloudConfigFile)
 	app.Flag("alibaba-cloud-zone-type", "When using the Alibaba Cloud provider, filter for zones of this type (optional, options: public, private)").Default(defaultConfig.AlibabaCloudZoneType).EnumVar(&cfg.AlibabaCloudZoneType, "", "public", "private")
-	app.Flag("aws-zone-type", "When using the AWS provider, filter for zones of this type (optional, options: public, private)").Default(defaultConfig.AWSZoneType).EnumVar(&cfg.AWSZoneType, "", "public", "private")
+	app.Flag("aws-zone-type", "When using the AWS provider, filter for zones of this type (optional, default: any, options: public, private)").Default(defaultConfig.AWSZoneType).EnumVar(&cfg.AWSZoneType, "", "public", "private")
 	app.Flag("aws-zone-tags", "When using the AWS provider, filter for zones with these tags").Default("").StringsVar(&cfg.AWSZoneTagFilter)
 	app.Flag("aws-profile", "When using the AWS provider, name of the profile to use").Default("").StringsVar(&cfg.AWSProfiles)
 	app.Flag("aws-assume-role", "When using the AWS API, assume this IAM role. Useful for hosted zones in another AWS account. Specify the full ARN, e.g. `arn:aws:iam::123455567:role/external-dns` (optional)").Default(defaultConfig.AWSAssumeRole).StringVar(&cfg.AWSAssumeRole)
diff --git a/pkg/http/http.go b/pkg/http/http.go
new file mode 100644
index 00000000..b6069f1a
--- /dev/null
+++ b/pkg/http/http.go
@@ -0,0 +1,97 @@
+/*
+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.
+*/
+
+// ref: https://github.com/linki/instrumented_http/blob/master/client.go
+
+package http
+
+import (
+	"fmt"
+	"net/http"
+	"strings"
+	"time"
+
+	"github.com/prometheus/client_golang/prometheus"
+)
+
+var (
+	requestDuration = prometheus.NewSummaryVec(
+		prometheus.SummaryOpts{
+			Name:        "request_duration_seconds",
+			Help:        "The HTTP request latencies in seconds.",
+			Subsystem:   "http",
+			ConstLabels: prometheus.Labels{"handler": "instrumented_http"},
+			Objectives:  map[float64]float64{0.5: 0.05, 0.9: 0.01, 0.99: 0.001},
+		},
+		[]string{"scheme", "host", "path", "method", "status"},
+	)
+)
+
+func init() {
+	prometheus.MustRegister(requestDuration)
+}
+
+type CustomRoundTripper struct {
+	next http.RoundTripper
+}
+
+// CancelRequest is a no-op to satisfy interfaces that require it.
+// https://github.com/kubernetes/client-go/blob/34f52c14eaed7e50c845cc14e85e1c4c91e77470/transport/transport.go#L346
+func (r *CustomRoundTripper) CancelRequest(_ *http.Request) {
+}
+
+func (r *CustomRoundTripper) RoundTrip(req *http.Request) (*http.Response, error) {
+	start := time.Now()
+	resp, err := r.next.RoundTrip(req)
+
+	status := ""
+	if resp != nil {
+		status = fmt.Sprintf("%d", resp.StatusCode)
+	}
+
+	labels := prometheus.Labels{
+		"scheme": req.URL.Scheme,
+		"host":   req.URL.Host,
+		"path":   pathProcessor(req.URL.Path),
+		"method": req.Method,
+		"status": status,
+	}
+	requestDuration.With(labels).Observe(time.Since(start).Seconds())
+	return resp, err
+}
+
+func NewInstrumentedClient(next *http.Client) *http.Client {
+	if next == nil {
+		next = http.DefaultClient
+	}
+
+	next.Transport = NewInstrumentedTransport(next.Transport)
+
+	return next
+}
+
+func NewInstrumentedTransport(next http.RoundTripper) http.RoundTripper {
+	if next == nil {
+		next = http.DefaultTransport
+	}
+
+	return &CustomRoundTripper{next: next}
+}
+
+func pathProcessor(path string) string {
+	parts := strings.Split(path, "/")
+	return parts[len(parts)-1]
+}
diff --git a/pkg/metrics/metrics.go b/pkg/metrics/metrics.go
index d65e172e..73e6ea5d 100644
--- a/pkg/metrics/metrics.go
+++ b/pkg/metrics/metrics.go
@@ -17,25 +17,44 @@ limitations under the License.
 package metrics
 
 import (
-	"runtime"
+	"fmt"
 
 	"github.com/prometheus/client_golang/prometheus"
+	"github.com/prometheus/common/version"
 	log "github.com/sirupsen/logrus"
 
 	cfg "sigs.k8s.io/external-dns/pkg/apis/externaldns"
 )
 
+const (
+	Namespace = "external_dns"
+)
+
 var (
 	RegisterMetric = NewMetricsRegister()
 )
 
-func NewMetricsRegister() *MetricRegistry {
-	reg := prometheus.WrapRegistererWith(
-		prometheus.Labels{
+func init() {
+	RegisterMetric.MustRegister(NewGaugeFuncMetric(prometheus.GaugeOpts{
+		Namespace: Namespace,
+		Name:      "build_info",
+		Help: fmt.Sprintf(
+			"A metric with a constant '1' value labeled with 'version' and 'revision' of %s and the 'go_version', 'os' and the 'arch' used the build.",
+			Namespace,
+		),
+		ConstLabels: prometheus.Labels{
 			"version":    cfg.Version,
-			"arch":       runtime.GOARCH,
-			"go_version": runtime.Version(),
+			"revision":   version.GetRevision(),
+			"go_version": version.GoVersion,
+			"os":         version.GoOS,
+			"arch":       version.GoArch,
 		},
+	}))
+}
+
+func NewMetricsRegister() *MetricRegistry {
+	reg := prometheus.WrapRegistererWith(
+		prometheus.Labels{},
 		prometheus.DefaultRegisterer)
 	return &MetricRegistry{
 		Registerer: reg,
@@ -54,7 +73,7 @@ func NewMetricsRegister() *MetricRegistry {
 //	}
 func (m *MetricRegistry) MustRegister(cs IMetric) {
 	switch v := cs.(type) {
-	case CounterMetric, GaugeMetric, CounterVecMetric, GaugeVecMetric:
+	case CounterMetric, GaugeMetric, CounterVecMetric, GaugeVecMetric, GaugeFuncMetric:
 		if _, exists := m.mName[cs.Get().FQDN]; exists {
 			return
 		} else {
@@ -70,6 +89,8 @@ func (m *MetricRegistry) MustRegister(cs IMetric) {
 			m.Registerer.MustRegister(metric.Gauge)
 		case CounterVecMetric:
 			m.Registerer.MustRegister(metric.CounterVec)
+		case GaugeFuncMetric:
+			m.Registerer.MustRegister(metric.GaugeFunc)
 		}
 		log.Debugf("Register metric: %s", cs.Get().FQDN)
 	default:
diff --git a/pkg/metrics/models.go b/pkg/metrics/models.go
index 1aaf708e..8c8fb4d4 100644
--- a/pkg/metrics/models.go
+++ b/pkg/metrics/models.go
@@ -88,6 +88,7 @@ func (g GaugeVecMetric) SetWithLabels(value float64, lvs ...string) {
 }
 
 func NewGaugeWithOpts(opts prometheus.GaugeOpts) GaugeMetric {
+	opts.Namespace = Namespace
 	return GaugeMetric{
 		Metric: Metric{
 			Type:      "gauge",
@@ -104,6 +105,7 @@ func NewGaugeWithOpts(opts prometheus.GaugeOpts) GaugeMetric {
 // NewGaugedVectorOpts creates a new GaugeVec based on the provided GaugeOpts and
 // partitioned by the given label names.
 func NewGaugedVectorOpts(opts prometheus.GaugeOpts, labelNames []string) GaugeVecMetric {
+	opts.Namespace = Namespace
 	return GaugeVecMetric{
 		Metric: Metric{
 			Type:      "gauge",
@@ -118,6 +120,7 @@ func NewGaugedVectorOpts(opts prometheus.GaugeOpts, labelNames []string) GaugeVe
 }
 
 func NewCounterWithOpts(opts prometheus.CounterOpts) CounterMetric {
+	opts.Namespace = Namespace
 	return CounterMetric{
 		Metric: Metric{
 			Type:      "counter",
@@ -132,6 +135,7 @@ func NewCounterWithOpts(opts prometheus.CounterOpts) CounterMetric {
 }
 
 func NewCounterVecWithOpts(opts prometheus.CounterOpts, labelNames []string) CounterVecMetric {
+	opts.Namespace = Namespace
 	return CounterVecMetric{
 		Metric: Metric{
 			Type:      "counter",
@@ -144,3 +148,31 @@ func NewCounterVecWithOpts(opts prometheus.CounterOpts, labelNames []string) Cou
 		CounterVec: prometheus.NewCounterVec(opts, labelNames),
 	}
 }
+
+type GaugeFuncMetric struct {
+	Metric
+	GaugeFunc prometheus.GaugeFunc
+}
+
+func (g GaugeFuncMetric) Get() *Metric {
+	return &g.Metric
+}
+
+func NewGaugeFuncMetric(opts prometheus.GaugeOpts) GaugeFuncMetric {
+	return GaugeFuncMetric{
+		Metric: Metric{
+			Type: "gauge",
+			Name: opts.Name,
+			FQDN: func() string {
+				if opts.Subsystem != "" {
+					return fmt.Sprintf("%s_%s", opts.Subsystem, opts.Name)
+				}
+				return opts.Name
+			}(),
+			Namespace: opts.Namespace,
+			Subsystem: opts.Subsystem,
+			Help:      opts.Help,
+		},
+		GaugeFunc: prometheus.NewGaugeFunc(opts, func() float64 { return 1 }),
+	}
+}
diff --git a/plan/plan.go b/plan/plan.go
index 8061a5b3..cf7db885 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/aws/aws.go b/provider/aws/aws.go
index 1ddb35cc..873d860d 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)
 			}
 		}
 
diff --git a/provider/aws/config.go b/provider/aws/config.go
index 5908150e..ecc53c90 100644
--- a/provider/aws/config.go
+++ b/provider/aws/config.go
@@ -20,16 +20,16 @@ import (
 	"context"
 	"fmt"
 	"net/http"
-	"strings"
 
 	awsv2 "github.com/aws/aws-sdk-go-v2/aws"
 	"github.com/aws/aws-sdk-go-v2/aws/retry"
 	"github.com/aws/aws-sdk-go-v2/config"
 	stscredsv2 "github.com/aws/aws-sdk-go-v2/credentials/stscreds"
 	"github.com/aws/aws-sdk-go-v2/service/sts"
-	"github.com/linki/instrumented_http"
 	"github.com/sirupsen/logrus"
 
+	extdnshttp "sigs.k8s.io/external-dns/pkg/http"
+
 	"sigs.k8s.io/external-dns/pkg/apis/externaldns"
 )
 
@@ -84,12 +84,7 @@ func newV2Config(awsConfig AWSSessionConfig) (awsv2.Config, error) {
 		config.WithRetryer(func() awsv2.Retryer {
 			return retry.AddWithMaxAttempts(retry.NewStandard(), awsConfig.APIRetries)
 		}),
-		config.WithHTTPClient(instrumented_http.NewClient(&http.Client{}, &instrumented_http.Callbacks{
-			PathProcessor: func(path string) string {
-				parts := strings.Split(path, "/")
-				return parts[len(parts)-1]
-			},
-		})),
+		config.WithHTTPClient(extdnshttp.NewInstrumentedClient(&http.Client{})),
 		config.WithSharedConfigProfile(awsConfig.Profile),
 	}
 
diff --git a/provider/awssd/aws_sd.go b/provider/awssd/aws_sd.go
index 510fee81..9ee0822a 100644
--- a/provider/awssd/aws_sd.go
+++ b/provider/awssd/aws_sd.go
@@ -78,7 +78,7 @@ type AWSSDProvider struct {
 	// only consider namespaces ending in this suffix
 	namespaceFilter *endpoint.DomainFilter
 	// filter namespace by type (private or public)
-	namespaceTypeFilter sdtypes.NamespaceFilter
+	namespaceTypeFilter []sdtypes.NamespaceFilter
 	// enables service without instances cleanup
 	cleanEmptyService bool
 	// filter services for removal
@@ -102,21 +102,28 @@ func NewAWSSDProvider(domainFilter *endpoint.DomainFilter, namespaceType string,
 	return p, nil
 }
 
-// newSdNamespaceFilter initialized AWS SD Namespace Filter based on given string config
-func newSdNamespaceFilter(namespaceTypeConfig string) sdtypes.NamespaceFilter {
+// newSdNamespaceFilter returns NamespaceFilter based on the given namespace type configuration.
+// If the config is "public", it filters for public namespaces; if "private", for private namespaces.
+// For any other value (including empty), it returns filters for both public and private namespaces.
+// ref: https://docs.aws.amazon.com/cloud-map/latest/api/API_ListNamespaces.html
+func newSdNamespaceFilter(namespaceTypeConfig string) []sdtypes.NamespaceFilter {
 	switch namespaceTypeConfig {
 	case sdNamespaceTypePublic:
-		return sdtypes.NamespaceFilter{
-			Name:   sdtypes.NamespaceFilterNameType,
-			Values: []string{string(sdtypes.NamespaceTypeDnsPublic)},
+		return []sdtypes.NamespaceFilter{
+			{
+				Name:   sdtypes.NamespaceFilterNameType,
+				Values: []string{string(sdtypes.NamespaceTypeDnsPublic)},
+			},
 		}
 	case sdNamespaceTypePrivate:
-		return sdtypes.NamespaceFilter{
-			Name:   sdtypes.NamespaceFilterNameType,
-			Values: []string{string(sdtypes.NamespaceTypeDnsPrivate)},
+		return []sdtypes.NamespaceFilter{
+			{
+				Name:   sdtypes.NamespaceFilterNameType,
+				Values: []string{string(sdtypes.NamespaceTypeDnsPrivate)},
+			},
 		}
 	default:
-		return sdtypes.NamespaceFilter{}
+		return []sdtypes.NamespaceFilter{}
 	}
 }
 
@@ -354,7 +361,7 @@ func (p *AWSSDProvider) ListNamespaces(ctx context.Context) ([]*sdtypes.Namespac
 	namespaces := make([]*sdtypes.NamespaceSummary, 0)
 
 	paginator := sd.NewListNamespacesPaginator(p.client, &sd.ListNamespacesInput{
-		Filters: []sdtypes.NamespaceFilter{p.namespaceTypeFilter},
+		Filters: p.namespaceTypeFilter,
 	})
 	for paginator.HasMorePages() {
 		resp, err := paginator.NextPage(ctx)
diff --git a/provider/cached_provider.go b/provider/cached_provider.go
index 8f86d704..ff9d2d49 100644
--- a/provider/cached_provider.go
+++ b/provider/cached_provider.go
@@ -31,7 +31,6 @@ import (
 var (
 	cachedRecordsCallsTotal = metrics.NewCounterVecWithOpts(
 		prometheus.CounterOpts{
-			Namespace: "external_dns",
 			Subsystem: "provider",
 			Name:      "cache_records_calls",
 			Help:      "Number of calls to the provider cache Records list.",
@@ -42,7 +41,6 @@ var (
 	)
 	cachedApplyChangesCallsTotal = metrics.NewCounterWithOpts(
 		prometheus.CounterOpts{
-			Namespace: "external_dns",
 			Subsystem: "provider",
 			Name:      "cache_apply_changes_calls",
 			Help:      "Number of calls to the provider cache ApplyChanges.",
diff --git a/provider/cloudflare/cloudflare.go b/provider/cloudflare/cloudflare.go
index 8914032c..0b432b3f 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/google/google.go b/provider/google/google.go
index 68217e37..fd521fc0 100644
--- a/provider/google/google.go
+++ b/provider/google/google.go
@@ -20,17 +20,17 @@ import (
 	"context"
 	"fmt"
 	"sort"
-	"strings"
 	"time"
 
 	"cloud.google.com/go/compute/metadata"
-	"github.com/linki/instrumented_http"
 	log "github.com/sirupsen/logrus"
 	"golang.org/x/oauth2/google"
 	dns "google.golang.org/api/dns/v1"
 	googleapi "google.golang.org/api/googleapi"
 	"google.golang.org/api/option"
 
+	extdnshttp "sigs.k8s.io/external-dns/pkg/http"
+
 	"sigs.k8s.io/external-dns/endpoint"
 	"sigs.k8s.io/external-dns/plan"
 	"sigs.k8s.io/external-dns/provider"
@@ -131,12 +131,7 @@ func NewGoogleProvider(ctx context.Context, project string, domainFilter *endpoi
 		return nil, err
 	}
 
-	gcloud = instrumented_http.NewClient(gcloud, &instrumented_http.Callbacks{
-		PathProcessor: func(path string) string {
-			parts := strings.Split(path, "/")
-			return parts[len(parts)-1]
-		},
-	})
+	gcloud = extdnshttp.NewInstrumentedClient(gcloud)
 
 	dnsClient, err := dns.NewService(ctx, option.WithHTTPClient(gcloud))
 	if err != nil {
diff --git a/provider/pihole/client.go b/provider/pihole/client.go
index cccf8523..f133735d 100644
--- a/provider/pihole/client.go
+++ b/provider/pihole/client.go
@@ -28,10 +28,11 @@ import (
 	"net/url"
 	"strings"
 
-	"github.com/linki/instrumented_http"
 	log "github.com/sirupsen/logrus"
 	"golang.org/x/net/html"
 
+	extdnshttp "sigs.k8s.io/external-dns/pkg/http"
+
 	"sigs.k8s.io/external-dns/endpoint"
 	"sigs.k8s.io/external-dns/provider"
 )
@@ -71,7 +72,8 @@ func newPiholeClient(cfg PiholeConfig) (piholeAPI, error) {
 			},
 		},
 	}
-	cl := instrumented_http.NewClient(httpClient, &instrumented_http.Callbacks{})
+
+	cl := extdnshttp.NewInstrumentedClient(httpClient)
 
 	p := &piholeClient{
 		cfg:        cfg,
diff --git a/provider/pihole/clientV6.go b/provider/pihole/clientV6.go
index 0d220b03..3168b497 100644
--- a/provider/pihole/clientV6.go
+++ b/provider/pihole/clientV6.go
@@ -30,9 +30,10 @@ import (
 	"strconv"
 	"strings"
 
-	"github.com/linki/instrumented_http"
 	log "github.com/sirupsen/logrus"
 
+	extdnshttp "sigs.k8s.io/external-dns/pkg/http"
+
 	"sigs.k8s.io/external-dns/endpoint"
 	"sigs.k8s.io/external-dns/provider"
 )
@@ -65,7 +66,7 @@ func newPiholeClientV6(cfg PiholeConfig) (piholeAPI, error) {
 		},
 	}
 
-	cl := instrumented_http.NewClient(httpClient, &instrumented_http.Callbacks{})
+	cl := extdnshttp.NewInstrumentedClient(httpClient)
 
 	p := &piholeClientV6{
 		cfg:        cfg,
@@ -164,17 +165,17 @@ func (p *piholeClientV6) listRecords(ctx context.Context, rtype string) ([]*endp
 		DNSName, Target = recs[1], recs[0]
 		switch rtype {
 		case endpoint.RecordTypeA:
-			//PiHole return A and AAAA records. Filter to only keep the A records
+			// PiHole return A and AAAA records. Filter to only keep the A records
 			if !isValidIPv4(Target) {
 				continue
 			}
 		case endpoint.RecordTypeAAAA:
-			//PiHole return A and AAAA records. Filter to only keep the AAAA records
+			// PiHole return A and AAAA records. Filter to only keep the AAAA records
 			if !isValidIPv6(Target) {
 				continue
 			}
 		case endpoint.RecordTypeCNAME:
-			//PiHole return only CNAME records.
+			// PiHole return only CNAME records.
 			// CNAME format is DNSName,target, ttl?
 			DNSName, Target = recs[0], recs[1]
 			if len(recs) == 3 { // TTL is present
diff --git a/provider/webhook/webhook.go b/provider/webhook/webhook.go
index 7e5da118..787ace17 100644
--- a/provider/webhook/webhook.go
+++ b/provider/webhook/webhook.go
@@ -43,7 +43,6 @@ const (
 var (
 	recordsErrorsGauge = metrics.NewGaugeWithOpts(
 		prometheus.GaugeOpts{
-			Namespace: "external_dns",
 			Subsystem: "webhook_provider",
 			Name:      "records_errors_total",
 			Help:      "Errors with Records method",
@@ -51,7 +50,6 @@ var (
 	)
 	recordsRequestsGauge = metrics.NewGaugeWithOpts(
 		prometheus.GaugeOpts{
-			Namespace: "external_dns",
 			Subsystem: "webhook_provider",
 			Name:      "records_requests_total",
 			Help:      "Requests with Records method",
@@ -59,7 +57,6 @@ var (
 	)
 	applyChangesErrorsGauge = metrics.NewGaugeWithOpts(
 		prometheus.GaugeOpts{
-			Namespace: "external_dns",
 			Subsystem: "webhook_provider",
 			Name:      "applychanges_errors_total",
 			Help:      "Errors with ApplyChanges method",
@@ -67,7 +64,6 @@ var (
 	)
 	applyChangesRequestsGauge = metrics.NewGaugeWithOpts(
 		prometheus.GaugeOpts{
-			Namespace: "external_dns",
 			Subsystem: "webhook_provider",
 			Name:      "applychanges_requests_total",
 			Help:      "Requests with ApplyChanges method",
@@ -75,7 +71,6 @@ var (
 	)
 	adjustEndpointsErrorsGauge = metrics.NewGaugeWithOpts(
 		prometheus.GaugeOpts{
-			Namespace: "external_dns",
 			Subsystem: "webhook_provider",
 			Name:      "adjustendpoints_errors_total",
 			Help:      "Errors with AdjustEndpoints method",
@@ -83,7 +78,6 @@ var (
 	)
 	adjustEndpointsRequestsGauge = metrics.NewGaugeWithOpts(
 		prometheus.GaugeOpts{
-			Namespace: "external_dns",
 			Subsystem: "webhook_provider",
 			Name:      "adjustendpoints_requests_total",
 			Help:      "Requests with AdjustEndpoints method",
diff --git a/source/informers/fake.go b/source/informers/fake.go
new file mode 100644
index 00000000..aed7b3ae
--- /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/node.go b/source/node.go
index 53a438d4..28c879f9 100644
--- a/source/node.go
+++ b/source/node.go
@@ -27,7 +27,6 @@ import (
 	kubeinformers "k8s.io/client-go/informers"
 	coreinformers "k8s.io/client-go/informers/core/v1"
 	"k8s.io/client-go/kubernetes"
-	"k8s.io/client-go/tools/cache"
 
 	"sigs.k8s.io/external-dns/endpoint"
 	"sigs.k8s.io/external-dns/source/annotations"
@@ -69,13 +68,7 @@ func NewNodeSource(
 	nodeInformer := informerFactory.Core().V1().Nodes()
 
 	// Add default resource event handler to properly initialize informer.
-	nodeInformer.Informer().AddEventHandler(
-		cache.ResourceEventHandlerFuncs{
-			AddFunc: func(obj interface{}) {
-				log.Debug("node added")
-			},
-		},
-	)
+	_, _ = nodeInformer.Informer().AddEventHandler(informers.DefaultEventHandler())
 
 	informerFactory.Start(ctx.Done())
 
@@ -172,7 +165,8 @@ func (ns *nodeSource) Endpoints(_ context.Context) ([]*endpoint.Endpoint, error)
 	return endpointsSlice, nil
 }
 
-func (ns *nodeSource) AddEventHandler(_ context.Context, _ func()) {
+func (ns *nodeSource) AddEventHandler(_ context.Context, handler func()) {
+	_, _ = ns.nodeInformer.Informer().AddEventHandler(eventHandlerFunc(handler))
 }
 
 // nodeAddress returns the node's externalIP and if that's not found, the node's internalIP
diff --git a/source/pod.go b/source/pod.go
index 25597fde..baa0e58e 100644
--- a/source/pod.go
+++ b/source/pod.go
@@ -29,7 +29,6 @@ import (
 	kubeinformers "k8s.io/client-go/informers"
 	coreinformers "k8s.io/client-go/informers/core/v1"
 	"k8s.io/client-go/kubernetes"
-	"k8s.io/client-go/tools/cache"
 
 	"sigs.k8s.io/external-dns/endpoint"
 	"sigs.k8s.io/external-dns/source/annotations"
@@ -76,18 +75,8 @@ func NewPodSource(
 		return nil, fmt.Errorf("failed to add indexers to pod informer: %w", err)
 	}
 
-	_, _ = podInformer.Informer().AddEventHandler(
-		cache.ResourceEventHandlerFuncs{
-			AddFunc: func(obj interface{}) {
-			},
-		},
-	)
-	_, _ = nodeInformer.Informer().AddEventHandler(
-		cache.ResourceEventHandlerFuncs{
-			AddFunc: func(obj interface{}) {
-			},
-		},
-	)
+	_, _ = podInformer.Informer().AddEventHandler(informers.DefaultEventHandler())
+	_, _ = nodeInformer.Informer().AddEventHandler(informers.DefaultEventHandler())
 
 	informerFactory.Start(ctx.Done())
 
@@ -114,7 +103,8 @@ func NewPodSource(
 	}, nil
 }
 
-func (*podSource) AddEventHandler(_ context.Context, _ func()) {
+func (ps *podSource) AddEventHandler(_ context.Context, handler func()) {
+	_, _ = ps.podInformer.Informer().AddEventHandler(eventHandlerFunc(handler))
 }
 
 func (ps *podSource) Endpoints(_ context.Context) ([]*endpoint.Endpoint, error) {
diff --git a/source/service.go b/source/service.go
index 24f3b60e..a2a8f8f7 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,32 +106,42 @@ func NewServiceSource(ctx context.Context, kubeClient kubernetes.Interface, name
 		return nil, err
 	}
 
+	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
+		}
+	}
+
 	var nodeInformer coreinformers.NodeInformer
-	if sTypesFilter.isNodeInformerRequired() {
+	if sTypesFilter.isRequired(v1.ServiceTypeNodePort) {
 		nodeInformer = informerFactory.Core().V1().Nodes()
 		_, _ = nodeInformer.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
-	}
-
 	informerFactory.Start(ctx.Done())
 
 	// wait for the local cache to be populated.
@@ -742,13 +733,19 @@ func (sc *serviceSource) extractNodePortTargets(svc *v1.Service) (endpoint.Targe
 	access := getAccessFromAnnotations(svc.Annotations)
 	switch access {
 	case "public":
-		return append(externalIPs, ipv6IPs...), nil
+		if sc.exposeInternalIPv6 {
+			return append(externalIPs, ipv6IPs...), nil
+		}
+		return externalIPs, nil
 	case "private":
 		return internalIPs, nil
 	}
 
 	if len(externalIPs) > 0 {
-		return append(externalIPs, ipv6IPs...), nil
+		if sc.exposeInternalIPv6 {
+			return append(externalIPs, ipv6IPs...), nil
+		}
+		return externalIPs, nil
 	}
 
 	return internalIPs, nil
@@ -802,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))
 	}
 }
@@ -842,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/store.go b/source/store.go
index 8d69a31b..c77e89c3 100644
--- a/source/store.go
+++ b/source/store.go
@@ -22,12 +22,11 @@ import (
 	"fmt"
 	"net/http"
 	"os"
-	"strings"
+
 	"sync"
 	"time"
 
 	"github.com/cloudfoundry-community/go-cfclient"
-	"github.com/linki/instrumented_http"
 	openshift "github.com/openshift/client-go/route/clientset/versioned"
 	log "github.com/sirupsen/logrus"
 	istioclient "istio.io/client-go/pkg/clientset/versioned"
@@ -38,6 +37,8 @@ import (
 	"k8s.io/client-go/tools/clientcmd"
 	gateway "sigs.k8s.io/gateway-api/pkg/client/clientset/versioned"
 
+	extdnshttp "sigs.k8s.io/external-dns/pkg/http"
+
 	"sigs.k8s.io/external-dns/pkg/apis/externaldns"
 )
 
@@ -623,14 +624,11 @@ func instrumentedRESTConfig(kubeConfig, apiServerURL string, requestTimeout time
 	if err != nil {
 		return nil, err
 	}
+
 	config.WrapTransport = func(rt http.RoundTripper) http.RoundTripper {
-		return instrumented_http.NewTransport(rt, &instrumented_http.Callbacks{
-			PathProcessor: func(path string) string {
-				parts := strings.Split(path, "/")
-				return parts[len(parts)-1]
-			},
-		})
+		return extdnshttp.NewInstrumentedTransport(rt)
 	}
+
 	config.Timeout = requestTimeout
 	return config, nil
 }



Test patch:
diff --git a/internal/gen/docs/metrics/main_test.go b/internal/gen/docs/metrics/main_test.go
index a6d1efc2..bea1d75e 100644
--- a/internal/gen/docs/metrics/main_test.go
+++ b/internal/gen/docs/metrics/main_test.go
@@ -37,7 +37,7 @@ func TestComputeMetrics(t *testing.T) {
 		t.Errorf("Expected not empty metrics registry, got %d", len(reg.Metrics))
 	}
 
-	assert.Len(t, reg.Metrics, 19)
+	assert.Len(t, reg.Metrics, 20)
 }
 
 func TestGenerateMarkdownTableRenderer(t *testing.T) {
diff --git a/internal/idna/idna_test.go b/internal/idna/idna_test.go
new file mode 100644
index 00000000..f3ae93c4
--- /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/pkg/http/http_test.go b/pkg/http/http_test.go
new file mode 100644
index 00000000..cd17f387
--- /dev/null
+++ b/pkg/http/http_test.go
@@ -0,0 +1,81 @@
+/*
+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 http
+
+import (
+	"fmt"
+	"net/http"
+	"testing"
+
+	"github.com/stretchr/testify/require"
+)
+
+type dummyTransport struct{}
+
+func (d *dummyTransport) RoundTrip(req *http.Request) (*http.Response, error) {
+	return nil, fmt.Errorf("dummy error")
+}
+
+func TestNewInstrumentedTransport(t *testing.T) {
+	dt := &dummyTransport{}
+	rt := NewInstrumentedTransport(dt)
+	crt, ok := rt.(*CustomRoundTripper)
+	require.True(t, ok)
+	require.Equal(t, dt, crt.next)
+
+	// Should default to http.DefaultTransport if nil
+	rt2 := NewInstrumentedTransport(nil)
+	crt2, ok := rt2.(*CustomRoundTripper)
+	require.True(t, ok)
+	require.Equal(t, http.DefaultTransport, crt2.next)
+}
+
+func TestNewInstrumentedClient(t *testing.T) {
+	client := &http.Client{Transport: &dummyTransport{}}
+	result := NewInstrumentedClient(client)
+	require.Equal(t, client, result)
+	_, ok := result.Transport.(*CustomRoundTripper)
+	require.True(t, ok)
+
+	// Should default to http.DefaultClient if nil
+	result2 := NewInstrumentedClient(nil)
+	require.Equal(t, http.DefaultClient, result2)
+	_, ok = result2.Transport.(*CustomRoundTripper)
+	require.True(t, ok)
+}
+
+func TestPathProcessor(t *testing.T) {
+	tests := []struct {
+		input    string
+		expected string
+	}{
+		{"/foo/bar", "bar"},
+		{"/foo/", ""},
+		{"/", ""},
+		{"", ""},
+		{"/foo/bar/baz", "baz"},
+		{"foo/bar", "bar"},
+		{"foo", "foo"},
+		{"foo/", ""},
+	}
+
+	for _, tt := range tests {
+		t.Run(tt.input, func(t *testing.T) {
+			require.Equal(t, tt.expected, pathProcessor(tt.input))
+		})
+	}
+}
diff --git a/pkg/metrics/models_test.go b/pkg/metrics/models_test.go
index 70f50ea1..738f9f6e 100644
--- a/pkg/metrics/models_test.go
+++ b/pkg/metrics/models_test.go
@@ -17,18 +17,17 @@ limitations under the License.
 package metrics
 
 import (
+	"reflect"
 	"testing"
 
-	dto "github.com/prometheus/client_model/go"
-
 	"github.com/prometheus/client_golang/prometheus"
+	dto "github.com/prometheus/client_model/go"
 	"github.com/stretchr/testify/assert"
 )
 
 func TestNewGaugeWithOpts(t *testing.T) {
 	opts := prometheus.GaugeOpts{
 		Name:      "test_gauge",
-		Namespace: "test_namespace",
 		Subsystem: "test_subsystem",
 		Help:      "This is a test gauge",
 	}
@@ -37,7 +36,7 @@ func TestNewGaugeWithOpts(t *testing.T) {
 
 	assert.Equal(t, "gauge", gaugeMetric.Type)
 	assert.Equal(t, "test_gauge", gaugeMetric.Name)
-	assert.Equal(t, "test_namespace", gaugeMetric.Namespace)
+	assert.Equal(t, Namespace, gaugeMetric.Namespace)
 	assert.Equal(t, "test_subsystem", gaugeMetric.Subsystem)
 	assert.Equal(t, "This is a test gauge", gaugeMetric.Help)
 	assert.Equal(t, "test_subsystem_test_gauge", gaugeMetric.FQDN)
@@ -47,7 +46,6 @@ func TestNewGaugeWithOpts(t *testing.T) {
 func TestNewCounterWithOpts(t *testing.T) {
 	opts := prometheus.CounterOpts{
 		Name:      "test_counter",
-		Namespace: "test_namespace",
 		Subsystem: "test_subsystem",
 		Help:      "This is a test counter",
 	}
@@ -56,7 +54,7 @@ func TestNewCounterWithOpts(t *testing.T) {
 
 	assert.Equal(t, "counter", counterMetric.Type)
 	assert.Equal(t, "test_counter", counterMetric.Name)
-	assert.Equal(t, "test_namespace", counterMetric.Namespace)
+	assert.Equal(t, Namespace, counterMetric.Namespace)
 	assert.Equal(t, "test_subsystem", counterMetric.Subsystem)
 	assert.Equal(t, "This is a test counter", counterMetric.Help)
 	assert.Equal(t, "test_subsystem_test_counter", counterMetric.FQDN)
@@ -77,7 +75,7 @@ func TestNewCounterVecWithOpts(t *testing.T) {
 
 	assert.Equal(t, "counter", counterVecMetric.Type)
 	assert.Equal(t, "test_counter_vec", counterVecMetric.Name)
-	assert.Equal(t, "test_namespace", counterVecMetric.Namespace)
+	assert.Equal(t, Namespace, counterVecMetric.Namespace)
 	assert.Equal(t, "test_subsystem", counterVecMetric.Subsystem)
 	assert.Equal(t, "This is a test counter vector", counterVecMetric.Help)
 	assert.Equal(t, "test_subsystem_test_counter_vec", counterVecMetric.FQDN)
@@ -113,3 +111,20 @@ func TestGaugeV_SetWithLabels(t *testing.T) {
 
 	assert.Len(t, m.Label, 2)
 }
+
+func TestNewBuildInfoCollector(t *testing.T) {
+	metric := NewGaugeFuncMetric(prometheus.GaugeOpts{
+		Namespace: Namespace,
+		Name:      "build_info",
+		ConstLabels: prometheus.Labels{
+			"version":   "0.0.1",
+			"goversion": "1.24",
+			"arch":      "arm64",
+		},
+	})
+
+	desc := metric.GaugeFunc.Desc()
+
+	assert.Equal(t, "external_dns_build_info", reflect.ValueOf(desc).Elem().FieldByName("fqName").String())
+	assert.Contains(t, desc.String(), "version=\"0.0.1\"")
+}
diff --git a/provider/awssd/aws_sd_test.go b/provider/awssd/aws_sd_test.go
index 3bf59220..3cb4e758 100644
--- a/provider/awssd/aws_sd_test.go
+++ b/provider/awssd/aws_sd_test.go
@@ -254,14 +254,14 @@ func TestAWSSDProvider_ApplyChanges_Update(t *testing.T) {
 	ctx := context.Background()
 
 	// apply creates
-	provider.ApplyChanges(ctx, &plan.Changes{
+	_ = provider.ApplyChanges(ctx, &plan.Changes{
 		Create: oldEndpoints,
 	})
 
 	ctx = context.Background()
 
 	// apply update
-	provider.ApplyChanges(ctx, &plan.Changes{
+	_ = provider.ApplyChanges(ctx, &plan.Changes{
 		UpdateOld: oldEndpoints,
 		UpdateNew: newEndpoints,
 	})
@@ -306,6 +306,7 @@ func TestAWSSDProvider_ListNamespaces(t *testing.T) {
 	}{
 		{"public filter", endpoint.NewDomainFilter([]string{}), "public", []*sdtypes.NamespaceSummary{namespaceToNamespaceSummary(namespaces["public"])}},
 		{"private filter", endpoint.NewDomainFilter([]string{}), "private", []*sdtypes.NamespaceSummary{namespaceToNamespaceSummary(namespaces["private"])}},
+		{"optional filter", endpoint.NewDomainFilter([]string{}), "", []*sdtypes.NamespaceSummary{namespaceToNamespaceSummary(namespaces["public"]), namespaceToNamespaceSummary(namespaces["private"])}},
 		{"domain filter", endpoint.NewDomainFilter([]string{"public.com"}), "", []*sdtypes.NamespaceSummary{namespaceToNamespaceSummary(namespaces["public"])}},
 		{"non-existing domain", endpoint.NewDomainFilter([]string{"xxx.com"}), "", []*sdtypes.NamespaceSummary{}},
 	} {
@@ -913,7 +914,7 @@ func TestAWSSDProvider_RegisterInstance(t *testing.T) {
 	}
 
 	// AWS NLB instance (ALIAS)
-	provider.RegisterInstance(context.Background(), services["private"]["alias-srv"], &endpoint.Endpoint{
+	_ = provider.RegisterInstance(context.Background(), services["private"]["alias-srv"], &endpoint.Endpoint{
 		RecordType: endpoint.RecordTypeCNAME,
 		DNSName:    "service1.private.com.",
 		RecordTTL:  300,
@@ -927,7 +928,7 @@ func TestAWSSDProvider_RegisterInstance(t *testing.T) {
 	}
 
 	// CNAME instance
-	provider.RegisterInstance(context.Background(), services["private"]["cname-srv"], &endpoint.Endpoint{
+	_ = provider.RegisterInstance(context.Background(), services["private"]["cname-srv"], &endpoint.Endpoint{
 		RecordType: endpoint.RecordTypeCNAME,
 		DNSName:    "service2.private.com.",
 		RecordTTL:  300,
@@ -1001,7 +1002,7 @@ func TestAWSSDProvider_DeregisterInstance(t *testing.T) {
 
 	provider := newTestAWSSDProvider(api, endpoint.NewDomainFilter([]string{}), "", "")
 
-	provider.DeregisterInstance(context.Background(), services["private"]["srv1"], endpoint.NewEndpoint("srv1.private.com.", endpoint.RecordTypeA, "1.2.3.4"))
+	_ = provider.DeregisterInstance(context.Background(), services["private"]["srv1"], endpoint.NewEndpoint("srv1.private.com.", endpoint.RecordTypeA, "1.2.3.4"))
 
 	assert.Empty(t, instances["srv1"])
 }
diff --git a/provider/cloudflare/cloudflare_test.go b/provider/cloudflare/cloudflare_test.go
index 79b67000..ccc3d005 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")
+}
diff --git a/provider/zonefinder_test.go b/provider/zonefinder_test.go
index dccfe5ad..4eddd36c 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("???")
 
diff --git a/source/node_test.go b/source/node_test.go
index 160d5994..b362cdfd 100644
--- a/source/node_test.go
+++ b/source/node_test.go
@@ -26,7 +26,10 @@ import (
 
 	log "github.com/sirupsen/logrus"
 	"github.com/sirupsen/logrus/hooks/test"
+	"github.com/stretchr/testify/mock"
 	"k8s.io/client-go/kubernetes"
+	corev1lister "k8s.io/client-go/listers/core/v1"
+	"k8s.io/client-go/tools/cache"
 
 	"sigs.k8s.io/external-dns/internal/testutils"
 
@@ -619,6 +622,39 @@ func TestResourceLabelIsSetForEachNodeEndpoint(t *testing.T) {
 	}
 }
 
+func TestNodeSource_AddEventHandler(t *testing.T) {
+	fakeInformer := new(fakeNodeInformer)
+	inf := testInformer{}
+	fakeInformer.On("Informer").Return(&inf)
+
+	nSource := &nodeSource{
+		nodeInformer: fakeInformer,
+	}
+
+	handlerCalled := false
+	handler := func() { handlerCalled = true }
+
+	nSource.AddEventHandler(t.Context(), handler)
+
+	fakeInformer.AssertNumberOfCalls(t, "Informer", 1)
+	assert.False(t, handlerCalled)
+	assert.Equal(t, 1, inf.times)
+}
+
+type fakeNodeInformer struct {
+	mock.Mock
+	informer cache.SharedIndexInformer
+}
+
+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())
+}
+
 type nodeListBuilder struct {
 	nodes []v1.Node
 }
diff --git a/source/pod_test.go b/source/pod_test.go
index f2d67da5..e6e0f8b0 100644
--- a/source/pod_test.go
+++ b/source/pod_test.go
@@ -23,9 +23,13 @@ import (
 	"testing"
 
 	log "github.com/sirupsen/logrus"
+	"github.com/stretchr/testify/assert"
+	"github.com/stretchr/testify/mock"
 	"github.com/stretchr/testify/require"
 	corev1 "k8s.io/api/core/v1"
 	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
+	corev1lister "k8s.io/client-go/listers/core/v1"
+	"k8s.io/client-go/tools/cache"
 
 	"sigs.k8s.io/external-dns/endpoint"
 	"sigs.k8s.io/external-dns/internal/testutils"
@@ -909,6 +913,39 @@ func TestPodSourceLogs(t *testing.T) {
 	}
 }
 
+func TestPodSource_AddEventHandler(t *testing.T) {
+	fakeInformer := new(fakePodInformer)
+	inf := testInformer{}
+	fakeInformer.On("Informer").Return(&inf)
+
+	pSource := &podSource{
+		podInformer: fakeInformer,
+	}
+
+	handlerCalled := false
+	handler := func() { handlerCalled = true }
+
+	pSource.AddEventHandler(t.Context(), handler)
+
+	fakeInformer.AssertNumberOfCalls(t, "Informer", 1)
+	assert.False(t, handlerCalled)
+	assert.Equal(t, 1, inf.times)
+}
+
+type fakePodInformer struct {
+	mock.Mock
+	informer cache.SharedIndexInformer
+}
+
+func (f *fakePodInformer) Informer() cache.SharedIndexInformer {
+	args := f.Called()
+	return args.Get(0).(cache.SharedIndexInformer)
+}
+
+func (f *fakePodInformer) Lister() corev1lister.PodLister {
+	return corev1lister.NewPodLister(f.Informer().GetIndexer())
+}
+
 func nodesFixturesIPv6() []*corev1.Node {
 	return []*corev1.Node{
 		{
diff --git a/source/service_test.go b/source/service_test.go
index ecbe1fc8..07df803b 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"}},
@@ -1735,7 +1736,7 @@ func TestServiceSourceNodePortServices(t *testing.T) {
 			expected: []*endpoint.Endpoint{
 				{DNSName: "_foo._tcp.foo.example.org", Targets: endpoint.Targets{"0 50 30192 foo.example.org"}, RecordType: endpoint.RecordTypeSRV},
 				{DNSName: "foo.example.org", Targets: endpoint.Targets{"54.10.11.1", "54.10.11.2"}, RecordType: endpoint.RecordTypeA},
-				{DNSName: "foo.example.org", Targets: endpoint.Targets{"2001:DB8::1", "2001:DB8::2"}, RecordType: endpoint.RecordTypeAAAA},
+				{DNSName: "foo.example.org", Targets: endpoint.Targets{"2001:DB8::1", "2001:DB8::3"}, RecordType: endpoint.RecordTypeAAAA},
 			},
 			nodes: []*v1.Node{{
 				ObjectMeta: metav1.ObjectMeta{
@@ -1745,7 +1746,8 @@ func TestServiceSourceNodePortServices(t *testing.T) {
 					Addresses: []v1.NodeAddress{
 						{Type: v1.NodeExternalIP, Address: "54.10.11.1"},
 						{Type: v1.NodeInternalIP, Address: "10.0.1.1"},
-						{Type: v1.NodeInternalIP, Address: "2001:DB8::1"},
+						{Type: v1.NodeExternalIP, Address: "2001:DB8::1"},
+						{Type: v1.NodeInternalIP, Address: "2001:DB8::2"},
 					},
 				},
 			}, {
@@ -1756,7 +1758,8 @@ func TestServiceSourceNodePortServices(t *testing.T) {
 					Addresses: []v1.NodeAddress{
 						{Type: v1.NodeExternalIP, Address: "54.10.11.2"},
 						{Type: v1.NodeInternalIP, Address: "10.0.1.2"},
-						{Type: v1.NodeInternalIP, Address: "2001:DB8::2"},
+						{Type: v1.NodeExternalIP, Address: "2001:DB8::3"},
+						{Type: v1.NodeInternalIP, Address: "2001:DB8::4"},
 					},
 				},
 			}},
@@ -1779,7 +1782,8 @@ func TestServiceSourceNodePortServices(t *testing.T) {
 					Addresses: []v1.NodeAddress{
 						{Type: v1.NodeExternalIP, Address: "54.10.11.1"},
 						{Type: v1.NodeInternalIP, Address: "10.0.1.1"},
-						{Type: v1.NodeInternalIP, Address: "2001:DB8::1"},
+						{Type: v1.NodeExternalIP, Address: "2001:DB8::1"},
+						{Type: v1.NodeInternalIP, Address: "2001:DB8::2"},
 					},
 				},
 			}, {
@@ -1790,7 +1794,8 @@ func TestServiceSourceNodePortServices(t *testing.T) {
 					Addresses: []v1.NodeAddress{
 						{Type: v1.NodeExternalIP, Address: "54.10.11.2"},
 						{Type: v1.NodeInternalIP, Address: "10.0.1.2"},
-						{Type: v1.NodeInternalIP, Address: "2001:DB8::2"},
+						{Type: v1.NodeExternalIP, Address: "2001:DB8::3"},
+						{Type: v1.NodeInternalIP, Address: "2001:DB8::4"},
 					},
 				},
 			}},
@@ -1806,7 +1811,7 @@ func TestServiceSourceNodePortServices(t *testing.T) {
 			expected: []*endpoint.Endpoint{
 				{DNSName: "_foo._tcp.foo.bar.example.com", Targets: endpoint.Targets{"0 50 30192 foo.bar.example.com"}, RecordType: endpoint.RecordTypeSRV},
 				{DNSName: "foo.bar.example.com", Targets: endpoint.Targets{"54.10.11.1", "54.10.11.2"}, RecordType: endpoint.RecordTypeA},
-				{DNSName: "foo.bar.example.com", Targets: endpoint.Targets{"2001:DB8::1", "2001:DB8::2"}, RecordType: endpoint.RecordTypeAAAA},
+				{DNSName: "foo.bar.example.com", Targets: endpoint.Targets{"2001:DB8::1", "2001:DB8::3"}, RecordType: endpoint.RecordTypeAAAA},
 			},
 			nodes: []*v1.Node{{
 				ObjectMeta: metav1.ObjectMeta{
@@ -1816,7 +1821,8 @@ func TestServiceSourceNodePortServices(t *testing.T) {
 					Addresses: []v1.NodeAddress{
 						{Type: v1.NodeExternalIP, Address: "54.10.11.1"},
 						{Type: v1.NodeInternalIP, Address: "10.0.1.1"},
-						{Type: v1.NodeInternalIP, Address: "2001:DB8::1"},
+						{Type: v1.NodeExternalIP, Address: "2001:DB8::1"},
+						{Type: v1.NodeInternalIP, Address: "2001:DB8::2"},
 					},
 				},
 			}, {
@@ -1827,7 +1833,8 @@ func TestServiceSourceNodePortServices(t *testing.T) {
 					Addresses: []v1.NodeAddress{
 						{Type: v1.NodeExternalIP, Address: "54.10.11.2"},
 						{Type: v1.NodeInternalIP, Address: "10.0.1.2"},
-						{Type: v1.NodeInternalIP, Address: "2001:DB8::2"},
+						{Type: v1.NodeExternalIP, Address: "2001:DB8::3"},
+						{Type: v1.NodeInternalIP, Address: "2001:DB8::4"},
 					},
 				},
 			}},
@@ -1880,7 +1887,7 @@ func TestServiceSourceNodePortServices(t *testing.T) {
 			expected: []*endpoint.Endpoint{
 				{DNSName: "_foo._tcp.foo.example.org", Targets: endpoint.Targets{"0 50 30192 foo.example.org"}, RecordType: endpoint.RecordTypeSRV},
 				{DNSName: "foo.example.org", Targets: endpoint.Targets{"54.10.11.2"}, RecordType: endpoint.RecordTypeA},
-				{DNSName: "foo.example.org", Targets: endpoint.Targets{"2001:DB8::2"}, RecordType: endpoint.RecordTypeAAAA},
+				{DNSName: "foo.example.org", Targets: endpoint.Targets{"2001:DB8::3"}, RecordType: endpoint.RecordTypeAAAA},
 			},
 			nodes: []*v1.Node{{
 				ObjectMeta: metav1.ObjectMeta{
@@ -1890,7 +1897,8 @@ func TestServiceSourceNodePortServices(t *testing.T) {
 					Addresses: []v1.NodeAddress{
 						{Type: v1.NodeExternalIP, Address: "54.10.11.1"},
 						{Type: v1.NodeInternalIP, Address: "10.0.1.1"},
-						{Type: v1.NodeInternalIP, Address: "2001:DB8::1"},
+						{Type: v1.NodeExternalIP, Address: "2001:DB8::1"},
+						{Type: v1.NodeInternalIP, Address: "2001:DB8::2"},
 					},
 				},
 			}, {
@@ -1901,7 +1909,8 @@ func TestServiceSourceNodePortServices(t *testing.T) {
 					Addresses: []v1.NodeAddress{
 						{Type: v1.NodeExternalIP, Address: "54.10.11.2"},
 						{Type: v1.NodeInternalIP, Address: "10.0.1.2"},
-						{Type: v1.NodeInternalIP, Address: "2001:DB8::2"},
+						{Type: v1.NodeExternalIP, Address: "2001:DB8::3"},
+						{Type: v1.NodeInternalIP, Address: "2001:DB8::4"},
 					},
 				},
 			}},
@@ -1924,7 +1933,7 @@ func TestServiceSourceNodePortServices(t *testing.T) {
 			expected: []*endpoint.Endpoint{
 				{DNSName: "_foo._tcp.foo.example.org", Targets: endpoint.Targets{"0 50 30192 foo.example.org"}, RecordType: endpoint.RecordTypeSRV},
 				{DNSName: "foo.example.org", Targets: endpoint.Targets{"54.10.11.2"}, RecordType: endpoint.RecordTypeA},
-				{DNSName: "foo.example.org", Targets: endpoint.Targets{"2001:DB8::2"}, RecordType: endpoint.RecordTypeAAAA},
+				{DNSName: "foo.example.org", Targets: endpoint.Targets{"2001:DB8::3"}, RecordType: endpoint.RecordTypeAAAA},
 			},
 			nodes: []*v1.Node{{
 				ObjectMeta: metav1.ObjectMeta{
@@ -1934,7 +1943,8 @@ func TestServiceSourceNodePortServices(t *testing.T) {
 					Addresses: []v1.NodeAddress{
 						{Type: v1.NodeExternalIP, Address: "54.10.11.1"},
 						{Type: v1.NodeInternalIP, Address: "10.0.1.1"},
-						{Type: v1.NodeInternalIP, Address: "2001:DB8::1"},
+						{Type: v1.NodeExternalIP, Address: "2001:DB8::1"},
+						{Type: v1.NodeInternalIP, Address: "2001:DB8::2"},
 					},
 				},
 			}, {
@@ -1945,7 +1955,8 @@ func TestServiceSourceNodePortServices(t *testing.T) {
 					Addresses: []v1.NodeAddress{
 						{Type: v1.NodeExternalIP, Address: "54.10.11.2"},
 						{Type: v1.NodeInternalIP, Address: "10.0.1.2"},
-						{Type: v1.NodeInternalIP, Address: "2001:DB8::2"},
+						{Type: v1.NodeExternalIP, Address: "2001:DB8::3"},
+						{Type: v1.NodeInternalIP, Address: "2001:DB8::4"},
 					},
 				},
 			}},
@@ -2098,7 +2109,7 @@ func TestServiceSourceNodePortServices(t *testing.T) {
 			}},
 		},
 		{
-			title:            "access=public annotation NodePort services return an endpoint with public IP addresses of the cluster's nodes",
+			title:            "access=public annotation NodePort services return an endpoint with external IP addresses of the cluster's nodes if exposeInternalIPv6 is unset",
 			svcNamespace:     "testing",
 			svcName:          "foo",
 			svcType:          v1.ServiceTypeNodePort,
@@ -2111,7 +2122,7 @@ func TestServiceSourceNodePortServices(t *testing.T) {
 			expected: []*endpoint.Endpoint{
 				{DNSName: "_foo._tcp.foo.example.org", Targets: endpoint.Targets{"0 50 30192 foo.example.org"}, RecordType: endpoint.RecordTypeSRV},
 				{DNSName: "foo.example.org", Targets: endpoint.Targets{"54.10.11.1", "54.10.11.2"}, RecordType: endpoint.RecordTypeA},
-				{DNSName: "foo.example.org", Targets: endpoint.Targets{"2001:DB8::1", "2001:DB8::2"}, RecordType: endpoint.RecordTypeAAAA},
+				{DNSName: "foo.example.org", Targets: endpoint.Targets{"2001:DB8::1", "2001:DB8::3"}, RecordType: endpoint.RecordTypeAAAA},
 			},
 			nodes: []*v1.Node{{
 				ObjectMeta: metav1.ObjectMeta{
@@ -2121,7 +2132,8 @@ func TestServiceSourceNodePortServices(t *testing.T) {
 					Addresses: []v1.NodeAddress{
 						{Type: v1.NodeExternalIP, Address: "54.10.11.1"},
 						{Type: v1.NodeInternalIP, Address: "10.0.1.1"},
-						{Type: v1.NodeInternalIP, Address: "2001:DB8::1"},
+						{Type: v1.NodeExternalIP, Address: "2001:DB8::1"},
+						{Type: v1.NodeInternalIP, Address: "2001:DB8::2"},
 					},
 				},
 			}, {
@@ -2132,9 +2144,53 @@ func TestServiceSourceNodePortServices(t *testing.T) {
 					Addresses: []v1.NodeAddress{
 						{Type: v1.NodeExternalIP, Address: "54.10.11.2"},
 						{Type: v1.NodeInternalIP, Address: "10.0.1.2"},
+						{Type: v1.NodeExternalIP, Address: "2001:DB8::3"},
+						{Type: v1.NodeInternalIP, Address: "2001:DB8::4"},
+					},
+				},
+			}},
+		},
+		{
+			title:            "access=public annotation NodePort services return an endpoint with public IP addresses of the cluster's nodes if exposeInternalIPv6 is set to true",
+			svcNamespace:     "testing",
+			svcName:          "foo",
+			svcType:          v1.ServiceTypeNodePort,
+			svcTrafficPolicy: v1.ServiceExternalTrafficPolicyTypeCluster,
+			labels:           map[string]string{},
+			annotations: map[string]string{
+				hostnameAnnotationKey: "foo.example.org.",
+				accessAnnotationKey:   "public",
+			},
+			exposeInternalIPv6: true,
+			expected: []*endpoint.Endpoint{
+				{DNSName: "_foo._tcp.foo.example.org", Targets: endpoint.Targets{"0 50 30192 foo.example.org"}, RecordType: endpoint.RecordTypeSRV},
+				{DNSName: "foo.example.org", Targets: endpoint.Targets{"54.10.11.1", "54.10.11.2"}, RecordType: endpoint.RecordTypeA},
+				{DNSName: "foo.example.org", Targets: endpoint.Targets{"2001:DB8::1", "2001:DB8::2", "2001:DB8::3", "2001:DB8::4"}, RecordType: endpoint.RecordTypeAAAA},
+			},
+			nodes: []*v1.Node{{
+				ObjectMeta: metav1.ObjectMeta{
+					Name: "node1",
+				},
+				Status: v1.NodeStatus{
+					Addresses: []v1.NodeAddress{
+						{Type: v1.NodeExternalIP, Address: "54.10.11.1"},
+						{Type: v1.NodeInternalIP, Address: "10.0.1.1"},
+						{Type: v1.NodeExternalIP, Address: "2001:DB8::1"},
 						{Type: v1.NodeInternalIP, Address: "2001:DB8::2"},
 					},
 				},
+			}, {
+				ObjectMeta: metav1.ObjectMeta{
+					Name: "node2",
+				},
+				Status: v1.NodeStatus{
+					Addresses: []v1.NodeAddress{
+						{Type: v1.NodeExternalIP, Address: "54.10.11.2"},
+						{Type: v1.NodeInternalIP, Address: "10.0.1.2"},
+						{Type: v1.NodeExternalIP, Address: "2001:DB8::3"},
+						{Type: v1.NodeInternalIP, Address: "2001:DB8::4"},
+					},
+				},
 			}},
 		},
 		{
@@ -2443,6 +2499,7 @@ func TestHeadlessServices(t *testing.T) {
 		podsReady                []bool
 		publishNotReadyAddresses bool
 		nodes                    []v1.Node
+		serviceTypesFilter       []string
 		expected                 []*endpoint.Endpoint
 		expectError              bool
 	}{
@@ -2473,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"}},
@@ -2507,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"}},
@@ -2541,6 +2600,7 @@ func TestHeadlessServices(t *testing.T) {
 			[]bool{true, true},
 			false,
 			[]v1.Node{},
+			[]string{},
 			[]*endpoint.Endpoint{},
 			false,
 		},
@@ -2572,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)},
@@ -2607,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)},
@@ -2641,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"}},
@@ -2674,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"}},
@@ -2708,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"}},
 			},
@@ -2740,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"}},
 			},
@@ -2772,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"}},
 			},
@@ -2806,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"}},
 			},
@@ -2840,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"}},
 			},
@@ -2884,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"}},
 			},
@@ -2932,6 +3002,7 @@ func TestHeadlessServices(t *testing.T) {
 					},
 				},
 			},
+			[]string{},
 			[]*endpoint.Endpoint{
 				{DNSName: "service.example.org", RecordType: endpoint.RecordTypeAAAA, Targets: endpoint.Targets{"2001:db8::5"}},
 			},
@@ -2976,6 +3047,7 @@ func TestHeadlessServices(t *testing.T) {
 					},
 				},
 			},
+			[]string{},
 			[]*endpoint.Endpoint{
 				{DNSName: "service.example.org", RecordType: endpoint.RecordTypeAAAA, Targets: endpoint.Targets{"2001:db8::4"}},
 			},
@@ -3024,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"}},
@@ -3058,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"}},
 			},
@@ -3091,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"}},
 			},
@@ -3187,7 +3262,7 @@ func TestHeadlessServices(t *testing.T) {
 				true,
 				false,
 				false,
-				[]string{},
+				tc.serviceTypesFilter,
 				tc.ignoreHostnameAnnotation,
 				labels.Everything(),
 				false,
@@ -3939,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{
@@ -4079,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},
 			},
@@ -4121,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},
 			},
@@ -4316,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)
 			},
 		},
 		{
@@ -4325,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)
 			},
 		},
 	}
@@ -4626,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)
+		})
+	}
+}