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  )
  15  
  16  const defaultBaseURL = "https://mijn.host/api/v2/"
  17  
  18  const authorizationHeader = "API-Key"
  19  
  20  // Client a mijn.host DNS API client.
  21  type Client struct {
  22  	apiKey string
  23  
  24  	baseURL    *url.URL
  25  	HTTPClient *http.Client
  26  }
  27  
  28  // NewClient creates a new Client.
  29  func NewClient(apiKey string) *Client {
  30  	baseURL, _ := url.Parse(defaultBaseURL)
  31  
  32  	return &Client{
  33  		apiKey:     apiKey,
  34  		baseURL:    baseURL,
  35  		HTTPClient: &http.Client{Timeout: 10 * time.Second},
  36  	}
  37  }
  38  
  39  // ListDomains Retrieve all domains from an account.
  40  // https://mijn.host/api/doc/api-3563872
  41  func (c *Client) ListDomains(ctx context.Context) ([]Domain, error) {
  42  	endpoint := c.baseURL.JoinPath("domains")
  43  
  44  	req, err := newJSONRequest(ctx, http.MethodGet, endpoint, nil)
  45  	if err != nil {
  46  		return nil, fmt.Errorf("create request: %w", err)
  47  	}
  48  
  49  	var results Response[DomainData]
  50  
  51  	err = c.do(req, &results)
  52  	if err != nil {
  53  		return nil, err
  54  	}
  55  
  56  	return results.Data.Domains, nil
  57  }
  58  
  59  // GetRecords Retrieve DNS records of specific domain.
  60  // https://mijn.host/api/doc/api-3563906
  61  func (c *Client) GetRecords(ctx context.Context, domain string) ([]Record, error) {
  62  	endpoint := c.baseURL.JoinPath("domains", domain, "dns")
  63  
  64  	req, err := newJSONRequest(ctx, http.MethodGet, endpoint, nil)
  65  	if err != nil {
  66  		return nil, fmt.Errorf("create request: %w", err)
  67  	}
  68  
  69  	var results Response[RecordData]
  70  
  71  	err = c.do(req, &results)
  72  	if err != nil {
  73  		return nil, err
  74  	}
  75  
  76  	return results.Data.Records, nil
  77  }
  78  
  79  // UpdateRecords Update DNS records of specific domain.
  80  // https://mijn.host/api/doc/api-3563907
  81  func (c *Client) UpdateRecords(ctx context.Context, domain string, records []Record) error {
  82  	endpoint := c.baseURL.JoinPath("domains", domain, "dns")
  83  
  84  	req, err := newJSONRequest(ctx, http.MethodPut, endpoint, RecordData{Records: records})
  85  	if err != nil {
  86  		return fmt.Errorf("create request: %w", err)
  87  	}
  88  
  89  	err = c.do(req, nil)
  90  	if err != nil {
  91  		return err
  92  	}
  93  
  94  	return nil
  95  }
  96  
  97  func (c *Client) do(req *http.Request, result any) error {
  98  	req.Header.Set(authorizationHeader, c.apiKey)
  99  
 100  	resp, err := c.HTTPClient.Do(req)
 101  	if err != nil {
 102  		return errutils.NewHTTPDoError(req, err)
 103  	}
 104  
 105  	defer func() { _ = resp.Body.Close() }()
 106  
 107  	if resp.StatusCode != http.StatusOK {
 108  		return parseError(req, resp)
 109  	}
 110  
 111  	if result == nil {
 112  		return nil
 113  	}
 114  
 115  	raw, err := io.ReadAll(resp.Body)
 116  	if err != nil {
 117  		return errutils.NewReadResponseError(req, resp.StatusCode, err)
 118  	}
 119  
 120  	err = json.Unmarshal(raw, result)
 121  	if err != nil {
 122  		return errutils.NewUnmarshalError(req, resp.StatusCode, raw, err)
 123  	}
 124  
 125  	return nil
 126  }
 127  
 128  func newJSONRequest(ctx context.Context, method string, endpoint *url.URL, payload any) (*http.Request, error) {
 129  	buf := new(bytes.Buffer)
 130  
 131  	if payload != nil {
 132  		err := json.NewEncoder(buf).Encode(payload)
 133  		if err != nil {
 134  			return nil, fmt.Errorf("failed to create request JSON body: %w", err)
 135  		}
 136  	}
 137  
 138  	req, err := http.NewRequestWithContext(ctx, method, endpoint.String(), buf)
 139  	if err != nil {
 140  		return nil, fmt.Errorf("unable to create request: %w", err)
 141  	}
 142  
 143  	req.Header.Set("Accept", "application/json")
 144  
 145  	if payload != nil {
 146  		req.Header.Set("Content-Type", "application/json")
 147  	}
 148  
 149  	return req, nil
 150  }
 151  
 152  func parseError(req *http.Request, resp *http.Response) error {
 153  	raw, _ := io.ReadAll(resp.Body)
 154  
 155  	var errAPI APIError
 156  
 157  	err := json.Unmarshal(raw, &errAPI)
 158  	if err != nil {
 159  		return errutils.NewUnexpectedStatusCodeError(req, resp.StatusCode, raw)
 160  	}
 161  
 162  	return &errAPI
 163  }
 164