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