dnscontrol/providers/linode/api.go
2018-02-05 16:17:20 -05:00

246 lines
5.4 KiB
Go

package linode
import (
"bytes"
"encoding/json"
"fmt"
"net/http"
"net/url"
"github.com/pkg/errors"
)
const (
mediaType = "application/json"
defaultBaseURL = "https://api.linode.com/v4/"
domainsPath = "domains"
)
func (c *LinodeApi) fetchDomainList() error {
c.domainIndex = map[string]int{}
page := 1
for {
dr := &domainResponse{}
endpoint := fmt.Sprintf("%s?page=%d", domainsPath, page)
if err := c.get(endpoint, dr); err != nil {
return errors.Errorf("Error fetching domain list from Linode: %s", err)
}
for _, domain := range dr.Data {
c.domainIndex[domain.Domain] = domain.ID
}
if len(dr.Data) == 0 || dr.Page >= dr.Pages {
break
}
page++
}
return nil
}
func (c *LinodeApi) getRecords(id int) ([]domainRecord, error) {
records := []domainRecord{}
page := 1
for {
dr := &recordResponse{}
endpoint := fmt.Sprintf("%s/%d/records?page=%d", domainsPath, id, page)
if err := c.get(endpoint, dr); err != nil {
return nil, errors.Errorf("Error fetching record list from Linode: %s", err)
}
records = append(records, dr.Data...)
if len(dr.Data) == 0 || dr.Page >= dr.Pages {
break
}
page++
}
return records, nil
}
func (c *LinodeApi) createRecord(domainID int, rec *recordEditRequest) (*domainRecord, error) {
endpoint := fmt.Sprintf("%s/%d/records", domainsPath, domainID)
req, err := c.newRequest(http.MethodPost, endpoint, rec)
if err != nil {
return nil, err
}
resp, err := c.client.Do(req)
if err != nil {
return nil, err
}
if resp.StatusCode != http.StatusOK {
return nil, c.handleErrors(resp)
}
record := &domainRecord{}
defer resp.Body.Close()
decoder := json.NewDecoder(resp.Body)
if err := decoder.Decode(record); err != nil {
return nil, err
}
return record, nil
}
func (c *LinodeApi) modifyRecord(domainID, recordID int, rec *recordEditRequest) error {
endpoint := fmt.Sprintf("%s/%d/records/%d", domainsPath, domainID, recordID)
req, err := c.newRequest(http.MethodPut, endpoint, rec)
if err != nil {
return err
}
resp, err := c.client.Do(req)
if err != nil {
return err
}
if resp.StatusCode != http.StatusOK {
return c.handleErrors(resp)
}
return nil
}
func (c *LinodeApi) deleteRecord(domainID, recordID int) error {
endpoint := fmt.Sprintf("%s/%d/records/%d", domainsPath, domainID, recordID)
req, err := c.newRequest(http.MethodDelete, endpoint, nil)
if err != nil {
return err
}
resp, err := c.client.Do(req)
if err != nil {
return err
}
if resp.StatusCode != http.StatusOK {
return c.handleErrors(resp)
}
return nil
}
func (c *LinodeApi) newRequest(method, endpoint string, body interface{}) (*http.Request, error) {
rel, err := url.Parse(endpoint)
if err != nil {
return nil, err
}
u := c.baseURL.ResolveReference(rel)
buf := new(bytes.Buffer)
if body != nil {
err = json.NewEncoder(buf).Encode(body)
if err != nil {
return nil, err
}
}
req, err := http.NewRequest(method, u.String(), buf)
if err != nil {
return nil, err
}
req.Header.Add("Content-Type", mediaType)
req.Header.Add("Accept", mediaType)
return req, nil
}
func (c *LinodeApi) get(endpoint string, target interface{}) error {
req, err := c.newRequest(http.MethodGet, endpoint, nil)
if err != nil {
return err
}
resp, err := c.client.Do(req)
if err != nil {
return err
}
if resp.StatusCode != http.StatusOK {
return c.handleErrors(resp)
}
defer resp.Body.Close()
decoder := json.NewDecoder(resp.Body)
return decoder.Decode(target)
}
func (c *LinodeApi) handleErrors(resp *http.Response) error {
defer resp.Body.Close()
decoder := json.NewDecoder(resp.Body)
errs := &errorResponse{}
if err := decoder.Decode(errs); err != nil {
return errors.Errorf("bad status code from Linode: %d not 200. Failed to decode response", resp.StatusCode)
}
buf := bytes.NewBufferString(fmt.Sprintf("bad status code from Linode: %d not 200", resp.StatusCode))
for _, err := range errs.Errors {
buf.WriteString("\n- ")
if err.Field != "" {
buf.WriteString(err.Field)
buf.WriteString(": ")
}
buf.WriteString(err.Reason)
}
return errors.New(buf.String())
}
type basicResponse struct {
Results int `json:"results"`
Pages int `json:"pages"`
Page int `json:"page"`
}
type domainResponse struct {
basicResponse
Data []struct {
ID int `json:"id"`
Domain string `json:"domain"`
} `json:"data"`
}
type recordResponse struct {
basicResponse
Data []domainRecord `json:"data"`
}
type domainRecord struct {
ID int `json:"id"`
Type string `json:"type"`
Name string `json:"name"`
Target string `json:"target"`
Priority uint16 `json:"priority"`
Weight uint16 `json:"weight"`
Port uint16 `json:"port"`
Service string `json:"service"`
Protocol string `json:"protocol"`
TTLSec uint32 `json:"ttl_sec"`
}
type recordEditRequest struct {
Type string `json:"type,omitempty"`
Name string `json:"name,omitempty"`
Target string `json:"target,omitempty"`
Priority int `json:"priority,omitempty"`
Weight int `json:"weight,omitempty"`
Port int `json:"port,omitempty"`
Service string `json:"service,omitempty"`
Protocol string `json:"protocol,omitempty"`
// Documented as field `ttl` in the documentation, but in reality `ttl_sec` should be used
TTL int `json:"ttl_sec,omitempty"`
}
type errorResponse struct {
Errors []struct {
Field string `json:"field"`
Reason string `json:"reason"`
} `json:"errors"`
}