client.go raw
1 package internal
2
3 import (
4 "bytes"
5 "context"
6 "encoding/json"
7 "fmt"
8 "io"
9 "net/http"
10 "net/url"
11 "time"
12
13 "github.com/go-acme/lego/v4/providers/dns/internal/errutils"
14 "github.com/go-acme/lego/v4/providers/dns/internal/useragent"
15 )
16
17 const defaultBaseURL = "https://api.gigahost.no/api/v0"
18
19 const authorizationHeader = "Authorization"
20
21 // Client the Gigahost.no API client.
22 type Client struct {
23 BaseURL *url.URL
24 HTTPClient *http.Client
25 }
26
27 // NewClient creates a new Client.
28 func NewClient() *Client {
29 baseURL, _ := url.Parse(defaultBaseURL)
30
31 return &Client{
32 BaseURL: baseURL,
33 HTTPClient: &http.Client{Timeout: 10 * time.Second},
34 }
35 }
36
37 // GetZones returns all zones.
38 func (c *Client) GetZones(ctx context.Context) ([]Zone, error) {
39 endpoint := c.BaseURL.JoinPath("dns", "zones")
40
41 req, err := newJSONRequest(ctx, http.MethodGet, endpoint, nil)
42 if err != nil {
43 return nil, err
44 }
45
46 var result APIResponse[[]Zone]
47
48 err = c.do(ctx, req, &result)
49 if err != nil {
50 return nil, err
51 }
52
53 return result.Data, nil
54 }
55
56 // GetZoneRecords returns all records for a zone.
57 func (c *Client) GetZoneRecords(ctx context.Context, zoneID string) ([]Record, error) {
58 endpoint := c.BaseURL.JoinPath("dns", "zones", zoneID, "records")
59
60 req, err := newJSONRequest(ctx, http.MethodGet, endpoint, nil)
61 if err != nil {
62 return nil, err
63 }
64
65 var result APIResponse[[]Record]
66
67 err = c.do(ctx, req, &result)
68 if err != nil {
69 return nil, err
70 }
71
72 return result.Data, nil
73 }
74
75 // CreateNewRecord creates a new record.
76 func (c *Client) CreateNewRecord(ctx context.Context, zoneID string, record Record) error {
77 endpoint := c.BaseURL.JoinPath("dns", "zones", zoneID, "records")
78
79 req, err := newJSONRequest(ctx, http.MethodPost, endpoint, record)
80 if err != nil {
81 return err
82 }
83
84 return c.do(ctx, req, nil)
85 }
86
87 // DeleteRecord deletes a record.
88 func (c *Client) DeleteRecord(ctx context.Context, zoneID, recordID, name, recordType string) error {
89 endpoint := c.BaseURL.JoinPath("dns", "zones", zoneID, "records", recordID)
90
91 query := endpoint.Query()
92 query.Set("name", name)
93 query.Set("type", recordType)
94 endpoint.RawQuery = query.Encode()
95
96 req, err := newJSONRequest(ctx, http.MethodDelete, endpoint, nil)
97 if err != nil {
98 return err
99 }
100
101 return c.do(ctx, req, nil)
102 }
103
104 func (c *Client) do(ctx context.Context, req *http.Request, result any) error {
105 useragent.SetHeader(req.Header)
106
107 req.Header.Set(authorizationHeader, "Bearer "+getToken(ctx))
108
109 resp, err := c.HTTPClient.Do(req)
110 if err != nil {
111 return errutils.NewHTTPDoError(req, err)
112 }
113
114 defer func() { _ = resp.Body.Close() }()
115
116 if resp.StatusCode/100 != 2 {
117 return parseError(req, resp)
118 }
119
120 if result == nil {
121 return nil
122 }
123
124 raw, err := io.ReadAll(resp.Body)
125 if err != nil {
126 return errutils.NewReadResponseError(req, resp.StatusCode, err)
127 }
128
129 err = json.Unmarshal(raw, result)
130 if err != nil {
131 return errutils.NewUnmarshalError(req, resp.StatusCode, raw, err)
132 }
133
134 return nil
135 }
136
137 func newJSONRequest(ctx context.Context, method string, endpoint *url.URL, payload any) (*http.Request, error) {
138 buf := new(bytes.Buffer)
139
140 if payload != nil {
141 err := json.NewEncoder(buf).Encode(payload)
142 if err != nil {
143 return nil, fmt.Errorf("failed to create request JSON body: %w", err)
144 }
145 }
146
147 req, err := http.NewRequestWithContext(ctx, method, endpoint.String(), buf)
148 if err != nil {
149 return nil, fmt.Errorf("unable to create request: %w", err)
150 }
151
152 req.Header.Set("Accept", "application/json")
153
154 if payload != nil {
155 req.Header.Set("Content-Type", "application/json")
156 }
157
158 return req, nil
159 }
160
161 func parseError(req *http.Request, resp *http.Response) error {
162 raw, _ := io.ReadAll(resp.Body)
163
164 var errAPI APIError
165
166 err := json.Unmarshal(raw, &errAPI)
167 if err != nil {
168 return errutils.NewUnexpectedStatusCodeError(req, resp.StatusCode, raw)
169 }
170
171 return &errAPI
172 }
173