github.com/teknogeek/dnscontrol/v2@v2.10.1-0.20200227202244-ae299b55ba42/providers/linode/api.go (about)

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