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  	"github.com/go-acme/lego/v4/providers/dns/internal/useragent"
  15  )
  16  
  17  const defaultBaseURL = "https://usersapiv2.epik.com/v2"
  18  
  19  // Client the Epik API client.
  20  type Client struct {
  21  	signature string
  22  
  23  	baseURL    *url.URL
  24  	HTTPClient *http.Client
  25  }
  26  
  27  // NewClient Creates a new Client.
  28  func NewClient(signature string) *Client {
  29  	baseURL, _ := url.Parse(defaultBaseURL)
  30  
  31  	return &Client{
  32  		signature:  signature,
  33  		baseURL:    baseURL,
  34  		HTTPClient: &http.Client{Timeout: 5 * time.Second},
  35  	}
  36  }
  37  
  38  // GetDNSRecords gets DNS records for a domain.
  39  // https://docs.userapi.epik.com/v2/#/DNS%20Host%20Records/getDnsRecord
  40  func (c *Client) GetDNSRecords(ctx context.Context, domain string) ([]Record, error) {
  41  	endpoint := c.createEndpoint(domain, url.Values{})
  42  
  43  	req, err := newJSONRequest(ctx, http.MethodGet, endpoint, nil)
  44  	if err != nil {
  45  		return nil, err
  46  	}
  47  
  48  	var data GetDNSRecordResponse
  49  
  50  	err = c.do(req, &data)
  51  	if err != nil {
  52  		return nil, err
  53  	}
  54  
  55  	return data.Data.Records, nil
  56  }
  57  
  58  // CreateHostRecord creates a record for a domain.
  59  // https://docs.userapi.epik.com/v2/#/DNS%20Host%20Records/createHostRecord
  60  func (c *Client) CreateHostRecord(ctx context.Context, domain string, record RecordRequest) (*Data, error) {
  61  	endpoint := c.createEndpoint(domain, url.Values{})
  62  
  63  	payload := CreateHostRecords{Payload: record}
  64  
  65  	req, err := newJSONRequest(ctx, http.MethodPost, endpoint, payload)
  66  	if err != nil {
  67  		return nil, err
  68  	}
  69  
  70  	var data Data
  71  
  72  	err = c.do(req, &data)
  73  	if err != nil {
  74  		return nil, err
  75  	}
  76  
  77  	return &data, nil
  78  }
  79  
  80  // RemoveHostRecord removes a record for a domain.
  81  // https://docs.userapi.epik.com/v2/#/DNS%20Host%20Records/removeHostRecord
  82  func (c *Client) RemoveHostRecord(ctx context.Context, domain, recordID string) (*Data, error) {
  83  	params := url.Values{}
  84  	params.Set("ID", recordID)
  85  
  86  	endpoint := c.createEndpoint(domain, params)
  87  
  88  	req, err := newJSONRequest(ctx, http.MethodDelete, endpoint, nil)
  89  	if err != nil {
  90  		return nil, err
  91  	}
  92  
  93  	var data Data
  94  
  95  	err = c.do(req, &data)
  96  	if err != nil {
  97  		return nil, err
  98  	}
  99  
 100  	return &data, nil
 101  }
 102  
 103  func (c *Client) do(req *http.Request, result any) error {
 104  	useragent.SetHeader(req.Header)
 105  
 106  	resp, err := c.HTTPClient.Do(req)
 107  	if err != nil {
 108  		return errutils.NewHTTPDoError(req, err)
 109  	}
 110  
 111  	defer func() { _ = resp.Body.Close() }()
 112  
 113  	if resp.StatusCode != http.StatusOK {
 114  		return parseError(req, resp)
 115  	}
 116  
 117  	if result == nil {
 118  		return nil
 119  	}
 120  
 121  	raw, err := io.ReadAll(resp.Body)
 122  	if err != nil {
 123  		return errutils.NewReadResponseError(req, resp.StatusCode, err)
 124  	}
 125  
 126  	err = json.Unmarshal(raw, result)
 127  	if err != nil {
 128  		return errutils.NewUnmarshalError(req, resp.StatusCode, raw, err)
 129  	}
 130  
 131  	return nil
 132  }
 133  
 134  func (c *Client) createEndpoint(domain string, params url.Values) *url.URL {
 135  	endpoint := c.baseURL.JoinPath("domains", domain, "records")
 136  
 137  	params.Set("SIGNATURE", c.signature)
 138  	endpoint.RawQuery = params.Encode()
 139  
 140  	return endpoint
 141  }
 142  
 143  func newJSONRequest(ctx context.Context, method string, endpoint *url.URL, payload any) (*http.Request, error) {
 144  	buf := new(bytes.Buffer)
 145  
 146  	if payload != nil {
 147  		err := json.NewEncoder(buf).Encode(payload)
 148  		if err != nil {
 149  			return nil, fmt.Errorf("failed to create request JSON body: %w", err)
 150  		}
 151  	}
 152  
 153  	req, err := http.NewRequestWithContext(ctx, method, endpoint.String(), buf)
 154  	if err != nil {
 155  		return nil, fmt.Errorf("unable to create request: %w", err)
 156  	}
 157  
 158  	req.Header.Set("Accept", "application/json")
 159  
 160  	if payload != nil {
 161  		req.Header.Set("Content-Type", "application/json")
 162  	}
 163  
 164  	return req, nil
 165  }
 166  
 167  func parseError(req *http.Request, resp *http.Response) error {
 168  	raw, _ := io.ReadAll(resp.Body)
 169  
 170  	var apiErr APIError
 171  
 172  	err := json.Unmarshal(raw, &apiErr)
 173  	if err != nil {
 174  		return errutils.NewUnexpectedStatusCodeError(req, resp.StatusCode, raw)
 175  	}
 176  
 177  	return &apiErr
 178  }
 179