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