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 "golang.org/x/oauth2"
15 )
16
17 const defaultBaseURL = "https://api.netlify.com/api/v1"
18
19 // Client Netlify API client.
20 type Client struct {
21 baseURL *url.URL
22 httpClient *http.Client
23 }
24
25 // NewClient creates a new Client.
26 func NewClient(hc *http.Client) *Client {
27 baseURL, _ := url.Parse(defaultBaseURL)
28
29 if hc == nil {
30 hc = &http.Client{Timeout: 5 * time.Second}
31 }
32
33 return &Client{baseURL: baseURL, httpClient: hc}
34 }
35
36 // GetRecords gets a DNS records.
37 func (c *Client) GetRecords(ctx context.Context, zoneID string) ([]DNSRecord, error) {
38 endpoint := c.baseURL.JoinPath("dns_zones", zoneID, "dns_records")
39
40 req, err := newJSONRequest(ctx, http.MethodGet, endpoint, nil)
41 if err != nil {
42 return nil, fmt.Errorf("failed to create request: %w", err)
43 }
44
45 resp, err := c.httpClient.Do(req)
46 if err != nil {
47 return nil, errutils.NewHTTPDoError(req, err)
48 }
49
50 defer func() { _ = resp.Body.Close() }()
51
52 if resp.StatusCode != http.StatusOK {
53 return nil, errutils.NewUnexpectedResponseStatusCodeError(req, resp)
54 }
55
56 raw, err := io.ReadAll(resp.Body)
57 if err != nil {
58 return nil, errutils.NewReadResponseError(req, resp.StatusCode, err)
59 }
60
61 var records []DNSRecord
62
63 err = json.Unmarshal(raw, &records)
64 if err != nil {
65 return nil, errutils.NewUnmarshalError(req, resp.StatusCode, raw, err)
66 }
67
68 return records, nil
69 }
70
71 // CreateRecord creates a DNS records.
72 func (c *Client) CreateRecord(ctx context.Context, zoneID string, record DNSRecord) (*DNSRecord, error) {
73 endpoint := c.baseURL.JoinPath("dns_zones", zoneID, "dns_records")
74
75 req, err := newJSONRequest(ctx, http.MethodPost, endpoint, record)
76 if err != nil {
77 return nil, fmt.Errorf("failed to create request: %w", err)
78 }
79
80 resp, err := c.httpClient.Do(req)
81 if err != nil {
82 return nil, errutils.NewHTTPDoError(req, err)
83 }
84
85 defer func() { _ = resp.Body.Close() }()
86
87 if resp.StatusCode != http.StatusCreated {
88 return nil, errutils.NewUnexpectedResponseStatusCodeError(req, resp)
89 }
90
91 raw, err := io.ReadAll(resp.Body)
92 if err != nil {
93 return nil, errutils.NewReadResponseError(req, resp.StatusCode, err)
94 }
95
96 var recordResp DNSRecord
97
98 err = json.Unmarshal(raw, &recordResp)
99 if err != nil {
100 return nil, errutils.NewUnmarshalError(req, resp.StatusCode, raw, err)
101 }
102
103 return &recordResp, nil
104 }
105
106 // RemoveRecord removes a DNS records.
107 func (c *Client) RemoveRecord(ctx context.Context, zoneID, recordID string) error {
108 endpoint := c.baseURL.JoinPath("dns_zones", zoneID, "dns_records", recordID)
109
110 req, err := newJSONRequest(ctx, http.MethodDelete, endpoint, nil)
111 if err != nil {
112 return err
113 }
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 != http.StatusNoContent {
123 return errutils.NewUnexpectedResponseStatusCodeError(req, resp)
124 }
125
126 return nil
127 }
128
129 func newJSONRequest(ctx context.Context, method string, endpoint *url.URL, payload any) (*http.Request, error) {
130 buf := new(bytes.Buffer)
131
132 if payload != nil {
133 err := json.NewEncoder(buf).Encode(payload)
134 if err != nil {
135 return nil, fmt.Errorf("failed to create request JSON body: %w", err)
136 }
137 }
138
139 req, err := http.NewRequestWithContext(ctx, method, endpoint.String(), buf)
140 if err != nil {
141 return nil, fmt.Errorf("unable to create request: %w", err)
142 }
143
144 req.Header.Set("Accept", "application/json")
145
146 if payload != nil {
147 req.Header.Set("Content-Type", "application/json; charset=utf-8")
148 }
149
150 return req, nil
151 }
152
153 func OAuthStaticAccessToken(client *http.Client, accessToken string) *http.Client {
154 if client == nil {
155 client = &http.Client{Timeout: 5 * time.Second}
156 }
157
158 client.Transport = &oauth2.Transport{
159 Source: oauth2.StaticTokenSource(&oauth2.Token{AccessToken: accessToken}),
160 Base: client.Transport,
161 }
162
163 return client
164 }
165