mirror of
https://github.com/psviderski/uncloud.git
synced 2026-10-06 13:18:58 +00:00
feat(dns): allow setting an externally managed cluster domain (uc dns set)
* feat: add uc dns set
This allows manual setting of the cluster's domain name. I use this to
make the local cluster admin aware that this has been done.
(and then manually update the external dns)
```
% uc dns set ctrl.u.science.ru.nl
Set cluster domain: ctrl.u.science.ru.nl
Deploy the Caddy reverse proxy service ('uc caddy deploy') to enable internet access to your services via the reserved or your custom domain.
% uc dns show
ctrl.u.science.ru.nl
% uc dns set ctrl.u.science.ru.nl
Error: domain already reserved
% uc dns release
Released cluster domain: ctrl.u.science.ru.nl
```
See: #37
Signed-off-by: Miek Gieben <miek@miek.nl>
* docs
Signed-off-by: Miek Gieben <miek@miek.nl>
* check validatity
Signed-off-by: Miek Gieben <miek@miek.nl>
* No need to call out to update anything, as set doesnt use uncloud dns
Signed-off-by: Miek Gieben <miek@miek.nl>
* Ignore domain when there is no endpoint
Prevents this error, when you know what you're doing:
Error: failed to update DNS records pointing to caddy service: create cluster domain records in Uncloud DNS: rpc error: code = Unknown desc = Post "/domains/ctrl.u.science.ru.nl/records": unsupported protocol scheme ""
Signed-off-by: Miek Gieben <miek@miek.nl>
* from main
Signed-off-by: Miek Gieben <miek@miek.nl>
* mise proto && make cli-docs
Signed-off-by: Miek Gieben <miek@miek.nl>
* Rebase and and generate protos again
Signed-off-by: Miek Gieben <miek@miek.nl>
* Rebase and fix
Signed-off-by: Miek Gieben <miek@miek.nl>
* feat(dns): allow setting an externally managed cluster domain
---------
Signed-off-by: Miek Gieben <miek@miek.nl>
Co-authored-by: Pasha Sviderski <me@psviderski.name>
This commit is contained in:
1 parent
3482eeefab
commit
417e402c73
21 files changed
+463
-133
No files matched your search
@@ -4,20 +4,24 @@ import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"strings"
|
||||
|
||||
"github.com/miekg/dns"
|
||||
"github.com/psviderski/uncloud/api/pb"
|
||||
"github.com/psviderski/uncloud/internal/dns"
|
||||
undns "github.com/psviderski/uncloud/internal/dns"
|
||||
"github.com/psviderski/uncloud/internal/machine/store"
|
||||
"google.golang.org/grpc/codes"
|
||||
"google.golang.org/grpc/status"
|
||||
"google.golang.org/protobuf/proto"
|
||||
"google.golang.org/protobuf/types/known/emptypb"
|
||||
)
|
||||
|
||||
// uncloudDNSKey is the key used to store the details of the reserved domain in the store.
|
||||
// uncloudDNSKey stores the cluster domain and, when reserved, its Uncloud DNS credentials.
|
||||
const uncloudDNSKey = "uncloud_dns"
|
||||
|
||||
type uncloudDNSDomain struct {
|
||||
// Endpoint is the API endpoint of the Uncloud DNS service where the domain is reserved.
|
||||
// An empty endpoint means the domain is managed externally.
|
||||
Endpoint string
|
||||
Name string
|
||||
// TODO: encrypt the token in the store.
|
||||
@@ -34,14 +38,14 @@ func (c *Cluster) ReserveDomain(ctx context.Context, req *pb.ReserveDomainReques
|
||||
}
|
||||
|
||||
if _, err := c.storedDomain(ctx); err == nil {
|
||||
return nil, status.Errorf(codes.AlreadyExists, "domain already reserved")
|
||||
return nil, status.Error(codes.AlreadyExists, "cluster domain already configured")
|
||||
} else {
|
||||
if s := status.Convert(err); s.Code() != codes.NotFound {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
dnsClient := dns.NewClient()
|
||||
dnsClient := undns.NewClient()
|
||||
name, token, err := dnsClient.ReserveDomain(req.Endpoint)
|
||||
if err != nil {
|
||||
return nil, status.Error(codes.Internal, err.Error())
|
||||
@@ -60,7 +64,7 @@ func (c *Cluster) ReserveDomain(ctx context.Context, req *pb.ReserveDomainReques
|
||||
return nil, status.Errorf(codes.Internal, "store reserved domain: %v", err)
|
||||
}
|
||||
|
||||
return &pb.Domain{Name: name}, nil
|
||||
return &pb.Domain{Name: name, Reserved: proto.Bool(true)}, nil
|
||||
}
|
||||
|
||||
func (c *Cluster) GetDomain(ctx context.Context, _ *emptypb.Empty) (*pb.Domain, error) {
|
||||
@@ -73,7 +77,7 @@ func (c *Cluster) GetDomain(ctx context.Context, _ *emptypb.Empty) (*pb.Domain,
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &pb.Domain{Name: domain.Name}, nil
|
||||
return &pb.Domain{Name: domain.Name, Reserved: proto.Bool(domain.Endpoint != "")}, nil
|
||||
}
|
||||
|
||||
func (c *Cluster) storedDomain(ctx context.Context) (uncloudDNSDomain, error) {
|
||||
@@ -103,13 +107,63 @@ func (c *Cluster) ReleaseDomain(ctx context.Context, _ *emptypb.Empty) (*pb.Doma
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if domain.Endpoint == "" {
|
||||
return nil, status.Error(codes.FailedPrecondition,
|
||||
"cluster domain is set manually, use 'uc dns set \"\"' to unset it")
|
||||
}
|
||||
|
||||
if err = c.store.Delete(ctx, uncloudDNSKey); err != nil {
|
||||
return nil, status.Errorf(codes.Internal, "delete domain from store: %v", err)
|
||||
}
|
||||
// TODO: implement and call Uncloud DNS endpoint to release/delete the domain.
|
||||
|
||||
return &pb.Domain{Name: domain.Name}, nil
|
||||
return &pb.Domain{Name: domain.Name, Reserved: proto.Bool(true)}, nil
|
||||
}
|
||||
|
||||
func (c *Cluster) SetDomain(ctx context.Context, req *pb.SetDomainRequest) (*emptypb.Empty, error) {
|
||||
if err := c.checkReady(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
name := req.GetName()
|
||||
if name != "" {
|
||||
if labels, ok := dns.IsDomainName(name); !ok || labels < 2 {
|
||||
return nil, status.Errorf(codes.InvalidArgument,
|
||||
"invalid cluster domain '%s': must be a valid domain name with at least two labels", name)
|
||||
}
|
||||
}
|
||||
name = strings.ToLower(strings.TrimSuffix(name, "."))
|
||||
|
||||
existing, err := c.storedDomain(ctx)
|
||||
if err != nil && status.Code(err) != codes.NotFound {
|
||||
return nil, err
|
||||
}
|
||||
if name == "" {
|
||||
if err == nil {
|
||||
if existing.Endpoint != "" {
|
||||
return nil, status.Error(codes.FailedPrecondition,
|
||||
"cluster domain is reserved in Uncloud DNS, use 'uc dns release' to release it")
|
||||
}
|
||||
if err := c.store.Delete(ctx, uncloudDNSKey); err != nil {
|
||||
return nil, status.Errorf(codes.Internal, "unset cluster domain: %v", err)
|
||||
}
|
||||
}
|
||||
return &emptypb.Empty{}, nil
|
||||
}
|
||||
if err == nil {
|
||||
return nil, status.Error(codes.AlreadyExists, "cluster domain already configured")
|
||||
}
|
||||
|
||||
domain := uncloudDNSDomain{Name: name}
|
||||
domainJSON, err := json.Marshal(domain)
|
||||
if err != nil {
|
||||
return nil, status.Errorf(codes.Internal, "marshal set domain for store: %v", err)
|
||||
}
|
||||
if err = c.store.Put(ctx, uncloudDNSKey, domainJSON); err != nil {
|
||||
return nil, status.Errorf(codes.Internal, "store set domain: %v", err)
|
||||
}
|
||||
|
||||
return &emptypb.Empty{}, nil
|
||||
}
|
||||
|
||||
func (c *Cluster) CreateDomainRecords(
|
||||
@@ -124,12 +178,16 @@ func (c *Cluster) CreateDomainRecords(
|
||||
return nil, err
|
||||
}
|
||||
|
||||
dnsClient := dns.NewClient()
|
||||
recordsReq := make([]dns.RecordRequest, len(req.Records))
|
||||
if domain.Endpoint == "" {
|
||||
return nil, status.Error(codes.FailedPrecondition, "cluster domain is not reserved in Uncloud DNS")
|
||||
}
|
||||
|
||||
dnsClient := undns.NewClient()
|
||||
recordsReq := make([]undns.RecordRequest, len(req.Records))
|
||||
for i, r := range req.Records {
|
||||
recordsReq[i] = dns.RecordRequest{
|
||||
recordsReq[i] = undns.RecordRequest{
|
||||
Name: r.Name,
|
||||
Type: dns.RecordType(r.Type.String()),
|
||||
Type: undns.RecordType(r.Type.String()),
|
||||
Values: r.Values,
|
||||
}
|
||||
}
|
||||
@@ -149,9 +207,9 @@ func (c *Cluster) CreateDomainRecords(
|
||||
}
|
||||
|
||||
switch r.Type {
|
||||
case dns.RecordTypeA:
|
||||
case undns.RecordTypeA:
|
||||
resp.Records[i].Type = pb.DNSRecord_A
|
||||
case dns.RecordTypeAAAA:
|
||||
case undns.RecordTypeAAAA:
|
||||
resp.Records[i].Type = pb.DNSRecord_AAAA
|
||||
}
|
||||
}
|
||||
|
||||
Reference in new issue
Block a user