mirror of
https://github.com/kubernetes-sigs/external-dns.git
synced 2025-08-05 17:16:59 +02:00
Merge branch 'kubernetes-sigs:master' into fix-events
This commit is contained in:
commit
03f7c8d92e
8
.github/ISSUE_TEMPLATE/---bug-report.md
vendored
8
.github/ISSUE_TEMPLATE/---bug-report.md
vendored
@ -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**:
|
||||
|
2
.github/workflows/dependency-update.yaml
vendored
2
.github/workflows/dependency-update.yaml
vendored
@ -17,7 +17,7 @@ jobs:
|
||||
uses: actions/checkout@v4.2.2
|
||||
# https://github.com/renovatebot/github-action
|
||||
- name: self-hosted renovate
|
||||
uses: renovatebot/github-action@v43.0.4
|
||||
uses: renovatebot/github-action@v43.0.5
|
||||
with:
|
||||
# https://docs.github.com/en/actions/security-for-github-actions/security-guides/automatic-token-authentication
|
||||
token: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
@ -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)
|
||||
|
@ -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 |
|
||||
|
@ -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
|
||||
|
||||
|
215
docs/tutorials/myra.md
Normal file
215
docs/tutorials/myra.md
Normal file
@ -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
|
@ -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)
|
||||
}
|
||||
|
70
go.mod
70
go.mod
@ -4,7 +4,7 @@ go 1.24.2
|
||||
|
||||
require (
|
||||
cloud.google.com/go/compute/metadata v0.7.0
|
||||
github.com/Azure/azure-sdk-for-go/sdk/azcore v1.18.1
|
||||
github.com/Azure/azure-sdk-for-go/sdk/azcore v1.18.2
|
||||
github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.10.1
|
||||
github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/dns/armdns v1.2.0
|
||||
github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/privatedns/armprivatedns v1.3.0
|
||||
@ -13,23 +13,23 @@ require (
|
||||
github.com/akamai/AkamaiOPEN-edgegrid-golang v1.2.2
|
||||
github.com/alecthomas/kingpin/v2 v2.4.0
|
||||
github.com/aliyun/alibaba-cloud-sdk-go v1.63.107
|
||||
github.com/aws/aws-sdk-go-v2 v1.36.6
|
||||
github.com/aws/aws-sdk-go-v2/config v1.29.18
|
||||
github.com/aws/aws-sdk-go-v2/credentials v1.17.71
|
||||
github.com/aws/aws-sdk-go-v2/feature/dynamodb/attributevalue v1.19.6
|
||||
github.com/aws/aws-sdk-go-v2/service/dynamodb v1.44.1
|
||||
github.com/aws/aws-sdk-go-v2/service/route53 v1.53.1
|
||||
github.com/aws/aws-sdk-go-v2/service/servicediscovery v1.35.8
|
||||
github.com/aws/aws-sdk-go-v2/service/sts v1.34.1
|
||||
github.com/aws/aws-sdk-go-v2 v1.37.1
|
||||
github.com/aws/aws-sdk-go-v2/config v1.30.2
|
||||
github.com/aws/aws-sdk-go-v2/credentials v1.18.2
|
||||
github.com/aws/aws-sdk-go-v2/feature/dynamodb/attributevalue v1.20.1
|
||||
github.com/aws/aws-sdk-go-v2/service/dynamodb v1.45.1
|
||||
github.com/aws/aws-sdk-go-v2/service/route53 v1.54.1
|
||||
github.com/aws/aws-sdk-go-v2/service/servicediscovery v1.36.1
|
||||
github.com/aws/aws-sdk-go-v2/service/sts v1.35.1
|
||||
github.com/bodgit/tsig v1.2.2
|
||||
github.com/cenkalti/backoff/v5 v5.0.2
|
||||
github.com/civo/civogo v0.6.1
|
||||
github.com/cenkalti/backoff/v5 v5.0.3
|
||||
github.com/civo/civogo v0.6.3
|
||||
github.com/cloudflare/cloudflare-go v0.115.0
|
||||
github.com/cloudflare/cloudflare-go/v4 v4.6.0
|
||||
github.com/cloudfoundry-community/go-cfclient v0.0.0-20190201205600-f136f9222381
|
||||
github.com/datawire/ambassador v1.12.4
|
||||
github.com/denverdino/aliyungo v0.0.0-20230411124812-ab98a9173ace
|
||||
github.com/digitalocean/godo v1.159.0
|
||||
github.com/digitalocean/godo v1.161.0
|
||||
github.com/dnsimple/dnsimple-go v1.7.0
|
||||
github.com/exoscale/egoscale v0.102.3
|
||||
github.com/ffledgling/pdns-go v0.0.0-20180219074714-524e7daccd99
|
||||
@ -38,35 +38,35 @@ require (
|
||||
github.com/goccy/go-yaml v1.18.0
|
||||
github.com/google/go-cmp v0.7.0
|
||||
github.com/google/uuid v1.6.0
|
||||
github.com/linode/linodego v1.53.0
|
||||
github.com/linode/linodego v1.54.0
|
||||
github.com/maxatome/go-testdeep v1.14.0
|
||||
github.com/miekg/dns v1.1.67
|
||||
github.com/openshift/api v0.0.0-20230607130528-611114dca681
|
||||
github.com/openshift/client-go v0.0.0-20230607134213-3cd0021bbee3
|
||||
github.com/oracle/oci-go-sdk/v65 v65.96.0
|
||||
github.com/oracle/oci-go-sdk/v65 v65.97.0
|
||||
github.com/ovh/go-ovh v1.9.0
|
||||
github.com/patrickmn/go-cache v2.1.0+incompatible
|
||||
github.com/pluralsh/gqlclient v1.12.2
|
||||
github.com/projectcontour/contour v1.32.0
|
||||
github.com/prometheus/client_golang v1.22.0
|
||||
github.com/prometheus/client_golang v1.23.0
|
||||
github.com/prometheus/client_model v0.6.2
|
||||
github.com/prometheus/common v0.65.0
|
||||
github.com/scaleway/scaleway-sdk-go v1.0.0-beta.34
|
||||
github.com/sirupsen/logrus v1.9.3
|
||||
github.com/stretchr/testify v1.10.0
|
||||
github.com/transip/gotransip/v6 v6.26.0
|
||||
go.etcd.io/etcd/api/v3 v3.6.3
|
||||
go.etcd.io/etcd/client/v3 v3.6.3
|
||||
go.etcd.io/etcd/api/v3 v3.6.4
|
||||
go.etcd.io/etcd/client/v3 v3.6.4
|
||||
go.uber.org/ratelimit v0.3.1
|
||||
golang.org/x/net v0.42.0
|
||||
golang.org/x/oauth2 v0.30.0
|
||||
golang.org/x/sync v0.16.0
|
||||
golang.org/x/text v0.27.0
|
||||
golang.org/x/time v0.12.0
|
||||
google.golang.org/api v0.243.0
|
||||
google.golang.org/api v0.244.0
|
||||
gopkg.in/ns1/ns1-go.v2 v2.14.4
|
||||
istio.io/api v1.26.2
|
||||
istio.io/client-go v1.26.2
|
||||
istio.io/api v1.26.3
|
||||
istio.io/client-go v1.26.3
|
||||
k8s.io/api v0.33.3
|
||||
k8s.io/apimachinery v0.33.3
|
||||
k8s.io/client-go v0.33.3
|
||||
@ -80,22 +80,22 @@ require (
|
||||
cloud.google.com/go/auth/oauth2adapt v0.2.8 // indirect
|
||||
code.cloudfoundry.org/gofileutils v0.0.0-20170111115228-4d0c80011a0f // indirect
|
||||
github.com/99designs/gqlgen v0.17.73 // indirect
|
||||
github.com/Azure/azure-sdk-for-go/sdk/internal v1.11.1 // indirect
|
||||
github.com/Azure/azure-sdk-for-go/sdk/internal v1.11.2 // indirect
|
||||
github.com/AzureAD/microsoft-authentication-library-for-go v1.4.2 // indirect
|
||||
github.com/Masterminds/semver v1.5.0 // indirect
|
||||
github.com/alecthomas/units v0.0.0-20211218093645-b94a6e3cc137 // indirect
|
||||
github.com/alexbrainman/sspi v0.0.0-20180613141037-e580b900e9f5 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.16.33 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.37 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.37 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.18.1 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/internal/configsources v1.4.1 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.7.1 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/internal/ini v1.8.3 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/service/dynamodbstreams v1.26.1 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.12.4 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/endpoint-discovery v1.10.18 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.12.18 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/service/sso v1.25.6 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/service/ssooidc v1.30.4 // indirect
|
||||
github.com/aws/smithy-go v1.22.4 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/service/dynamodbstreams v1.27.1 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.13.0 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/endpoint-discovery v1.11.1 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.13.1 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/service/sso v1.26.1 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/service/ssooidc v1.31.1 // indirect
|
||||
github.com/aws/smithy-go v1.22.5 // indirect
|
||||
github.com/benbjohnson/clock v1.3.0 // indirect
|
||||
github.com/beorn7/perks v1.0.1 // indirect
|
||||
github.com/cespare/xxhash/v2 v2.3.0 // indirect
|
||||
@ -154,7 +154,7 @@ require (
|
||||
github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c // indirect
|
||||
github.com/pkg/errors v0.9.1 // indirect
|
||||
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect
|
||||
github.com/prometheus/procfs v0.15.1 // indirect
|
||||
github.com/prometheus/procfs v0.16.1 // indirect
|
||||
github.com/rivo/uniseg v0.2.0 // indirect
|
||||
github.com/schollz/progressbar/v3 v3.8.6 // indirect
|
||||
github.com/shopspring/decimal v1.3.1 // indirect
|
||||
@ -170,7 +170,7 @@ require (
|
||||
github.com/x448/float16 v0.8.4 // indirect
|
||||
github.com/xhit/go-str2duration/v2 v2.1.0 // indirect
|
||||
github.com/youmark/pkcs8 v0.0.0-20240726163527-a2c0da244d78 // indirect
|
||||
go.etcd.io/etcd/client/pkg/v3 v3.6.3 // indirect
|
||||
go.etcd.io/etcd/client/pkg/v3 v3.6.4 // indirect
|
||||
go.opentelemetry.io/auto/sdk v1.1.0 // indirect
|
||||
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.61.0 // indirect
|
||||
go.opentelemetry.io/otel v1.36.0 // indirect
|
||||
@ -185,8 +185,8 @@ require (
|
||||
golang.org/x/term v0.33.0 // indirect
|
||||
golang.org/x/tools v0.34.0 // indirect
|
||||
google.golang.org/genproto/googleapis/api v0.0.0-20250603155806-513f23925822 // indirect
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20250715232539-7130f93afb79 // indirect
|
||||
google.golang.org/grpc v1.73.0 // indirect
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20250728155136-f173205681a0 // indirect
|
||||
google.golang.org/grpc v1.74.2 // indirect
|
||||
google.golang.org/protobuf v1.36.6 // indirect
|
||||
gopkg.in/evanphx/json-patch.v4 v4.12.0 // indirect
|
||||
gopkg.in/inf.v0 v0.9.1 // indirect
|
||||
|
140
go.sum
140
go.sum
@ -16,14 +16,14 @@ github.com/99designs/gqlgen v0.17.73 h1:A3Ki+rHWqKbAOlg5fxiZBnz6OjW3nwupDHEG15gE
|
||||
github.com/99designs/gqlgen v0.17.73/go.mod h1:2RyGWjy2k7W9jxrs8MOQthXGkD3L3oGr0jXW3Pu8lGg=
|
||||
github.com/Azure/azure-sdk-for-go v16.2.1+incompatible h1:KnPIugL51v3N3WwvaSmZbxukD1WuWXOiE9fRdu32f2I=
|
||||
github.com/Azure/azure-sdk-for-go v16.2.1+incompatible/go.mod h1:9XXNKU+eRnpl9moKnB4QOLf1HestfXbmab5FXxiDBjc=
|
||||
github.com/Azure/azure-sdk-for-go/sdk/azcore v1.18.1 h1:Wc1ml6QlJs2BHQ/9Bqu1jiyggbsSjramq2oUmp5WeIo=
|
||||
github.com/Azure/azure-sdk-for-go/sdk/azcore v1.18.1/go.mod h1:Ot/6aikWnKWi4l9QB7qVSwa8iMphQNqkWALMoNT3rzM=
|
||||
github.com/Azure/azure-sdk-for-go/sdk/azcore v1.18.2 h1:Hr5FTipp7SL07o2FvoVOX9HRiRH3CR3Mj8pxqCcdD5A=
|
||||
github.com/Azure/azure-sdk-for-go/sdk/azcore v1.18.2/go.mod h1:QyVsSSN64v5TGltphKLQ2sQxe4OBQg0J1eKRcVBnfgE=
|
||||
github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.10.1 h1:B+blDbyVIG3WaikNxPnhPiJ1MThR03b3vKGtER95TP4=
|
||||
github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.10.1/go.mod h1:JdM5psgjfBf5fo2uWOZhflPWyDBZ/O/CNAH9CtsuZE4=
|
||||
github.com/Azure/azure-sdk-for-go/sdk/azidentity/cache v0.3.2 h1:yz1bePFlP5Vws5+8ez6T3HWXPmwOK7Yvq8QxDBD3SKY=
|
||||
github.com/Azure/azure-sdk-for-go/sdk/azidentity/cache v0.3.2/go.mod h1:Pa9ZNPuoNu/GztvBSKk9J1cDJW6vk/n0zLtV4mgd8N8=
|
||||
github.com/Azure/azure-sdk-for-go/sdk/internal v1.11.1 h1:FPKJS1T+clwv+OLGt13a8UjqeRuh0O4SJ3lUriThc+4=
|
||||
github.com/Azure/azure-sdk-for-go/sdk/internal v1.11.1/go.mod h1:j2chePtV91HrC22tGoRX3sGY42uF13WzmmV80/OdVAA=
|
||||
github.com/Azure/azure-sdk-for-go/sdk/internal v1.11.2 h1:9iefClla7iYpfYWdzPCRDozdmndjTm8DXdpCzPajMgA=
|
||||
github.com/Azure/azure-sdk-for-go/sdk/internal v1.11.2/go.mod h1:XtLgD3ZD34DAaVIIAyG3objl5DynM3CQ/vMcbBNJZGI=
|
||||
github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/dns/armdns v1.2.0 h1:lpOxwrQ919lCZoNCd69rVt8u1eLZuMORrGXqy8sNf3c=
|
||||
github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/dns/armdns v1.2.0/go.mod h1:fSvRkb8d26z9dbL40Uf/OO6Vo9iExtZK3D0ulRV+8M0=
|
||||
github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/internal/v3 v3.1.0 h1:2qsIIvxVT+uE6yrNldntJKlLRgxGbZ85kgtz5SNBhMw=
|
||||
@ -114,44 +114,44 @@ github.com/aws/aws-lambda-go v1.13.3/go.mod h1:4UKl9IzQMoD+QF79YdCuzCwp8VbmG4VAQ
|
||||
github.com/aws/aws-sdk-go v1.15.11/go.mod h1:mFuSZ37Z9YOHbQEwBWztmVzqXrEkub65tZoCYDt7FT0=
|
||||
github.com/aws/aws-sdk-go v1.27.0/go.mod h1:KmX6BPdI08NWTb3/sm4ZGu5ShLoqVDhKgpiN924inxo=
|
||||
github.com/aws/aws-sdk-go-v2 v0.18.0/go.mod h1:JWVYvqSMppoMJC0x5wdwiImzgXTI9FuZwxzkQq9wy+g=
|
||||
github.com/aws/aws-sdk-go-v2 v1.36.6 h1:zJqGjVbRdTPojeCGWn5IR5pbJwSQSBh5RWFTQcEQGdU=
|
||||
github.com/aws/aws-sdk-go-v2 v1.36.6/go.mod h1:EYrzvCCN9CMUTa5+6lf6MM4tq3Zjp8UhSGR/cBsjai0=
|
||||
github.com/aws/aws-sdk-go-v2/config v1.29.18 h1:x4T1GRPnqKV8HMJOMtNktbpQMl3bIsfx8KbqmveUO2I=
|
||||
github.com/aws/aws-sdk-go-v2/config v1.29.18/go.mod h1:bvz8oXugIsH8K7HLhBv06vDqnFv3NsGDt2Znpk7zmOU=
|
||||
github.com/aws/aws-sdk-go-v2/credentials v1.17.71 h1:r2w4mQWnrTMJjOyIsZtGp3R3XGY3nqHn8C26C2lQWgA=
|
||||
github.com/aws/aws-sdk-go-v2/credentials v1.17.71/go.mod h1:E7VF3acIup4GB5ckzbKFrCK0vTvEQxOxgdq4U3vcMCY=
|
||||
github.com/aws/aws-sdk-go-v2/feature/dynamodb/attributevalue v1.19.6 h1:gBfrCR6IwAhmx+oCf9i9FJo1+Cxx5f0In+PaYQbkqbU=
|
||||
github.com/aws/aws-sdk-go-v2/feature/dynamodb/attributevalue v1.19.6/go.mod h1:zAO6MqUum/2yfE/Ig1LPPtzCBudQtrGBaz1gcNzgAoY=
|
||||
github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.16.33 h1:D9ixiWSG4lyUBL2DDNK924Px9V/NBVpML90MHqyTADY=
|
||||
github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.16.33/go.mod h1:caS/m4DI+cij2paz3rtProRBI4s/+TCiWoaWZuQ9010=
|
||||
github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.37 h1:osMWfm/sC/L4tvEdQ65Gri5ZZDCUpuYJZbTTDrsn4I0=
|
||||
github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.37/go.mod h1:ZV2/1fbjOPr4G4v38G3Ww5TBT4+hmsK45s/rxu1fGy0=
|
||||
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.37 h1:v+X21AvTb2wZ+ycg1gx+orkB/9U6L7AOp93R7qYxsxM=
|
||||
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.37/go.mod h1:G0uM1kyssELxmJ2VZEfG0q2npObR3BAkF3c1VsfVnfs=
|
||||
github.com/aws/aws-sdk-go-v2 v1.37.1 h1:SMUxeNz3Z6nqGsXv0JuJXc8w5YMtrQMuIBmDx//bBDY=
|
||||
github.com/aws/aws-sdk-go-v2 v1.37.1/go.mod h1:9Q0OoGQoboYIAJyslFyF1f5K1Ryddop8gqMhWx/n4Wg=
|
||||
github.com/aws/aws-sdk-go-v2/config v1.30.2 h1:YE1BmSc4fFYqFgN1mN8uzrtc7R9x+7oSWeX8ckoltAw=
|
||||
github.com/aws/aws-sdk-go-v2/config v1.30.2/go.mod h1:UNrLGZ6jfAVjgVJpkIxjLufRJqTXCVYOpkeVf83kwBo=
|
||||
github.com/aws/aws-sdk-go-v2/credentials v1.18.2 h1:mfm0GKY/PHLhs7KO0sUaOtFnIQ15Qqxt+wXbO/5fIfs=
|
||||
github.com/aws/aws-sdk-go-v2/credentials v1.18.2/go.mod h1:v0SdJX6ayPeZFQxgXUKw5RhLpAoZUuynxWDfh8+Eknc=
|
||||
github.com/aws/aws-sdk-go-v2/feature/dynamodb/attributevalue v1.20.1 h1:1ToPL5M0nYwkIOTb9r+ION0ZZe9xemRe1mRMWMw5ihs=
|
||||
github.com/aws/aws-sdk-go-v2/feature/dynamodb/attributevalue v1.20.1/go.mod h1:dDdNpGWZdj4AxADkfM1IG1IutBmSJM7zURhUNOVv/lE=
|
||||
github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.18.1 h1:owmNBboeA0kHKDcdF8KiSXmrIuXZustfMGGytv6OMkM=
|
||||
github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.18.1/go.mod h1:Bg1miN59SGxrZqlP8vJZSmXW+1N8Y1MjQDq1OfuNod8=
|
||||
github.com/aws/aws-sdk-go-v2/internal/configsources v1.4.1 h1:ksZXBYv80EFTcgc8OJO48aQ8XDWXIQL7gGasPeCoTzI=
|
||||
github.com/aws/aws-sdk-go-v2/internal/configsources v1.4.1/go.mod h1:HSksQyyJETVZS7uM54cir0IgxttTD+8aEoJMPGepHBI=
|
||||
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.7.1 h1:+dn/xF/05utS7tUhjIcndbuaPjfll2LhbH1cCDGLYUQ=
|
||||
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.7.1/go.mod h1:hyAGz30LHdm5KBZDI58MXx5lDVZ5CUfvfTZvMu4HCZo=
|
||||
github.com/aws/aws-sdk-go-v2/internal/ini v1.8.3 h1:bIqFDwgGXXN1Kpp99pDOdKMTTb5d2KyU5X/BZxjOkRo=
|
||||
github.com/aws/aws-sdk-go-v2/internal/ini v1.8.3/go.mod h1:H5O/EsxDWyU+LP/V8i5sm8cxoZgc2fdNR9bxlOFrQTo=
|
||||
github.com/aws/aws-sdk-go-v2/service/dynamodb v1.44.1 h1:UoEWyfuQ/yNOuDENk5nn+AgNCH2Y5yzQEv6YbTyhIV8=
|
||||
github.com/aws/aws-sdk-go-v2/service/dynamodb v1.44.1/go.mod h1:K1I47BjiTRX00pBxfJLYK80QFRcf6blev2wbjgC5Cyc=
|
||||
github.com/aws/aws-sdk-go-v2/service/dynamodbstreams v1.26.1 h1:WD2RDt93+IgNvlxEKkx/b3BQrpw5G/YpDHvGXweO5wE=
|
||||
github.com/aws/aws-sdk-go-v2/service/dynamodbstreams v1.26.1/go.mod h1:8ZWruWnVWtJwjSHEtMWFcI1W6L6PD6i+uKCJ9EiJBbE=
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.12.4 h1:CXV68E2dNqhuynZJPB80bhPQwAKqBWVer887figW6Jc=
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.12.4/go.mod h1:/xFi9KtvBXP97ppCz1TAEvU1Uf66qvid89rbem3wCzQ=
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/endpoint-discovery v1.10.18 h1:QnGWwpTiazs1Y74RwA8VUfAtKuJQbnQ98DBFnSywj0s=
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/endpoint-discovery v1.10.18/go.mod h1:gWOI6Vb0Bbmsi0Ejvtt3RkwKpdoa/SOYTVUlzqYPRLc=
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.12.18 h1:vvbXsA2TVO80/KT7ZqCbx934dt6PY+vQ8hZpUZ/cpYg=
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.12.18/go.mod h1:m2JJHledjBGNMsLOF1g9gbAxprzq3KjC8e4lxtn+eWg=
|
||||
github.com/aws/aws-sdk-go-v2/service/route53 v1.53.1 h1:R3nSX1hguRy6MnknHiepSvqnnL8ansFwK2hidPesAYU=
|
||||
github.com/aws/aws-sdk-go-v2/service/route53 v1.53.1/go.mod h1:fmSiB4OAghn85lQgk7XN9l9bpFg5Bm1v3HuaXKytPEw=
|
||||
github.com/aws/aws-sdk-go-v2/service/servicediscovery v1.35.8 h1:PPQUm3zG6XzctspDTWC6vO3DvP/RZ+04RB11r98yb6E=
|
||||
github.com/aws/aws-sdk-go-v2/service/servicediscovery v1.35.8/go.mod h1:C1n2zhotURaNj/BNgdPdhXh/i6V53rI3RmVEaNDakSM=
|
||||
github.com/aws/aws-sdk-go-v2/service/sso v1.25.6 h1:rGtWqkQbPk7Bkwuv3NzpE/scwwL9sC1Ul3tn9x83DUI=
|
||||
github.com/aws/aws-sdk-go-v2/service/sso v1.25.6/go.mod h1:u4ku9OLv4TO4bCPdxf4fA1upaMaJmP9ZijGk3AAOC6Q=
|
||||
github.com/aws/aws-sdk-go-v2/service/ssooidc v1.30.4 h1:OV/pxyXh+eMA0TExHEC4jyWdumLxNbzz1P0zJoezkJc=
|
||||
github.com/aws/aws-sdk-go-v2/service/ssooidc v1.30.4/go.mod h1:8Mm5VGYwtm+r305FfPSuc+aFkrypeylGYhFim6XEPoc=
|
||||
github.com/aws/aws-sdk-go-v2/service/sts v1.34.1 h1:aUrLQwJfZtwv3/ZNG2xRtEen+NqI3iesuacjP51Mv1s=
|
||||
github.com/aws/aws-sdk-go-v2/service/sts v1.34.1/go.mod h1:3wFBZKoWnX3r+Sm7in79i54fBmNfwhdNdQuscCw7QIk=
|
||||
github.com/aws/smithy-go v1.22.4 h1:uqXzVZNuNexwc/xrh6Tb56u89WDlJY6HS+KC0S4QSjw=
|
||||
github.com/aws/smithy-go v1.22.4/go.mod h1:t1ufH5HMublsJYulve2RKmHDC15xu1f26kHCp/HgceI=
|
||||
github.com/aws/aws-sdk-go-v2/service/dynamodb v1.45.1 h1:gFD9BLrXox2Q5zxFwyD2OnGb40YYofQ/anaGxVP848Q=
|
||||
github.com/aws/aws-sdk-go-v2/service/dynamodb v1.45.1/go.mod h1:J+qJkxNypYjDcwXldBH+ox2T7OshtP6LOq5VhU0v6hg=
|
||||
github.com/aws/aws-sdk-go-v2/service/dynamodbstreams v1.27.1 h1:H4W48E0/zjiHLlL59/Y0DpaB+krXsuarjwrquCwMtT4=
|
||||
github.com/aws/aws-sdk-go-v2/service/dynamodbstreams v1.27.1/go.mod h1:nGsqtVMMjTeFot6U+rLj+mpOcZybPoxyQPMKY4GHwQo=
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.13.0 h1:6+lZi2JeGKtCraAj1rpoZfKqnQ9SptseRZioejfUOLM=
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.13.0/go.mod h1:eb3gfbVIxIoGgJsi9pGne19dhCBpK6opTYpQqAmdy44=
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/endpoint-discovery v1.11.1 h1:/E4JUPMI8LRX2XpXsbmKN42l1lZPoLjGJ/Kun97pLc0=
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/endpoint-discovery v1.11.1/go.mod h1:qgbd/t8S8y5e87KPQ4kC0kyxZ0K6nC1QiDtFMoxlsOo=
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.13.1 h1:ky79ysLMxhwk5rxJtS+ILd3Mc8kC5fhsLBrP27r6h4I=
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.13.1/go.mod h1:+2MmkvFvPYM1vsozBWduoLJUi5maxFk5B7KJFECujhY=
|
||||
github.com/aws/aws-sdk-go-v2/service/route53 v1.54.1 h1:DvwcqU6ec5NNCACSSEYKuTg9J3PDFFlngkwV0k7wvaI=
|
||||
github.com/aws/aws-sdk-go-v2/service/route53 v1.54.1/go.mod h1:POH50FEbIpazXJUVj2hbpJT819o2UF547G+BJBM7HQM=
|
||||
github.com/aws/aws-sdk-go-v2/service/servicediscovery v1.36.1 h1:EqupyVMtt84ZljchBzq+X+pPwuYhUT7dzfBoDqC0DB4=
|
||||
github.com/aws/aws-sdk-go-v2/service/servicediscovery v1.36.1/go.mod h1:HrkmhW8FU7GObElHC6Lm3sosolbig21w00VOm77Vsss=
|
||||
github.com/aws/aws-sdk-go-v2/service/sso v1.26.1 h1:uWaz3DoNK9MNhm7i6UGxqufwu3BEuJZm72WlpGwyVtY=
|
||||
github.com/aws/aws-sdk-go-v2/service/sso v1.26.1/go.mod h1:ILpVNjL0BO+Z3Mm0SbEeUoYS9e0eJWV1BxNppp0fcb8=
|
||||
github.com/aws/aws-sdk-go-v2/service/ssooidc v1.31.1 h1:XdG6/o1/ZDmn3wJU5SRAejHaWgKS4zHv0jBamuKuS2k=
|
||||
github.com/aws/aws-sdk-go-v2/service/ssooidc v1.31.1/go.mod h1:oiotGTKadCOCl3vg/tYh4k45JlDF81Ka8rdumNhEnIQ=
|
||||
github.com/aws/aws-sdk-go-v2/service/sts v1.35.1 h1:iF4Xxkc0H9c/K2dS0zZw3SCkj0Z7n6AMnUiiyoJND+I=
|
||||
github.com/aws/aws-sdk-go-v2/service/sts v1.35.1/go.mod h1:0bxIatfN0aLq4mjoLDeBpOjOke68OsFlXPDFJ7V0MYw=
|
||||
github.com/aws/smithy-go v1.22.5 h1:P9ATCXPMb2mPjYBgueqJNCA5S9UfktsW0tTxi+a7eqw=
|
||||
github.com/aws/smithy-go v1.22.5/go.mod h1:t1ufH5HMublsJYulve2RKmHDC15xu1f26kHCp/HgceI=
|
||||
github.com/benbjohnson/clock v1.3.0 h1:ip6w0uFQkncKQ979AypyG0ER7mqUSBdKLOgAle/AT8A=
|
||||
github.com/benbjohnson/clock v1.3.0/go.mod h1:J11/hYXuz8f4ySSvYwY0FKfm+ezbsZBKZxNJlLklBHA=
|
||||
github.com/beorn7/perks v0.0.0-20160804104726-4c0e84591b9a/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q=
|
||||
@ -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.3 h1:AgTJa2C8Q6q+vFfsaa41vVZBQXVOiUdb/JbWpNuDlc8=
|
||||
github.com/civo/civogo v0.6.3/go.mod h1:LaEbkszc+9nXSh4YNG0sYXFGYqdQFmXXzQg0gESs2hc=
|
||||
github.com/clbanning/x2j v0.0.0-20191024224557-825249438eec/go.mod h1:jMjuTZXRI4dUb/I5gc9Hdhagfvm9+RyrPryS/auMzxE=
|
||||
github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
|
||||
github.com/cloudflare/cloudflare-go v0.115.0 h1:84/dxeeXweCc0PN5Cto44iTA8AkG1fyT11yPO5ZB7sM=
|
||||
@ -252,8 +252,8 @@ github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZm
|
||||
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f h1:lO4WD4F/rVNCu3HqELle0jiPLLBs70cWOduZpkS1E78=
|
||||
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f/go.mod h1:cuUVRXasLTGF7a8hSLbxyZXjz+1KgoB3wDUb6vlszIc=
|
||||
github.com/dgryski/go-sip13 v0.0.0-20181026042036-e10d5fee7954/go.mod h1:vAd38F8PWV+bWy6jNmig1y/TA+kYO4g3RSRF0IAv0no=
|
||||
github.com/digitalocean/godo v1.159.0 h1:GQLfVueriDHYpwLzDcbydHs6nBvQBO8/r8r9imPC434=
|
||||
github.com/digitalocean/godo v1.159.0/go.mod h1:tYeiWY5ZXVpU48YaFv0M5irUFHXGorZpDNm7zzdWMzM=
|
||||
github.com/digitalocean/godo v1.161.0 h1:Q/3ImcotZp0GV9FY/dnLj9TmfOd+a7ZN/UNuhgDHI/Q=
|
||||
github.com/digitalocean/godo v1.161.0/go.mod h1:tYeiWY5ZXVpU48YaFv0M5irUFHXGorZpDNm7zzdWMzM=
|
||||
github.com/dnaeon/go-vcr v1.0.1/go.mod h1:aBB1+wY4s93YsC3HHjMBMrwTj2R9FHDzUr9KyGc8n1E=
|
||||
github.com/dnsimple/dnsimple-go v1.7.0 h1:JKu9xJtZ3SqOC+BuYgAWeab7+EEx0sz422vu8j611ZY=
|
||||
github.com/dnsimple/dnsimple-go v1.7.0/go.mod h1:EKpuihlWizqYafSnQHGCd/gyvy3HkEQJ7ODB4KdV8T8=
|
||||
@ -676,8 +676,8 @@ github.com/lib/pq v1.7.0/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o=
|
||||
github.com/liggitt/tabwriter v0.0.0-20181228230101-89fcab3d43de/go.mod h1:zAbeS9B/r2mtpb6U+EI2rYA5OAXxsYw6wTamcNW+zcE=
|
||||
github.com/lightstep/lightstep-tracer-common/golang/gogo v0.0.0-20190605223551-bc2310a04743/go.mod h1:qklhhLq1aX+mtWk9cPHPzaBjWImj5ULL6C7HFJtXQMM=
|
||||
github.com/lightstep/lightstep-tracer-go v0.18.1/go.mod h1:jlF1pusYV4pidLvZ+XD0UBX0ZE6WURAspgAczcDHrL4=
|
||||
github.com/linode/linodego v1.53.0 h1:UWr7bUUVMtcfsuapC+6blm6+jJLPd7Tf9MZUpdOERnI=
|
||||
github.com/linode/linodego v1.53.0/go.mod h1:bI949fZaVchjWyKIA08hNyvAcV6BAS+PM2op3p7PAWA=
|
||||
github.com/linode/linodego v1.54.0 h1:29vTV5YjqjjwPxWLE8Qp1zgDDXM5ifAQ2T6azAYsj/w=
|
||||
github.com/linode/linodego v1.54.0/go.mod h1:VHlFAbhj18634Cd7B7L5D723kFKFQMOxzIutSMcWsB4=
|
||||
github.com/lithammer/dedent v1.1.0/go.mod h1:jrXYCQtgg0nJiN+StA2KgR7w6CiQNv9Fd/Z9BP0jIOc=
|
||||
github.com/lyft/protoc-gen-star v0.4.10/go.mod h1:mE8fbna26u7aEA2QCVvvfBU/ZrPgocG1206xAFPcs94=
|
||||
github.com/lyft/protoc-gen-validate v0.0.13/go.mod h1:XbGvPuh87YZc5TdIa2/I4pLk0QoUACkjt2znoq26NVQ=
|
||||
@ -819,8 +819,8 @@ github.com/openzipkin-contrib/zipkin-go-opentracing v0.4.5/go.mod h1:/wsWhb9smxS
|
||||
github.com/openzipkin/zipkin-go v0.1.6/go.mod h1:QgAqvLzwWbR/WpD4A3cGpPtJrZXNIiJc5AZX7/PBEpw=
|
||||
github.com/openzipkin/zipkin-go v0.2.1/go.mod h1:NaW6tEwdmWMaCDZzg8sh+IBNOxHMPnhQw8ySjnjRyN4=
|
||||
github.com/openzipkin/zipkin-go v0.2.2/go.mod h1:NaW6tEwdmWMaCDZzg8sh+IBNOxHMPnhQw8ySjnjRyN4=
|
||||
github.com/oracle/oci-go-sdk/v65 v65.96.0 h1:ew0WavsB6N/I6etYCC160cD5qDXbek/1xZgujqTzork=
|
||||
github.com/oracle/oci-go-sdk/v65 v65.96.0/go.mod h1:u6XRPsw9tPziBh76K7GrrRXPa8P8W3BQeqJ6ZZt9VLA=
|
||||
github.com/oracle/oci-go-sdk/v65 v65.97.0 h1:QyOOg/qCIY6vBSD+GHUfEQaJk9Wbl8a6VESkXfZ0fYo=
|
||||
github.com/oracle/oci-go-sdk/v65 v65.97.0/go.mod h1:u6XRPsw9tPziBh76K7GrrRXPa8P8W3BQeqJ6ZZt9VLA=
|
||||
github.com/ovh/go-ovh v1.9.0 h1:6K8VoL3BYjVV3In9tPJUdT7qMx9h0GExN9EXx1r2kKE=
|
||||
github.com/ovh/go-ovh v1.9.0/go.mod h1:cTVDnl94z4tl8pP1uZ/8jlVxntjSIf09bNcQ5TJSC7c=
|
||||
github.com/oxtoacart/bpool v0.0.0-20150712133111-4e1c5567d7c2 h1:CXwSGu/LYmbjEab5aMCs5usQRVBGThelUKBNnoSOuso=
|
||||
@ -864,8 +864,8 @@ github.com/prometheus/client_golang v0.9.3/go.mod h1:/TN21ttK/J9q6uSwhBd54HahCDf
|
||||
github.com/prometheus/client_golang v1.0.0/go.mod h1:db9x61etRT2tGnBNRi70OPL5FsnadC4Ky3P0J6CfImo=
|
||||
github.com/prometheus/client_golang v1.3.0/go.mod h1:hJaj2vgQTGQmVCsAACORcieXFeDPbaTKGT+JTgUa3og=
|
||||
github.com/prometheus/client_golang v1.6.0/go.mod h1:ZLOG9ck3JLRdB5MgO8f+lLTe83AXG6ro35rLTxvnIl4=
|
||||
github.com/prometheus/client_golang v1.22.0 h1:rb93p9lokFEsctTys46VnV1kLCDpVZ0a/Y92Vm0Zc6Q=
|
||||
github.com/prometheus/client_golang v1.22.0/go.mod h1:R7ljNsLXhuQXYZYtw6GAE9AZg8Y7vEW5scdCXrWRXC0=
|
||||
github.com/prometheus/client_golang v1.23.0 h1:ust4zpdl9r4trLY/gSjlm07PuiBq2ynaXXlptpfy8Uc=
|
||||
github.com/prometheus/client_golang v1.23.0/go.mod h1:i/o0R9ByOnHX0McrTMTyhYvKE4haaf2mW08I+jGAjEE=
|
||||
github.com/prometheus/client_model v0.0.0-20171117100541-99fa1f4be8e5/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo=
|
||||
github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo=
|
||||
github.com/prometheus/client_model v0.0.0-20190115171406-56726106282f/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo=
|
||||
@ -892,8 +892,8 @@ github.com/prometheus/procfs v0.0.2/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsT
|
||||
github.com/prometheus/procfs v0.0.5/go.mod h1:4A/X28fw3Fc593LaREMrKMqOKvUAntwMDaekg4FpcdQ=
|
||||
github.com/prometheus/procfs v0.0.8/go.mod h1:7Qr8sr6344vo1JqZ6HhLceV9o3AJ1Ff+GxbHq6oeK9A=
|
||||
github.com/prometheus/procfs v0.0.11/go.mod h1:lV6e/gmhEcM9IjHGsFOCxxuZ+z1YqCvr4OA4YeYWdaU=
|
||||
github.com/prometheus/procfs v0.15.1 h1:YagwOFzUgYfKKHX6Dr+sHT7km/hxC76UB0learggepc=
|
||||
github.com/prometheus/procfs v0.15.1/go.mod h1:fB45yRUv8NstnjriLhBQLuOUt+WW4BsoGhij/e3PBqk=
|
||||
github.com/prometheus/procfs v0.16.1 h1:hZ15bTNuirocR6u0JZ6BAHHmwS1p8B4P6MRqxtzMyRg=
|
||||
github.com/prometheus/procfs v0.16.1/go.mod h1:teAbpZRB1iIAJYREa1LsoWUXykVXA1KlTmWl8x/U+Is=
|
||||
github.com/prometheus/tsdb v0.7.1/go.mod h1:qhTCs0VvXwvX/y3TZrWD7rabWM+ijKTux40TwIPHuXU=
|
||||
github.com/rcrowley/go-metrics v0.0.0-20181016184325-3113b8401b8a/go.mod h1:bCqnVzQkZxMG4s8nGwiZ5l3QUCyqpo9Y+/ZMZ9VjZe4=
|
||||
github.com/redis/go-redis/v9 v9.8.0 h1:q3nRvjrlge/6UD7eTu/DSg2uYiU2mCL0G/uzBWqhicI=
|
||||
@ -1050,12 +1050,12 @@ github.com/ziutek/mymysql v1.5.4/go.mod h1:LMSpPZ6DbqWFxNCHW77HeMg9I646SAhApZ/wK
|
||||
go.etcd.io/bbolt v1.3.2/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU=
|
||||
go.etcd.io/bbolt v1.3.3/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU=
|
||||
go.etcd.io/etcd v0.0.0-20191023171146-3cf2f69b5738/go.mod h1:dnLIgRNXwCJa5e+c6mIZCrds/GIG4ncV9HhK5PX7jPg=
|
||||
go.etcd.io/etcd/api/v3 v3.6.3 h1:4Lftl1e6VzBsj5HPhLu8GGybjeT5qg9mug70RxTHmQQ=
|
||||
go.etcd.io/etcd/api/v3 v3.6.3/go.mod h1:eFhhvfR8Px1P6SEuLT600v+vrhdDTdcfMzmnxVXXSbk=
|
||||
go.etcd.io/etcd/client/pkg/v3 v3.6.3 h1:1yE8p3PFZ+CWaVyTZk+6ngSyMK8TaG2589W3KGm22ao=
|
||||
go.etcd.io/etcd/client/pkg/v3 v3.6.3/go.mod h1:sbdzr2cl3HzVmxNw//PH7aLGVtY4QySjQFuaCgcRFAI=
|
||||
go.etcd.io/etcd/client/v3 v3.6.3 h1:yKpdrcVK6jTfr/VuVuH5VesaLmUi8PLI9eXHTy5kpTM=
|
||||
go.etcd.io/etcd/client/v3 v3.6.3/go.mod h1:zDuGaiUvpECwqClZCUkHi6q2XSf2ejPbUB755QLXdL8=
|
||||
go.etcd.io/etcd/api/v3 v3.6.4 h1:7F6N7toCKcV72QmoUKa23yYLiiljMrT4xCeBL9BmXdo=
|
||||
go.etcd.io/etcd/api/v3 v3.6.4/go.mod h1:eFhhvfR8Px1P6SEuLT600v+vrhdDTdcfMzmnxVXXSbk=
|
||||
go.etcd.io/etcd/client/pkg/v3 v3.6.4 h1:9HBYrjppeOfFjBjaMTRxT3R7xT0GLK8EJMVC4xg6ok0=
|
||||
go.etcd.io/etcd/client/pkg/v3 v3.6.4/go.mod h1:sbdzr2cl3HzVmxNw//PH7aLGVtY4QySjQFuaCgcRFAI=
|
||||
go.etcd.io/etcd/client/v3 v3.6.4 h1:YOMrCfMhRzY8NgtzUsHl8hC2EBSnuqbR3dh84Uryl7A=
|
||||
go.etcd.io/etcd/client/v3 v3.6.4/go.mod h1:jaNNHCyg2FdALyKWnd7hxZXZxZANb0+KGY+YQaEMISo=
|
||||
go.mongodb.org/mongo-driver v1.0.3/go.mod h1:u7ryQJ+DOzQmeO7zB6MHyr8jkEQvC8vH7qLUO4lqsUM=
|
||||
go.mongodb.org/mongo-driver v1.1.1/go.mod h1:u7ryQJ+DOzQmeO7zB6MHyr8jkEQvC8vH7qLUO4lqsUM=
|
||||
go.mongodb.org/mongo-driver v1.1.2/go.mod h1:u7ryQJ+DOzQmeO7zB6MHyr8jkEQvC8vH7qLUO4lqsUM=
|
||||
@ -1350,8 +1350,8 @@ gonum.org/v1/plot v0.0.0-20190515093506-e2840ee46a6b/go.mod h1:Wt8AAjI+ypCyYX3nZ
|
||||
google.golang.org/api v0.0.0-20160322025152-9bf6e6e569ff/go.mod h1:4mhQ8q/RsB7i+udVvVy5NUi08OU8ZlA0gRVgrF7VFY0=
|
||||
google.golang.org/api v0.3.1/go.mod h1:6wY9I6uQWHQ8EM57III9mq/AjF+i8G65rmVagqKMtkk=
|
||||
google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE=
|
||||
google.golang.org/api v0.243.0 h1:sw+ESIJ4BVnlJcWu9S+p2Z6Qq1PjG77T8IJ1xtp4jZQ=
|
||||
google.golang.org/api v0.243.0/go.mod h1:GE4QtYfaybx1KmeHMdBnNnyLzBZCVihGBXAmJu/uUr8=
|
||||
google.golang.org/api v0.244.0 h1:lpkP8wVibSKr++NCD36XzTk/IzeKJ3klj7vbj+XU5pE=
|
||||
google.golang.org/api v0.244.0/go.mod h1:dMVhVcylamkirHdzEBAIQWUCgqY885ivNeZYd7VAVr8=
|
||||
google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
|
||||
google.golang.org/appengine v1.2.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
|
||||
google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
|
||||
@ -1369,8 +1369,8 @@ google.golang.org/genproto v0.0.0-20250603155806-513f23925822 h1:rHWScKit0gvAPuO
|
||||
google.golang.org/genproto v0.0.0-20250603155806-513f23925822/go.mod h1:HubltRL7rMh0LfnQPkMH4NPDFEWp0jw3vixw7jEM53s=
|
||||
google.golang.org/genproto/googleapis/api v0.0.0-20250603155806-513f23925822 h1:oWVWY3NzT7KJppx2UKhKmzPq4SRe0LdCijVRwvGeikY=
|
||||
google.golang.org/genproto/googleapis/api v0.0.0-20250603155806-513f23925822/go.mod h1:h3c4v36UTKzUiuaOKQ6gr3S+0hovBtUrXzTG/i3+XEc=
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20250715232539-7130f93afb79 h1:1ZwqphdOdWYXsUHgMpU/101nCtf/kSp9hOrcvFsnl10=
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20250715232539-7130f93afb79/go.mod h1:qQ0YXyHHx3XkvlzUtpXDkS29lDSafHMZBAZDc03LQ3A=
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20250728155136-f173205681a0 h1:MAKi5q709QWfnkkpNQ0M12hYJ1+e8qYVDyowc4U1XZM=
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20250728155136-f173205681a0/go.mod h1:qQ0YXyHHx3XkvlzUtpXDkS29lDSafHMZBAZDc03LQ3A=
|
||||
google.golang.org/grpc v0.0.0-20160317175043-d3ddb4469d5a/go.mod h1:yo6s7OP7yaDglbqo1J04qKzAhqBH6lvTonzMVmEdcZw=
|
||||
google.golang.org/grpc v1.17.0/go.mod h1:6QZJwpn2B+Zp71q/5VxRsJ6NXXVCE5NRUHRo+f3cWCs=
|
||||
google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
|
||||
@ -1384,8 +1384,8 @@ google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQ
|
||||
google.golang.org/grpc v1.26.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
|
||||
google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
|
||||
google.golang.org/grpc v1.29.1/go.mod h1:itym6AZVZYACWQqET3MqgPpjcuV5QH3BxFS3IjizoKk=
|
||||
google.golang.org/grpc v1.73.0 h1:VIWSmpI2MegBtTuFt5/JWy2oXxtjJ/e89Z70ImfD2ok=
|
||||
google.golang.org/grpc v1.73.0/go.mod h1:50sbHOUqWoCQGI8V2HQLJM0B+LMlIUjNSZmow7EVBQc=
|
||||
google.golang.org/grpc v1.74.2 h1:WoosgB65DlWVC9FqI82dGsZhWFNBSLjQ84bjROOpMu4=
|
||||
google.golang.org/grpc v1.74.2/go.mod h1:CtQ+BGjaAIXHs/5YS3i473GqwBBa1zGQNevxdeBEXrM=
|
||||
google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8=
|
||||
google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0=
|
||||
google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM=
|
||||
@ -1452,10 +1452,10 @@ honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWh
|
||||
honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
||||
honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
||||
honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg=
|
||||
istio.io/api v1.26.2 h1:gLkGSB2nkqA/9u/tE/OMEv+U4Fhci2JZgIqjA0CxMak=
|
||||
istio.io/api v1.26.2/go.mod h1:DTVGH6CLXj5W8FF9JUD3Tis78iRgT1WeuAnxfTz21Wg=
|
||||
istio.io/client-go v1.26.2 h1:XWvQzBM69vB2xo1bzrp+CYbE8KlRAPotR4ls5Vv35EM=
|
||||
istio.io/client-go v1.26.2/go.mod h1:eAImguSJPdaDiSSS2CEsywNHE8WWfqd3WfS18Rj8ynI=
|
||||
istio.io/api v1.26.3 h1:/TiA7bJi24yBQSgpLy5vHhFkobf4DWS1L+CuUxNk4os=
|
||||
istio.io/api v1.26.3/go.mod h1:DTVGH6CLXj5W8FF9JUD3Tis78iRgT1WeuAnxfTz21Wg=
|
||||
istio.io/client-go v1.26.3 h1:ryF4+Nyz5wDO4mVCzXcm2W+fqbnekY88Z36hTcv5fnw=
|
||||
istio.io/client-go v1.26.3/go.mod h1:u2p5L7UvjNswrrlHZ+QMlUOjERK2sXputywzyNhtTMg=
|
||||
k8s.io/api v0.18.0/go.mod h1:q2HRQkfDzHMBZL9l/y9rH63PkQl4vae0xRT+8prbrK8=
|
||||
k8s.io/api v0.18.2/go.mod h1:SJCWI7OLzhZSvbY7U8zwNl9UA4o1fizoug34OV/2r78=
|
||||
k8s.io/api v0.18.4/go.mod h1:lOIQAKYgai1+vz9J7YcDZwC26Z0zQewYOGWdyIPUUQ4=
|
||||
|
29
internal/idna/idna.go
Normal file
29
internal/idna/idna.go
Normal file
@ -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),
|
||||
)
|
||||
)
|
59
internal/idna/idna_test.go
Normal file
59
internal/idna/idna_test.go
Normal file
@ -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)
|
||||
})
|
||||
}
|
||||
}
|
10
plan/plan.go
10
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)
|
||||
}
|
||||
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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{}
|
||||
|
||||
|
@ -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")
|
||||
}
|
||||
|
@ -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("???")
|
||||
|
||||
|
60
source/informers/fake.go
Normal file
60
source/informers/fake.go
Normal file
@ -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())
|
||||
}
|
@ -96,28 +96,9 @@ func NewServiceSource(ctx context.Context, kubeClient kubernetes.Interface, name
|
||||
// Set the resync period to 0 to prevent processing when nothing has changed
|
||||
informerFactory := kubeinformers.NewSharedInformerFactoryWithOptions(kubeClient, 0, kubeinformers.WithNamespace(namespace))
|
||||
serviceInformer := informerFactory.Core().V1().Services()
|
||||
endpointSlicesInformer := informerFactory.Discovery().V1().EndpointSlices()
|
||||
podInformer := informerFactory.Core().V1().Pods()
|
||||
|
||||
// Add default resource event handlers to properly initialize informer.
|
||||
_, _ = serviceInformer.Informer().AddEventHandler(
|
||||
cache.ResourceEventHandlerFuncs{
|
||||
AddFunc: func(obj interface{}) {
|
||||
},
|
||||
},
|
||||
)
|
||||
_, _ = endpointSlicesInformer.Informer().AddEventHandler(
|
||||
cache.ResourceEventHandlerFuncs{
|
||||
AddFunc: func(obj interface{}) {
|
||||
},
|
||||
},
|
||||
)
|
||||
_, _ = podInformer.Informer().AddEventHandler(
|
||||
cache.ResourceEventHandlerFuncs{
|
||||
AddFunc: func(obj interface{}) {
|
||||
},
|
||||
},
|
||||
)
|
||||
_, _ = serviceInformer.Informer().AddEventHandler(informers.DefaultEventHandler())
|
||||
|
||||
// Transform the slice into a map so it will be way much easier and fast to filter later
|
||||
sTypesFilter, err := newServiceTypesFilter(serviceTypeFilter)
|
||||
@ -125,30 +106,40 @@ func NewServiceSource(ctx context.Context, kubeClient kubernetes.Interface, name
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var nodeInformer coreinformers.NodeInformer
|
||||
if sTypesFilter.isNodeInformerRequired() {
|
||||
nodeInformer = informerFactory.Core().V1().Nodes()
|
||||
_, _ = nodeInformer.Informer().AddEventHandler(informers.DefaultEventHandler())
|
||||
var endpointSlicesInformer discoveryinformers.EndpointSliceInformer
|
||||
var podInformer coreinformers.PodInformer
|
||||
if sTypesFilter.isRequired(v1.ServiceTypeNodePort, v1.ServiceTypeClusterIP) {
|
||||
endpointSlicesInformer = informerFactory.Discovery().V1().EndpointSlices()
|
||||
podInformer = informerFactory.Core().V1().Pods()
|
||||
|
||||
_, _ = endpointSlicesInformer.Informer().AddEventHandler(informers.DefaultEventHandler())
|
||||
_, _ = podInformer.Informer().AddEventHandler(informers.DefaultEventHandler())
|
||||
|
||||
// Add an indexer to the EndpointSlice informer to index by the service name label
|
||||
err = endpointSlicesInformer.Informer().AddIndexers(cache.Indexers{
|
||||
serviceNameIndexKey: func(obj any) ([]string, error) {
|
||||
endpointSlice, ok := obj.(*discoveryv1.EndpointSlice)
|
||||
if !ok {
|
||||
// This should never happen because the Informer should only contain EndpointSlice objects
|
||||
return nil, fmt.Errorf("expected %T but got %T instead", endpointSlice, obj)
|
||||
}
|
||||
serviceName := endpointSlice.Labels[discoveryv1.LabelServiceName]
|
||||
if serviceName == "" {
|
||||
return nil, nil
|
||||
}
|
||||
key := types.NamespacedName{Namespace: endpointSlice.Namespace, Name: serviceName}.String()
|
||||
return []string{key}, nil
|
||||
},
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
// Add an indexer to the EndpointSlice informer to index by the service name label
|
||||
err = endpointSlicesInformer.Informer().AddIndexers(cache.Indexers{
|
||||
serviceNameIndexKey: func(obj any) ([]string, error) {
|
||||
endpointSlice, ok := obj.(*discoveryv1.EndpointSlice)
|
||||
if !ok {
|
||||
// This should never happen because the Informer should only contain EndpointSlice objects
|
||||
return nil, fmt.Errorf("expected %T but got %T instead", endpointSlice, obj)
|
||||
}
|
||||
serviceName := endpointSlice.Labels[discoveryv1.LabelServiceName]
|
||||
if serviceName == "" {
|
||||
return nil, nil
|
||||
}
|
||||
key := types.NamespacedName{Namespace: endpointSlice.Namespace, Name: serviceName}.String()
|
||||
return []string{key}, nil
|
||||
},
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
var nodeInformer coreinformers.NodeInformer
|
||||
if sTypesFilter.isRequired(v1.ServiceTypeNodePort) {
|
||||
nodeInformer = informerFactory.Core().V1().Nodes()
|
||||
_, _ = nodeInformer.Informer().AddEventHandler(informers.DefaultEventHandler())
|
||||
}
|
||||
|
||||
informerFactory.Start(ctx.Done())
|
||||
@ -808,10 +799,10 @@ func (sc *serviceSource) AddEventHandler(_ context.Context, handler func()) {
|
||||
// Right now there is no way to remove event handler from informer, see:
|
||||
// https://github.com/kubernetes/kubernetes/issues/79610
|
||||
_, _ = sc.serviceInformer.Informer().AddEventHandler(eventHandlerFunc(handler))
|
||||
if sc.listenEndpointEvents {
|
||||
if sc.listenEndpointEvents && sc.serviceTypeFilter.isRequired(v1.ServiceTypeNodePort, v1.ServiceTypeClusterIP) {
|
||||
_, _ = sc.endpointSlicesInformer.Informer().AddEventHandler(eventHandlerFunc(handler))
|
||||
}
|
||||
if sc.serviceTypeFilter.isNodeInformerRequired() {
|
||||
if sc.serviceTypeFilter.isRequired(v1.ServiceTypeNodePort) {
|
||||
_, _ = sc.nodeInformer.Informer().AddEventHandler(eventHandlerFunc(handler))
|
||||
}
|
||||
}
|
||||
@ -848,12 +839,18 @@ func (sc *serviceTypes) isProcessed(serviceType v1.ServiceType) bool {
|
||||
return !sc.enabled || sc.types[serviceType]
|
||||
}
|
||||
|
||||
func (sc *serviceTypes) isNodeInformerRequired() bool {
|
||||
if !sc.enabled {
|
||||
// isRequired returns true if service type filtering is disabled or if any of the provided service types are present in the filter.
|
||||
// If no options are provided, it returns true.
|
||||
func (sc *serviceTypes) isRequired(opts ...v1.ServiceType) bool {
|
||||
if len(opts) == 0 || !sc.enabled {
|
||||
return true
|
||||
}
|
||||
_, ok := sc.types[v1.ServiceTypeNodePort]
|
||||
return ok
|
||||
for _, opt := range opts {
|
||||
if _, ok := sc.types[opt]; ok {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// conditionToBool converts an EndpointConditions condition to a bool value.
|
||||
|
@ -37,6 +37,7 @@ import (
|
||||
"k8s.io/apimachinery/pkg/labels"
|
||||
"k8s.io/apimachinery/pkg/util/intstr"
|
||||
"k8s.io/client-go/kubernetes/fake"
|
||||
"sigs.k8s.io/external-dns/source/informers"
|
||||
|
||||
"sigs.k8s.io/external-dns/endpoint"
|
||||
"sigs.k8s.io/external-dns/internal/testutils"
|
||||
@ -251,7 +252,7 @@ func testServiceSourceEndpoints(t *testing.T) {
|
||||
},
|
||||
externalIPs: []string{},
|
||||
lbs: []string{"1.2.3.4"},
|
||||
serviceTypesFilter: []string{},
|
||||
serviceTypesFilter: []string{string(v1.ServiceTypeLoadBalancer)},
|
||||
expected: []*endpoint.Endpoint{
|
||||
{DNSName: "foo.example.org", RecordType: endpoint.RecordTypeA, Targets: endpoint.Targets{"1.2.3.4"}},
|
||||
},
|
||||
@ -296,7 +297,7 @@ func testServiceSourceEndpoints(t *testing.T) {
|
||||
annotations: map[string]string{},
|
||||
externalIPs: []string{},
|
||||
lbs: []string{"1.2.3.4"},
|
||||
serviceTypesFilter: []string{},
|
||||
serviceTypesFilter: []string{string(v1.ServiceTypeLoadBalancer), string(v1.ServiceTypeNodePort)},
|
||||
expected: []*endpoint.Endpoint{
|
||||
{DNSName: "foo.fqdn.org", RecordType: endpoint.RecordTypeA, Targets: endpoint.Targets{"1.2.3.4"}},
|
||||
{DNSName: "foo.fqdn.com", RecordType: endpoint.RecordTypeA, Targets: endpoint.Targets{"1.2.3.4"}},
|
||||
@ -2498,6 +2499,7 @@ func TestHeadlessServices(t *testing.T) {
|
||||
podsReady []bool
|
||||
publishNotReadyAddresses bool
|
||||
nodes []v1.Node
|
||||
serviceTypesFilter []string
|
||||
expected []*endpoint.Endpoint
|
||||
expectError bool
|
||||
}{
|
||||
@ -2528,6 +2530,7 @@ func TestHeadlessServices(t *testing.T) {
|
||||
[]bool{true, true},
|
||||
false,
|
||||
[]v1.Node{},
|
||||
[]string{},
|
||||
[]*endpoint.Endpoint{
|
||||
{DNSName: "foo-0.service.example.org", RecordType: endpoint.RecordTypeA, Targets: endpoint.Targets{"1.1.1.1"}},
|
||||
{DNSName: "foo-1.service.example.org", RecordType: endpoint.RecordTypeA, Targets: endpoint.Targets{"1.1.1.2"}},
|
||||
@ -2562,6 +2565,7 @@ func TestHeadlessServices(t *testing.T) {
|
||||
[]bool{true, true},
|
||||
false,
|
||||
[]v1.Node{},
|
||||
[]string{string(v1.ServiceTypeClusterIP), string(v1.ServiceTypeLoadBalancer)},
|
||||
[]*endpoint.Endpoint{
|
||||
{DNSName: "foo-0.service.example.org", RecordType: endpoint.RecordTypeAAAA, Targets: endpoint.Targets{"2001:db8::1"}},
|
||||
{DNSName: "foo-1.service.example.org", RecordType: endpoint.RecordTypeAAAA, Targets: endpoint.Targets{"2001:db8::2"}},
|
||||
@ -2596,6 +2600,7 @@ func TestHeadlessServices(t *testing.T) {
|
||||
[]bool{true, true},
|
||||
false,
|
||||
[]v1.Node{},
|
||||
[]string{},
|
||||
[]*endpoint.Endpoint{},
|
||||
false,
|
||||
},
|
||||
@ -2627,6 +2632,7 @@ func TestHeadlessServices(t *testing.T) {
|
||||
[]bool{true, true},
|
||||
false,
|
||||
[]v1.Node{},
|
||||
[]string{},
|
||||
[]*endpoint.Endpoint{
|
||||
{DNSName: "foo-0.service.example.org", RecordType: endpoint.RecordTypeA, Targets: endpoint.Targets{"1.1.1.1"}, RecordTTL: endpoint.TTL(1)},
|
||||
{DNSName: "foo-1.service.example.org", RecordType: endpoint.RecordTypeA, Targets: endpoint.Targets{"1.1.1.2"}, RecordTTL: endpoint.TTL(1)},
|
||||
@ -2662,6 +2668,7 @@ func TestHeadlessServices(t *testing.T) {
|
||||
[]bool{true, true},
|
||||
false,
|
||||
[]v1.Node{},
|
||||
[]string{},
|
||||
[]*endpoint.Endpoint{
|
||||
{DNSName: "foo-0.service.example.org", RecordType: endpoint.RecordTypeAAAA, Targets: endpoint.Targets{"2001:db8::1"}, RecordTTL: endpoint.TTL(1)},
|
||||
{DNSName: "foo-1.service.example.org", RecordType: endpoint.RecordTypeAAAA, Targets: endpoint.Targets{"2001:db8::2"}, RecordTTL: endpoint.TTL(1)},
|
||||
@ -2696,6 +2703,7 @@ func TestHeadlessServices(t *testing.T) {
|
||||
[]bool{true, false},
|
||||
false,
|
||||
[]v1.Node{},
|
||||
[]string{},
|
||||
[]*endpoint.Endpoint{
|
||||
{DNSName: "foo-0.service.example.org", RecordType: endpoint.RecordTypeA, Targets: endpoint.Targets{"1.1.1.1"}},
|
||||
{DNSName: "service.example.org", RecordType: endpoint.RecordTypeA, Targets: endpoint.Targets{"1.1.1.1"}},
|
||||
@ -2729,6 +2737,7 @@ func TestHeadlessServices(t *testing.T) {
|
||||
[]bool{true, false},
|
||||
true,
|
||||
[]v1.Node{},
|
||||
[]string{},
|
||||
[]*endpoint.Endpoint{
|
||||
{DNSName: "foo-0.service.example.org", RecordType: endpoint.RecordTypeA, Targets: endpoint.Targets{"1.1.1.1"}},
|
||||
{DNSName: "foo-1.service.example.org", RecordType: endpoint.RecordTypeA, Targets: endpoint.Targets{"1.1.1.2"}},
|
||||
@ -2763,6 +2772,7 @@ func TestHeadlessServices(t *testing.T) {
|
||||
[]bool{true, true},
|
||||
false,
|
||||
[]v1.Node{},
|
||||
[]string{},
|
||||
[]*endpoint.Endpoint{
|
||||
{DNSName: "service.example.org", RecordType: endpoint.RecordTypeA, Targets: endpoint.Targets{"1.1.1.1", "1.1.1.2"}},
|
||||
},
|
||||
@ -2795,6 +2805,7 @@ func TestHeadlessServices(t *testing.T) {
|
||||
[]bool{true, true, true},
|
||||
false,
|
||||
[]v1.Node{},
|
||||
[]string{},
|
||||
[]*endpoint.Endpoint{
|
||||
{DNSName: "service.example.org", RecordType: endpoint.RecordTypeA, Targets: endpoint.Targets{"1.1.1.1", "1.1.1.2"}},
|
||||
},
|
||||
@ -2827,6 +2838,7 @@ func TestHeadlessServices(t *testing.T) {
|
||||
[]bool{true, true, true},
|
||||
false,
|
||||
[]v1.Node{},
|
||||
[]string{},
|
||||
[]*endpoint.Endpoint{
|
||||
{DNSName: "service.example.org", RecordType: endpoint.RecordTypeAAAA, Targets: endpoint.Targets{"2001:db8::1", "2001:db8::2"}},
|
||||
},
|
||||
@ -2861,6 +2873,7 @@ func TestHeadlessServices(t *testing.T) {
|
||||
[]bool{true, true, true},
|
||||
false,
|
||||
[]v1.Node{},
|
||||
[]string{string(v1.ServiceTypeClusterIP)},
|
||||
[]*endpoint.Endpoint{
|
||||
{DNSName: "service.example.org", RecordType: endpoint.RecordTypeA, Targets: endpoint.Targets{"1.2.3.4"}},
|
||||
},
|
||||
@ -2895,6 +2908,7 @@ func TestHeadlessServices(t *testing.T) {
|
||||
[]bool{true, true, true},
|
||||
false,
|
||||
[]v1.Node{},
|
||||
[]string{},
|
||||
[]*endpoint.Endpoint{
|
||||
{DNSName: "service.example.org", RecordType: endpoint.RecordTypeAAAA, Targets: endpoint.Targets{"2001:db8::4"}},
|
||||
},
|
||||
@ -2939,6 +2953,7 @@ func TestHeadlessServices(t *testing.T) {
|
||||
},
|
||||
},
|
||||
},
|
||||
[]string{},
|
||||
[]*endpoint.Endpoint{
|
||||
{DNSName: "service.example.org", RecordType: endpoint.RecordTypeA, Targets: endpoint.Targets{"1.2.3.4"}},
|
||||
},
|
||||
@ -2987,6 +3002,7 @@ func TestHeadlessServices(t *testing.T) {
|
||||
},
|
||||
},
|
||||
},
|
||||
[]string{},
|
||||
[]*endpoint.Endpoint{
|
||||
{DNSName: "service.example.org", RecordType: endpoint.RecordTypeAAAA, Targets: endpoint.Targets{"2001:db8::5"}},
|
||||
},
|
||||
@ -3031,6 +3047,7 @@ func TestHeadlessServices(t *testing.T) {
|
||||
},
|
||||
},
|
||||
},
|
||||
[]string{},
|
||||
[]*endpoint.Endpoint{
|
||||
{DNSName: "service.example.org", RecordType: endpoint.RecordTypeAAAA, Targets: endpoint.Targets{"2001:db8::4"}},
|
||||
},
|
||||
@ -3079,6 +3096,7 @@ func TestHeadlessServices(t *testing.T) {
|
||||
},
|
||||
},
|
||||
},
|
||||
[]string{},
|
||||
[]*endpoint.Endpoint{
|
||||
{DNSName: "service.example.org", RecordType: endpoint.RecordTypeA, Targets: endpoint.Targets{"1.2.3.4"}},
|
||||
{DNSName: "service.example.org", RecordType: endpoint.RecordTypeAAAA, Targets: endpoint.Targets{"2001:db8::4"}},
|
||||
@ -3113,6 +3131,7 @@ func TestHeadlessServices(t *testing.T) {
|
||||
[]bool{true, true, true},
|
||||
false,
|
||||
[]v1.Node{},
|
||||
[]string{},
|
||||
[]*endpoint.Endpoint{
|
||||
{DNSName: "service.example.org", RecordType: endpoint.RecordTypeA, Targets: endpoint.Targets{"1.2.3.4"}},
|
||||
},
|
||||
@ -3146,6 +3165,7 @@ func TestHeadlessServices(t *testing.T) {
|
||||
[]bool{true, true, true},
|
||||
false,
|
||||
[]v1.Node{},
|
||||
[]string{},
|
||||
[]*endpoint.Endpoint{
|
||||
{DNSName: "service.example.org", RecordType: endpoint.RecordTypeAAAA, Targets: endpoint.Targets{"2001:db8::4"}},
|
||||
},
|
||||
@ -3242,7 +3262,7 @@ func TestHeadlessServices(t *testing.T) {
|
||||
true,
|
||||
false,
|
||||
false,
|
||||
[]string{},
|
||||
tc.serviceTypesFilter,
|
||||
tc.ignoreHostnameAnnotation,
|
||||
labels.Everything(),
|
||||
false,
|
||||
@ -3994,7 +4014,6 @@ func TestHeadlessServicesHostIP(t *testing.T) {
|
||||
t.Run(tc.title, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
// Create a Kubernetes testing client
|
||||
kubernetes := fake.NewClientset()
|
||||
|
||||
service := &v1.Service{
|
||||
@ -4134,7 +4153,7 @@ func TestExternalServices(t *testing.T) {
|
||||
},
|
||||
"111.111.111.111",
|
||||
[]string{},
|
||||
[]string{},
|
||||
[]string{string(v1.ServiceTypeNodePort), string(v1.ServiceTypeExternalName)},
|
||||
[]*endpoint.Endpoint{
|
||||
{DNSName: "service.example.org", Targets: endpoint.Targets{"111.111.111.111"}, RecordType: endpoint.RecordTypeA},
|
||||
},
|
||||
@ -4176,7 +4195,7 @@ func TestExternalServices(t *testing.T) {
|
||||
},
|
||||
"remote.example.com",
|
||||
[]string{},
|
||||
[]string{},
|
||||
[]string{string(v1.ServiceTypeExternalName)},
|
||||
[]*endpoint.Endpoint{
|
||||
{DNSName: "service.example.org", Targets: endpoint.Targets{"remote.example.com"}, RecordType: endpoint.RecordTypeCNAME},
|
||||
},
|
||||
@ -4371,6 +4390,8 @@ func TestNewServiceSourceInformersEnabled(t *testing.T) {
|
||||
assert.NotNil(t, svc.serviceTypeFilter)
|
||||
assert.False(t, svc.serviceTypeFilter.enabled)
|
||||
assert.NotNil(t, svc.nodeInformer)
|
||||
assert.NotNil(t, svc.serviceInformer)
|
||||
assert.NotNil(t, svc.endpointSlicesInformer)
|
||||
},
|
||||
},
|
||||
{
|
||||
@ -4380,17 +4401,49 @@ func TestNewServiceSourceInformersEnabled(t *testing.T) {
|
||||
assert.NotNil(t, svc)
|
||||
assert.NotNil(t, svc.serviceTypeFilter)
|
||||
assert.True(t, svc.serviceTypeFilter.enabled)
|
||||
assert.NotNil(t, svc.serviceInformer)
|
||||
assert.Nil(t, svc.nodeInformer)
|
||||
assert.NotNil(t, svc.endpointSlicesInformer)
|
||||
assert.NotNil(t, svc.podInformer)
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "serviceTypeFilter contains NodePort",
|
||||
svcFilter: []string{string(v1.ServiceTypeNodePort)},
|
||||
name: "serviceTypeFilter contains NodePort and ExternalName",
|
||||
svcFilter: []string{string(v1.ServiceTypeNodePort), string(v1.ServiceTypeExternalName)},
|
||||
asserts: func(svc *serviceSource) {
|
||||
assert.NotNil(t, svc)
|
||||
assert.NotNil(t, svc.serviceTypeFilter)
|
||||
assert.True(t, svc.serviceTypeFilter.enabled)
|
||||
assert.NotNil(t, svc.serviceInformer)
|
||||
assert.NotNil(t, svc.nodeInformer)
|
||||
assert.NotNil(t, svc.endpointSlicesInformer)
|
||||
assert.NotNil(t, svc.podInformer)
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "serviceTypeFilter contains ExternalName",
|
||||
svcFilter: []string{string(v1.ServiceTypeExternalName)},
|
||||
asserts: func(svc *serviceSource) {
|
||||
assert.NotNil(t, svc)
|
||||
assert.NotNil(t, svc.serviceTypeFilter)
|
||||
assert.True(t, svc.serviceTypeFilter.enabled)
|
||||
assert.NotNil(t, svc.serviceInformer)
|
||||
assert.Nil(t, svc.nodeInformer)
|
||||
assert.Nil(t, svc.endpointSlicesInformer)
|
||||
assert.Nil(t, svc.podInformer)
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "serviceTypeFilter contains LoadBalancer",
|
||||
svcFilter: []string{string(v1.ServiceTypeLoadBalancer)},
|
||||
asserts: func(svc *serviceSource) {
|
||||
assert.NotNil(t, svc)
|
||||
assert.NotNil(t, svc.serviceTypeFilter)
|
||||
assert.True(t, svc.serviceTypeFilter.enabled)
|
||||
assert.NotNil(t, svc.serviceInformer)
|
||||
assert.Nil(t, svc.nodeInformer)
|
||||
assert.Nil(t, svc.endpointSlicesInformer)
|
||||
assert.Nil(t, svc.podInformer)
|
||||
},
|
||||
},
|
||||
}
|
||||
@ -4681,32 +4734,126 @@ func createTestServicesByType(namespace string, typeCounts map[v1.ServiceType]in
|
||||
|
||||
func TestServiceTypes_isNodeInformerRequired(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
filter []string
|
||||
want bool
|
||||
name string
|
||||
filter []string
|
||||
required []v1.ServiceType
|
||||
want bool
|
||||
}{
|
||||
{
|
||||
name: "NodePort type present",
|
||||
filter: []string{string(v1.ServiceTypeNodePort)},
|
||||
want: true,
|
||||
name: "NodePort required and filter is empty",
|
||||
filter: []string{},
|
||||
required: []v1.ServiceType{v1.ServiceTypeNodePort},
|
||||
want: true,
|
||||
},
|
||||
{
|
||||
name: "NodePort type absent, filter enabled",
|
||||
filter: []string{string(v1.ServiceTypeLoadBalancer)},
|
||||
want: false,
|
||||
name: "NodePort type present",
|
||||
filter: []string{string(v1.ServiceTypeNodePort)},
|
||||
required: []v1.ServiceType{v1.ServiceTypeNodePort},
|
||||
want: true,
|
||||
},
|
||||
{
|
||||
name: "NodePort and other filters present",
|
||||
filter: []string{string(v1.ServiceTypeLoadBalancer), string(v1.ServiceTypeNodePort)},
|
||||
want: true,
|
||||
name: "NodePort type absent, filter enabled",
|
||||
filter: []string{string(v1.ServiceTypeLoadBalancer)},
|
||||
required: []v1.ServiceType{v1.ServiceTypeNodePort},
|
||||
want: false,
|
||||
},
|
||||
{
|
||||
name: "NodePort and other filters present",
|
||||
filter: []string{string(v1.ServiceTypeLoadBalancer), string(v1.ServiceTypeNodePort)},
|
||||
required: []v1.ServiceType{v1.ServiceTypeNodePort},
|
||||
want: true,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
filter, _ := newServiceTypesFilter(tt.filter)
|
||||
got := filter.isNodeInformerRequired()
|
||||
got := filter.isRequired(tt.required...)
|
||||
assert.Equal(t, tt.want, got)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestServiceSource_AddEventHandler(t *testing.T) {
|
||||
var fakeServiceInformer *informers.FakeServiceInformer
|
||||
var fakeEdpInformer *informers.FakeEndpointSliceInformer
|
||||
var fakeNodeInformer *informers.FakeNodeInformer
|
||||
tests := []struct {
|
||||
name string
|
||||
filter []string
|
||||
times int
|
||||
asserts func(t *testing.T, s *serviceSource)
|
||||
}{
|
||||
{
|
||||
name: "AddEventHandler should trigger all event handlers when empty filter is provided",
|
||||
filter: []string{},
|
||||
times: 3,
|
||||
asserts: func(t *testing.T, s *serviceSource) {
|
||||
fakeServiceInformer.AssertNumberOfCalls(t, "Informer", 1)
|
||||
fakeEdpInformer.AssertNumberOfCalls(t, "Informer", 1)
|
||||
fakeNodeInformer.AssertNumberOfCalls(t, "Informer", 1)
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "AddEventHandler should trigger only service event handler",
|
||||
filter: []string{string(v1.ServiceTypeExternalName), string(v1.ServiceTypeLoadBalancer)},
|
||||
times: 1,
|
||||
asserts: func(t *testing.T, s *serviceSource) {
|
||||
fakeServiceInformer.AssertNumberOfCalls(t, "Informer", 1)
|
||||
fakeEdpInformer.AssertNumberOfCalls(t, "Informer", 0)
|
||||
fakeNodeInformer.AssertNumberOfCalls(t, "Informer", 0)
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "AddEventHandler should configure only service event handler",
|
||||
filter: []string{string(v1.ServiceTypeExternalName), string(v1.ServiceTypeLoadBalancer), string(v1.ServiceTypeClusterIP)},
|
||||
times: 2,
|
||||
asserts: func(t *testing.T, s *serviceSource) {
|
||||
fakeServiceInformer.AssertNumberOfCalls(t, "Informer", 1)
|
||||
fakeEdpInformer.AssertNumberOfCalls(t, "Informer", 1)
|
||||
fakeNodeInformer.AssertNumberOfCalls(t, "Informer", 0)
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "AddEventHandler should configure all service event handlers",
|
||||
filter: []string{string(v1.ServiceTypeNodePort)},
|
||||
times: 3,
|
||||
asserts: func(t *testing.T, s *serviceSource) {
|
||||
fakeServiceInformer.AssertNumberOfCalls(t, "Informer", 1)
|
||||
fakeEdpInformer.AssertNumberOfCalls(t, "Informer", 1)
|
||||
fakeNodeInformer.AssertNumberOfCalls(t, "Informer", 1)
|
||||
},
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
fakeServiceInformer = new(informers.FakeServiceInformer)
|
||||
infSvc := testInformer{}
|
||||
fakeServiceInformer.On("Informer").Return(&infSvc)
|
||||
|
||||
fakeEdpInformer = new(informers.FakeEndpointSliceInformer)
|
||||
infEdp := testInformer{}
|
||||
fakeEdpInformer.On("Informer").Return(&infEdp)
|
||||
|
||||
fakeNodeInformer = new(informers.FakeNodeInformer)
|
||||
infNode := testInformer{}
|
||||
fakeNodeInformer.On("Informer").Return(&infNode)
|
||||
|
||||
filter, _ := newServiceTypesFilter(tt.filter)
|
||||
|
||||
svcSource := &serviceSource{
|
||||
endpointSlicesInformer: fakeEdpInformer,
|
||||
serviceInformer: fakeServiceInformer,
|
||||
nodeInformer: fakeNodeInformer,
|
||||
serviceTypeFilter: filter,
|
||||
listenEndpointEvents: true,
|
||||
}
|
||||
|
||||
svcSource.AddEventHandler(t.Context(), func() {})
|
||||
|
||||
assert.Equal(t, tt.times, infSvc.times+infEdp.times+infNode.times)
|
||||
|
||||
tt.asserts(t, svcSource)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
@ -37,6 +37,8 @@ import (
|
||||
"k8s.io/client-go/tools/clientcmd"
|
||||
gateway "sigs.k8s.io/gateway-api/pkg/client/clientset/versioned"
|
||||
|
||||
"sigs.k8s.io/external-dns/source/types"
|
||||
|
||||
extdnshttp "sigs.k8s.io/external-dns/pkg/http"
|
||||
|
||||
"sigs.k8s.io/external-dns/pkg/apis/externaldns"
|
||||
@ -332,53 +334,53 @@ func ByNames(ctx context.Context, p ClientGenerator, names []string, cfg *Config
|
||||
// because they have simpler initialization requirements.
|
||||
func BuildWithConfig(ctx context.Context, source string, p ClientGenerator, cfg *Config) (Source, error) {
|
||||
switch source {
|
||||
case "node":
|
||||
case types.Node:
|
||||
return buildNodeSource(ctx, p, cfg)
|
||||
case "service":
|
||||
case types.Service:
|
||||
return buildServiceSource(ctx, p, cfg)
|
||||
case "ingress":
|
||||
case types.Ingress:
|
||||
return buildIngressSource(ctx, p, cfg)
|
||||
case "pod":
|
||||
case types.Pod:
|
||||
return buildPodSource(ctx, p, cfg)
|
||||
case "gateway-httproute":
|
||||
case types.GatewayHttpRoute:
|
||||
return NewGatewayHTTPRouteSource(p, cfg)
|
||||
case "gateway-grpcroute":
|
||||
case types.GatewayGrpcRoute:
|
||||
return NewGatewayGRPCRouteSource(p, cfg)
|
||||
case "gateway-tlsroute":
|
||||
case types.GatewayTlsRoute:
|
||||
return NewGatewayTLSRouteSource(p, cfg)
|
||||
case "gateway-tcproute":
|
||||
case types.GatewayTcpRoute:
|
||||
return NewGatewayTCPRouteSource(p, cfg)
|
||||
case "gateway-udproute":
|
||||
case types.GatewayUdpRoute:
|
||||
return NewGatewayUDPRouteSource(p, cfg)
|
||||
case "istio-gateway":
|
||||
case types.IstioGateway:
|
||||
return buildIstioGatewaySource(ctx, p, cfg)
|
||||
case "istio-virtualservice":
|
||||
case types.IstioVirtualService:
|
||||
return buildIstioVirtualServiceSource(ctx, p, cfg)
|
||||
case "cloudfoundry":
|
||||
case types.Cloudfoundry:
|
||||
return buildCloudFoundrySource(ctx, p, cfg)
|
||||
case "ambassador-host":
|
||||
case types.AmbassadorHost:
|
||||
return buildAmbassadorHostSource(ctx, p, cfg)
|
||||
case "contour-httpproxy":
|
||||
case types.ContourHTTPProxy:
|
||||
return buildContourHTTPProxySource(ctx, p, cfg)
|
||||
case "gloo-proxy":
|
||||
case types.GlooProxy:
|
||||
return buildGlooProxySource(ctx, p, cfg)
|
||||
case "traefik-proxy":
|
||||
case types.TraefikProxy:
|
||||
return buildTraefikProxySource(ctx, p, cfg)
|
||||
case "openshift-route":
|
||||
case types.OpenShiftRoute:
|
||||
return buildOpenShiftRouteSource(ctx, p, cfg)
|
||||
case "fake":
|
||||
case types.Fake:
|
||||
return NewFakeSource(cfg.FQDNTemplate)
|
||||
case "connector":
|
||||
case types.Connector:
|
||||
return NewConnectorSource(cfg.ConnectorServer)
|
||||
case "crd":
|
||||
case types.CRD:
|
||||
return buildCRDSource(ctx, p, cfg)
|
||||
case "skipper-routegroup":
|
||||
case types.SkipperRouteGroup:
|
||||
return buildSkipperRouteGroupSource(ctx, cfg)
|
||||
case "kong-tcpingress":
|
||||
case types.KongTCPIngress:
|
||||
return buildKongTCPIngressSource(ctx, p, cfg)
|
||||
case "f5-virtualserver":
|
||||
case types.F5VirtualServer:
|
||||
return buildF5VirtualServerSource(ctx, p, cfg)
|
||||
case "f5-transportserver":
|
||||
case types.F5TransportServer:
|
||||
return buildF5TransportServerSource(ctx, p, cfg)
|
||||
}
|
||||
return nil, ErrSourceNotFound
|
||||
|
@ -21,7 +21,7 @@ import (
|
||||
"errors"
|
||||
"testing"
|
||||
|
||||
cfclient "github.com/cloudfoundry-community/go-cfclient"
|
||||
"github.com/cloudfoundry-community/go-cfclient"
|
||||
openshift "github.com/openshift/client-go/route/clientset/versioned"
|
||||
"github.com/stretchr/testify/mock"
|
||||
"github.com/stretchr/testify/suite"
|
||||
@ -34,6 +34,7 @@ import (
|
||||
fakeDynamic "k8s.io/client-go/dynamic/fake"
|
||||
"k8s.io/client-go/kubernetes"
|
||||
fakeKube "k8s.io/client-go/kubernetes/fake"
|
||||
"sigs.k8s.io/external-dns/source/types"
|
||||
gateway "sigs.k8s.io/gateway-api/pkg/client/clientset/versioned"
|
||||
)
|
||||
|
||||
@ -168,7 +169,10 @@ func (suite *ByNamesTestSuite) TestAllInitialized() {
|
||||
}: "IngressRouteUDPList",
|
||||
}), nil)
|
||||
|
||||
sources, err := ByNames(context.TODO(), mockClientGenerator, []string{"service", "ingress", "istio-gateway", "contour-httpproxy", "kong-tcpingress", "f5-virtualserver", "f5-transportserver", "traefik-proxy", "fake"}, &Config{})
|
||||
sources, err := ByNames(context.TODO(), mockClientGenerator, []string{
|
||||
types.Service, types.Ingress, types.IstioGateway, types.ContourHTTPProxy,
|
||||
types.KongTCPIngress, types.F5VirtualServer, types.F5TransportServer, types.TraefikProxy, types.Fake,
|
||||
}, &Config{})
|
||||
suite.NoError(err, "should not generate errors")
|
||||
suite.Len(sources, 9, "should generate all nine sources")
|
||||
}
|
||||
@ -177,7 +181,7 @@ func (suite *ByNamesTestSuite) TestOnlyFake() {
|
||||
mockClientGenerator := new(MockClientGenerator)
|
||||
mockClientGenerator.On("KubeClient").Return(fakeKube.NewSimpleClientset(), nil)
|
||||
|
||||
sources, err := ByNames(context.TODO(), mockClientGenerator, []string{"fake"}, &Config{})
|
||||
sources, err := ByNames(context.TODO(), mockClientGenerator, []string{types.Fake}, &Config{})
|
||||
suite.NoError(err, "should not generate errors")
|
||||
suite.Len(sources, 1, "should generate fake source")
|
||||
suite.Nil(mockClientGenerator.kubeClient, "client should not be created")
|
||||
@ -197,9 +201,9 @@ func (suite *ByNamesTestSuite) TestKubeClientFails() {
|
||||
mockClientGenerator.On("KubeClient").Return(nil, errors.New("foo"))
|
||||
|
||||
sourcesDependentOnKubeClient := []string{
|
||||
"node", "service", "ingress", "pod", "istio-gateway", "istio-virtualservice",
|
||||
"ambassador-host", "gloo-proxy", "traefik-proxy", "crd", "kong-tcpingress",
|
||||
"f5-virtualserver", "f5-transportserver",
|
||||
types.Node, types.Service, types.Ingress, types.Pod, types.IstioGateway, types.IstioVirtualService,
|
||||
types.AmbassadorHost, types.GlooProxy, types.TraefikProxy, types.CRD, types.KongTCPIngress,
|
||||
types.F5VirtualServer, types.F5TransportServer,
|
||||
}
|
||||
|
||||
for _, source := range sourcesDependentOnKubeClient {
|
||||
@ -214,7 +218,7 @@ func (suite *ByNamesTestSuite) TestIstioClientFails() {
|
||||
mockClientGenerator.On("IstioClient").Return(nil, errors.New("foo"))
|
||||
mockClientGenerator.On("DynamicKubernetesClient").Return(nil, errors.New("foo"))
|
||||
|
||||
sourcesDependentOnIstioClient := []string{"istio-gateway", "istio-virtualservice"}
|
||||
sourcesDependentOnIstioClient := []string{types.IstioGateway, types.IstioVirtualService}
|
||||
|
||||
for _, source := range sourcesDependentOnIstioClient {
|
||||
_, err := ByNames(context.TODO(), mockClientGenerator, []string{source}, &Config{})
|
||||
@ -228,8 +232,10 @@ func (suite *ByNamesTestSuite) TestDynamicKubernetesClientFails() {
|
||||
mockClientGenerator.On("IstioClient").Return(istiofake.NewSimpleClientset(), nil)
|
||||
mockClientGenerator.On("DynamicKubernetesClient").Return(nil, errors.New("foo"))
|
||||
|
||||
sourcesDependentOnDynamicKubernetesClient := []string{"ambassador-host", "contour-httpproxy", "gloo-proxy", "traefik-proxy",
|
||||
"kong-tcpingress", "f5-virtualserver", "f5-transportserver"}
|
||||
sourcesDependentOnDynamicKubernetesClient := []string{
|
||||
types.AmbassadorHost, types.ContourHTTPProxy, types.GlooProxy, types.TraefikProxy,
|
||||
types.KongTCPIngress, types.F5VirtualServer, types.F5TransportServer,
|
||||
}
|
||||
|
||||
for _, source := range sourcesDependentOnDynamicKubernetesClient {
|
||||
_, err := ByNames(context.TODO(), mockClientGenerator, []string{source}, &Config{})
|
||||
|
46
source/types/types.go
Normal file
46
source/types/types.go
Normal file
@ -0,0 +1,46 @@
|
||||
/*
|
||||
Copyright 2025 The Kubernetes Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package types
|
||||
|
||||
type Type = string
|
||||
|
||||
const (
|
||||
Node Type = "node"
|
||||
Service Type = "service"
|
||||
Ingress Type = "ingress"
|
||||
Pod Type = "pod"
|
||||
GatewayHttpRoute Type = "gateway-httproute"
|
||||
GatewayGrpcRoute Type = "gateway-grpcroute"
|
||||
GatewayTlsRoute Type = "gateway-tlsroute"
|
||||
GatewayTcpRoute Type = "gateway-tcproute"
|
||||
GatewayUdpRoute Type = "gateway-udproute"
|
||||
IstioGateway Type = "istio-gateway"
|
||||
IstioVirtualService Type = "istio-virtualservice"
|
||||
Cloudfoundry Type = "cloudfoundry"
|
||||
AmbassadorHost Type = "ambassador-host"
|
||||
ContourHTTPProxy Type = "contour-httpproxy"
|
||||
GlooProxy Type = "gloo-proxy"
|
||||
TraefikProxy Type = "traefik-proxy"
|
||||
OpenShiftRoute Type = "openshift-route"
|
||||
Fake Type = "fake"
|
||||
Connector Type = "connector"
|
||||
CRD Type = "crd"
|
||||
SkipperRouteGroup Type = "skipper-routegroup"
|
||||
KongTCPIngress Type = "kong-tcpingress"
|
||||
F5VirtualServer Type = "f5-virtualserver"
|
||||
F5TransportServer Type = "f5-transportserver"
|
||||
)
|
Loading…
Reference in New Issue
Block a user