client.go raw

   1  package internal
   2  
   3  import (
   4  	"context"
   5  	"encoding/json"
   6  	"fmt"
   7  	"io"
   8  	"net/http"
   9  	"net/url"
  10  	"strings"
  11  	"time"
  12  
  13  	"github.com/go-acme/lego/v4/providers/dns/internal/errutils"
  14  	querystring "github.com/google/go-querystring/query"
  15  )
  16  
  17  // Client the Direct Admin API client.
  18  type Client struct {
  19  	baseURL    *url.URL
  20  	HTTPClient *http.Client
  21  
  22  	username string
  23  	password string
  24  }
  25  
  26  // NewClient creates a new Client.
  27  func NewClient(baseURL, username, password string) (*Client, error) {
  28  	api, err := url.Parse(baseURL)
  29  	if err != nil {
  30  		return nil, err
  31  	}
  32  
  33  	return &Client{
  34  		baseURL:    api,
  35  		HTTPClient: &http.Client{Timeout: 10 * time.Second},
  36  		username:   username,
  37  		password:   password,
  38  	}, nil
  39  }
  40  
  41  func (c *Client) SetRecord(ctx context.Context, domain string, record Record) error {
  42  	data, err := querystring.Values(record)
  43  	if err != nil {
  44  		return err
  45  	}
  46  
  47  	data.Set("action", "add")
  48  
  49  	return c.do(ctx, domain, data)
  50  }
  51  
  52  func (c *Client) DeleteRecord(ctx context.Context, domain string, record Record) error {
  53  	data, err := querystring.Values(record)
  54  	if err != nil {
  55  		return err
  56  	}
  57  
  58  	data.Set("action", "delete")
  59  
  60  	return c.do(ctx, domain, data)
  61  }
  62  
  63  func (c *Client) do(ctx context.Context, domain string, data url.Values) error {
  64  	endpoint := c.baseURL.JoinPath("CMD_API_DNS_CONTROL")
  65  
  66  	query := endpoint.Query()
  67  	query.Set("domain", domain)
  68  	query.Set("json", "yes")
  69  	endpoint.RawQuery = query.Encode()
  70  
  71  	req, err := http.NewRequestWithContext(ctx, http.MethodPost, endpoint.String(), strings.NewReader(data.Encode()))
  72  	if err != nil {
  73  		return fmt.Errorf("unable to create request: %w", err)
  74  	}
  75  
  76  	req.SetBasicAuth(c.username, c.password)
  77  	req.Header.Add("Content-Type", "application/x-www-form-urlencoded")
  78  
  79  	resp, err := c.HTTPClient.Do(req)
  80  	if err != nil {
  81  		return errutils.NewHTTPDoError(req, err)
  82  	}
  83  
  84  	defer func() { _ = resp.Body.Close() }()
  85  
  86  	if resp.StatusCode != http.StatusOK {
  87  		return parseError(req, resp)
  88  	}
  89  
  90  	return nil
  91  }
  92  
  93  func parseError(req *http.Request, resp *http.Response) error {
  94  	raw, _ := io.ReadAll(resp.Body)
  95  
  96  	var errInfo APIError
  97  
  98  	err := json.Unmarshal(raw, &errInfo)
  99  	if err != nil {
 100  		return errutils.NewUnexpectedStatusCodeError(req, resp.StatusCode, raw)
 101  	}
 102  
 103  	return fmt.Errorf("[status code %d] %w", resp.StatusCode, errInfo)
 104  }
 105