Synchronize your DNS to multiple providers from a simple DSL
  • Go 91.7%
  • JavaScript 6.4%
  • Dogescript 1.8%
Find a file
Phil Pennock 1132bedcf9
CHORE: Replace parser.ParseDir with golang.org/x/tools/go/packages (#4195)
parser.ParseDir is deprecated since Go 1.25. Use packages.Load with
NeedTypes and NeedTypesInfo to correctly extract CanUse* constant
integer values via TypesInfo.Defs, preserving the test's ability to
detect missing providerCapabilityChecks entries.

---

This is a *replacement* PR for #4154. I took my review feedback from
that PR, copied the markdown into two local files, and prompted Claude
Code (using Sonnet 4.6) with:

> Branch sx-pr/4154 contains one commit, which purports to "Replace
parser.ParseDir, which is deprecated". But it is broken. This is a test
of the codebase, via AST checks, that various providers.CanUse* checks
are called appropriately. Files `local.pdp.review1.1` and
`local.pdp.review1.2` (git-ignored) contain my review feedback, pointing
out how the change is broken and how it can be validated. You are on a
new git branch based off current main; please provide a correct
replacement, and validate that it works.

The next section is choice quotes from Claude's analysis while working,
and the final section was Claude's summary at end-of-work.

I have independently reviewed the code, it looks good, and I've
validated that the tests look reasonable and correctly fail when a test
is commented out (to ensure the AI did not fabricate that). I think this
is a sane way forward.

I thought it would be amusing to use Claude to critique Claude's earlier
work.

---

Now I understand the problem clearly. The broken PR sets
`capabilityInts[name.Name] = 0` for all constants (the comment even
admits it), which means every `CanUse*` constant maps to `int 0`, and
since `capIntsToNames[0]` finds the first entry, all checks pass
trivially. The test is completely neutered.

The fix is to use `packages.NeedTypes`/`NeedTypesInfo` (already
requested in the config) to actually read the integer values of the
constants. Let me look at the providers package to understand the
constants:

[...]

The problem is clear. The broken PR:
1. Uses `packages.Load` correctly to get type info (`NeedTypes`,
`NeedTypesInfo`)
2. But then abandons the type info and sets all `capabilityInts` to `0`,
making every `CanUse*` constant map to `CanAutoDNSSEC` (which is in
`capIntsToNames[0]`), so all checks pass trivially

The fix is to use `providers.TypesInfo.Defs` to look up the actual
`*types.Const` value for each constant. Let me write the corrected test
file:
  
--- 

Both validations from the review pass:

1. Correct output: integer values are real (`(4)`, `(9)`, etc.) and each
is paired with its correct record type — matches the "before" output
exactly.
2. Test actually catches omissions: commenting out the DNAME entry
causes `FAIL` with `MISSING: providers.CanUseDNAME (9)`.

What was wrong in the PR: The code switched from `parser.ParseDir` to
`packages.Load` correctly, but the author gave up on extracting real
integer values and wrote `capabilityInts[name.Name] = 0` for everything.
Since all constants got value `0`, they all matched `capIntsToNames[0] =
AUTODNSSEC`, making the test trivially pass regardless of what was in
`providerCapabilityChecks`.

The fix: Instead of iterating the raw AST and guessing values, use
`providers.TypesInfo.Defs` (which is what `NeedTypesInfo` provides) to
look up each identifier's `*types.Const` object and call
`constant.Int64Val(c.Val())` to get the actual iota integer.

Co-authored-by: Phil's PT AI Bot Account <phil+ai@pennock-tech.com>
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-08 07:33:12 -07:00
.github CICD: Integration test output not visible (#4230) 2026-05-07 22:40:32 -07:00
bin CICD: Automate release prep steps: bin/prepare_release.sh (#4145) 2026-03-09 15:18:07 -04:00
build Fix imports 2026-05-03 09:50:12 -04:00
commands DOCS: Unwrap hard-wrapped paragraphs in GitBook documentation (#4225) 2026-05-08 07:31:52 -07:00
docs Fix reference to github source 2026-05-03 09:50:12 -04:00
documentation DOCS: Unwrap hard-wrapped paragraphs in GitBook documentation (#4225) 2026-05-08 07:31:52 -07:00
integrationTest Fix imports 2026-05-03 09:50:12 -04:00
models Fix imports 2026-05-03 09:50:12 -04:00
pkg CHORE: Replace parser.ParseDir with golang.org/x/tools/go/packages (#4195) 2026-05-08 07:33:12 -07:00
providers goimports 2026-05-03 09:50:12 -04:00
.editorconfig DOCS: expand upon concurrency (#3676) 2025-07-22 10:01:10 -04:00
.git-blame-ignore-revs Add .git-blame-ignore-revs 2025-01-03 13:19:39 -05:00
.gitattributes DOCS: Fix many minor typos (#3752) 2025-09-02 11:00:25 -04:00
.gitignore REFACTOR: BIND/GANDI_V5 add "RP" record type, rewrite CLOUDFLAREAPI CF_* and more (#3886) 2025-12-04 16:42:20 -05:00
.golangci.yml cicd(gocov): Upgrade from golang.org/x/exp/slices to slices (#4217) 2026-05-02 17:15:03 -04:00
.goreleaser.yml BUILD: Fix typo in GoReleaser GHCR image path (#4223) 2026-05-03 13:57:00 -04:00
.linkspector.yml Fix .linkspector.yml 2026-05-03 09:50:12 -04:00
.nvmrc CICD: Detect broken HTML links in the documentation as part of the pipeline (#3439) 2025-03-21 10:56:41 -04:00
.prettierrc Format helpers.js (#205) 2017-09-15 13:12:09 -04:00
Dockerfile Build(deps): Bump alpine from 3.23.3 to 3.23.4 (#4212) 2026-05-02 10:50:59 -04:00
go.mod CHORE: Update dependencies (#4229) 2026-05-07 06:42:47 -07:00
go.sum CHORE: Update dependencies (#4229) 2026-05-07 06:42:47 -07:00
LICENSE Fix README 2026-05-03 09:50:12 -04:00
main.go Fix imports 2026-05-03 09:50:12 -04:00
OWNERS Fix tom's github and email address 2026-05-03 09:50:12 -04:00
package-lock.json CHORE: Update dependencies (#4229) 2026-05-07 06:42:47 -07:00
package.json CHORE: Update deps and re-run generate.sh (#3990) 2026-01-20 13:33:12 -05:00
README.md BUILD: Fix typo in GoReleaser GHCR image path (#4223) 2026-05-03 13:57:00 -04:00
SECURITY.md Fix imports 2026-05-03 09:50:12 -04:00
staticcheck.conf CHORE: Satisfy Staticcheck and remove unused code (#2331) 2023-05-08 16:49:26 -04:00

DNSControl

DNSControl/dnscontrol/build Google Group PkgGoDev

DNSControl is a system for maintaining DNS zones. It has two parts: a domain specific language (DSL) for describing DNS zones plus software that processes the DSL and pushes the resulting zones to DNS providers such as Route53, Cloudflare, and Gandi. It can send the same DNS records to multiple providers. It even generates the most beautiful BIND zone files ever. It runs anywhere Go runs (Linux, macOS, Windows). The provider model is extensible, so more providers can be added.

Currently supported DNS providers:

  • AdGuard Home
  • Akamai Edge DNS
  • Alibaba Cloud DNS (ALIDNS)
  • AutoDNS
  • AWS Route 53
  • AXFR+DDNS
  • Azure DNS
  • Azure Private DNS
  • BIND
  • Bunny DNS
  • CentralNic Reseller (CNR) - formerly RRPProxy
  • Cloudflare
  • ClouDNS
  • CSC Global (Experimental)
  • deSEC
  • DigitalOcean
  • DNS Made Easy
  • DNScale
  • DNSimple
  • Domainnameshop (Domeneshop)
  • Exoscale
  • Fortigate
  • Gandi
  • Gcore
  • Gidinet
  • Google DNS
  • Hetzner
  • hosting.de
  • Huawei Cloud DNS
  • Hurricane Electric DNS
  • Infomaniak
  • INWX
  • Joker
  • Linode
  • Loopia
  • LuaDNS
  • Microsoft Windows Server DNS Server
  • MikroTik RouterOS
  • Mythic Beasts
  • Name.com
  • Namecheap
  • Netcup
  • Netlify
  • NS1
  • Oracle Cloud
  • OVH
  • Packetframe
  • Porkbun
  • PowerDNS
  • Realtime Register
  • RWTH DNS-Admin
  • Sakura Cloud
  • SoftLayer
  • TransIP
  • UniFi Network
  • Vercel
  • Vultr

Currently supported Domain Registrars:

  • AWS Route 53
  • CentralNic Reseller (CNR) - formerly RRPProxy
  • CSC Global
  • DNSimple
  • DNSOVERHTTPS
  • Dynadot
  • easyname
  • Gandi
  • Gidinet
  • hosting.de
  • Internet.bs
  • INWX
  • Loopia
  • Name.com
  • Namecheap
  • OpenSRS
  • OVH
  • Porkbun
  • Realtime Register

Stack Overflow uses this system to manage hundreds of domains and subdomains across multiple registrars and DNS providers.

You can think of it as a DNS compiler. The configuration files are written in a DSL that looks a lot like JavaScript. It is compiled to an intermediate representation (IR). Compiler back-ends use the IR to update your DNS zones on services such as Route53, Cloudflare, and Gandi, or systems such as BIND.

An Example

dnsconfig.js:

// define our registrar and providers
var REG_NAMECOM = NewRegistrar("name.com");
var r53 = NewDnsProvider("r53")

D("example.com", REG_NAMECOM, DnsProvider(r53),
  A("@", "1.2.3.4"),
  CNAME("www","@"),
  MX("@",5,"mail.myserver.com."),
  A("test", "5.6.7.8")
)

Running dnscontrol preview will talk to the providers (here name.com as registrar and route 53 as the dns host), and determine what changes need to be made.

Running dnscontrol push will make those changes with the provider and my dns records will be correctly updated.

The easiest way to run DNSControl is to use the Docker container:

docker run --rm -it -v "$(pwd):/dns"  ghcr.io/dnscontrol/dnscontrol preview

See Getting Started page on documentation site to get started!

Benefits

  • Less error-prone than editing a BIND zone file.
  • More reproducible than clicking buttons on a web portal.
  • Easily switch between DNS providers: The DNSControl language is vendor-agnostic. If you use it to maintain your DNS zone records, you can switch between DNS providers easily. In fact, DNSControl will upload your DNS records to multiple providers, which means you can test one while switching to another. We've switched providers 3 times in three years and we've never lost a DNS record.
  • Apply CI/CD principles to DNS! StackOverflow maintains their DNSControl configurations in Git and use our CI system to roll out changes. Keeping DNS information in a VCS means we have full history. Using CI enables us to include unit-tests and system-tests. Remember when you forgot to include a "." at the end of an MX record? We haven't had that problem since we included a test to make sure Tom doesn't make that mistake... again.
  • Adopt (GitOps) PR-based updates. Allow developers to send updates as PRs, which you can review before you approve.
  • Variables save time! Assign an IP address to a constant and use the variable name throughout the file. Need to change the IP address globally? Just change the variable and "recompile."
  • Macros! Define your SPF records, MX records, or other repeated data once and re-use them for all domains.
  • Control Cloudflare from a single source of truth. Enable/disable Cloudflare proxying (the "orange cloud" button) directly from your DNSControl files.
  • Keep similar domains in sync with transforms and other features. If one domain is supposed to be a filtered version of another, this is easy to set up.
  • It is extendable! All the DNS providers are written as plugins. Writing new plugins is very easy.

Installation

DNSControl can be installed via packages for macOS, Linux and Windows, or from source code. See the official instructions.

Via GitHub Actions (GHA)

See dnscontrol-action or gacts/install-dnscontrol.

Deprecation warnings (updated 2025-11-21)

  • REV() will switch from RFC2317 to RFC4183 in v5.0. This is a breaking change. Warnings are output if your configuration is affected. No date has been announced for v5.0. See https://docs.dnscontrol.org/language-reference/top-level-functions/revcompat
  • NAMEDOTCOM, OPENSRS, and SOFTLAYER need maintainers! These providers have no maintainer. Maintainers respond to PRs and fix bugs in a timely manner, and try to stay on top of protocol changes. Interested in being a hero and adopting them? Contact tal at what exit dot org.

More info at our website

The website: https://docs.dnscontrol.org/

The getting started guide: https://docs.dnscontrol.org/getting-started/getting-started

Stargazers over time

Stargazers over time