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 "time"
13
14 "github.com/go-acme/lego/v4/providers/dns/internal/errutils"
15 )
16
17 const baseURL = "https://public-api.sonic.net/dyndns"
18
19 // Client Sonic client.
20 type Client struct {
21 userID string
22 apiKey string
23
24 baseURL string
25 HTTPClient *http.Client
26 }
27
28 // NewClient creates a Client.
29 func NewClient(userID, apiKey string) (*Client, error) {
30 if userID == "" || apiKey == "" {
31 return nil, errors.New("credentials are missing")
32 }
33
34 return &Client{
35 userID: userID,
36 apiKey: apiKey,
37 baseURL: baseURL,
38 HTTPClient: &http.Client{Timeout: 10 * time.Second},
39 }, nil
40 }
41
42 // SetRecord creates or updates a TXT records.
43 // Sonic does not provide a delete record API endpoint.
44 // https://public-api.sonic.net/dyndns#updating_or_adding_host_records
45 func (c *Client) SetRecord(ctx context.Context, hostname, value string, ttl int) error {
46 payload := &Record{
47 UserID: c.userID,
48 APIKey: c.apiKey,
49 Hostname: hostname,
50 Value: value,
51 TTL: ttl,
52 Type: "TXT",
53 }
54
55 body, err := json.Marshal(payload)
56 if err != nil {
57 return fmt.Errorf("failed to create request JSON body: %w", err)
58 }
59
60 endpoint, err := url.JoinPath(c.baseURL, "host")
61 if err != nil {
62 return err
63 }
64
65 req, err := http.NewRequestWithContext(ctx, http.MethodPut, endpoint, bytes.NewReader(body))
66 if err != nil {
67 return fmt.Errorf("unable to create request: %w", err)
68 }
69
70 req.Header.Set("Accept", "application/json")
71 req.Header.Set("content-type", "application/json")
72
73 resp, err := c.HTTPClient.Do(req)
74 if err != nil {
75 return errutils.NewHTTPDoError(req, err)
76 }
77
78 defer func() { _ = resp.Body.Close() }()
79
80 raw, err := io.ReadAll(resp.Body)
81 if err != nil {
82 return errutils.NewReadResponseError(req, resp.StatusCode, err)
83 }
84
85 r := APIResponse{}
86
87 err = json.Unmarshal(raw, &r)
88 if err != nil {
89 return errutils.NewUnmarshalError(req, resp.StatusCode, raw, err)
90 }
91
92 if r.Result != 200 {
93 return fmt.Errorf("API response code: %d, %s", r.Result, r.Message)
94 }
95
96 return nil
97 }
98