provider

package module
v1.0.2 Latest Latest
Warning

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

Go to latest
Published: Nov 8, 2025 License: MIT Imports: 13 Imported by: 6

README

Abstract Provider for libdns

This package helps reduce duplicated and fragmented code across different DNS providers by implementing the core logic for the main libdns interfaces:

  • RecordGetter
  • RecordAppender
  • RecordSetter
  • RecordDeleter
  • ZoneLister

As defined in the libdns contracts.

It works on the principle that this provider helper fetches all records for a zone, generates a change list, and passes that list to the client to apply.

By doing so, the only thing you need to implement is a client. This approach allows faster development of new providers and ensures more consistent behavior, since all contract logic is handled and maintained in one central place.


Client

A client implementation should follow this interface signature:

GetDNSList(ctx context.Context, domain string) ([]libdns.Record, error)
SetDNSList(ctx context.Context, domain string, change ChangeList) ([]libdns.Record, error)

A simple implementation could look like this:

func (c *client) create(ctx context.Context, domain string, record *libdns.RR) error {
	// ...
	return nil
}

func (c *client) remove(ctx context.Context, domain string, record *libdns.RR) error {
	// ...
	return nil
}

func (c *client) SetDNSList(ctx context.Context, domain string, change ChangeList) ([]libdns.Record, error) {

	for record := range change.Iterate(provider.Delete) {
		if err := c.remove(ctx, domain, record); err != nil {
			return nil, err
		}
	}

	for record := range change.Iterate(provider.Create) {
		if err := c.create(ctx, domain, record); err != nil {
			return nil, err
		}
	}

	return nil, nil
}

Provider

Because Go doesn’t support class-level abstraction, this package provides helper functions that your provider can call directly:

type Provider struct {
	client Client
	mutex  sync.RWMutex
}

func (p *Provider) getClient() Client {
	// initialize client...
	return p.client
}

func (p *Provider) GetRecords(ctx context.Context, zone string) ([]libdns.Record, error) {
	return GetRecords(ctx, &p.mutex, p.getClient(), zone)
}

func (p *Provider) AppendRecords(ctx context.Context, zone string, recs []libdns.Record) ([]libdns.Record, error) {
	return AppendRecords(ctx, &p.mutex, p.getClient(), zone, recs)
}

func (p *Provider) SetRecords(ctx context.Context, zone string, recs []libdns.Record) ([]libdns.Record, error) {
	return SetRecords(ctx, &p.mutex, p.getClient(), zone, recs)
}

func (p *Provider) DeleteRecords(ctx context.Context, zone string, recs []libdns.Record) ([]libdns.Record, error) {
	return DeleteRecords(ctx, &p.mutex, p.getClient(), zone, recs)
}

var (
	_ libdns.RecordGetter   = (*Provider)(nil)
	_ libdns.RecordAppender = (*Provider)(nil)
	_ libdns.RecordSetter   = (*Provider)(nil)
	_ libdns.RecordDeleter  = (*Provider)(nil)
)

Implemented Interfaces

Interface Implementation Function
libdns.RecordGetter GetRecords
libdns.RecordAppender AppendRecords
libdns.RecordSetter SetRecords
libdns.RecordDeleter DeleteRecords
libdns.ZoneLister ListZones

Documentation

Index

Constants

View Source
const (
	OutputNone        OutputLevel = 0x00
	OutputVerbose                 = 0x01
	OutputVeryVerbose             = 0x02
	OutputDebug                   = 0x03
)

Variables

This section is empty.

Functions

func AppendRecords

func AppendRecords(ctx context.Context, mutex sync.Locker, client Client, zone string, records []libdns.Record) ([]libdns.Record, error)

AppendRecords appends new records to the change list performing validation.

The assumption is that when the full list is returned to the provider, the provider will handle any necessary validation and return an error if any issues are found.

func DeleteRecords

func DeleteRecords(ctx context.Context, mutex sync.Locker, client Client, zone string, deletes []libdns.Record) ([]libdns.Record, error)

DeleteRecords marks the input records for deletion when they exactly match or partially match existing records, following the rules defined in the libdns contract.

For more details, see: https://github.com/libdns/libdns/blob/master/libdns.go#L228C1-L237C43

func GetRecords

func GetRecords(ctx context.Context, mutex sync.Locker, client Client, zone string) ([]libdns.Record, error)

GetRecords retrieves all records for the given zone from the client and ensures that the returned records are properly typed according to their specific RR type.

func IsInList

func IsInList(item *libdns.RR, records *[]libdns.Record, ttl bool) bool

func ListZones

func ListZones(ctx context.Context, mutex sync.Locker, client ZoneAwareClient) ([]libdns.Zone, error)

ListZones returns all available zones. Most APIs support listing of all managed domains, which can be used as zones.

This function ensures that the returned domain names include a trailing dot to indicate the root zone.

func RecordIterator

func RecordIterator(records *[]libdns.Record) iter.Seq2[*libdns.Record, libdns.RR]

func SetRecords

