package module
v0.2.2 Latest Latest

This package is not in the latest version of its module.

Go to latest
Published: Mar 21, 2024 License: MIT Imports: 5 Imported by: 297


libdns - Universal DNS provider APIs for Go

⚠️ Work-in-progress. Exported APIs are subject to change.

libdns is a collection of free-range DNS provider client implementations written in Go! With libdns packages, your Go program can manage DNS records across any supported providers. A "provider" is a service or program that manages a DNS zone.

This repository defines the core interfaces that provider packages should implement. They are small and idiomatic Go interfaces with well-defined semantics.

The interfaces include:

See full godoc for detailed documentation.


To work with DNS records managed by Cloudflare, for example, we can use libdns/cloudflare:

import (

ctx := context.TODO()

zone := ""

// configure the DNS provider (choose any from
provider := cloudflare.Provider{APIToken: "topsecret"}

// list records
recs, err := provider.GetRecords(ctx, zone)

// create records (AppendRecords is similar)
newRecs, err := provider.SetRecords(ctx, zone, []libdns.Record{
		Type:  "A",
		Name:  "sub",
		Value: "",

// delete records (this example uses provider-assigned ID)
deletedRecs, err := provider.DeleteRecords(ctx, zone, []libdns.Record{
		ID: "foobar",

// no matter which provider you use, the code stays the same!
// (some providers have caveats; see their package documentation)

Implementing new provider packages

Provider packages are 100% written and maintained by the community! Collectively, we all maintain the packages for providers we individually use.

Instructions for adding new libdns packages are on this repo's wiki. Please feel free to contribute yours!

Similar projects

OctoDNS is a suite of tools written in Python for managing DNS. However, its approach is a bit heavy-handed when all you need are small, incremental changes to a zone:

WARNING: OctoDNS assumes ownership of any domain you point it to. When you tell it to act it will do whatever is necessary to try and match up states including deleting any unexpected records. Be careful when playing around with OctoDNS.

This is incredibly useful when you are maintaining your own zone file, but risky when you just need incremental changes.

StackExchange/dnscontrol is written in Go, but is similar to OctoDNS in that it tends to obliterate your entire zone and replace it with your input. Again, this is very useful if you are maintaining your own master list of records, but doesn't do well for simply adding or removing records.

go-acme/lego has support for a huge number of DNS providers (75+!), but their APIs are only capable of setting and deleting TXT records for ACME challenges.

libdns takes inspiration from the above projects but aims for a more generally-useful set of APIs that homogenize pretty well across providers. In contrast to the above projects, libdns can add, set, delete, and get arbitrary records from a zone without obliterating it (although syncing up an entire zone is also possible!). Its APIs also include context so long-running calls can be cancelled early, for example to accommodate on-line config changes downstream. libdns interfaces are also smaller and more composable. Additionally, libdns can grow to support a nearly infinite number of DNS providers without added bloat, because each provider implementation is a separate Go module, which keeps your builds lean and fast.

In summary, the goal is that libdns providers can do what the above libraries/tools can do, but with more flexibility: they can create and delete TXT records for ACME challenges, they can replace entire zones, but they can also do incremental changes or simply read records.

Record abstraction

How records are represented across providers varies widely, and each kind of record has different fields and semantics. In time, our goal is for the libdns.Record type to be able to represent most of them as concisely and simply as possible, with the interface methods able to deliver on most of the possible zone operations.

Realistically, libdns should enable most common record manipulations, but may not be able to fit absolutely 100% of all possibilities with DNS in a provider-agnostic way. That is probably OK; and given the wide varieties in DNS record types and provider APIs, it would be unreasonable to expect otherwise. We are not aiming for 100% fulfillment of 100% of users' requirements; more like 100% fulfillment of ~90% of users' requirements.



Package libdns defines core interfaces that should be implemented by DNS provider clients. They are small and idiomatic Go interfaces with well-defined semantics.

Records are described independently of any particular zone, a convention that grants Record structs portability across zones. As such, record names are partially qualified, i.e. relative to the zone. For example, an A record called "sub" in zone "" represents a fully-qualified domain name (FQDN) of "". Implementations should expect that input records conform to this standard, while also ensuring that output records do; adjustments to record names may need to be made before or after provider API calls, for example, to maintain consistency with all other libdns packages. Helper functions are available in this package to convert between relative and absolute names.

Although zone names are a required input, libdns does not coerce any particular representation of DNS zones; only records. Since zone name and records are separate inputs in libdns interfaces, it is up to the caller to pair a zone's name with its records in a way that works for them.

All interface implementations must be safe for concurrent/parallel use, meaning 1) no data races, and 2) simultaneous method calls must result in either both their expected outcomes or an error.

For example, if AppendRecords() is called at the same time and two API requests are made to the provider at the same time, the result of both requests must be visible after they both complete; if the provider does not synchronize the writing of the zone file and one request overwrites the other, then the client implementation must take care to synchronize on behalf of the incompetent provider. This synchronization need not be global; for example: the scope of synchronization might only need to be within the same zone, allowing multiple requests at once as long as all of them are for different zones. (Exact logic depends on the provider.)




This section is empty.


This section is empty.


func AbsoluteName added in v0.2.0

func AbsoluteName(name, zone string) string

AbsoluteName makes name into a fully-qualified domain name (FQDN) by prepending it to zone and tidying up the dots. For example, an input of name "sub" and zone "" will return "".

fmt.Println(AbsoluteName("sub", ""))

func RelativeName added in v0.2.0

func RelativeName(fqdn, zone string) string

RelativeName makes fqdn relative to zone. For example, for a FQDN of "" and a zone of "", it outputs "sub".

If fqdn cannot be expressed relative to zone, the input fqdn is returned.

fmt.Println(RelativeName("", ""))



type Record

type Record struct {
	// provider-specific metadata
	ID string

	// general record fields
	Type  string
	Name  string // partially-qualified (relative to zone)
	Value string
	TTL   time.Duration

	// type-dependent record fields
	Priority uint // HTTPS, MX, SRV, and URI records
	Weight   uint // SRV and URI records

Record is a generalized representation of a DNS record.

The values of this struct should be free of zone-file-specific syntax, except if this struct's fields do not sufficiently represent all the fields of a certain record type; in that case, the remaining data for which there are not specific fields should be stored in the Value as it appears in the zone file.

func (Record) ToSRV added in v0.2.2

func (r Record) ToSRV() (SRV, error)

ToSRV parses the record into a SRV struct with fully-parsed, literal values.

EXPERIMENTAL; subject to change or removal.

type RecordAppender

type RecordAppender interface {
	// AppendRecords creates the requested records in the given zone
	// and returns the populated records that were created. It never
	// changes existing records.
	// Implementations must honor context cancellation and be safe for
	// concurrent use.
	AppendRecords(ctx context.Context, zone string, recs []Record) ([]Record, error)

RecordAppender can non-destructively add new records to a DNS zone.

type RecordDeleter

type RecordDeleter interface {
	// DeleteRecords deletes the given records from the zone if they exist.
	// It returns the records that were deleted.
	// Records that have an ID to associate it with a particular resource on
	// the provider will be directly deleted. If no ID is given, this method
	// may use what information is given to do lookups and delete only
	// matching records.
	// Implementations must honor context cancellation and be safe for
	// concurrent use.
	DeleteRecords(ctx context.Context, zone string, recs []Record) ([]Record, error)

RecordDeleter can delete records from a DNS zone.

type RecordGetter

type RecordGetter interface {
	// GetRecords returns all the records in the DNS zone.
	// Implementations must honor context cancellation and be safe for
	// concurrent use.
	GetRecords(ctx context.Context, zone string) ([]Record, error)

RecordGetter can get records from a DNS zone.

type RecordSetter

type RecordSetter interface {
	// SetRecords updates the zone so that the records described in the
	// input are reflected in the output. It may create or overwrite
	// records or -- depending on the record type -- delete records to
	// maintain parity with the input. No other records are affected.
	// It returns the records which were set.
	// Records that have an ID associating it with a particular resource
	// on the provider will be directly replaced. If no ID is given, this
	// method may use what information is given to do lookups and will
	// ensure that only necessary changes are made to the zone.
	// Implementations must honor context cancellation and be safe for
	// concurrent use.
	SetRecords(ctx context.Context, zone string, recs []Record) ([]Record, error)

RecordSetter can set new or update existing records in a DNS zone.

type SRV added in v0.2.2

type SRV struct {
	Service  string // no leading "_"
	Proto    string // no leading "_"
	Name     string
	Priority uint
	Weight   uint
	Port     uint
	Target   string

SRV contains all the parsed data of an SRV record.

EXPERIMENTAL; subject to change or removal.

func (SRV) ToRecord added in v0.2.2

func (s SRV) ToRecord() Record

ToRecord converts the parsed SRV data to a Record struct.

EXPERIMENTAL; subject to change or removal.

type Zone added in v0.2.2

type Zone struct {
	Name string

Zone is a generalized representation of a DNS zone.

type ZoneLister added in v0.2.2

type ZoneLister interface {
	// ListZones returns the list of available DNS zones for use by
	// other libdns methods.
	// Implementations must honor context cancellation and be safe for
	// concurrent use.
	ListZones(ctx context.Context) ([]Zone, error)

ZoneLister can list available DNS zones.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL