From 2d84059d7f44a442045e6c1c7baa59e35b34bbe5 Mon Sep 17 00:00:00 2001 From: Pavel Sviderski Date: Mon, 24 Feb 2025 20:33:08 +1000 Subject: [PATCH] feat(dns): client for creating ingress records --- internal/cli/client/dns.go | 33 +++++++++++++++++++++++++++++ internal/dns/api.go | 18 ++++++++++++++++ internal/dns/client.go | 43 ++++++++++++++++++++++++++++++++++++++ 3 files changed, 94 insertions(+) create mode 100644 internal/cli/client/dns.go diff --git a/internal/cli/client/dns.go b/internal/cli/client/dns.go new file mode 100644 index 00000000..b13c6dee --- /dev/null +++ b/internal/cli/client/dns.go @@ -0,0 +1,33 @@ +package client + +import ( + "context" + "fmt" + "uncloud/internal/machine/api/pb" +) + +// TODO: +func (cli *Client) CreateIngressRecords(ctx context.Context, serviceID string) ([]*pb.DNSRecord, error) { + // TODO: + // - Inspect the service and get the list of machines it runs on. + // - For each machine get the machine's public IP address(s). + // - Update the wildcard DNS record for the service with the public IP addresses (call Cluster API). + + req := &pb.CreateDomainRecordsRequest{ + Records: []*pb.DNSRecord{ + { + Name: "*", + Type: pb.DNSRecord_A, + // TODO: Get the public IP addresses of the machines running Caddy containers. + Values: []string{"1.2.3.4", "5.6.7.8"}, + }, + // TODO: Add AAAA record with routable IPv6 addresses of machines running Caddy containers. + }, + } + resp, err := cli.CreateDomainRecords(ctx, req) + if err != nil { + return nil, fmt.Errorf("create cluster domain records in Uncloud DNS: %w", err) + } + + return resp.Records, nil +} diff --git a/internal/dns/api.go b/internal/dns/api.go index 6d8cfc01..ec160cc6 100644 --- a/internal/dns/api.go +++ b/internal/dns/api.go @@ -1,10 +1,28 @@ package dns +const ( + RecordTypeA RecordType = "A" + RecordTypeAAAA RecordType = "AAAA" +) + +type RecordType string + type DomainResponse struct { Name string `json:"name,omitempty"` Token string `json:"token,omitempty"` } +type RecordRequest struct { + Name string `json:"name,omitempty"` + Type RecordType `json:"type,omitempty"` + Values []string `json:"values,omitempty"` +} + +type RecordResponse struct { + RecordRequest + FQDN string `json:"fqdn,omitempty"` +} + type AuthErrorResponse struct { Status int `json:"status,omitempty"` Message string `json:"msg,omitempty"` diff --git a/internal/dns/client.go b/internal/dns/client.go index 146d6b27..3f253bab 100644 --- a/internal/dns/client.go +++ b/internal/dns/client.go @@ -1,6 +1,7 @@ package dns import ( + "bytes" "encoding/json" "errors" "fmt" @@ -17,6 +18,10 @@ 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. @@ -55,6 +60,35 @@ func (c *client) ReserveDomain(endpoint string) (string, string, error) { return domain, resp.Token, nil } +func (c *client) CreateRecords(endpoint, domain, token string, records []RecordRequest) ([]RecordResponse, error) { + // TODO: update Uncloud DNS service to not use a leading dot for domain names. + if !strings.HasPrefix(domain, ".") { + domain = "." + domain + } + 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 { @@ -116,3 +150,12 @@ func (c *client) do(req *http.Request, responseBody any) error { 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 +}