dnscontrol/models
tridion f1b30a1a04
feat: Add IGNORE_EXTERNAL_DNS() for Kubernetes external-dns coexistence (#3869)s
## Summary

This PR adds a new domain modifier `IGNORE_EXTERNAL_DNS()` that
automatically detects and ignores DNS records managed by Kubernetes
[external-dns](https://github.com/kubernetes-sigs/external-dns)
controller.

**Related Issue:** This addresses the feature request discussed in
StackExchange/dnscontrol#935 (Idea: Ownership system), where
@tlimoncelli indicated openness to accepting a PR for this
functionality.

## Problem

When running DNSControl alongside Kubernetes external-dns, users face a
challenge:

- **external-dns** dynamically creates DNS records based on Kubernetes
Ingress/Service resources
- Users cannot use `IGNORE()` because they cannot predict which record
names external-dns will create
- Using `NO_PURGE()` is too broad - it prevents DNSControl from cleaning
up any orphaned records

The fundamental issue is that `IGNORE()` requires static patterns known
at config-time, but external-dns creates records dynamically at runtime.

## Solution

`IGNORE_EXTERNAL_DNS()` solves this by detecting external-dns managed
records at runtime:

```javascript
D("example.com", REG_CHANGEME, DnsProvider(DSP_MY_PROVIDER),
    IGNORE_EXTERNAL_DNS(),  // Automatically ignore external-dns managed records
    A("@", "1.2.3.4"),
    CNAME("www", "@")
);
```

### How It Works

external-dns uses a TXT record registry to track ownership. For each
managed record, it creates a TXT record like:

- `a-myapp.example.com` → TXT containing
`heritage=external-dns,external-dns/owner=...`
- `cname-api.example.com` → TXT containing
`heritage=external-dns,external-dns/owner=...`

This PR:
1. Scans existing TXT records for the `heritage=external-dns` marker
2. Parses the TXT record name prefix (e.g., `a-`, `cname-`) to determine
the managed record type
3. Automatically adds those records to the ignore list during diff
operations

## Changes

| File | Purpose |
|------|---------|
| `models/domain.go` | Add `IgnoreExternalDNS` field to DomainConfig |
| `pkg/js/helpers.js` | Add `IGNORE_EXTERNAL_DNS()` JavaScript helper |
| `pkg/diff2/externaldns.go` | Core detection logic for external-dns TXT
records |
| `pkg/diff2/externaldns_test.go` | Unit tests for detection logic |
| `pkg/diff2/handsoff.go` | Integrate external-dns detection into
handsoff() |
| `pkg/diff2/diff2.go` | Pass IgnoreExternalDNS flag to handsoff() |
| `commands/types/dnscontrol.d.ts` | TypeScript definitions for IDE
support |
| `documentation/.../IGNORE_EXTERNAL_DNS.md` | User documentation |

## Design Philosophy

This follows DNSControl's pattern of convenience builders (like
`M365_BUILDER`, `SPF_BUILDER`, `DKIM_BUILDER`) that make complex
operations simple. Just as those builders abstract away implementation
details, `IGNORE_EXTERNAL_DNS()` abstracts away the complexity of
detecting external-dns managed records.

## Testing

All unit tests pass:
```
go test ./pkg/diff2/... -v  # Tests detection logic
go test ./pkg/js/...        # Tests JS helpers
go build ./...              # Builds successfully
```

## Caveats Documented

- Only supports TXT registry (the default for external-dns)
- Requires external-dns to use default naming conventions
- May need updates if external-dns changes its registry format

---------

Co-authored-by: Tom Limoncelli <6293917+tlimoncelli@users.noreply.github.com>
2025-12-03 08:56:55 -05:00
..
dns.go BUGFIX: IDN support is broken for domain names (#3845) 2025-11-29 12:17:44 -05:00
dns_test.go REFACTOR: export models func downcase() (#2207) 2023-03-21 22:11:25 -07:00
dnsrr.go NEW FEATURE: Add SMIMEA support for BIND and deSEC (#3786) 2025-10-10 11:16:13 -04:00
domain.go feat: Add IGNORE_EXTERNAL_DNS() for Kubernetes external-dns coexistence (#3869)s 2025-12-03 08:56:55 -05:00
provider.go Bugfix: Providers with batched updates might not report the correct number of changes (#3108) 2024-09-16 12:20:30 -04:00
quotes.go REFACTOR: Opinion: TXT records are one long string (#2631) 2023-12-04 17:45:25 -05:00
quotes_test.go CHORE: Fix lint warnings from golangci-lint (#3311) 2025-01-13 11:33:12 -05:00
rawrecord.go CLOUDFLAREAPI: Add CF_SINGLE_REDIRECT to manage "dynamic single" redirects (#3035) 2024-07-08 12:38:38 -04:00
record.go AKAMAIEDGEDNS: Add ALIAS and AKAMAITLC support to the Akamai Edge DNS provider (#3836) 2025-11-14 09:48:42 -05:00
record_test.go feat(validation): Validation errors now include the line number (#3788) 2025-10-10 13:48:41 -04:00
recorddb.go CHORE: Fix lint warnings from golangci-lint (#3311) 2025-01-13 11:33:12 -05:00
recordtype.go refactor(RecordConfig): Use ChangeType() instead of assignment (#3441) 2025-02-18 14:01:34 -05:00
t_caa.go BUGFIX: IDN support is broken for domain names (#3845) 2025-11-29 12:17:44 -05:00
t_dnskey.go NEW RECORD TYPE: DNSKEY (#2917) 2024-04-22 09:54:12 -04:00
t_ds.go
t_loc.go CLOUDFLARE: Add LOC support (#3857) 2025-12-01 09:12:10 -05:00
t_mx.go CHORE: Fix lint warnings from golangci-lint (#3311) 2025-01-13 11:33:12 -05:00
t_naptr.go CHORE: Fix lint warnings from golangci-lint (#3311) 2025-01-13 11:33:12 -05:00
t_parse.go AKAMAIEDGEDNS: Add ALIAS and AKAMAITLC support to the Akamai Edge DNS provider (#3836) 2025-11-14 09:48:42 -05:00
t_smimea.go NEW FEATURE: Add SMIMEA support for BIND and deSEC (#3786) 2025-10-10 11:16:13 -04:00
t_soa.go CHORE: Fix lint warnings from golangci-lint (#3311) 2025-01-13 11:33:12 -05:00
t_srv.go CHORE: Fix lint warnings from golangci-lint (#3311) 2025-01-13 11:33:12 -05:00
t_sshfp.go CHORE: Fix lint warnings from golangci-lint (#3311) 2025-01-13 11:33:12 -05:00
t_svcb.go CHORE: Fix lint warnings from golangci-lint (#3311) 2025-01-13 11:33:12 -05:00
t_tlsa.go CHORE: Fix lint warnings from golangci-lint (#3311) 2025-01-13 11:33:12 -05:00
t_txt.go POWERDNS: New record type: LUA (#3815) 2025-11-03 11:32:28 -05:00
target.go AKAMAIEDGEDNS: Add ALIAS and AKAMAITLC support to the Akamai Edge DNS provider (#3836) 2025-11-14 09:48:42 -05:00
tdwarn.go
tonameservers_test.go
unknown.go CHORE: Linting and comment-fixing (#2937) 2024-05-07 14:47:28 -04:00
unmanaged.go deadcode: DebugUnmanagedConfig 2024-03-03 15:42:49 -05:00