mirror of
https://github.com/StackExchange/dnscontrol.git
synced 2024-11-14 12:15:06 +08:00
246 lines
5.4 KiB
Go
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"`
|
|
}
|