func SetRecords(ctx context.Context, mutex sync.Locker, client Client, zone string, records []libdns.Record) ([]libdns.Record, error)

SetRecords updates existing records by marking them as either NoChange or Delete based on the given input, and appends the input records with state Create. This ensures compliance with the libdns contract and produces the expected results.

Example provided by the contract can be found here: https://github.com/libdns/libdns/blob/master/libdns.go#L182-L216

Types

type ChangeList

type ChangeList interface {
	// Iterate wil return an iterator that returns records that
	// match the given state. For example, when called like
	// `Iterate(Delete)` will only return records marked for
	// removal. The ChangeState can be combined to iterate
	// multiple states like `Iterate(Delete|Create)` which
	// will return all records that are marked delete or
	// as created.
	Iterate(state ChangeState) iter.Seq[*libdns.RR]
	// Creates will return a slice of records that are
	// marked for creating
	Creates() []*libdns.RR
	// Deletes will return a slice of records that are
	// marked for deleting
	Deletes() []*libdns.RR
	// GetList will return a slice of records that
	// represents the new dns list which can be used
	// to update the whole set for a zone
	GetList() []*libdns.RR
	// Has wil check if this list has records for
	// given state
	Has(state ChangeState) bool
	// contains filtered or unexported methods
}

func NewChangeList

func NewChangeList(size ...int) ChangeList

type ChangeRecord

type ChangeRecord struct {
	// contains filtered or unexported fields
}

type ChangeState

type ChangeState uint8
const (
	NoChange ChangeState = 1 << iota
	Delete
	Create
)

type Client

type Client interface {
	// GetDNSList returns all DNS records available for the given zone.
	//
	// The returned records can be of the opaque RR type. If the provider supports
	// parsing, the records will be automatically parsed before being returned.
	GetDNSList(ctx context.Context, domain string) ([]libdns.Record, error)

	// SetDNSList processes a ChangeList and updates DNS records based on their state.
	//
	// This allows the client to focus only on handling the changes, while the provider
	// logic for appending, setting, and deleting records is centralized.
	//
	// Example: iterating through individual changes
	//
	//    // Remove records marked for deletion
	//    for remove := range change.Iterate(Delete) {
	//        // remove record
	//    }
	//
	//    // Create records marked for creation
	//    for create := range change.Iterate(Create) {
	//        // create record
	//    }
	//
	// Example: updating the whole zone at once
	//
	//    // Generate a filtered list of all changes
	//    updatedRecords := change.GetList()
	//
	//    // Use this list to update the entire zone file in a single call
	//    client.UpdateZone(ctx, domain, updatedRecords)
	//
	// Notes:
	//  - If the client API supports full-zone updates and returns the new record set,
	//    this can be returned. The provider uses this to validate records and skip
	//    extra API calls.
	//  - For clients that do not support full-zone updates or handle records individually,
	//    returning nil is fine.
	SetDNSList(ctx context.Context, domain string, change ChangeList) ([]libdns.Record, error)
}

type DebugAware

type DebugAware interface {
	SetDebug(level OutputLevel, writer io.Writer)
}

DebugAware is an interface implemented by types that support configurable debug logging of client communication.

Implementations typically allow controlling the debug output level and destination writer used for HTTP or API requests.

Example:

type Provider struct {
	DebugLevel  OutputLevel
	DebugOutput io.Writer
	client      *http.Client
}

func (p *Provider) DebugOutputLevel() OutputLevel {
	return p.DebugLevel
}

func (p *Provider) DebugOutput() io.Writer {
	return p.DebugOutput
}

func (p *Provider) SetDebug(level OutputLevel, writer io.Writer) {
	p.DebugLevel = level
	p.DebugOutput = writer
}

func (p *Provider) getClient() *http.Client {
	if p.client == nil {
		p.client = &http.Client{
			Transport: &DebugTransport{
				RoundTripper: http.DefaultTransport,
				config:       p,
			},
		}
	}
	return p.client
}

type DebugConfig

type DebugConfig interface {
	DebugOutputLevel() OutputLevel
	DebugOutput() io.Writer
}

type DebugTransport

type DebugTransport struct {
	http.RoundTripper
	Config DebugConfig
}

DebugTransport is an HTTP transport wrapper that logs outgoing requests and incoming responses for debugging purposes.

It implements the http.RoundTripper interface and can be used to wrap an existing transport (such as http.DefaultTransport) to add debug output.

Example:

 client := &http.Client{
	 Transport: &DebugTransport{
		 RoundTripper: http.DefaultTransport,
		 config: ...
	 },
 }

func (*DebugTransport) RoundTrip

func (t *DebugTransport) RoundTrip(req *http.Request) (*http.Response, error)

type Domain

type Domain interface {
	Name() string
}

type OutputLevel

type OutputLevel uint8

type ZoneAwareClient

type ZoneAwareClient interface {
	Client
	Domains(ctx context.Context) ([]Domain, error)
}

Directories

Path Synopsis

Jump to

Keyboard shortcuts

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