external-dns/docs/contributing/source-wrappers.md
Seena Fallah 736a2d58ae
feat!: generalize PTR record support from rfc2136 to all providers (#6232)
* feat(metrics): add source wrapper metrics for invalid and deduplicated endpoints

Add GaugeVecMetric.Reset() to clear stale label combinations between cycles.

Introduce invalidEndpoints and deduplicatedEndpoints gauge vectors in the
source wrappers package, partitioned by record_type and source_type. The
dedup source wrapper now tracks rejected (invalid) and de-duplicated
endpoints per collection cycle.

Update the metrics documentation and bump the known metrics count.

Signed-off-by: Seena Fallah <seenafallah@gmail.com>

* feat(source): add PTR source wrapper for automatic reverse DNS

Implement ptrSource, a source wrapper that generates PTR endpoints from
A/AAAA records. The wrapper supports:

- Global default via WithCreatePTR (maps to --create-ptr flag)
- Per-endpoint override via record-type provider-specific property
- Grouping multiple hostnames sharing an IP into a single PTR endpoint
- Skipping wildcard DNS names

Add WithPTRSupported and WithCreatePTR options to the wrapper Config
and wire the PTR wrapper into the WrapSources chain when PTR is in
managed-record-types.

Signed-off-by: Seena Fallah <seenafallah@gmail.com>

* feat(config): add --create-ptr flag and deprecate --rfc2136-create-ptr

Add the generic --create-ptr boolean flag to Config, enabling automatic
PTR record creation for any provider. Add IsPTRSupported() helper that
checks whether PTR is included in --managed-record-types.

Add validation: --create-ptr (or legacy --rfc2136-create-ptr) now
requires PTR in --managed-record-types, preventing misconfiguration.

Mark --rfc2136-create-ptr as deprecated in the flag description.

Signed-off-by: Seena Fallah <seenafallah@gmail.com>

* refactor(rfc2136): remove inline PTR logic in favor of PTR source wrapper

Remove the createPTR field, AddReverseRecord, RemoveReverseRecord, and
GenerateReverseRecord methods from the rfc2136 provider. PTR record
generation is now handled generically by the PTR source wrapper before
records reach the provider.

Update the PTR creation test to supply pre-generated PTR endpoints
(simulating what the source wrapper produces) instead of relying on
the provider to create them internally.

Signed-off-by: Seena Fallah <seenafallah@gmail.com>

* feat(controller): wire PTR source wrapper into buildSource

Pass the top-level Config to buildSource so it can read IsPTRSupported()
and the CreatePTR / RFC2136CreatePTR flags. When PTR is in
managed-record-types, the PTR source wrapper is installed in the
wrapper chain with the combined create-ptr default.

Signed-off-by: Seena Fallah <seenafallah@gmail.com>

* chore(pdns): remove stale comment and fix whitespace

Remove an outdated comment about a single-target-per-tuple assumption
that no longer applies.

Signed-off-by: Seena Fallah <seenafallah@gmail.com>

* docs: add PTR records documentation and update existing guides

Add docs/advanced/ptr-records.md covering the --create-ptr flag,
per-resource annotation overrides, prerequisites, and usage examples.

Update:
- annotations.md: document record-type annotation
- flags.md: add --create-ptr, mark --rfc2136-create-ptr as deprecated
- tutorials/rfc2136.md: point to generic --create-ptr flag
- contributing/source-wrappers.md: add PTR wrapper to the chain
- mkdocs.yml: add PTR Records navigation entry

Signed-off-by: Seena Fallah <seenafallah@gmail.com>

* feat(rfc2136)!: remove rfc2136-create-ptr in favor of create-ptr

Signed-off-by: Seena Fallah <seenafallah@gmail.com>

---------

Signed-off-by: Seena Fallah <seenafallah@gmail.com>
2026-03-30 13:36:16 +05:30

172 lines
6.5 KiB
Markdown

# 🧩 Source Wrappers/Middleware
## Overview
In ExternalDNS, a **Source** is a component responsible for discovering DNS records from Kubernetes resources (e.g., `Ingress`, `Service`, `Gateway`, etc.).
**Source Wrappers** are middleware-like components that sit between the source and the plan generation. They extend or modify the behavior of the original sources by transforming, filtering, or enriching the DNS records before they're processed by the planner and provider.
---
## Why Wrappers?
Wrappers solve these key challenges:
- ✂️ **Filtering**: Remove unwanted targets or records from sources based on labels, annotations, targets and etc.
- 🔗 **Aggregation**: Combine Endpoints from multiple underlying sources. For example, from both Kubernetes Services and Ingresses.
- 🧹 **Deduplication**: Prevent duplicate DNS records across sources.
- 🌐 **Target transformation**: Rewrite targets for IPv6 networks or alter endpoint attributes like FQDNS or targets.
- 🧪 **Testing and simulation**: Use the `FakeSource` or wrappers for dry-runs or simulations.
- 🔁 **Composability**: Chain multiple behaviors without modifying core sources.
- 🔐 **Access Control**: Limits endpoint exposure based on policies or user access.
- 📊 **Observability**: Adds logging, debugging, or metrics around source behavior.
---
## Built In Wrappers
| Wrapper | Purpose | Use Case |
|:--------------------:|:----------------------------------------|:----------------------------------------------------|
| `MultiSource` | Combine multiple sources. | Aggregate `Ingress`, `Service`, etc. |
| `DedupSource` | Remove duplicate DNS records. | Avoid duplicate records from sources. |
| `TargetFilterSource` | Include/exclude targets based on CIDRs. | Exclude internal IPs. |
| `NAT64Source` | Add NAT64-prefixed AAAA records. | Support IPv6 with NAT64. |
| `PostProcessor` | Add records post-processing. | Configure TTL, filter provider-specific properties. |
| `PTRSource` | Generate PTR records from A/AAAA. | Automatic reverse DNS entries. |
### Use Cases
### 1.1 `TargetFilterSource`
Filters targets (e.g. IPs or hostnames) based on inclusion or exclusion rules.
📌 **Use case**: Only publish public IPs, exclude test environments.
```yaml
--target-net-filter=192.168.0.0/16
--exclude-target-nets=10.0.0.0/8
```
### 2.1 `NAT64Source`
Converts IPv4 targets to IPv6 using NAT64 prefixes.
📌 **Use case**: Publish AAAA records for IPv6-only clients in NAT64 environments.
```yaml
--nat64-prefix=64:ff9b::/96
```
### 3.1 `PostProcessor`
Applies post-processing to all endpoints after they are collected from sources.
📌 **Use case**
- Sets a minimum TTL on endpoints that have no TTL or a TTL below the configured minimum.
- Filters `ProviderSpecific` properties to retain only those belonging to the configured provider (e.g. `aws/evaluate-target-health` when provider is `aws`). Properties with no provider prefix (e.g. `alias`) are considered provider-agnostic and are always retained.
- Sets the `alias=true` provider-specific property on `CNAME` endpoints when `--prefer-alias` is enabled, signalling providers that support ALIAS records (e.g. PowerDNS, AWS) to use them instead of CNAMEs. Per-resource annotations already present are not overwritten.
```yaml
--min-ttl=60s
--provider=aws
--prefer-alias
```
---
## How Wrappers Work
Wrappers wrap a `Source` and implement the same `Source` interface (e.g., `Endpoints(ctx)`).
They typically follow this pattern:
```go
package wrappers
type myWrapper struct {
next source.Source
}
func (m *myWrapper) Endpoints(ctx context.Context) ([]*endpoint.Endpoint, error) {
eps, err := m.next.Endpoints(ctx)
if err != nil {
return nil, err
}
// Modify, filter, or enrich endpoints as needed
return eps, nil
}
// AddEventHandler must be implemented to satisfy the source.Source interface.
func (m *myWrapper) AddEventHandler(ctx context.Context, handler func()) {
log.Debugf("myWrapper: adding event handler")
m.next.AddEventHandler(ctx, handler)
}
```
This allows wrappers to be stacked or composed together.
---
### Composition of Wrappers
Wrappers are often composed like this:
```go
source := NewMultiSource(actualSources, defaultTargets)
source = NewDedupSource(source)
source = NewNAT64Source(source, cfg.NAT64Networks)
source = NewTargetFilterSource(source, targetFilter)
source = NewPostProcessor(source, WithTTL(minTTL), WithPostProcessorPreferAlias(preferAlias))
source = NewPTRSource(source, createPTR)
```
Each wrapper processes the output of the previous one.
---
## High Level Design
- Source: Implements the base logic for extracting DNS endpoints (e.g. IngressSource, ServiceSource, etc.)
- Wrappers: Decorate the source (e.g. DedupSource, TargetFilterSource) to enhance or filter endpoint data
- Plan: Compares the endpoints from Source with DNS state from Provider and produces create/update/delete changes
- Provider: Applies changes to actual DNS services (e.g. Route53, Cloudflare, Azure DNS)
```mermaid
sequenceDiagram
participant ExternalDNS
participant Source
participant Wrapper
participant DedupWrapper as DedupSource
participant Provider
participant Plan
ExternalDNS->>Source: Initialize source (e.g. Ingress, Service)
Source-->>ExternalDNS: Implements Source interface
ExternalDNS->>Wrapper: Wrap with decorators (e.g. dedup, filters)
Wrapper->>DedupWrapper: Compose with DedupSource
DedupWrapper-->>Wrapper: Return enriched Source
Wrapper-->>ExternalDNS: Return final wrapped Source
ExternalDNS->>Plan: Generate plan from Source
Plan->>Wrapper: Call Endpoints(ctx)
Wrapper->>DedupWrapper: Call Endpoints(ctx)
DedupWrapper->>Source: Call Endpoints(ctx)
Source-->>DedupWrapper: Return []*Endpoint
DedupWrapper-->>Wrapper: Return de-duplicated []*Endpoint
Wrapper-->>Wrapper: PostProcessor: set TTL, alias
Wrapper-->>Wrapper: PTRSource: generate PTR from A/AAAA
Wrapper-->>Plan: Return transformed []*Endpoint
ExternalDNS->>Provider: ApplyChanges(plan)
Provider-->>ExternalDNS: Sync DNS records
```
## Learn More
- [Source Interface](https://github.com/kubernetes-sigs/external-dns/blob/master/source/source.go)
- [Wrappers Source Code](https://github.com/kubernetes-sigs/external-dns/tree/master/source/wrappers)