Comment on page
Writing new DNS providers
I'll ignore all the small stuff and get to the point.
A typical provider implements 3 methods and DNSControl takes care of the rest:
- GetZoneRecords() -- Download the list of DNS records and return them as a list of RecordConfig structs.
- GetZoneRecordsCorrections() -- Generate a list of corrections.
- GetNameservers() -- Query the API and return the list of parent nameservers.
These three functions are all that's needed for
The goal of
GetZoneRecords()is to download all the DNS records, convert them to
models.RecordConfigformat, and return them as one big list (
The goal of
GetZoneRecordsCorrections()is to return a list of corrections. Each correction is a text string describing the change ("Delete CNAME record foo") and a function that, if called, will make the change (i.e. call the API and delete record foo).
dnscontrol previewsimply prints the text strings.
dnscontrol pushprints the strings and calls the functions. Because of how Go's functions work, the function will have everything it needs to make the change. Pretty cool, eh?
Calculating the difference between existing and desired is difficult. Luckily the work is done for you.
GetZoneRecordsCorrections()calls a a function in the
pkg/diff2module that generates a list of changes (usually an ADD, CHANGE, or DELETE) that can easily be turned into the API calls mentioned previously.
So, what does all this mean?
It basically means that writing a provider is as simple as writing code that (1) downloads the existing records, (2) converts each records into
models.RecordConfig, (3) write functions that perform adds, changes, and deletions.
Now that you understand the general process, here are the details.
A provider can be a DnsProvider, a Registrar, or both. We recommend you write the DnsProvider first, release it, and then write the Registrar if needed.
If you have any questions, please discuss them in the GitHub issue related to the request for this provider. Please let us know what was confusing so we can update this document with advice for future authors (or even better, update this document yourself.)
It's a good idea to start by copying a similar provider.
How can you tell a provider is similar?
Each DNS provider's API falls into one of 4 category. Some update one DNS record at a time. Others, the only change they permit is to upload the entire zone even if only one record changed! Others are somewhere in between: all records at a label must be updated at once, or all records in a RecordSet (the label + rType).
In summary, provider APIs basically fall into four general categories:
- Updates are done one record at a time (Record)
- Updates are done one label at a time (Label)
- Updates are done one label+type at a time (RecordSet)
- Updates require the entire zone to be uploaded (Zone).
To determine your provider's category, review your API documentation.
To determine an existing provider's category, see which
diff2.By*()function is used.
DNSControl provides 4 helper functions that do all the hard work for you. As input, they take the existing zone (what was downloaded via the API) and the desired zone (what is in
dnsconfig.js). They return a list of instructions. Implement handlers for the instructions and DNSControl is able to perform
The functions are:
- diff2.ByRecord() -- Updates are done one DNS record at a time. New records are added. Changes and deletes refer to an ID assigned to the record by the provider.
- diff2.ByLabel() -- Updates are done for an entire label. Adds and changes are done by sending one or more records that will appear at that label (i.e. www.example.com). Deletes delete all records at that label.
- diff2.ByRecordSet() -- Similar to ByLabel() but updates are done on the label+type level. If www.example.com has 2 A records and 2 MX records, updates must replace all the A records, or all the MX records, or add records of a different type.
- diff2.ByZone() -- Updates are done by uploading the entire zone every time.
pkg/diff2/diff2.gohas instructions about how to use the diff2 system.
Create a directory for the provider called
nameis all lowercase and represents the commonly-used name for the service.
The main driver should be called
providers/name/nameProvider.go. The API abstraction is usually in a separate file (often called
Directory names should be consitent. It should be all lowercase and match the ALLCAPS provider name.
If you are implementing a DNS Service Provider:
GetDomainCorrections()is a bit interesting. It returns a list of corrections to be made. These are in the form of functions that DNSControl can call to actually make the corrections.
If you are implementing a DNS Registrar:
GetRegistrarCorrections()returns a list of corrections to be made. These are in the form of functions that DNSControl can call to actually make the corrections.
Make sure the existing unit tests work. Add unit tests for any complex algorithms in the new code.
Run the unit tests with this command:
go test ./...
This is the most important kind of testing when adding a new provider. Integration tests use a test account and a real domain.
For example, this will run the tests using BIND:
cd integrationTest # NOTE: Not needed if already in that subdirectory
go test -v -verbose -provider BIND
(BIND is a good place to start since it doesn't require any API keys.)
This will run the tests on Amazon AWS Route53:
export R53_DOMAIN=dnscontroltest-r53.com # Use a test domain.
cd integrationTest # NOTE: Not needed if already in that subdirectory
go test -v -verbose -provider ROUTE53
- Slow tests? Add
-timeout nto increase the timeout for tests
go testkills the tests after 10 minutes by default. Some providers need more time.
- This flag must be before the
-verboseflag. Usually it is the first flag after
go test -timeout 20m -v -verbose -provider CLOUDFLAREAPI
- Run only certain tests using the
- Rather than running all the tests, run just the tests you want.
- These flags must be after the
go test -v -verbose -provider ROUTE53 -start 10 -end 20run tests 10-20 inclusive.
go test -v -verbose -provider ROUTE53 -start 5 -end 5runs only test 5.
go test -v -verbose -provider ROUTE53 -start 20skip the first 19 tests.
go test -v -verbose -provider ROUTE53 -end 20only run the first 20 tests.
README.md: Add the provider to the bullet list.
documentation/providers.md: Add the provider to the provider list.
documentation/SUMMARY.md: Add the provider to the provider list.
documentation/providers/PROVIDERNAME.md: Use one of the other files in that directory as a base.
OWNERS: Add the directory name and your GitHub username.
At this point you can submit a PR.
Actually you can submit the PR even earlier if you just want feedback, input, or have questions. This is just a good stopping place to submit a PR if you haven't already.
Some DNS providers have features that others do not. For example some support the SRV record. A provider announces what it can do using the capabilities system.
If a provider doesn't advertise a particular capability, the integration test system skips the appropriate tests. Therefore you might want to initially develop the provider with no particular capabilities advertised and code until all the integration tests work. Then enable capabilities one at a time to finish off the project.
Don't feel obligated to implement everything at once. In fact, we'd prefer a few small PRs than one big one. Focus on getting the basic provider working well before adding these extras.
Operational features have names like
providers.CanUseAlias. The list of optional "capabilities" are in the file
Capabilities are processed early by DNSControl. For example if a provider doesn't support SRV records, DNSControl will error out when parsing
dnscontrol.jsrather than waiting until the API fails at the very end.
Enable optional capabilities in the
nameProvider.gofile and run the integration tests to see what works and what doesn't. Fix any bugs and repeat, repeat, repeat until you have all the capabilities you want to implement.
FYI: If a provider's capabilities changes, run
go generateto update the documentation.
go vet ./...
golint is deprecated and frozen but it is still useful as it does a few checks that haven't been re-implemented in staticcheck. However golink fails on any file that uses generics, so be prepared to ignore errors about
expected '(', found '[' (and 1 more errors)
go get -u golang.org/x/lint/golint
go install golang.org/x/lint/golint
In the repo root, open
.goreleaser.ymland add the provider to
Here are some last-minute things to check before you submit your PR.
go generateto make sure all generated files are fresh.
- 5.Re-read the maintainer's responsibilities bullet list. By submitting a provider you agree to maintain it, respond to bugs, periodically re-run the integration test to verify nothing has broken, and if we don't hear from you for 2 months we may disable the provider.