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 "time"
11
12 "github.com/go-acme/lego/v4/providers/dns/internal/errutils"
13 )
14
15 // DefaultBaseURL the default API endpoint.
16 const DefaultBaseURL = "https://api.dreamhost.com"
17
18 const (
19 cmdAddRecord = "dns-add_record"
20 cmdRemoveRecord = "dns-remove_record"
21 )
22
23 // Client the Dreamhost API client.
24 type Client struct {
25 apiKey string
26
27 BaseURL string
28 HTTPClient *http.Client
29 }
30
31 // NewClient Creates a new Client.
32 func NewClient(apiKey string) *Client {
33 return &Client{
34 apiKey: apiKey,
35 BaseURL: DefaultBaseURL,
36 HTTPClient: &http.Client{Timeout: 5 * time.Second},
37 }
38 }
39
40 // AddRecord adds a TXT record.
41 func (c *Client) AddRecord(ctx context.Context, domain, value string) error {
42 query, err := c.buildEndpoint(cmdAddRecord, domain, value)
43 if err != nil {
44 return err
45 }
46
47 return c.updateTxtRecord(ctx, query)
48 }
49
50 // RemoveRecord removes a TXT record.
51 func (c *Client) RemoveRecord(ctx context.Context, domain, value string) error {
52 query, err := c.buildEndpoint(cmdRemoveRecord, domain, value)
53 if err != nil {
54 return err
55 }
56
57 return c.updateTxtRecord(ctx, query)
58 }
59
60 // action is either cmdAddRecord or cmdRemoveRecord.
61 func (c *Client) buildEndpoint(action, domain, txt string) (*url.URL, error) {
62 endpoint, err := url.Parse(c.BaseURL)
63 if err != nil {
64 return nil, err
65 }
66
67 query := endpoint.Query()
68 query.Set("key", c.apiKey)
69 query.Set("cmd", action)
70 query.Set("format", "json")
71 query.Set("record", domain)
72 query.Set("type", "TXT")
73 query.Set("value", txt)
74 query.Set("comment", url.QueryEscape("Managed By lego"))
75 endpoint.RawQuery = query.Encode()
76
77 return endpoint, nil
78 }
79
80 // updateTxtRecord will either add or remove a TXT record.
81 func (c *Client) updateTxtRecord(ctx context.Context, endpoint *url.URL) error {
82 req, err := http.NewRequestWithContext(ctx, http.MethodGet, endpoint.String(), http.NoBody)
83 if err != nil {
84 return fmt.Errorf("unable to create request: %w", err)
85 }
86
87 resp, err := c.HTTPClient.Do(req)
88 if err != nil {
89 return errutils.NewHTTPDoError(req, err)
90 }
91
92 defer func() { _ = resp.Body.Close() }()
93
94 if resp.StatusCode != http.StatusOK {
95 return errutils.NewUnexpectedResponseStatusCodeError(req, resp)
96 }
97
98 raw, err := io.ReadAll(resp.Body)
99 if err != nil {
100 return errutils.NewReadResponseError(req, resp.StatusCode, err)
101 }
102
103 var response apiResponse
104
105 err = json.Unmarshal(raw, &response)
106 if err != nil {
107 return errutils.NewUnmarshalError(req, resp.StatusCode, raw, err)
108 }
109
110 if response.Result == "error" {
111 return fmt.Errorf("add TXT record failed: %s", response.Data)
112 }
113
114 return nil
115 }
116