client.go raw

   1  package internal
   2  
   3  import (
   4  	"bytes"
   5  	"context"
   6  	"encoding/json"
   7  	"errors"
   8  	"fmt"
   9  	"io"
  10  	"net/http"
  11  	"net/url"
  12  	"time"
  13  
  14  	"github.com/go-acme/lego/v4/providers/dns/internal/errutils"
  15  	"github.com/go-acme/lego/v4/providers/dns/internal/useragent"
  16  )
  17  
  18  const defaultBaseURL = "https://engine.anexia-it.com"
  19  
  20  // Client the Anexia CloudDNS API client.
  21  type Client struct {
  22  	token string
  23  
  24  	BaseURL    *url.URL
  25  	HTTPClient *http.Client
  26  }
  27  
  28  // NewClient creates a new Client.
  29  func NewClient(token string) (*Client, error) {
  30  	if token == "" {
  31  		return nil, errors.New("credentials missing")
  32  	}
  33  
  34  	baseURL, _ := url.Parse(defaultBaseURL)
  35  
  36  	return &Client{
  37  		token:      token,
  38  		BaseURL:    baseURL,
  39  		HTTPClient: &http.Client{Timeout: 10 * time.Second},
  40  	}, nil
  41  }
  42  
  43  func (c *Client) CreateRecord(ctx context.Context, zoneName string, record Record) (*Zone, error) {
  44  	endpoint := c.BaseURL.JoinPath("api", "clouddns", "v1", "zone.json", zoneName, "records")
  45  
  46  	req, err := newJSONRequest(ctx, http.MethodPost, endpoint, record)
  47  	if err != nil {
  48  		return nil, err
  49  	}
  50  
  51  	var zone Zone
  52  
  53  	err = c.do(req, &zone)
  54  	if err != nil {
  55  		return nil, err
  56  	}
  57  
  58  	return &zone, nil
  59  }
  60  
  61  func (c *Client) DeleteRecord(ctx context.Context, zoneName, recordID string) error {
  62  	endpoint := c.BaseURL.JoinPath("api", "clouddns", "v1", "zone.json", zoneName, "records", recordID)
  63  
  64  	req, err := newJSONRequest(ctx, http.MethodDelete, endpoint, nil)
  65  	if err != nil {
  66  		return err
  67  	}
  68  
  69  	return c.do(req, nil)
  70  }
  71  
  72  func (c *Client) GetZone(ctx context.Context, zoneName string) (*Zone, error) {
  73  	endpoint := c.BaseURL.JoinPath("api", "clouddns", "v1", "zone.json", zoneName)
  74  
  75  	req, err := newJSONRequest(ctx, http.MethodGet, endpoint, nil)
  76  	if err != nil {
  77  		return nil, err
  78  	}
  79  
  80  	var zone Zone
  81  
  82  	err = c.do(req, &zone)
  83  	if err != nil {
  84  		return nil, err
  85  	}
  86  
  87  	return &zone, nil
  88  }
  89  
  90  func (c *Client) do(req *http.Request, result any) error {
  91  	useragent.SetHeader(req.Header)
  92  
  93  	req.Header.Add("Authorization", fmt.Sprintf("Token %s", c.token))
  94  
  95  	resp, err := c.HTTPClient.Do(req)
  96  	if err != nil {
  97  		return errutils.NewHTTPDoError(req, err)
  98  	}
  99  
 100  	defer func() { _ = resp.Body.Close() }()
 101  
 102  	if resp.StatusCode/100 != 2 {
 103  		return parseError(req, resp)
 104  	}
 105  
 106  	if result == nil {
 107  		return nil
 108  	}
 109  
 110  	raw, err := io.ReadAll(resp.Body)
 111  	if err != nil {
 112  		return errutils.NewReadResponseError(req, resp.StatusCode, err)
 113  	}
 114  
 115  	err = json.Unmarshal(raw, result)
 116  	if err != nil {
 117  		return errutils.NewUnmarshalError(req, resp.StatusCode, raw, err)
 118  	}
 119  
 120  	return nil
 121  }
 122  
 123  func newJSONRequest(ctx context.Context, method string, endpoint *url.URL, payload any) (*http.Request, error) {
 124  	buf := new(bytes.Buffer)
 125  
 126  	if payload != nil {
 127  		err := json.NewEncoder(buf).Encode(payload)
 128  		if err != nil {
 129  			return nil, fmt.Errorf("failed to create request JSON body: %w", err)
 130  		}
 131  	}
 132  
 133  	req, err := http.NewRequestWithContext(ctx, method, endpoint.String(), buf)
 134  	if err != nil {
 135  		return nil, fmt.Errorf("unable to create request: %w", err)
 136  	}
 137  
 138  	req.Header.Set("Accept", "application/json")
 139  
 140  	if payload != nil {
 141  		req.Header.Set("Content-Type", "application/json; charset=utf-8")
 142  	}
 143  
 144  	return req, nil
 145  }
 146  
 147  func parseError(req *http.Request, resp *http.Response) error {
 148  	raw, _ := io.ReadAll(resp.Body)
 149  
 150  	var errAPI APIError
 151  
 152  	err := json.Unmarshal(raw, &errAPI)
 153  	if err != nil {
 154  		return errutils.NewUnexpectedStatusCodeError(req, resp.StatusCode, raw)
 155  	}
 156  
 157  	return &errAPI
 158  }
 159