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 // defaultBaseURL is the GleSYS API endpoint used by Present and CleanUp.
17 const defaultBaseURL = "https://api.glesys.com/"
18
19 type Client struct {
20 apiUser string
21 apiKey string
22
23 baseURL *url.URL
24 HTTPClient *http.Client
25 }
26
27 func NewClient(apiUser, apiKey string) *Client {
28 baseURL, _ := url.Parse(defaultBaseURL)
29
30 return &Client{
31 apiUser: apiUser,
32 apiKey: apiKey,
33 baseURL: baseURL,
34 HTTPClient: &http.Client{Timeout: 5 * time.Second},
35 }
36 }
37
38 // AddTXTRecord adds a dns record to a domain.
39 // https://github.com/GleSYS/API/wiki/API-Documentation#domainaddrecord
40 func (c *Client) AddTXTRecord(ctx context.Context, domain, name, value string, ttl int) (int, error) {
41 endpoint := c.baseURL.JoinPath("domain", "addrecord")
42
43 request := addRecordRequest{
44 DomainName: domain,
45 Host: name,
46 Type: "TXT",
47 Data: value,
48 TTL: ttl,
49 }
50
51 req, err := newJSONRequest(ctx, http.MethodPost, endpoint, request)
52 if err != nil {
53 return 0, err
54 }
55
56 response, err := c.do(req)
57 if err != nil {
58 return 0, err
59 }
60
61 if response != nil && response.Response.Status.Code == http.StatusOK {
62 return response.Response.Record.RecordID, nil
63 }
64
65 return 0, err
66 }
67
68 // DeleteTXTRecord removes a dns record from a domain.
69 // https://github.com/GleSYS/API/wiki/API-Documentation#domaindeleterecord
70 func (c *Client) DeleteTXTRecord(ctx context.Context, recordID int) error {
71 endpoint := c.baseURL.JoinPath("domain", "deleterecord")
72
73 request := deleteRecordRequest{RecordID: recordID}
74
75 req, err := newJSONRequest(ctx, http.MethodPost, endpoint, request)
76 if err != nil {
77 return err
78 }
79
80 _, err = c.do(req)
81
82 return err
83 }
84
85 func (c *Client) do(req *http.Request) (*apiResponse, error) {
86 req.SetBasicAuth(c.apiUser, c.apiKey)
87
88 resp, err := c.HTTPClient.Do(req)
89 if err != nil {
90 return nil, errutils.NewHTTPDoError(req, err)
91 }
92
93 defer func() { _ = resp.Body.Close() }()
94
95 if resp.StatusCode/100 != 2 {
96 return nil, errutils.NewUnexpectedResponseStatusCodeError(req, resp)
97 }
98
99 raw, err := io.ReadAll(resp.Body)
100 if err != nil {
101 return nil, errutils.NewReadResponseError(req, resp.StatusCode, err)
102 }
103
104 var response apiResponse
105
106 err = json.Unmarshal(raw, &response)
107 if err != nil {
108 return nil, errutils.NewUnmarshalError(req, resp.StatusCode, raw, err)
109 }
110
111 return &response, nil
112 }
113
114 func newJSONRequest(ctx context.Context, method string, endpoint *url.URL, payload any) (*http.Request, error) {
115 buf := new(bytes.Buffer)
116
117 if payload != nil {
118 err := json.NewEncoder(buf).Encode(payload)
119 if err != nil {
120 return nil, fmt.Errorf("failed to create request JSON body: %w", err)
121 }
122 }
123
124 req, err := http.NewRequestWithContext(ctx, method, endpoint.String(), buf)
125 if err != nil {
126 return nil, fmt.Errorf("unable to create request: %w", err)
127 }
128
129 req.Header.Set("Accept", "application/json")
130
131 if payload != nil {
132 req.Header.Set("Content-Type", "application/json")
133 }
134
135 return req, nil
136 }
137