package dns import ( "strconv" "strings" "github.com/dnsimple/dnsimple-go/dnsimple" "github.com/pkg/errors" ) // Client holds metadata for the dns api client type Client struct { client *dnsimple.Client accountID string } // Record is the type used for a Zone record type Record dnsimple.ZoneRecord // New returns an implementation of a DNS interface func New(oauthToken string) *Client { credentials := dnsimple.NewOauthTokenCredentials(oauthToken) client := dnsimple.NewClient(credentials) return &Client{ client: client, } } // GetRecords returns all record for a host func (d *Client) GetRecords(host, kind string) ([]Record, error) { accountID, err := d.getAccountID() if err != nil { return nil, errors.Wrap(err, "unable to get account id") } subdomain, domain, err := splitHost(host) if err != nil { return nil, errors.Wrap(err, "unable to parse host") } records, err := d.client.Zones.ListRecords(accountID, domain, &dnsimple.ZoneRecordListOptions{}) if err != nil { return nil, errors.Wrap(err, "could not get list of records") } ret := []Record{} for _, record := range records.Data { if record.ZoneID == domain && record.Name == subdomain && record.Type == kind { ret = append(ret, Record(record)) } } return ret, nil } // CreateRecord adds a record to DNS func (d *Client) CreateRecord(host string, record Record) error { accountID, err := d.getAccountID() if err != nil { return errors.Wrap(err, "unable to get account id") } subdomain, domain, err := splitHost(host) if err != nil { return errors.Wrap(err, "unable to parse host") } newRecord := dnsimple.ZoneRecord(record) newRecord.Name = subdomain if _, err := d.client.Zones.CreateRecord(accountID, domain, newRecord); err != nil { return errors.Wrap(err, "could not create record") } return nil } // UpdateRecord returns all record for a host func (d *Client) UpdateRecord(record Record) error { accountID, err := d.getAccountID() if err != nil { return errors.Wrap(err, "unable to get account id") } if _, err := d.client.Zones.UpdateRecord(accountID, record.ZoneID, record.ID, dnsimple.ZoneRecord(record)); err != nil { return errors.Wrap(err, "could not update record") } return nil } // DeleteRecord returns all record for a host func (d *Client) DeleteRecord(record Record) error { accountID, err := d.getAccountID() if err != nil { return errors.Wrap(err, "unable to get account id") } if _, err := d.client.Zones.DeleteRecord(accountID, record.ZoneID, record.ID); err != nil { return errors.Wrap(err, "could not delete record") } return nil } func (d *Client) getAccountID() (string, error) { if d.accountID != "" { return d.accountID, nil } whoamiResponse, err := d.client.Identity.Whoami() if err != nil { return "", errors.Wrap(err, "could not get account information from dnsimple") } if whoamiResponse.Data.Account == nil { return "", errors.New("could not get account information") } d.accountID = strconv.Itoa(whoamiResponse.Data.Account.ID) return d.accountID, nil } // splitHost returns hostname into domain and subdomain strings. // For example: // "some.subdomain.example.com" → ("some.subdomain", "example.com", nil) // "example.com" → ("", "example.com", nil) func splitHost(host string) (subdomain string, domain string, err error) { subdomains := strings.Split(host, ".") if len(subdomains) >= 3 { domain = strings.Join(subdomains[len(subdomains)-2:], ".") subdomain = strings.Join(subdomains[:len(subdomains)-2], ".") } else if len(subdomains) == 2 { domain = strings.Join(subdomains, ".") } else { err = errors.New("invalid domain") } return subdomain, domain, err }