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 "time"
12
13 "github.com/go-acme/lego/v4/providers/dns/internal/errutils"
14 )
15
16 // DefaultEndpoint the default API endpoint.
17 const DefaultEndpoint = "https://api.zone.eu/v2/"
18
19 // Client the API client for Zoneee.
20 type Client struct {
21 username string
22 apiKey string
23
24 BaseURL *url.URL
25 HTTPClient *http.Client
26 }
27
28 // NewClient creates a new Client.
29 func NewClient(username, apiKey string) *Client {
30 baseURL, _ := url.Parse(DefaultEndpoint)
31
32 return &Client{
33 username: username,
34 apiKey: apiKey,
35 BaseURL: baseURL,
36 HTTPClient: &http.Client{Timeout: 5 * time.Second},
37 }
38 }
39
40 // GetTxtRecords get TXT records.
41 // https://api.zone.eu/v2#operation/getdnstxtrecords
42 func (c *Client) GetTxtRecords(ctx context.Context, domain string) ([]TXTRecord, error) {
43 endpoint := c.BaseURL.JoinPath("dns", domain, "txt")
44
45 req, err := newJSONRequest(ctx, http.MethodGet, endpoint, http.NoBody)
46 if err != nil {
47 return nil, err
48 }
49
50 var records []TXTRecord
51 if err := c.do(req, &records); err != nil {
52 return nil, err
53 }
54
55 return records, nil
56 }
57
58 // AddTxtRecord creates a TXT records.
59 // https://api.zone.eu/v2#operation/creatednstxtrecord
60 func (c *Client) AddTxtRecord(ctx context.Context, domain string, record TXTRecord) ([]TXTRecord, error) {
61 endpoint := c.BaseURL.JoinPath("dns", domain, "txt")
62
63 req, err := newJSONRequest(ctx, http.MethodPost, endpoint, record)
64 if err != nil {
65 return nil, err
66 }
67
68 var records []TXTRecord
69 if err := c.do(req, &records); err != nil {
70 return nil, err
71 }
72
73 return records, nil
74 }
75
76 // RemoveTxtRecord deletes a TXT record.
77 // https://api.zone.eu/v2#operation/deletednstxtrecord
78 func (c *Client) RemoveTxtRecord(ctx context.Context, domain, id string) error {
79 endpoint := c.BaseURL.JoinPath("dns", domain, "txt", id)
80
81 req, err := newJSONRequest(ctx, http.MethodDelete, endpoint, nil)
82 if err != nil {
83 return err
84 }
85
86 return c.do(req, nil)
87 }
88
89 func (c *Client) do(req *http.Request, result any) error {
90 req.SetBasicAuth(c.username, c.apiKey)
91
92 resp, err := c.HTTPClient.Do(req)
93 if err != nil {
94 return errutils.NewHTTPDoError(req, err)
95 }
96
97 defer func() { _ = resp.Body.Close() }()
98
99 if resp.StatusCode/100 != 2 {
100 return errutils.NewUnexpectedResponseStatusCodeError(req, resp)
101 }
102
103 if result == nil {
104 return nil
105 }
106
107 raw, err := io.ReadAll(resp.Body)
108 if err != nil {
109 return errutils.NewReadResponseError(req, resp.StatusCode, err)
110 }
111
112 err = json.Unmarshal(raw, result)
113 if err != nil {
114 return errutils.NewUnmarshalError(req, resp.StatusCode, raw, err)
115 }
116
117 return nil
118 }
119
120 func newJSONRequest(ctx context.Context, method string, endpoint *url.URL, payload any) (*http.Request, error) {
121 buf := new(bytes.Buffer)
122
123 if payload != nil {
124 err := json.NewEncoder(buf).Encode(payload)
125 if err != nil {
126 return nil, fmt.Errorf("failed to create request JSON body: %w", err)
127 }
128 }
129
130 req, err := http.NewRequestWithContext(ctx, method, endpoint.String(), buf)
131 if err != nil {
132 return nil, fmt.Errorf("unable to create request: %w", err)
133 }
134
135 req.Header.Set("Accept", "application/json")
136
137 if payload != nil {
138 req.Header.Set("Content-Type", "application/json")
139 }
140
141 return req, nil
142 }
143