mirror of
https://github.com/psviderski/uncloud.git
synced 2026-08-26 11:03:34 +00:00
153 lines
3.7 KiB
Go
153 lines
3.7 KiB
Go
package dns
|
|
|
|
import (
|
|
"bytes"
|
|
"encoding/json"
|
|
"errors"
|
|
"fmt"
|
|
"io"
|
|
"log/slog"
|
|
"net/http"
|
|
)
|
|
|
|
// The dns package code is based on https://github.com/acorn-io/runtime/blob/main/pkg/dns.
|
|
|
|
// Client handles interactions with the Uncloud DNS API service.
|
|
type Client interface {
|
|
// ReserveDomain calls Uncloud DNS to reserve a new domain. It returns the domain, a token for authentication,
|
|
// and an error.
|
|
ReserveDomain(endpoint string) (string, string, error)
|
|
|
|
// CreateRecords calls Uncloud DNS to create or update DNS records based on the supplied RecordRequests
|
|
// for the specified domain.
|
|
CreateRecords(endpoint, domain, token string, records []RecordRequest) ([]RecordResponse, error)
|
|
}
|
|
|
|
// ErrAuthNoDomain indicates that a request failed authentication because the domain was not found.
|
|
// If encountered, a new domain needs to be reserved.
|
|
var ErrAuthNoDomain = errors.New("the supplied domain failed authentication")
|
|
|
|
// NewClient creates a new AcornDNS client
|
|
func NewClient() Client {
|
|
return &client{
|
|
c: http.DefaultClient,
|
|
}
|
|
}
|
|
|
|
type client struct {
|
|
c *http.Client
|
|
}
|
|
|
|
func (c *client) ReserveDomain(endpoint string) (string, string, error) {
|
|
url := fmt.Sprintf("%s/%s", endpoint, "domains")
|
|
|
|
req, err := c.request(http.MethodPost, url, nil, "")
|
|
if err != nil {
|
|
return "", "", err
|
|
}
|
|
|
|
resp := &DomainResponse{}
|
|
err = c.do(req, resp)
|
|
if err != nil {
|
|
return "", "", err
|
|
}
|
|
|
|
return resp.Name, resp.Token, nil
|
|
}
|
|
|
|
func (c *client) CreateRecords(endpoint, domain, token string, records []RecordRequest) ([]RecordResponse, error) {
|
|
url := fmt.Sprintf("%s/domains/%s/records", endpoint, domain)
|
|
|
|
var resp []RecordResponse
|
|
for _, recordRequest := range records {
|
|
body, err := jsonBody(recordRequest)
|
|
if err != nil {
|
|
return resp, err
|
|
}
|
|
|
|
req, err := c.request(http.MethodPost, url, body, token)
|
|
if err != nil {
|
|
return resp, err
|
|
}
|
|
|
|
var recordResp RecordResponse
|
|
if err = c.do(req, &recordResp); err != nil {
|
|
return resp, err
|
|
}
|
|
resp = append(resp, recordResp)
|
|
}
|
|
|
|
return resp, nil
|
|
}
|
|
|
|
func (c *client) request(method string, url string, body io.Reader, token string) (*http.Request, error) {
|
|
req, err := http.NewRequest(method, url, body)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
req.Header.Add("Content-Type", "application/json")
|
|
|
|
if token != "" {
|
|
bearer := "Bearer " + token
|
|
req.Header.Add("Authorization", bearer)
|
|
}
|
|
|
|
return req, nil
|
|
}
|
|
|
|
func (c *client) do(req *http.Request, responseBody any) error {
|
|
slog.Debug("Making request to DNS service.", "method", req.Method, "url", req.URL)
|
|
|
|
resp, err := c.c.Do(req)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
slog.Debug("Response code for request to DNS service.",
|
|
"method", req.Method, "url", req.URL, "code", resp.StatusCode)
|
|
// When err is nil, resp contains a non-nil resp.Body which must be closed.
|
|
defer resp.Body.Close()
|
|
|
|
body, err := io.ReadAll(resp.Body)
|
|
if err != nil {
|
|
return fmt.Errorf("read response body: %w", err)
|
|
}
|
|
|
|
if resp.StatusCode == http.StatusUnauthorized {
|
|
var authError AuthErrorResponse
|
|
|
|
err = json.Unmarshal(body, &authError)
|
|
if err != nil {
|
|
return fmt.Errorf("unmarshal auth error response: %w", err)
|
|
}
|
|
|
|
if authError.Data.NoDomain {
|
|
return ErrAuthNoDomain
|
|
}
|
|
|
|
return errors.New("authentication failed")
|
|
}
|
|
|
|
if code := resp.StatusCode; code < 200 || code > 300 {
|
|
return fmt.Errorf("unexpected response status code: %d", code)
|
|
}
|
|
|
|
if responseBody != nil {
|
|
err = json.Unmarshal(body, responseBody)
|
|
if err != nil {
|
|
return fmt.Errorf("unmarshal response body (%s): %w", string(body), err)
|
|
}
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func jsonBody(payload any) (io.Reader, error) {
|
|
buf := &bytes.Buffer{}
|
|
err := json.NewEncoder(buf).Encode(payload)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
return buf, nil
|
|
}
|