Skip to content

Commit 4831555

Browse files
Merge branch 'master' into issue-5365-github-action
* master: test(tlsconfig): add unit tests (kubernetes-sigs#5381) fix(log testing): re-use logger library testing functionality (kubernetes-sigs#5368) chore(fqdn-template): fqdn templating move to specific folder and update documentation (kubernetes-sigs#5354) feat(code-quality): source/pod improve code coverage chore(source): code cleanup test(source): cover unhappy paths (kubernetes-sigs#5369) docs(tutorials): add IONOS Cloud setup tutorial for ExternalDNS (kubernetes-sigs#5364) fix typo chore(deps): update linter to v2.1.x fix(webhook): api json object plan.Changes case (kubernetes-sigs#5355) fix: add unit test for route never being accepted chore: remove unneccessary new line chore: move into pre-existing conditions check fix: add unit tests for generation check fix: check that parent generation matches current generation.
2 parents 83b26f2 + 1ac744f commit 4831555

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

54 files changed

+1406
-449
lines changed

.github/workflows/lint.yaml

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ jobs:
2626
files: '.'
2727
config_file: ".markdownlint.json"
2828

29-
- name: Set up Go 1.x
29+
- name: Set up Go
3030
uses: actions/setup-go@v5
3131
with:
3232
go-version-file: go.mod
@@ -44,11 +44,11 @@ jobs:
4444
4545
# https://github.com/golangci/golangci-lint-action?tab=readme-ov-file#verify
4646
- name: Verify linter configuration and Lint go code
47-
uses: golangci/golangci-lint-action@v7
47+
uses: golangci/golangci-lint-action@v8
4848
with:
4949
verify: true
5050
args: --timeout=30m
51-
version: v2.0
51+
version: v2.1
5252

5353
# Run Spectral
5454
- name: Lint OpenAPI spec

charts/external-dns/tests/deployment-flags_test.yaml

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -164,4 +164,3 @@ tests:
164164
asserts:
165165
- failedTemplate:
166166
errorMessage: "'txtPrefix' and 'txtSuffix' are mutually exclusive"
167-

docs/advanced/fqdn-templating.md

Lines changed: 306 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,306 @@
1+
# FQDN Templating Guide
2+
3+
## What is FQDN Templating?
4+
5+
**FQDN templating** is a feature that allows to dynamically construct Fully Qualified Domain Names (FQDNs) using a Go templating engine.
6+
Instead of relying solely on annotations or static names, you can use metadata from Kubernetes objects—such as service names, namespaces, and labels—to generate DNS records programmatically and dynamically.
7+
8+
This is useful for:
9+
10+
- Creating consistent naming conventions across environments.
11+
- Reducing boilerplate annotations.
12+
- Supporting multi-tenant or dynamic environments.
13+
- Migrating from one DNS scheme to another
14+
- Supporting multiple variants, such as a regional one and then one that doesn't or similar.
15+
16+
## How It Works
17+
18+
ExternalDNS has a flag: `--fqdn-template`, which defines a Go template for rendering the desired DNS names.
19+
20+
The template uses the following data from the source object (e.g., a `Service` or `Ingress`):
21+
22+
| Field | Description |
23+
|:--------------|:------------------------------------------------------------------|
24+
| `Name` | Name of the object (e.g., service) |
25+
| `Namespace` | Namespace of the object |
26+
| `Labels` | Map of labels applied to the object |
27+
| `Annotations` | Map of annotations |
28+
| `TargetName` | For `Service`, it's the service name; for `Ingress`, the hostname |
29+
| `Endpoint` | Contains more contextual endpoint info, such as IP/target |
30+
| `Controller` | Controller type (optional) |
31+
32+
## Supported Sources
33+
34+
<!-- TODO: generate from code -->
35+
36+
| Source | Description | FQDN Supported |
37+
|:-----------------------|:----------------------------------------------------------------|:--------------:|
38+
| `ambassador-host` | Queries Ambassador Host resources for endpoints. ||
39+
| `cloudfoundry` | Queries Cloud Foundry resources for endpoints. ||
40+
| `connector` | Queries a custom connector source for endpoints. ||
41+
| `contour-httpproxy` | Queries Contour HTTPProxy resources for endpoints. ||
42+
| `crd` | Queries Custom Resource Definitions (CRDs) for endpoints. ||
43+
| `empty` | Uses an empty source, typically for testing or no-op scenarios. ||
44+
| `f5-transportserver` | Queries F5 TransportServer resources for endpoints. ||
45+
| `f5-virtualserver` | Queries F5 VirtualServer resources for endpoints. ||
46+
| `fake` | Uses a fake source for testing purposes. ||
47+
| `gateway-grpcroute` | Queries GRPCRoute resources from the Gateway API. ||
48+
| `gateway-httproute` | Queries HTTPRoute resources from the Gateway API. ||
49+
| `gateway-tcproute` | Queries TCPRoute resources from the Gateway API. ||
50+
| `gateway-tlsroute` | Queries TLSRoute resources from the Gateway API. ||
51+
| `gateway-udproute` | Queries UDPRoute resources from the Gateway API. ||
52+
| `gloo-proxy` | Queries Gloo Proxy resources for endpoints. ||
53+
| `ingress` | Queries Kubernetes Ingress resources for endpoints. ||
54+
| `istio-gateway` | Queries Istio Gateway resources for endpoints. ||
55+
| `istio-virtualservice` | Queries Istio VirtualService resources for endpoints. ||
56+
| `kong-tcpingress` | Queries Kong TCPIngress resources for endpoints. ||
57+
| `node` | Queries Kubernetes Node resources for endpoints. ||
58+
| `openshift-route` | Queries OpenShift Route resources for endpoints. ||
59+
| `pod` | Queries Kubernetes Pod resources for endpoints. ||
60+
| `service` | Queries Kubernetes Service resources for endpoints. ||
61+
| `skipper-routegroup` | Queries Skipper RouteGroup resources for endpoints. ||
62+
| `traefik-proxy` | Queries Traefik Proxy resources for endpoints. ||
63+
64+
## Custom Functions
65+
66+
<!-- TODO: generate from code -->
67+
68+
| Function | Description |
69+
|:-------------|:-----------------------------------------------------------------------------------------|
70+
| `trimPrefix` | Function from the `strings` package. Returns `string` without the provided leading prefix. |
71+
72+
---
73+
74+
## Example Usage
75+
76+
> These examples should provide a solid foundation for implementing FQDN templating in your ExternalDNS setup.
77+
> If you have specific requirements or encounter issues, feel free to explore the issues or update this guide.
78+
79+
### Basic Usage
80+
81+
```yml
82+
apiVersion: v1
83+
kind: Service
84+
metadata:
85+
name: my-service
86+
namespace: my-namespace
87+
```
88+
89+
```sh
90+
external-dns \
91+
--provider=aws \
92+
--source=service \
93+
--fqdn-template="{{ .Name }}.example.com,{{ .Name }}.{{ .Namespace }}.example.tld"
94+
95+
# This will result in DNS entries like
96+
>route53> my-service.example.com
97+
>route53> my-service.my-namespace.example.tld
98+
```
99+
100+
### With Namespace
101+
102+
```yml
103+
---
104+
apiVersion: v1
105+
kind: Service
106+
metadata:
107+
name: my-service
108+
namespace: default
109+
---
110+
apiVersion: v1
111+
kind: Service
112+
metadata:
113+
name: other-service
114+
namespace: kube-system
115+
```
116+
117+
```yml
118+
args:
119+
--fqdn-template="{{.Name}}.{{.Namespace}}.example.com"
120+
121+
# This will result in DNS entries like
122+
# route53> my-service.default.example.com
123+
# route53> other-service.kube-system.example.com
124+
```
125+
126+
### Using Labels in Templates
127+
128+
You can also utilize labels in your FQDN templates to create more dynamic DNS entries. Assuming your service has:
129+
130+
```yml
131+
apiVersion: v1
132+
kind: Service
133+
metadata:
134+
name: my-service
135+
labels:
136+
environment: staging
137+
```
138+
139+
```yml
140+
args:
141+
--fqdn-template="{{ .Labels.environment }}.{{ .Name }}.example.com"
142+
143+
# This will result in DNS entries like
144+
# route53> staging.my-service.example.com
145+
```
146+
147+
### Multiple FQDN Templates
148+
149+
ExternalDNS allows specifying multiple FQDN templates, which can be useful when you want to create multiple DNS entries for a single service or ingress.
150+
151+
> Be cautious, as this will create multiple DNS records per resource, potentially increasing the number of API calls to your DNS provider.
152+
153+
```yml
154+
args:
155+
--fqdn-template={{.Name}}.example.com,{{.Name}}.svc.example.com
156+
```
157+
158+
### Conditional Templating combined with Annotations processing
159+
160+
In scenarios where you want to conditionally generate FQDNs based on annotations, you can use Go template functions like or to provide defaults.
161+
162+
```yml
163+
args:
164+
- --combine-fqdn-annotation # this is required to combine FQDN templating and annotation processing
165+
- --fqdn-template={{ or .Annotations.dns "invalid" }}.example.com
166+
- --exclude-domains=invalid.example.com
167+
```
168+
169+
### Using Annotations for FQDN Templating
170+
171+
This example demonstrates how to use annotations in Kubernetes objects to dynamically generate Fully Qualified Domain Names (FQDNs) using the --fqdn-template flag in ExternalDNS.
172+
173+
The Service object includes an annotation dns.company.com/label with the value my-org-tld-v2. This annotation is used as part of the FQDN template to construct the DNS name.
174+
175+
```yml
176+
apiVersion: v1
177+
kind: Service
178+
metadata:
179+
name: nginx-v2
180+
namespace: my-namespace
181+
annotations:
182+
dns.company.com/label: my-org-tld-v2
183+
spec:
184+
type: ClusterIP
185+
clusterIP: None
186+
```
187+
188+
The --fqdn-template flag is configured to use the annotation value (dns.company.com/label) and append the namespace and a custom domain (company.local) to generate the FQDN.
189+
190+
```yml
191+
args:
192+
--source=service
193+
--fqdn-template='{{ index .ObjectMeta.Annotations "dns.company.com/label" }}.{{ .Namespace }}.company.local'
194+
195+
# For the given Service object, the resulting FQDN will be:
196+
# route53> my-org-tld-v2.my-namespace.company.local
197+
```
198+
199+
### DNS Scheme Migration
200+
201+
If you're transitioning from one naming convention to another (e.g., from svc.cluster.local to svc.example.com), --fqdn-template allows you to generate the new records alongside or in place of the old ones — without requiring changes to your Kubernetes manifests.
202+
203+
```yml
204+
args:
205+
- --fqdn-template='{{.Name}}.new-dns.example.com'
206+
```
207+
208+
This helps automate DNS record migration while maintaining service continuity.
209+
210+
### Multi-Variant Domain Support
211+
212+
You can also support regional variants or multi-tenant architectures, where the same service is deployed to different regions or environments:
213+
214+
```yaml
215+
--fqdn-template='{{ .Name }}.{{ .Labels.env }}.{{ .Labels.region }}.example.com, {{ if eq .Labels.env "prod" }}{{ .Name }}.my-company.tld{{ end }}'
216+
```
217+
218+
With additional context (e.g., annotations), this can produce FQDNs like:
219+
220+
```yml
221+
api.prod.us-east-1.example.com
222+
api.my-company.tld
223+
```
224+
225+
This is helpful in scenarios such as:
226+
227+
- Blue/green deployments across domains
228+
- Staging vs. production resolution
229+
- Multi-cloud or multi-region failover strategies
230+
231+
## Tips
232+
233+
- If `--fqdn-template` is specified, ExternalDNS ignores any `external-dns.alpha.kubernetes.io/hostname` annotations.
234+
- You must still ensure the resulting FQDN is valid and unique.
235+
- Since Go templates can be error-prone, test your template with simple examples before deploying. Mismatched field names or nil values (e.g., missing labels) will result in errors or skipped entries.
236+
237+
## FaQ
238+
239+
### Can I specify multiple global FQDN templates?
240+
241+
Yes, you can. Pass in a comma separated list to --fqdn-template. Beware this will double (triple, etc) the amount of DNS entries based on how many services, ingresses and so on you have and will get you faster towards the API request limit of your DNS provider.
242+
243+
### Where to find template syntax
244+
245+
- [Go template syntax](https://pkg.go.dev/text/template)
246+
- [Go func builtins](https://github.com/golang/go/blob/master/src/text/template/funcs.go#L39-L63)
247+
248+
### FQDN Templating, Helm and improper templating syntax
249+
250+
The user encountered errors due to improper templating syntax:
251+
252+
```yml
253+
extraArgs:
254+
- --fqdn-template={{name}}.uat.example.com
255+
```
256+
257+
The correct syntax should include a dot prefix: `{{ .Name }}`.
258+
Additionally, when using Helm's `tpl` function, it's necessary to escape the braces to prevent premature evaluation:
259+
260+
```yml
261+
extraArgs:
262+
- --fqdn-template={{ `{{ .Name }}.uat.example.com` }}
263+
```
264+
265+
### Handling Subdomain-Only Hostnames
266+
267+
In [Issue #1872](https://github.com/kubernetes-sigs/external-dns/issues/1872), it was observed that ExternalDNS ignores the `--fqdn-template` when the ingress host field is set to a subdomain (e.g., foo) without a full domain.
268+
The expectation was that the template would still apply, generating entries like `foo.bar.example.com.`
269+
This highlights a limitation to be aware of when designing FQDN templates.
270+
271+
> :warning: This is currently not supported ! User would expect external-dns to generate a dns record according to the fqdnTemplate
272+
> e.g. if the ingress name: foo and host: foo is created while fqdnTemplate={{.Name}}.bar.example.com then a dns record foo.bar.example.com should be created
273+
274+
```yml
275+
apiVersion: extensions/v1beta1
276+
kind: Ingress
277+
metadata:
278+
name: foo
279+
spec:
280+
rules:
281+
- host: foo
282+
http:
283+
paths:
284+
- backend:
285+
serviceName: foo
286+
servicePort: 80
287+
path: /
288+
```
289+
290+
### Combining FQDN Template with Annotations
291+
292+
In [Issue #3318](https://github.com/kubernetes-sigs/external-dns/issues/3318), a question was raised about the interaction between --fqdn-template and --combine-fqdn-annotation.
293+
The discussion clarified that when both flags are used, ExternalDNS combines the FQDN generated from the template with the annotation value, providing flexibility in DNS name construction.
294+
295+
### Using Annotations for Dynamic FQDNs
296+
297+
In [Issue #2627](https://github.com/kubernetes-sigs/external-dns/issues/2627), a user aimed to generate DNS entries based on ingress annotations:
298+
299+
```yml
300+
args:
301+
- --fqdn-template={{.Annotations.hostname}}.example.com
302+
- --combine-fqdn-annotation
303+
- --domain-filter=example.com
304+
```
305+
306+
By setting the hostname annotation in the ingress resource, ExternalDNS constructs the FQDN accordingly. This approach allows for dynamic DNS entries without hardcoding hostnames.
File renamed without changes.
File renamed without changes.
File renamed without changes.

docs/contributing/dev-guide.md

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,36 @@ make generate-metrics-documentation
4848
We require all changes to be covered by acceptance tests and/or unit tests, depending on the situation.
4949
In the context of the `external-dns`, acceptance tests are tests of interactions with providers, such as creating, reading information about, and destroying DNS resources. In contrast, unit tests test functionality wholly within the codebase itself, such as function tests.
5050

51+
### Log Unit Testing
52+
53+
Testing log messages within codebase provides significant advantages, especially when it comes to debugging, monitoring, and gaining a deeper understanding of system behavior. Log library [build-in testing functionality](https://github.com/sirupsen/logrus?tab=readme-ov-file#testing)
54+
55+
This practice enables:
56+
57+
- Early detection of logging issues
58+
- Verification of Important Information
59+
- Ensuring Correct Severity Levels
60+
- Improving Observability and Monitoring
61+
- Driving Better Logging Practices
62+
63+
To illustrate how to unit test log output within functions, consider the following example:
64+
65+
```go
66+
import (
67+
"testing"
68+
69+
"sigs.k8s.io/external-dns/internal/testutils"
70+
)
71+
72+
func TestMe(t *testing.T) {
73+
hook := testutils.LogsUnderTestWithLogLeve(log.WarnLevel, t)
74+
... function under tests ...
75+
testutils.TestHelperLogContains("example warning message", hook, t)
76+
// provide negative assertion
77+
testuitls.TestHelperLogNotContains("this message should not be shown", hook, t)
78+
}
79+
```
80+
5181
### Continuous Integration
5282

5383
When submitting a pull request, you'll notice that we run several automated processes on your proposed change. Some of these processes are tests to ensure your contribution aligns with our standards. While we strive for accuracy, some users may find these tests confusing.

docs/faq.md

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -50,10 +50,6 @@ There are three sources of information for ExternalDNS to decide on DNS name. Ex
5050

5151
3. If `--fqdn-template` flag is specified, e.g. `--fqdn-template={{.Name}}.my-org.com`, ExternalDNS will use service/ingress specifications for the provided template to generate DNS name.
5252

53-
## Can I specify multiple global FQDN templates?
54-
55-
Yes, you can. Pass in a comma separated list to `--fqdn-template`. Beaware this will double (triple, etc) the amount of DNS entries based on how many services, ingresses and so on you have and will get you faster towards the API request limit of your DNS provider.
56-
5753
## Which Service and Ingress controllers are supported?
5854

5955
Regarding Services, we'll support the OSI Layer 4 load balancers that Kubernetes creates on AWS and Google Kubernetes Engine, and possibly other clusters running on Google Compute Engine.

0 commit comments

Comments
 (0)