Writing new DNS providers

Please do note that if you submit a new provider you will be
assigned bugs related to the provider in the future (unless
you designate someone else as the maintainer). More details
here.

Overview

I’ll ignore all the small stuff and get to the point.

A provider’s GetDomainCorrections() function is the workhorse
of the provider. It is what gets called by dnscontrol preview
and dnscontrol push.

How does a provider’s GetDomainCorrections() function work?

The goal of GetDomainCorrections() 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). Preview
mode simply prints the text strings. dnscontrol push prints 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?

So how does GetDomainCorrections() work?

First, some terminology: The DNS records specified in the dnsconfig.js
file are called the “desired” records. The DNS records stored at
the DNS service provider are called the “existing” records.

Every provider does the same basic process. The function
GetDomainCorrections() is called with a list of the desired DNS
records (dc.Records). It then contacts the provider’s API and
gathers the existing records. It converts the existing records into
a list of *models.RecordConfig.

Now that it has the desired and existing records in the appropriate
format, differ.IncrementalDiff(existingRecords) is called and
does all the hard work of understanding the DNS records and figuring
out what changes need to be made. It generates lists of adds,
deletes, and changes.

GetDomainCorrections() then generates the list of models.Corrections()
and returns. DNSControl takes care of the rest.

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.

Oh, and what if the API simply requires that the entire zonefile be uploaded
every time? We still generate the text descriptions of the changes (so that
dnscontrol preview looks nice) but the functions are just no-ops, except
for one that uploads the new zonefile.

Now that you understand the general process, here are the details.

Step 1: General advice

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 dicuss 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.)

Step 2: Pick a base provider

Pick a similar provider as your base. Providers basically fall
into three general categories:

zone: The API requires you to upload the entire zone every time. (BIND, GANDI).

incremental-label: Similar to incremental, but the API requires you to update all the records related to a particular label each time. For example, if a label (www.example.com) has an A and MX record, any change requires replacing all the records for that label.

TODO: Categorize DNSIMPLE, NAMECHEAP

All providers use the “diff” module to detect differences. It takes
two zones and returns records that are unchanged, created, deleted,
and modified. The incremental providers use the differences to
update individual records or recordsets. The zone providers use the
information to print a human-readable list of what is being changed,
but upload the entire new zone.

Step 3: Create the driver skeleton

Create a directory for the provider called providers/name where
name is 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
api.go).

Step 6: 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.

Step 7: Capabilities

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.CanUseSRV and
providers.CanUseAlias. The list of optional “capabilities” are
in the file dnscontrol/providers/providers.go (look for CanUseAlias).

Capabilities are processed early by DNSControl. For example if a
provider doesn’t support SRV records, DNSControl will error out
when parsing dnscontrol.js rather than waiting until the API fails
at the very end.

Enable optional capabilities in the nameProvider.go file 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 generate to update
the documentation.

Vendoring Dependencies

If your provider depends on other go packages, then you must vendor them. To do this, use govendor. A command like this is usually suffient: