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  	querystring "github.com/google/go-querystring/query"
  16  )
  17  
  18  const defaultBaseURL = "https://pddimp.yandex.ru/api2/admin/dns"
  19  
  20  const successCode = "ok"
  21  
  22  const pddTokenHeader = "PddToken"
  23  
  24  type Client struct {
  25  	pddToken string
  26  
  27  	baseURL    *url.URL
  28  	HTTPClient *http.Client
  29  }
  30  
  31  func NewClient(pddToken string) (*Client, error) {
  32  	if pddToken == "" {
  33  		return nil, errors.New("PDD token is required")
  34  	}
  35  
  36  	baseURL, _ := url.Parse(defaultBaseURL)
  37  
  38  	return &Client{
  39  		pddToken:   pddToken,
  40  		baseURL:    baseURL,
  41  		HTTPClient: &http.Client{Timeout: 10 * time.Second},
  42  	}, nil
  43  }
  44  
  45  func (c *Client) AddRecord(ctx context.Context, payload Record) (*Record, error) {
  46  	endpoint := c.baseURL.JoinPath("add")
  47  
  48  	req, err := newRequest(ctx, http.MethodPost, endpoint, payload)
  49  	if err != nil {
  50  		return nil, err
  51  	}
  52  
  53  	r := AddResponse{}
  54  
  55  	err = c.do(req, &r)
  56  	if err != nil {
  57  		return nil, err
  58  	}
  59  
  60  	return r.Record, nil
  61  }
  62  
  63  func (c *Client) RemoveRecord(ctx context.Context, payload Record) (int, error) {
  64  	endpoint := c.baseURL.JoinPath("del")
  65  
  66  	req, err := newRequest(ctx, http.MethodPost, endpoint, payload)
  67  	if err != nil {
  68  		return 0, err
  69  	}
  70  
  71  	r := RemoveResponse{}
  72  
  73  	err = c.do(req, &r)
  74  	if err != nil {
  75  		return 0, err
  76  	}
  77  
  78  	return r.RecordID, nil
  79  }
  80  
  81  func (c *Client) GetRecords(ctx context.Context, domain string) ([]Record, error) {
  82  	endpoint := c.baseURL.JoinPath("list")
  83  
  84  	payload := struct {
  85  		Domain string `url:"domain"`
  86  	}{Domain: domain}
  87  
  88  	req, err := newRequest(ctx, http.MethodGet, endpoint, payload)
  89  	if err != nil {
  90  		return nil, err
  91  	}
  92  
  93  	r := ListResponse{}
  94  
  95  	err = c.do(req, &r)
  96  	if err != nil {
  97  		return nil, err
  98  	}
  99  
 100  	return r.Records, nil
 101  }
 102  
 103  func (c *Client) do(req *http.Request, result Response) error {
 104  	req.Header.Set(pddTokenHeader, c.pddToken)
 105  
 106  	resp, err := c.HTTPClient.Do(req)
 107  	if err != nil {
 108  		return errutils.NewHTTPDoError(req, err)
 109  	}
 110  
 111  	defer func() { _ = resp.Body.Close() }()
 112  
 113  	raw, err := io.ReadAll(resp.Body)
 114  	if err != nil {
 115  		return errutils.NewReadResponseError(req, resp.StatusCode, err)
 116  	}
 117  
 118  	err = json.Unmarshal(raw, result)
 119  	if err != nil {
 120  		return errutils.NewUnmarshalError(req, resp.StatusCode, raw, err)
 121  	}
 122  
 123  	if result.GetSuccess() != successCode {
 124  		return fmt.Errorf("error during operation: %s %s", result.GetSuccess(), result.GetError())
 125  	}
 126  
 127  	return nil
 128  }
 129  
 130  func newRequest(ctx context.Context, method string, endpoint *url.URL, payload any) (*http.Request, error) {
 131  	buf := new(bytes.Buffer)
 132  
 133  	if payload != nil {
 134  		switch method {
 135  		case http.MethodPost:
 136  			values, err := querystring.Values(payload)
 137  			if err != nil {
 138  				return nil, err
 139  			}
 140  
 141  			buf.WriteString(values.Encode())
 142  
 143  		case http.MethodGet:
 144  			values, err := querystring.Values(payload)
 145  			if err != nil {
 146  				return nil, err
 147  			}
 148  
 149  			endpoint.RawQuery = values.Encode()
 150  		}
 151  	}
 152  
 153  	req, err := http.NewRequestWithContext(ctx, method, endpoint.String(), buf)
 154  	if err != nil {
 155  		return nil, fmt.Errorf("unable to create request: %w", err)
 156  	}
 157  
 158  	if method == http.MethodPost {
 159  		req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
 160  	}
 161  
 162  	return req, nil
 163  }
 164