client.go raw
1 package internal
2
3 import (
4 "bytes"
5 "context"
6 "encoding/json"
7 "fmt"
8 "io"
9 "net/http"
10 "time"
11
12 "github.com/go-acme/lego/v4/providers/dns/internal/errutils"
13 )
14
15 const apiEndpoint = "https://njal.la/api/1/"
16
17 const authorizationHeader = "Authorization"
18
19 // Client is a Njalla API client.
20 type Client struct {
21 token string
22
23 apiEndpoint string
24 HTTPClient *http.Client
25 }
26
27 // NewClient creates a new Client.
28 func NewClient(token string) *Client {
29 return &Client{
30 token: token,
31 apiEndpoint: apiEndpoint,
32 HTTPClient: &http.Client{Timeout: 5 * time.Second},
33 }
34 }
35
36 // AddRecord adds a record.
37 func (c *Client) AddRecord(ctx context.Context, record Record) (*Record, error) {
38 data := APIRequest{
39 Method: "add-record",
40 Params: record,
41 }
42
43 req, err := newJSONRequest(ctx, http.MethodPost, c.apiEndpoint, data)
44 if err != nil {
45 return nil, err
46 }
47
48 var result APIResponse[*Record]
49
50 err = c.do(req, &result)
51 if err != nil {
52 return nil, err
53 }
54
55 return result.Result, nil
56 }
57
58 // RemoveRecord removes a record.
59 func (c *Client) RemoveRecord(ctx context.Context, id, domain string) error {
60 data := APIRequest{
61 Method: "remove-record",
62 Params: Record{
63 ID: id,
64 Domain: domain,
65 },
66 }
67
68 req, err := newJSONRequest(ctx, http.MethodPost, c.apiEndpoint, data)
69 if err != nil {
70 return err
71 }
72
73 err = c.do(req, &APIResponse[json.RawMessage]{})
74 if err != nil {
75 return err
76 }
77
78 return nil
79 }
80
81 // ListRecords list the records for one domain.
82 func (c *Client) ListRecords(ctx context.Context, domain string) ([]Record, error) {
83 data := APIRequest{
84 Method: "list-records",
85 Params: Record{
86 Domain: domain,
87 },
88 }
89
90 req, err := newJSONRequest(ctx, http.MethodPost, c.apiEndpoint, data)
91 if err != nil {
92 return nil, err
93 }
94
95 var result APIResponse[Records]
96
97 err = c.do(req, &result)
98 if err != nil {
99 return nil, err
100 }
101
102 return result.Result.Records, nil
103 }
104
105 func (c *Client) do(req *http.Request, result Response) error {
106 req.Header.Set(authorizationHeader, "Njalla "+c.token)
107
108 resp, err := c.HTTPClient.Do(req)
109 if err != nil {
110 return errutils.NewHTTPDoError(req, err)
111 }
112
113 defer func() { _ = resp.Body.Close() }()
114
115 if resp.StatusCode != http.StatusOK {
116 return errutils.NewUnexpectedResponseStatusCodeError(req, resp)
117 }
118
119 raw, err := io.ReadAll(resp.Body)
120 if err != nil {
121 return errutils.NewReadResponseError(req, resp.StatusCode, err)
122 }
123
124 err = json.Unmarshal(raw, result)
125 if err != nil {
126 return errutils.NewUnmarshalError(req, resp.StatusCode, raw, err)
127 }
128
129 return result.GetError()
130 }
131
132 func newJSONRequest(ctx context.Context, method, endpoint string, payload any) (*http.Request, error) {
133 buf := new(bytes.Buffer)
134
135 if payload != nil {
136 err := json.NewEncoder(buf).Encode(payload)
137 if err != nil {
138 return nil, fmt.Errorf("failed to create request JSON body: %w", err)
139 }
140 }
141
142 req, err := http.NewRequestWithContext(ctx, method, endpoint, buf)
143 if err != nil {
144 return nil, fmt.Errorf("unable to create request: %w", err)
145 }
146
147 req.Header.Set("Accept", "application/json")
148
149 if payload != nil {
150 req.Header.Set("Content-Type", "application/json")
151 }
152
153 return req, nil
154 }
155