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 "strconv"
13 "time"
14
15 "github.com/go-acme/lego/v4/providers/dns/internal/errutils"
16 querystring "github.com/google/go-querystring/query"
17 )
18
19 const defaultBaseURL = "https://api.v2.rainyun.com/product/"
20
21 // Client the Rain Yun API client.
22 type Client struct {
23 apiKey string
24
25 baseURL *url.URL
26 HTTPClient *http.Client
27 }
28
29 // NewClient creates a new Client.
30 func NewClient(apiKey string) (*Client, error) {
31 if apiKey == "" {
32 return nil, errors.New("credentials missing")
33 }
34
35 baseURL, _ := url.Parse(defaultBaseURL)
36
37 return &Client{
38 apiKey: apiKey,
39 baseURL: baseURL,
40 HTTPClient: &http.Client{Timeout: 10 * time.Second},
41 }, nil
42 }
43
44 func (c *Client) AddRecord(ctx context.Context, domainID int, record Record) error {
45 endpoint := c.baseURL.JoinPath("domain", strconv.Itoa(domainID), "dns")
46
47 req, err := newJSONRequest(ctx, http.MethodPost, endpoint, record)
48 if err != nil {
49 return err
50 }
51
52 return c.do(req, nil)
53 }
54
55 func (c *Client) DeleteRecord(ctx context.Context, domainID, recordID int) error {
56 endpoint := c.baseURL.JoinPath("domain", strconv.Itoa(domainID), "dns")
57
58 values, err := querystring.Values(Record{ID: recordID})
59 if err != nil {
60 return err
61 }
62
63 endpoint.RawQuery = values.Encode()
64
65 req, err := newJSONRequest(ctx, http.MethodDelete, endpoint, nil)
66 if err != nil {
67 return err
68 }
69
70 return c.do(req, nil)
71 }
72
73 func (c *Client) ListRecords(ctx context.Context, domainID int) ([]Record, error) {
74 endpoint := c.baseURL.JoinPath("domain", strconv.Itoa(domainID), "dns")
75
76 query := endpoint.Query()
77 query.Set("limit", "100")
78 query.Set("page_no", "1")
79 endpoint.RawQuery = query.Encode()
80
81 req, err := newJSONRequest(ctx, http.MethodGet, endpoint, nil)
82 if err != nil {
83 return nil, err
84 }
85
86 var recordData APIResponse[Record]
87
88 err = c.do(req, &recordData)
89 if err != nil {
90 return nil, err
91 }
92
93 return recordData.Data.Records, nil
94 }
95
96 func (c *Client) ListDomains(ctx context.Context) ([]Domain, error) {
97 endpoint := c.baseURL.JoinPath("domain")
98
99 query := endpoint.Query()
100 query.Set("options", `{"columnFilters":{"domains.Domain":""},"sort":[],"page":1,"perPage":100}`)
101 endpoint.RawQuery = query.Encode()
102
103 req, err := newJSONRequest(ctx, http.MethodGet, endpoint, nil)
104 if err != nil {
105 return nil, err
106 }
107
108 var domainData APIResponse[Domain]
109
110 err = c.do(req, &domainData)
111 if err != nil {
112 return nil, err
113 }
114
115 return domainData.Data.Records, nil
116 }
117
118 func (c *Client) do(req *http.Request, result any) error {
119 req.Header.Add("x-api-key", c.apiKey)
120
121 resp, err := c.HTTPClient.Do(req)
122 if err != nil {
123 return errutils.NewHTTPDoError(req, err)
124 }
125
126 defer func() { _ = resp.Body.Close() }()
127
128 if resp.StatusCode/100 != 2 {
129 return parseError(req, resp)
130 }
131
132 if result == nil {
133 return nil
134 }
135
136 raw, err := io.ReadAll(resp.Body)
137 if err != nil {
138 return errutils.NewReadResponseError(req, resp.StatusCode, err)
139 }
140
141 err = json.Unmarshal(raw, result)
142 if err != nil {
143 return errutils.NewUnmarshalError(req, resp.StatusCode, raw, err)
144 }
145
146 return nil
147 }
148
149 func newJSONRequest(ctx context.Context, method string, endpoint *url.URL, payload any) (*http.Request, error) {
150 buf := new(bytes.Buffer)
151
152 if payload != nil {
153 err := json.NewEncoder(buf).Encode(payload)
154 if err != nil {
155 return nil, fmt.Errorf("failed to create request JSON body: %w", err)
156 }
157 }
158
159 req, err := http.NewRequestWithContext(ctx, method, endpoint.String(), buf)
160 if err != nil {
161 return nil, fmt.Errorf("unable to create request: %w", err)
162 }
163
164 req.Header.Set("Accept", "application/json")
165
166 if payload != nil {
167 req.Header.Set("Content-Type", "application/json")
168 }
169
170 return req, nil
171 }
172
173 func parseError(req *http.Request, resp *http.Response) error {
174 raw, _ := io.ReadAll(resp.Body)
175
176 var errAPI APIError
177
178 err := json.Unmarshal(raw, &errAPI)
179 if err != nil {
180 return errutils.NewUnexpectedStatusCodeError(req, resp.StatusCode, raw)
181 }
182
183 return &errAPI
184 }
185