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