client.go raw

   1  package internal
   2  
   3  import (
   4  	"bytes"
   5  	"context"
   6  	"encoding/json"
   7  	"fmt"
   8  	"io"
   9  	"net/http"
  10  	"net/url"
  11  	"time"
  12  
  13  	"github.com/go-acme/lego/v4/providers/dns/internal/errutils"
  14  	"golang.org/x/oauth2"
  15  )
  16  
  17  const defaultBaseURL = "https://api.netlify.com/api/v1"
  18  
  19  // Client Netlify API client.
  20  type Client struct {
  21  	baseURL    *url.URL
  22  	httpClient *http.Client
  23  }
  24  
  25  // NewClient creates a new Client.
  26  func NewClient(hc *http.Client) *Client {
  27  	baseURL, _ := url.Parse(defaultBaseURL)
  28  
  29  	if hc == nil {
  30  		hc = &http.Client{Timeout: 5 * time.Second}
  31  	}
  32  
  33  	return &Client{baseURL: baseURL, httpClient: hc}
  34  }
  35  
  36  // GetRecords gets a DNS records.
  37  func (c *Client) GetRecords(ctx context.Context, zoneID string) ([]DNSRecord, error) {
  38  	endpoint := c.baseURL.JoinPath("dns_zones", zoneID, "dns_records")
  39  
  40  	req, err := newJSONRequest(ctx, http.MethodGet, endpoint, nil)
  41  	if err != nil {
  42  		return nil, fmt.Errorf("failed to create request: %w", err)
  43  	}
  44  
  45  	resp, err := c.httpClient.Do(req)
  46  	if err != nil {
  47  		return nil, errutils.NewHTTPDoError(req, err)
  48  	}
  49  
  50  	defer func() { _ = resp.Body.Close() }()
  51  
  52  	if resp.StatusCode != http.StatusOK {
  53  		return nil, errutils.NewUnexpectedResponseStatusCodeError(req, resp)
  54  	}
  55  
  56  	raw, err := io.ReadAll(resp.Body)
  57  	if err != nil {
  58  		return nil, errutils.NewReadResponseError(req, resp.StatusCode, err)
  59  	}
  60  
  61  	var records []DNSRecord
  62  
  63  	err = json.Unmarshal(raw, &records)
  64  	if err != nil {
  65  		return nil, errutils.NewUnmarshalError(req, resp.StatusCode, raw, err)
  66  	}
  67  
  68  	return records, nil
  69  }
  70  
  71  // CreateRecord creates a DNS records.
  72  func (c *Client) CreateRecord(ctx context.Context, zoneID string, record DNSRecord) (*DNSRecord, error) {
  73  	endpoint := c.baseURL.JoinPath("dns_zones", zoneID, "dns_records")
  74  
  75  	req, err := newJSONRequest(ctx, http.MethodPost, endpoint, record)
  76  	if err != nil {
  77  		return nil, fmt.Errorf("failed to create request: %w", err)
  78  	}
  79  
  80  	resp, err := c.httpClient.Do(req)
  81  	if err != nil {
  82  		return nil, errutils.NewHTTPDoError(req, err)
  83  	}
  84  
  85  	defer func() { _ = resp.Body.Close() }()
  86  
  87  	if resp.StatusCode != http.StatusCreated {
  88  		return nil, errutils.NewUnexpectedResponseStatusCodeError(req, resp)
  89  	}
  90  
  91  	raw, err := io.ReadAll(resp.Body)
  92  	if err != nil {
  93  		return nil, errutils.NewReadResponseError(req, resp.StatusCode, err)
  94  	}
  95  
  96  	var recordResp DNSRecord
  97  
  98  	err = json.Unmarshal(raw, &recordResp)
  99  	if err != nil {
 100  		return nil, errutils.NewUnmarshalError(req, resp.StatusCode, raw, err)
 101  	}
 102  
 103  	return &recordResp, nil
 104  }
 105  
 106  // RemoveRecord removes a DNS records.
 107  func (c *Client) RemoveRecord(ctx context.Context, zoneID, recordID string) error {
 108  	endpoint := c.baseURL.JoinPath("dns_zones", zoneID, "dns_records", recordID)
 109  
 110  	req, err := newJSONRequest(ctx, http.MethodDelete, endpoint, nil)
 111  	if err != nil {
 112  		return err
 113  	}
 114  
 115  	resp, err := c.httpClient.Do(req)
 116  	if err != nil {
 117  		return errutils.NewHTTPDoError(req, err)
 118  	}
 119  
 120  	defer func() { _ = resp.Body.Close() }()
 121  
 122  	if resp.StatusCode != http.StatusNoContent {
 123  		return errutils.NewUnexpectedResponseStatusCodeError(req, resp)
 124  	}
 125  
 126  	return nil
 127  }
 128  
 129  func newJSONRequest(ctx context.Context, method string, endpoint *url.URL, payload any) (*http.Request, error) {
 130  	buf := new(bytes.Buffer)
 131  
 132  	if payload != nil {
 133  		err := json.NewEncoder(buf).Encode(payload)
 134  		if err != nil {
 135  			return nil, fmt.Errorf("failed to create request JSON body: %w", err)
 136  		}
 137  	}
 138  
 139  	req, err := http.NewRequestWithContext(ctx, method, endpoint.String(), buf)
 140  	if err != nil {
 141  		return nil, fmt.Errorf("unable to create request: %w", err)
 142  	}
 143  
 144  	req.Header.Set("Accept", "application/json")
 145  
 146  	if payload != nil {
 147  		req.Header.Set("Content-Type", "application/json; charset=utf-8")
 148  	}
 149  
 150  	return req, nil
 151  }
 152  
 153  func OAuthStaticAccessToken(client *http.Client, accessToken string) *http.Client {
 154  	if client == nil {
 155  		client = &http.Client{Timeout: 5 * time.Second}
 156  	}
 157  
 158  	client.Transport = &oauth2.Transport{
 159  		Source: oauth2.StaticTokenSource(&oauth2.Token{AccessToken: accessToken}),
 160  		Base:   client.Transport,
 161  	}
 162  
 163  	return client
 164  }
 165