github.com/teknogeek/dnscontrol@v0.2.8/providers/linode/api.go (about)

     1  package linode
     2  
     3  import (
     4  	"bytes"
     5  	"encoding/json"
     6  	"fmt"
     7  	"net/http"
     8  	"net/url"
     9  
    10  	"github.com/pkg/errors"
    11  )
    12  
    13  const (
    14  	mediaType      = "application/json"
    15  	defaultBaseURL = "https://api.linode.com/v4/"
    16  	domainsPath    = "domains"
    17  )
    18  
    19  func (c *LinodeApi) fetchDomainList() error {
    20  	c.domainIndex = map[string]int{}
    21  	page := 1
    22  	for {
    23  		dr := &domainResponse{}
    24  		endpoint := fmt.Sprintf("%s?page=%d", domainsPath, page)
    25  		if err := c.get(endpoint, dr); err != nil {
    26  			return errors.Errorf("Error fetching domain list from Linode: %s", err)
    27  		}
    28  		for _, domain := range dr.Data {
    29  			c.domainIndex[domain.Domain] = domain.ID
    30  		}
    31  		if len(dr.Data) == 0 || dr.Page >= dr.Pages {
    32  			break
    33  		}
    34  		page++
    35  	}
    36  	return nil
    37  }
    38  
    39  func (c *LinodeApi) getRecords(id int) ([]domainRecord, error) {
    40  	records := []domainRecord{}
    41  	page := 1
    42  	for {
    43  		dr := &recordResponse{}
    44  		endpoint := fmt.Sprintf("%s/%d/records?page=%d", domainsPath, id, page)
    45  		if err := c.get(endpoint, dr); err != nil {
    46  			return nil, errors.Errorf("Error fetching record list from Linode: %s", err)
    47  		}
    48  
    49  		records = append(records, dr.Data...)
    50  
    51  		if len(dr.Data) == 0 || dr.Page >= dr.Pages {
    52  			break
    53  		}
    54  		page++
    55  	}
    56  
    57  	return records, nil
    58  }
    59  
    60  func (c *LinodeApi) createRecord(domainID int, rec *recordEditRequest) (*domainRecord, error) {
    61  	endpoint := fmt.Sprintf("%s/%d/records", domainsPath, domainID)
    62  
    63  	req, err := c.newRequest(http.MethodPost, endpoint, rec)
    64  	if err != nil {
    65  		return nil, err
    66  	}
    67  
    68  	resp, err := c.client.Do(req)
    69  	if err != nil {
    70  		return nil, err
    71  	}
    72  
    73  	if resp.StatusCode != http.StatusOK {
    74  		return nil, c.handleErrors(resp)
    75  	}
    76  
    77  	record := &domainRecord{}
    78  
    79  	defer resp.Body.Close()
    80  	decoder := json.NewDecoder(resp.Body)
    81  	if err := decoder.Decode(record); err != nil {
    82  		return nil, err
    83  	}
    84  
    85  	return record, nil
    86  }
    87  
    88  func (c *LinodeApi) modifyRecord(domainID, recordID int, rec *recordEditRequest) error {
    89  	endpoint := fmt.Sprintf("%s/%d/records/%d", domainsPath, domainID, recordID)
    90  
    91  	req, err := c.newRequest(http.MethodPut, endpoint, rec)
    92  	if err != nil {
    93  		return err
    94  	}
    95  
    96  	resp, err := c.client.Do(req)
    97  	if err != nil {
    98  		return err
    99  	}
   100  
   101  	if resp.StatusCode != http.StatusOK {
   102  		return c.handleErrors(resp)
   103  	}
   104  
   105  	return nil
   106  }
   107  
   108  func (c *LinodeApi) deleteRecord(domainID, recordID int) error {
   109  	endpoint := fmt.Sprintf("%s/%d/records/%d", domainsPath, domainID, recordID)
   110  	req, err := c.newRequest(http.MethodDelete, endpoint, nil)
   111  	if err != nil {
   112  		return err
   113  	}
   114  
   115  	resp, err := c.client.Do(req)
   116  	if err != nil {
   117  		return err
   118  	}
   119  
   120  	if resp.StatusCode != http.StatusOK {
   121  		return c.handleErrors(resp)
   122  	}
   123  
   124  	return nil
   125  }
   126  
   127  func (c *LinodeApi) newRequest(method, endpoint string, body interface{}) (*http.Request, error) {
   128  	rel, err := url.Parse(endpoint)
   129  	if err != nil {
   130  		return nil, err
   131  	}
   132  
   133  	u := c.baseURL.ResolveReference(rel)
   134  
   135  	buf := new(bytes.Buffer)
   136  	if body != nil {
   137  		err = json.NewEncoder(buf).Encode(body)
   138  		if err != nil {
   139  			return nil, err
   140  		}
   141  	}
   142  
   143  	req, err := http.NewRequest(method, u.String(), buf)
   144  	if err != nil {
   145  		return nil, err
   146  	}
   147  
   148  	req.Header.Add("Content-Type", mediaType)
   149  	req.Header.Add("Accept", mediaType)
   150  	return req, nil
   151  }
   152  
   153  func (c *LinodeApi) get(endpoint string, target interface{}) error {
   154  	req, err := c.newRequest(http.MethodGet, endpoint, nil)
   155  	if err != nil {
   156  		return err
   157  	}
   158  	resp, err := c.client.Do(req)
   159  	if err != nil {
   160  		return err
   161  	}
   162  	if resp.StatusCode != http.StatusOK {
   163  		return c.handleErrors(resp)
   164  	}
   165  	defer resp.Body.Close()
   166  	decoder := json.NewDecoder(resp.Body)
   167  	return decoder.Decode(target)
   168  }
   169  
   170  func (c *LinodeApi) handleErrors(resp *http.Response) error {
   171  	defer resp.Body.Close()
   172  	decoder := json.NewDecoder(resp.Body)
   173  
   174  	errs := &errorResponse{}
   175  
   176  	if err := decoder.Decode(errs); err != nil {
   177  		return errors.Errorf("bad status code from Linode: %d not 200. Failed to decode response", resp.StatusCode)
   178  	}
   179  
   180  	buf := bytes.NewBufferString(fmt.Sprintf("bad status code from Linode: %d not 200", resp.StatusCode))
   181  
   182  	for _, err := range errs.Errors {
   183  		buf.WriteString("\n- ")
   184  
   185  		if err.Field != "" {
   186  			buf.WriteString(err.Field)
   187  			buf.WriteString(": ")
   188  		}
   189  
   190  		buf.WriteString(err.Reason)
   191  	}
   192  
   193  	return errors.New(buf.String())
   194  }
   195  
   196  type basicResponse struct {
   197  	Results int `json:"results"`
   198  	Pages   int `json:"pages"`
   199  	Page    int `json:"page"`
   200  }
   201  
   202  type domainResponse struct {
   203  	basicResponse
   204  	Data []struct {
   205  		ID     int    `json:"id"`
   206  		Domain string `json:"domain"`
   207  	} `json:"data"`
   208  }
   209  
   210  type recordResponse struct {
   211  	basicResponse
   212  	Data []domainRecord `json:"data"`
   213  }
   214  
   215  type domainRecord struct {
   216  	ID       int    `json:"id"`
   217  	Type     string `json:"type"`
   218  	Name     string `json:"name"`
   219  	Target   string `json:"target"`
   220  	Priority uint16 `json:"priority"`
   221  	Weight   uint16 `json:"weight"`
   222  	Port     uint16 `json:"port"`
   223  	Service  string `json:"service"`
   224  	Protocol string `json:"protocol"`
   225  	TTLSec   uint32 `json:"ttl_sec"`
   226  }
   227  
   228  type recordEditRequest struct {
   229  	Type     string `json:"type,omitempty"`
   230  	Name     string `json:"name,omitempty"`
   231  	Target   string `json:"target,omitempty"`
   232  	Priority int    `json:"priority,omitempty"`
   233  	Weight   int    `json:"weight,omitempty"`
   234  	Port     int    `json:"port,omitempty"`
   235  	Service  string `json:"service,omitempty"`
   236  	Protocol string `json:"protocol,omitempty"`
   237  	// Documented as field `ttl` in the documentation, but in reality `ttl_sec` should be used
   238  	TTL int `json:"ttl_sec,omitempty"`
   239  }
   240  
   241  type errorResponse struct {
   242  	Errors []struct {
   243  		Field  string `json:"field"`
   244  		Reason string `json:"reason"`
   245  	} `json:"errors"`
   246  }