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 "os"
11 "path/filepath"
12 "strings"
13 "time"
14 "unicode"
15
16 "github.com/go-acme/lego/v4/providers/dns/internal/errutils"
17 querystring "github.com/google/go-querystring/query"
18 )
19
20 const baseURL = "https://api.internet.bs"
21
22 // status SUCCESS, PENDING, FAILURE.
23 const statusSuccess = "SUCCESS"
24
25 // Client is the API client.
26 type Client struct {
27 apiKey string
28 password string
29
30 debug bool
31
32 baseURL *url.URL
33 HTTPClient *http.Client
34 }
35
36 // NewClient creates a new Client.
37 func NewClient(apiKey, password string) *Client {
38 baseURL, _ := url.Parse(baseURL)
39
40 return &Client{
41 apiKey: apiKey,
42 password: password,
43 baseURL: baseURL,
44 HTTPClient: &http.Client{Timeout: 10 * time.Second},
45 }
46 }
47
48 // AddRecord The command is intended to add a new DNS record to a specific zone (domain).
49 func (c *Client) AddRecord(ctx context.Context, query RecordQuery) error {
50 var r APIResponse
51
52 err := c.doRequest(ctx, "Add", query, &r)
53 if err != nil {
54 return err
55 }
56
57 if r.Status != statusSuccess {
58 return r
59 }
60
61 return nil
62 }
63
64 // RemoveRecord The command is intended to remove a DNS record from a specific zone.
65 func (c *Client) RemoveRecord(ctx context.Context, query RecordQuery) error {
66 var r APIResponse
67
68 err := c.doRequest(ctx, "Remove", query, &r)
69 if err != nil {
70 return err
71 }
72
73 if r.Status != statusSuccess {
74 return r
75 }
76
77 return nil
78 }
79
80 // ListRecords The command is intended to retrieve the list of DNS records for a specific domain.
81 func (c *Client) ListRecords(ctx context.Context, query ListRecordQuery) ([]Record, error) {
82 var l ListResponse
83
84 err := c.doRequest(ctx, "List", query, &l)
85 if err != nil {
86 return nil, err
87 }
88
89 if l.Status != statusSuccess {
90 return nil, l.APIResponse
91 }
92
93 return l.Records, nil
94 }
95
96 func (c *Client) doRequest(ctx context.Context, action string, params, result any) error {
97 endpoint := c.baseURL.JoinPath("Domain", "DnsRecord", action)
98
99 values, err := querystring.Values(params)
100 if err != nil {
101 return fmt.Errorf("parse query parameters: %w", err)
102 }
103
104 values.Set("apiKey", c.apiKey)
105 values.Set("password", c.password)
106 values.Set("ResponseFormat", "JSON")
107
108 req, err := http.NewRequestWithContext(ctx, http.MethodPost, endpoint.String(), strings.NewReader(values.Encode()))
109 if err != nil {
110 return fmt.Errorf("unable to create request: %w", err)
111 }
112
113 req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
114
115 resp, err := c.HTTPClient.Do(req)
116 if err != nil {
117 return errutils.NewHTTPDoError(req, err)
118 }
119
120 defer func() { _ = resp.Body.Close() }()
121
122 if resp.StatusCode/100 != 2 {
123 return errutils.NewUnexpectedResponseStatusCodeError(req, resp)
124 }
125
126 if c.debug {
127 return dump(endpoint, resp, result)
128 }
129
130 raw, err := io.ReadAll(resp.Body)
131 if err != nil {
132 return errutils.NewReadResponseError(req, resp.StatusCode, err)
133 }
134
135 err = json.Unmarshal(raw, result)
136 if err != nil {
137 return errutils.NewUnmarshalError(req, resp.StatusCode, raw, err)
138 }
139
140 return nil
141 }
142
143 func dump(endpoint *url.URL, resp *http.Response, response any) error {
144 raw, err := io.ReadAll(resp.Body)
145 if err != nil {
146 return err
147 }
148
149 fields := strings.FieldsFunc(endpoint.Path, func(r rune) bool {
150 return !unicode.IsLetter(r) && !unicode.IsNumber(r)
151 })
152
153 err = os.WriteFile(filepath.Join("fixtures", strings.Join(fields, "_")+".json"), raw, 0o666)
154 if err != nil {
155 return err
156 }
157
158 return json.Unmarshal(raw, response)
159 }
160