client.go raw
1 package internal
2
3 import (
4 "context"
5 "encoding/json"
6 "fmt"
7 "io"
8 "net/http"
9 "net/url"
10 "time"
11
12 "github.com/go-acme/lego/v4/providers/dns/internal/errutils"
13 )
14
15 const defaultBaseURL = "https://api.beget.com/api/"
16
17 // Client the beget.com client.
18 type Client struct {
19 login string
20 password string
21
22 BaseURL *url.URL
23 HTTPClient *http.Client
24 }
25
26 // NewClient Creates a beget.com client.
27 func NewClient(login, password string) *Client {
28 baseURL, _ := url.Parse(defaultBaseURL)
29
30 return &Client{
31 login: login,
32 password: password,
33 BaseURL: baseURL,
34 HTTPClient: &http.Client{Timeout: 5 * time.Second},
35 }
36 }
37
38 // GetTXTRecords returns TXT records.
39 // https://beget.com/ru/kb/api/funkczii-upravleniya-dns#getdata
40 func (c *Client) GetTXTRecords(ctx context.Context, domain string) ([]Record, error) {
41 request := GetRecordsRequest{Fqdn: domain}
42
43 resp, err := c.doRequest(ctx, request, "dns", "getData")
44 if err != nil {
45 return nil, err
46 }
47
48 err = resp.HasError()
49 if err != nil {
50 return nil, err
51 }
52
53 result := GetRecordsResult{}
54
55 err = json.Unmarshal(resp.Answer.Result, &result)
56 if err != nil {
57 return nil, fmt.Errorf("unmarshal result: %s: %w", string(resp.Answer.Result), err)
58 }
59
60 return result.Records.TXT, nil
61 }
62
63 // ChangeTXTRecord changes TXT records.
64 // https://beget.com/ru/kb/api/funkczii-upravleniya-dns#changerecords
65 func (c *Client) ChangeTXTRecord(ctx context.Context, domain string, records []Record) error {
66 request := ChangeRecordsRequest{
67 Fqdn: domain,
68 Records: RecordList{TXT: records},
69 }
70
71 resp, err := c.doRequest(ctx, request, "dns", "changeRecords")
72 if err != nil {
73 return err
74 }
75
76 return resp.HasError()
77 }
78
79 func (c *Client) doRequest(ctx context.Context, data any, fragments ...string) (*APIResponse, error) {
80 endpoint := c.BaseURL.JoinPath(fragments...)
81
82 inputData, err := json.Marshal(data)
83 if err != nil {
84 return nil, fmt.Errorf("failed to mashall input data: %w", err)
85 }
86
87 query := endpoint.Query()
88 query.Add("input_data", string(inputData))
89 query.Add("login", c.login)
90 query.Add("passwd", c.password)
91 query.Add("input_format", "json")
92 query.Add("output_format", "json")
93 endpoint.RawQuery = query.Encode()
94
95 req, err := http.NewRequestWithContext(ctx, http.MethodGet, endpoint.String(), http.NoBody)
96 if err != nil {
97 return nil, fmt.Errorf("unable to create request: %w", err)
98 }
99
100 resp, err := c.HTTPClient.Do(req)
101 if err != nil {
102 return nil, errutils.NewHTTPDoError(req, err)
103 }
104
105 defer func() { _ = resp.Body.Close() }()
106
107 if resp.StatusCode/100 != 2 {
108 return nil, parseError(req, resp)
109 }
110
111 raw, err := io.ReadAll(resp.Body)
112 if err != nil {
113 return nil, errutils.NewReadResponseError(req, resp.StatusCode, err)
114 }
115
116 var apiResp APIResponse
117
118 err = json.Unmarshal(raw, &apiResp)
119 if err != nil {
120 return nil, errutils.NewUnmarshalError(req, resp.StatusCode, raw, err)
121 }
122
123 return &apiResp, nil
124 }
125
126 func parseError(req *http.Request, resp *http.Response) error {
127 raw, _ := io.ReadAll(resp.Body)
128
129 var apiResp APIResponse
130
131 err := json.Unmarshal(raw, &apiResp)
132 if err != nil {
133 return errutils.NewUnexpectedStatusCodeError(req, resp.StatusCode, raw)
134 }
135
136 return fmt.Errorf("[status code %d] %w", resp.StatusCode, apiResp)
137 }
138