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 querystring "github.com/google/go-querystring/query"
16 )
17
18 const defaultBaseURL = "https://pddimp.yandex.ru/api2/admin/dns"
19
20 const successCode = "ok"
21
22 const pddTokenHeader = "PddToken"
23
24 type Client struct {
25 pddToken string
26
27 baseURL *url.URL
28 HTTPClient *http.Client
29 }
30
31 func NewClient(pddToken string) (*Client, error) {
32 if pddToken == "" {
33 return nil, errors.New("PDD token is required")
34 }
35
36 baseURL, _ := url.Parse(defaultBaseURL)
37
38 return &Client{
39 pddToken: pddToken,
40 baseURL: baseURL,
41 HTTPClient: &http.Client{Timeout: 10 * time.Second},
42 }, nil
43 }
44
45 func (c *Client) AddRecord(ctx context.Context, payload Record) (*Record, error) {
46 endpoint := c.baseURL.JoinPath("add")
47
48 req, err := newRequest(ctx, http.MethodPost, endpoint, payload)
49 if err != nil {
50 return nil, err
51 }
52
53 r := AddResponse{}
54
55 err = c.do(req, &r)
56 if err != nil {
57 return nil, err
58 }
59
60 return r.Record, nil
61 }
62
63 func (c *Client) RemoveRecord(ctx context.Context, payload Record) (int, error) {
64 endpoint := c.baseURL.JoinPath("del")
65
66 req, err := newRequest(ctx, http.MethodPost, endpoint, payload)
67 if err != nil {
68 return 0, err
69 }
70
71 r := RemoveResponse{}
72
73 err = c.do(req, &r)
74 if err != nil {
75 return 0, err
76 }
77
78 return r.RecordID, nil
79 }
80
81 func (c *Client) GetRecords(ctx context.Context, domain string) ([]Record, error) {
82 endpoint := c.baseURL.JoinPath("list")
83
84 payload := struct {
85 Domain string `url:"domain"`
86 }{Domain: domain}
87
88 req, err := newRequest(ctx, http.MethodGet, endpoint, payload)
89 if err != nil {
90 return nil, err
91 }
92
93 r := ListResponse{}
94
95 err = c.do(req, &r)
96 if err != nil {
97 return nil, err
98 }
99
100 return r.Records, nil
101 }
102
103 func (c *Client) do(req *http.Request, result Response) error {
104 req.Header.Set(pddTokenHeader, c.pddToken)
105
106 resp, err := c.HTTPClient.Do(req)
107 if err != nil {
108 return errutils.NewHTTPDoError(req, err)
109 }
110
111 defer func() { _ = resp.Body.Close() }()
112
113 raw, err := io.ReadAll(resp.Body)
114 if err != nil {
115 return errutils.NewReadResponseError(req, resp.StatusCode, err)
116 }
117
118 err = json.Unmarshal(raw, result)
119 if err != nil {
120 return errutils.NewUnmarshalError(req, resp.StatusCode, raw, err)
121 }
122
123 if result.GetSuccess() != successCode {
124 return fmt.Errorf("error during operation: %s %s", result.GetSuccess(), result.GetError())
125 }
126
127 return nil
128 }
129
130 func newRequest(ctx context.Context, method string, endpoint *url.URL, payload any) (*http.Request, error) {
131 buf := new(bytes.Buffer)
132
133 if payload != nil {
134 switch method {
135 case http.MethodPost:
136 values, err := querystring.Values(payload)
137 if err != nil {
138 return nil, err
139 }
140
141 buf.WriteString(values.Encode())
142
143 case http.MethodGet:
144 values, err := querystring.Values(payload)
145 if err != nil {
146 return nil, err
147 }
148
149 endpoint.RawQuery = values.Encode()
150 }
151 }
152
153 req, err := http.NewRequestWithContext(ctx, method, endpoint.String(), buf)
154 if err != nil {
155 return nil, fmt.Errorf("unable to create request: %w", err)
156 }
157
158 if method == http.MethodPost {
159 req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
160 }
161
162 return req, nil
163 }
164