lego/providers/dns/desec/internal/client.go
2020-07-10 01:48:18 +02:00

229 lines
5.7 KiB
Go

package internal
import (
"bytes"
"encoding/json"
"fmt"
"io/ioutil"
"net/http"
"net/url"
"path"
)
const baseURL = "https://desec.io/api/v1/"
// Client deSec API client.
type Client struct {
HTTPClient *http.Client
BaseURL string
token string
}
// NewClient creates a new Client.
func NewClient(token string) *Client {
return &Client{
HTTPClient: http.DefaultClient,
BaseURL: baseURL,
token: token,
}
}
// GetTxtRRSet gets a RRSet.
// https://desec.readthedocs.io/en/latest/dns/rrsets.html#retrieving-a-specific-rrset
func (c *Client) GetTxtRRSet(domainName, subName string) (*RRSet, error) {
if subName == "" {
subName = "@"
}
endpoint, err := c.createEndpoint("domains", domainName, "rrsets", subName, "TXT")
if err != nil {
return nil, fmt.Errorf("failed to create endpoint: %w", err)
}
req, err := http.NewRequest(http.MethodGet, endpoint, nil)
if err != nil {
return nil, fmt.Errorf("failed to create request: %w", err)
}
req.Header.Set("Content-Type", "application/json")
req.Header.Set("Authorization", fmt.Sprintf("Token %s", c.token))
resp, err := c.HTTPClient.Do(req)
if err != nil {
return nil, fmt.Errorf("failed to call API: %w", err)
}
body, err := ioutil.ReadAll(resp.Body)
if err != nil {
return nil, fmt.Errorf("failed to read response body: %w", err)
}
if resp.StatusCode == http.StatusNotFound {
var notFound NotFound
err = json.Unmarshal(body, &notFound)
if err != nil {
return nil, fmt.Errorf("error: %d: %s", resp.StatusCode, string(body))
}
return nil, &notFound
}
if resp.StatusCode != http.StatusOK {
return nil, fmt.Errorf("error: %d: %s", resp.StatusCode, string(body))
}
var rrSet RRSet
err = json.Unmarshal(body, &rrSet)
if err != nil {
return nil, fmt.Errorf("failed to umarshal response body: %w", err)
}
return &rrSet, nil
}
// AddTxtRRSet creates a new RRSet.
// https://desec.readthedocs.io/en/latest/dns/rrsets.html#creating-a-tlsa-rrset
func (c *Client) AddTxtRRSet(rrSet RRSet) (*RRSet, error) {
endpoint, err := c.createEndpoint("domains", rrSet.Domain, "rrsets")
if err != nil {
return nil, fmt.Errorf("failed to create endpoint: %w", err)
}
raw, err := json.Marshal(rrSet)
if err != nil {
return nil, fmt.Errorf("failed to marshal request body: %w", err)
}
req, err := http.NewRequest(http.MethodPost, endpoint, bytes.NewReader(raw))
if err != nil {
return nil, fmt.Errorf("failed to create request: %w", err)
}
req.Header.Set("Content-Type", "application/json")
req.Header.Set("Authorization", fmt.Sprintf("Token %s", c.token))
resp, err := c.HTTPClient.Do(req)
if err != nil {
return nil, fmt.Errorf("failed to call API: %w", err)
}
body, err := ioutil.ReadAll(resp.Body)
if err != nil {
return nil, fmt.Errorf("failed to read response body: %w", err)
}
if resp.StatusCode != http.StatusCreated {
return nil, fmt.Errorf("error: %d: %s", resp.StatusCode, string(body))
}
var newRRSet RRSet
err = json.Unmarshal(body, &newRRSet)
if err != nil {
return nil, fmt.Errorf("failed to umarshal response body: %w", err)
}
return &newRRSet, nil
}
// UpdateTxtRRSet updates RRSet records.
// https://desec.readthedocs.io/en/latest/dns/rrsets.html#modifying-an-rrset
func (c *Client) UpdateTxtRRSet(domainName, subName string, records []string) (*RRSet, error) {
if subName == "" {
subName = "@"
}
endpoint, err := c.createEndpoint("domains", domainName, "rrsets", subName, "TXT")
if err != nil {
return nil, fmt.Errorf("failed to create endpoint: %w", err)
}
raw, err := json.Marshal(RRSet{Records: records})
if err != nil {
return nil, fmt.Errorf("failed to marshal request body: %w", err)
}
req, err := http.NewRequest(http.MethodPatch, endpoint, bytes.NewReader(raw))
if err != nil {
return nil, fmt.Errorf("failed to create request: %w", err)
}
req.Header.Set("Content-Type", "application/json")
req.Header.Set("Authorization", fmt.Sprintf("Token %s", c.token))
resp, err := c.HTTPClient.Do(req)
if err != nil {
return nil, fmt.Errorf("failed to call API: %w", err)
}
// when a RRSet is deleted (empty records)
if resp.StatusCode == http.StatusNoContent {
return nil, nil
}
body, err := ioutil.ReadAll(resp.Body)
if err != nil {
return nil, fmt.Errorf("failed to read response body: %w", err)
}
if resp.StatusCode != http.StatusOK {
return nil, fmt.Errorf("error: %d: %s", resp.StatusCode, string(body))
}
var updatedRRSet RRSet
err = json.Unmarshal(body, &updatedRRSet)
if err != nil {
return nil, fmt.Errorf("failed to umarshal response body: %w", err)
}
return &updatedRRSet, nil
}
// DeleteTxtRRSet deletes a RRset.
// https://desec.readthedocs.io/en/latest/dns/rrsets.html#deleting-an-rrset
func (c *Client) DeleteTxtRRSet(domainName, subName string) error {
if subName == "" {
subName = "@"
}
endpoint, err := c.createEndpoint("domains", domainName, "rrsets", subName, "TXT")
if err != nil {
return fmt.Errorf("failed to create endpoint: %w", err)
}
req, err := http.NewRequest(http.MethodDelete, endpoint, nil)
if err != nil {
return fmt.Errorf("failed to create request: %w", err)
}
req.Header.Set("Content-Type", "application/json")
req.Header.Set("Authorization", fmt.Sprintf("Token %s", c.token))
resp, err := c.HTTPClient.Do(req)
if err != nil {
return fmt.Errorf("failed to call API: %w", err)
}
if resp.StatusCode != http.StatusNoContent {
body, _ := ioutil.ReadAll(resp.Body)
return fmt.Errorf("error: %d: %s", resp.StatusCode, string(body))
}
return nil
}
func (c *Client) createEndpoint(parts ...string) (string, error) {
base, err := url.Parse(c.BaseURL)
if err != nil {
return "", err
}
endpoint, err := base.Parse(path.Join(base.Path, path.Join(parts...)))
if err != nil {
return "", err
}
endpoint.Path += "/"
return endpoint.String(), nil
}