client.go raw
1 package internal
2
3 import (
4 "bytes"
5 "context"
6 "encoding/json"
7 "fmt"
8 "io"
9 "net/http"
10 "net/url"
11 "strings"
12 "time"
13
14 "github.com/go-acme/lego/v4/providers/dns/internal/errutils"
15 "github.com/miekg/dns"
16 )
17
18 const defaultBaseURL = "https://my.rcodezero.at/api"
19
20 const authorizationHeader = "Authorization"
21
22 // Client for the RcodeZero API.
23 type Client struct {
24 apiToken string
25
26 baseURL *url.URL
27 HTTPClient *http.Client
28 }
29
30 // NewClient creates a new Client.
31 func NewClient(apiToken string) *Client {
32 baseURL, _ := url.Parse(defaultBaseURL)
33
34 return &Client{
35 apiToken: apiToken,
36 baseURL: baseURL,
37 HTTPClient: &http.Client{Timeout: 5 * time.Second},
38 }
39 }
40
41 func (c *Client) UpdateRecords(ctx context.Context, authZone string, sets []UpdateRRSet) (*APIResponse, error) {
42 endpoint := c.baseURL.JoinPath("v1", "acme", "zones", strings.TrimSuffix(dns.Fqdn(authZone), "."), "rrsets")
43
44 req, err := newJSONRequest(ctx, http.MethodPatch, endpoint, sets)
45 if err != nil {
46 return nil, err
47 }
48
49 return c.do(req)
50 }
51
52 func (c *Client) do(req *http.Request) (*APIResponse, error) {
53 req.Header.Set(authorizationHeader, "Bearer "+c.apiToken)
54
55 resp, err := c.HTTPClient.Do(req)
56 if err != nil {
57 return nil, errutils.NewHTTPDoError(req, err)
58 }
59
60 defer func() { _ = resp.Body.Close() }()
61
62 if resp.StatusCode/100 != 2 {
63 return nil, parseError(req, resp)
64 }
65
66 result := &APIResponse{}
67
68 raw, err := io.ReadAll(resp.Body)
69 if err != nil {
70 return nil, errutils.NewReadResponseError(req, resp.StatusCode, err)
71 }
72
73 err = json.Unmarshal(raw, result)
74 if err != nil {
75 return nil, errutils.NewUnmarshalError(req, resp.StatusCode, raw, err)
76 }
77
78 return result, nil
79 }
80
81 func newJSONRequest(ctx context.Context, method string, endpoint *url.URL, payload any) (*http.Request, error) {
82 buf := new(bytes.Buffer)
83
84 if payload != nil {
85 err := json.NewEncoder(buf).Encode(payload)
86 if err != nil {
87 return nil, fmt.Errorf("failed to create request JSON body: %w", err)
88 }
89 }
90
91 req, err := http.NewRequestWithContext(ctx, method, endpoint.String(), buf)
92 if err != nil {
93 return nil, fmt.Errorf("unable to create request: %w", err)
94 }
95
96 req.Header.Set("Accept", "application/json")
97
98 if payload != nil {
99 req.Header.Set("Content-Type", "application/json")
100 }
101
102 return req, nil
103 }
104
105 func parseError(req *http.Request, resp *http.Response) error {
106 raw, _ := io.ReadAll(resp.Body)
107
108 errAPI := &APIResponse{}
109
110 err := json.Unmarshal(raw, errAPI)
111 if err != nil {
112 return errutils.NewUnexpectedStatusCodeError(req, resp.StatusCode, raw)
113 }
114
115 return fmt.Errorf("[status code: %d] %w", resp.StatusCode, errAPI)
116 }
117