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