dnscontrol/pkg
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
..
bindserial Linting (#2308) 2023-05-01 18:40:10 -04:00
credsfile NEW FEATURE: Empty creds.json should not be an error (#3844) 2025-11-21 10:46:18 -05:00
diff Warn if IGNORE() is unreliable for this provider (#3683) 2025-07-30 20:41:49 -04:00
diff2 feat: Add IGNORE_EXTERNAL_DNS() for Kubernetes external-dns coexistence (#3869)s 2025-12-03 08:56:55 -05:00
dnsgraph CHORE: Fix lint warnings from golangci-lint (#3311) 2025-01-13 11:33:12 -05:00
dnssort CHORE: Fix lint warnings from golangci-lint (#3311) 2025-01-13 11:33:12 -05:00
dnstree CHORE: Fix lint warnings from golangci-lint (#3311) 2025-01-13 11:33:12 -05:00
domaintags BUGFIX: IDN support is broken for domain names (#3845) 2025-11-29 12:17:44 -05:00
js feat: Add IGNORE_EXTERNAL_DNS() for Kubernetes external-dns coexistence (#3869)s 2025-12-03 08:56:55 -05:00
nameservers CHORE: Fix lint warnings from golangci-lint (#3311) 2025-01-13 11:33:12 -05:00
normalize BUGFIX: IDN support is broken for domain names (#3845) 2025-11-29 12:17:44 -05:00
notifications DEPS: Switch to maintained fork of shoutrrr (#3838) 2025-11-17 11:52:26 -05:00
prettyzone POWERDNS: New record type: LUA (#3815) 2025-11-03 11:32:28 -05:00
printer BUGFIX: Reverse order of domain ascii/unicode displayed (#3872) 2025-12-02 10:47:11 -05:00
recorddb CHORE: Fix lint warnings from golangci-lint (#3311) 2025-01-13 11:33:12 -05:00
rejectif Joker: Implement DNS Provider (#3661) 2025-08-04 16:37:20 -04:00
rfc4183 CHORE: Fix lint warnings from golangci-lint (#3311) 2025-01-13 11:33:12 -05:00
rtypecontrol CHORE: Fix lint warnings from golangci-lint (#3311) 2025-01-13 11:33:12 -05:00
rtypes CHORE: Fix lint warnings from golangci-lint (#3311) 2025-01-13 11:33:12 -05:00
soautil CHORE: Satisfy Staticcheck and remove unused code (#2331) 2023-05-08 16:49:26 -04:00
spflib FEATURE: Optionally disable spfcache.json (#3765) 2025-09-13 12:23:46 -04:00
transform FEATURE: Extend PTR magic handling to support RFC4183 names (#3364) 2025-01-14 21:07:19 -05:00
txtutil Update deps (#3790) 2025-10-10 14:16:46 -04:00
version BUG: Output better "version" string when running main.go directly (#3658) 2025-07-09 16:35:23 -04:00
zonecache REFACTOR: Rename commands/zonecache.go (#3646) 2025-06-30 16:16:58 -04:00
zonerecs INWX: Fix INWX provider after their unexpected data-type breaking-change (#3855) 2025-11-29 12:17:13 -05:00