From 417e402c73edb3fef6f8b9962472d0bc0794122f Mon Sep 17 00:00:00 2001 From: Miek Gieben Date: Fri, 25 Sep 2026 08:36:01 +0200 Subject: [PATCH] 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 * docs Signed-off-by: Miek Gieben * check validatity Signed-off-by: Miek Gieben * No need to call out to update anything, as set doesnt use uncloud dns Signed-off-by: Miek Gieben * 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 * from main Signed-off-by: Miek Gieben * mise proto && make cli-docs Signed-off-by: Miek Gieben * Rebase and and generate protos again Signed-off-by: Miek Gieben * Rebase and fix Signed-off-by: Miek Gieben * feat(dns): allow setting an externally managed cluster domain --------- Signed-off-by: Miek Gieben Co-authored-by: Pasha Sviderski --- api/pb/cluster.pb.go | 273 ++++++++++++------ api/pb/cluster.proto | 9 + api/pb/cluster_grpc.pb.go | 38 +++ cmd/uc/caddy/deploy.go | 6 +- cmd/uc/dns/release.go | 1 + cmd/uc/dns/reserve.go | 3 +- cmd/uc/dns/root.go | 9 +- cmd/uc/dns/set.go | 69 +++++ cmd/uc/dns/show.go | 4 +- internal/machine/cluster/dns.go | 84 +++++- pkg/api/client.go | 2 +- pkg/api/dns.go | 8 + pkg/client/compose/deploy.go | 4 +- pkg/client/deploy/deploy.go | 4 +- pkg/client/dns.go | 27 +- website/docs/9-cli-reference/uc.md | 2 +- website/docs/9-cli-reference/uc_dns.md | 7 +- .../docs/9-cli-reference/uc_dns_release.md | 2 +- .../docs/9-cli-reference/uc_dns_reserve.md | 2 +- website/docs/9-cli-reference/uc_dns_set.md | 40 +++ website/docs/9-cli-reference/uc_dns_show.md | 2 +- 21 files changed, 463 insertions(+), 133 deletions(-) create mode 100644 cmd/uc/dns/set.go create mode 100644 pkg/api/dns.go create mode 100644 website/docs/9-cli-reference/uc_dns_set.md diff --git a/api/pb/cluster.pb.go b/api/pb/cluster.pb.go index 5efdf422..8295a382 100644 --- a/api/pb/cluster.pb.go +++ b/api/pb/cluster.pb.go @@ -124,7 +124,7 @@ func (x DNSRecord_RecordType) Number() protoreflect.EnumNumber { // Deprecated: Use DNSRecord_RecordType.Descriptor instead. func (DNSRecord_RecordType) EnumDescriptor() ([]byte, []int) { - return file_api_pb_cluster_proto_rawDescGZIP(), []int{9, 0} + return file_api_pb_cluster_proto_rawDescGZIP(), []int{10, 0} } type AddMachineRequest struct { @@ -392,6 +392,9 @@ type Domain struct { unknownFields protoimpl.UnknownFields Name string `protobuf:"bytes,1,opt,name=name,proto3" json:"name,omitempty"` + // Whether the domain is reserved in Uncloud DNS. Older daemons (<0.21) omit this field + // and only support reserved domains, so clients should treat absence as true. + Reserved *bool `protobuf:"varint,2,opt,name=reserved,proto3,oneof" json:"reserved,omitempty"` } func (x *Domain) Reset() { @@ -433,6 +436,13 @@ func (x *Domain) GetName() string { return "" } +func (x *Domain) GetReserved() bool { + if x != nil && x.Reserved != nil { + return *x.Reserved + } + return false +} + type ReserveDomainRequest struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache @@ -480,6 +490,54 @@ func (x *ReserveDomainRequest) GetEndpoint() string { return "" } +type SetDomainRequest struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + // An externally managed domain name. Empty clears a manually set domain. + Name string `protobuf:"bytes,1,opt,name=name,proto3" json:"name,omitempty"` +} + +func (x *SetDomainRequest) Reset() { + *x = SetDomainRequest{} + if protoimpl.UnsafeEnabled { + mi := &file_api_pb_cluster_proto_msgTypes[7] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *SetDomainRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*SetDomainRequest) ProtoMessage() {} + +func (x *SetDomainRequest) ProtoReflect() protoreflect.Message { + mi := &file_api_pb_cluster_proto_msgTypes[7] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use SetDomainRequest.ProtoReflect.Descriptor instead. +func (*SetDomainRequest) Descriptor() ([]byte, []int) { + return file_api_pb_cluster_proto_rawDescGZIP(), []int{7} +} + +func (x *SetDomainRequest) GetName() string { + if x != nil { + return x.Name + } + return "" +} + type CreateDomainRecordsRequest struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache @@ -491,7 +549,7 @@ type CreateDomainRecordsRequest struct { func (x *CreateDomainRecordsRequest) Reset() { *x = CreateDomainRecordsRequest{} if protoimpl.UnsafeEnabled { - mi := &file_api_pb_cluster_proto_msgTypes[7] + mi := &file_api_pb_cluster_proto_msgTypes[8] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -504,7 +562,7 @@ func (x *CreateDomainRecordsRequest) String() string { func (*CreateDomainRecordsRequest) ProtoMessage() {} func (x *CreateDomainRecordsRequest) ProtoReflect() protoreflect.Message { - mi := &file_api_pb_cluster_proto_msgTypes[7] + mi := &file_api_pb_cluster_proto_msgTypes[8] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -517,7 +575,7 @@ func (x *CreateDomainRecordsRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use CreateDomainRecordsRequest.ProtoReflect.Descriptor instead. func (*CreateDomainRecordsRequest) Descriptor() ([]byte, []int) { - return file_api_pb_cluster_proto_rawDescGZIP(), []int{7} + return file_api_pb_cluster_proto_rawDescGZIP(), []int{8} } func (x *CreateDomainRecordsRequest) GetRecords() []*DNSRecord { @@ -538,7 +596,7 @@ type CreateDomainRecordsResponse struct { func (x *CreateDomainRecordsResponse) Reset() { *x = CreateDomainRecordsResponse{} if protoimpl.UnsafeEnabled { - mi := &file_api_pb_cluster_proto_msgTypes[8] + mi := &file_api_pb_cluster_proto_msgTypes[9] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -551,7 +609,7 @@ func (x *CreateDomainRecordsResponse) String() string { func (*CreateDomainRecordsResponse) ProtoMessage() {} func (x *CreateDomainRecordsResponse) ProtoReflect() protoreflect.Message { - mi := &file_api_pb_cluster_proto_msgTypes[8] + mi := &file_api_pb_cluster_proto_msgTypes[9] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -564,7 +622,7 @@ func (x *CreateDomainRecordsResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use CreateDomainRecordsResponse.ProtoReflect.Descriptor instead. func (*CreateDomainRecordsResponse) Descriptor() ([]byte, []int) { - return file_api_pb_cluster_proto_rawDescGZIP(), []int{8} + return file_api_pb_cluster_proto_rawDescGZIP(), []int{9} } func (x *CreateDomainRecordsResponse) GetRecords() []*DNSRecord { @@ -587,7 +645,7 @@ type DNSRecord struct { func (x *DNSRecord) Reset() { *x = DNSRecord{} if protoimpl.UnsafeEnabled { - mi := &file_api_pb_cluster_proto_msgTypes[9] + mi := &file_api_pb_cluster_proto_msgTypes[10] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -600,7 +658,7 @@ func (x *DNSRecord) String() string { func (*DNSRecord) ProtoMessage() {} func (x *DNSRecord) ProtoReflect() protoreflect.Message { - mi := &file_api_pb_cluster_proto_msgTypes[9] + mi := &file_api_pb_cluster_proto_msgTypes[10] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -613,7 +671,7 @@ func (x *DNSRecord) ProtoReflect() protoreflect.Message { // Deprecated: Use DNSRecord.ProtoReflect.Descriptor instead. func (*DNSRecord) Descriptor() ([]byte, []int) { - return file_api_pb_cluster_proto_rawDescGZIP(), []int{9} + return file_api_pb_cluster_proto_rawDescGZIP(), []int{10} } func (x *DNSRecord) GetName() string { @@ -676,63 +734,72 @@ var file_api_pb_cluster_proto_rawDesc = []byte{ 0x65, 0x4d, 0x65, 0x6d, 0x62, 0x65, 0x72, 0x52, 0x08, 0x6d, 0x61, 0x63, 0x68, 0x69, 0x6e, 0x65, 0x73, 0x22, 0x26, 0x0a, 0x14, 0x52, 0x65, 0x6d, 0x6f, 0x76, 0x65, 0x4d, 0x61, 0x63, 0x68, 0x69, 0x6e, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x0e, 0x0a, 0x02, 0x69, 0x64, 0x18, - 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x02, 0x69, 0x64, 0x22, 0x1c, 0x0a, 0x06, 0x44, 0x6f, 0x6d, + 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x02, 0x69, 0x64, 0x22, 0x4a, 0x0a, 0x06, 0x44, 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x12, 0x12, 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, - 0x09, 0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x22, 0x32, 0x0a, 0x14, 0x52, 0x65, 0x73, 0x65, 0x72, - 0x76, 0x65, 0x44, 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, - 0x1a, 0x0a, 0x08, 0x65, 0x6e, 0x64, 0x70, 0x6f, 0x69, 0x6e, 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, - 0x09, 0x52, 0x08, 0x65, 0x6e, 0x64, 0x70, 0x6f, 0x69, 0x6e, 0x74, 0x22, 0x46, 0x0a, 0x1a, 0x43, - 0x72, 0x65, 0x61, 0x74, 0x65, 0x44, 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x52, 0x65, 0x63, 0x6f, 0x72, - 0x64, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x28, 0x0a, 0x07, 0x72, 0x65, 0x63, - 0x6f, 0x72, 0x64, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x0e, 0x2e, 0x61, 0x70, 0x69, - 0x2e, 0x44, 0x4e, 0x53, 0x52, 0x65, 0x63, 0x6f, 0x72, 0x64, 0x52, 0x07, 0x72, 0x65, 0x63, 0x6f, - 0x72, 0x64, 0x73, 0x22, 0x47, 0x0a, 0x1b, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x44, 0x6f, 0x6d, - 0x61, 0x69, 0x6e, 0x52, 0x65, 0x63, 0x6f, 0x72, 0x64, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, - 0x73, 0x65, 0x12, 0x28, 0x0a, 0x07, 0x72, 0x65, 0x63, 0x6f, 0x72, 0x64, 0x73, 0x18, 0x01, 0x20, - 0x03, 0x28, 0x0b, 0x32, 0x0e, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x44, 0x4e, 0x53, 0x52, 0x65, 0x63, - 0x6f, 0x72, 0x64, 0x52, 0x07, 0x72, 0x65, 0x63, 0x6f, 0x72, 0x64, 0x73, 0x22, 0x96, 0x01, 0x0a, - 0x09, 0x44, 0x4e, 0x53, 0x52, 0x65, 0x63, 0x6f, 0x72, 0x64, 0x12, 0x12, 0x0a, 0x04, 0x6e, 0x61, - 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x2d, - 0x0a, 0x04, 0x74, 0x79, 0x70, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x19, 0x2e, 0x61, - 0x70, 0x69, 0x2e, 0x44, 0x4e, 0x53, 0x52, 0x65, 0x63, 0x6f, 0x72, 0x64, 0x2e, 0x52, 0x65, 0x63, - 0x6f, 0x72, 0x64, 0x54, 0x79, 0x70, 0x65, 0x52, 0x04, 0x74, 0x79, 0x70, 0x65, 0x12, 0x16, 0x0a, - 0x06, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x73, 0x18, 0x03, 0x20, 0x03, 0x28, 0x09, 0x52, 0x06, 0x76, - 0x61, 0x6c, 0x75, 0x65, 0x73, 0x22, 0x2e, 0x0a, 0x0a, 0x52, 0x65, 0x63, 0x6f, 0x72, 0x64, 0x54, - 0x79, 0x70, 0x65, 0x12, 0x0f, 0x0a, 0x0b, 0x55, 0x4e, 0x53, 0x50, 0x45, 0x43, 0x49, 0x46, 0x49, - 0x45, 0x44, 0x10, 0x00, 0x12, 0x05, 0x0a, 0x01, 0x41, 0x10, 0x01, 0x12, 0x08, 0x0a, 0x04, 0x41, - 0x41, 0x41, 0x41, 0x10, 0x02, 0x32, 0xca, 0x03, 0x0a, 0x07, 0x43, 0x6c, 0x75, 0x73, 0x74, 0x65, - 0x72, 0x12, 0x3d, 0x0a, 0x0a, 0x41, 0x64, 0x64, 0x4d, 0x61, 0x63, 0x68, 0x69, 0x6e, 0x65, 0x12, - 0x16, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x41, 0x64, 0x64, 0x4d, 0x61, 0x63, 0x68, 0x69, 0x6e, 0x65, - 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x17, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x41, 0x64, - 0x64, 0x4d, 0x61, 0x63, 0x68, 0x69, 0x6e, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, - 0x12, 0x41, 0x0a, 0x0c, 0x4c, 0x69, 0x73, 0x74, 0x4d, 0x61, 0x63, 0x68, 0x69, 0x6e, 0x65, 0x73, - 0x12, 0x16, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, - 0x75, 0x66, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x1a, 0x19, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x4c, - 0x69, 0x73, 0x74, 0x4d, 0x61, 0x63, 0x68, 0x69, 0x6e, 0x65, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, - 0x6e, 0x73, 0x65, 0x12, 0x42, 0x0a, 0x0d, 0x52, 0x65, 0x6d, 0x6f, 0x76, 0x65, 0x4d, 0x61, 0x63, - 0x68, 0x69, 0x6e, 0x65, 0x12, 0x19, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x52, 0x65, 0x6d, 0x6f, 0x76, - 0x65, 0x4d, 0x61, 0x63, 0x68, 0x69, 0x6e, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, + 0x09, 0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x1f, 0x0a, 0x08, 0x72, 0x65, 0x73, 0x65, 0x72, + 0x76, 0x65, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x08, 0x48, 0x00, 0x52, 0x08, 0x72, 0x65, 0x73, + 0x65, 0x72, 0x76, 0x65, 0x64, 0x88, 0x01, 0x01, 0x42, 0x0b, 0x0a, 0x09, 0x5f, 0x72, 0x65, 0x73, + 0x65, 0x72, 0x76, 0x65, 0x64, 0x22, 0x32, 0x0a, 0x14, 0x52, 0x65, 0x73, 0x65, 0x72, 0x76, 0x65, + 0x44, 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x1a, 0x0a, + 0x08, 0x65, 0x6e, 0x64, 0x70, 0x6f, 0x69, 0x6e, 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, + 0x08, 0x65, 0x6e, 0x64, 0x70, 0x6f, 0x69, 0x6e, 0x74, 0x22, 0x26, 0x0a, 0x10, 0x53, 0x65, 0x74, + 0x44, 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x12, 0x0a, + 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x6e, 0x61, 0x6d, + 0x65, 0x22, 0x46, 0x0a, 0x1a, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x44, 0x6f, 0x6d, 0x61, 0x69, + 0x6e, 0x52, 0x65, 0x63, 0x6f, 0x72, 0x64, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, + 0x28, 0x0a, 0x07, 0x72, 0x65, 0x63, 0x6f, 0x72, 0x64, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, + 0x32, 0x0e, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x44, 0x4e, 0x53, 0x52, 0x65, 0x63, 0x6f, 0x72, 0x64, + 0x52, 0x07, 0x72, 0x65, 0x63, 0x6f, 0x72, 0x64, 0x73, 0x22, 0x47, 0x0a, 0x1b, 0x43, 0x72, 0x65, + 0x61, 0x74, 0x65, 0x44, 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x52, 0x65, 0x63, 0x6f, 0x72, 0x64, 0x73, + 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x28, 0x0a, 0x07, 0x72, 0x65, 0x63, 0x6f, + 0x72, 0x64, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x0e, 0x2e, 0x61, 0x70, 0x69, 0x2e, + 0x44, 0x4e, 0x53, 0x52, 0x65, 0x63, 0x6f, 0x72, 0x64, 0x52, 0x07, 0x72, 0x65, 0x63, 0x6f, 0x72, + 0x64, 0x73, 0x22, 0x96, 0x01, 0x0a, 0x09, 0x44, 0x4e, 0x53, 0x52, 0x65, 0x63, 0x6f, 0x72, 0x64, + 0x12, 0x12, 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, + 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x2d, 0x0a, 0x04, 0x74, 0x79, 0x70, 0x65, 0x18, 0x02, 0x20, 0x01, + 0x28, 0x0e, 0x32, 0x19, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x44, 0x4e, 0x53, 0x52, 0x65, 0x63, 0x6f, + 0x72, 0x64, 0x2e, 0x52, 0x65, 0x63, 0x6f, 0x72, 0x64, 0x54, 0x79, 0x70, 0x65, 0x52, 0x04, 0x74, + 0x79, 0x70, 0x65, 0x12, 0x16, 0x0a, 0x06, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x73, 0x18, 0x03, 0x20, + 0x03, 0x28, 0x09, 0x52, 0x06, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x73, 0x22, 0x2e, 0x0a, 0x0a, 0x52, + 0x65, 0x63, 0x6f, 0x72, 0x64, 0x54, 0x79, 0x70, 0x65, 0x12, 0x0f, 0x0a, 0x0b, 0x55, 0x4e, 0x53, + 0x50, 0x45, 0x43, 0x49, 0x46, 0x49, 0x45, 0x44, 0x10, 0x00, 0x12, 0x05, 0x0a, 0x01, 0x41, 0x10, + 0x01, 0x12, 0x08, 0x0a, 0x04, 0x41, 0x41, 0x41, 0x41, 0x10, 0x02, 0x32, 0x86, 0x04, 0x0a, 0x07, + 0x43, 0x6c, 0x75, 0x73, 0x74, 0x65, 0x72, 0x12, 0x3d, 0x0a, 0x0a, 0x41, 0x64, 0x64, 0x4d, 0x61, + 0x63, 0x68, 0x69, 0x6e, 0x65, 0x12, 0x16, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x41, 0x64, 0x64, 0x4d, + 0x61, 0x63, 0x68, 0x69, 0x6e, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x17, 0x2e, + 0x61, 0x70, 0x69, 0x2e, 0x41, 0x64, 0x64, 0x4d, 0x61, 0x63, 0x68, 0x69, 0x6e, 0x65, 0x52, 0x65, + 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x41, 0x0a, 0x0c, 0x4c, 0x69, 0x73, 0x74, 0x4d, 0x61, + 0x63, 0x68, 0x69, 0x6e, 0x65, 0x73, 0x12, 0x16, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, + 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x1a, 0x19, + 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x4d, 0x61, 0x63, 0x68, 0x69, 0x6e, 0x65, + 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x42, 0x0a, 0x0d, 0x52, 0x65, 0x6d, + 0x6f, 0x76, 0x65, 0x4d, 0x61, 0x63, 0x68, 0x69, 0x6e, 0x65, 0x12, 0x19, 0x2e, 0x61, 0x70, 0x69, + 0x2e, 0x52, 0x65, 0x6d, 0x6f, 0x76, 0x65, 0x4d, 0x61, 0x63, 0x68, 0x69, 0x6e, 0x65, 0x52, 0x65, + 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x16, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, + 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x12, 0x37, 0x0a, + 0x0d, 0x52, 0x65, 0x73, 0x65, 0x72, 0x76, 0x65, 0x44, 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x12, 0x19, + 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x52, 0x65, 0x73, 0x65, 0x72, 0x76, 0x65, 0x44, 0x6f, 0x6d, 0x61, + 0x69, 0x6e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x0b, 0x2e, 0x61, 0x70, 0x69, 0x2e, + 0x44, 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x12, 0x3a, 0x0a, 0x09, 0x53, 0x65, 0x74, 0x44, 0x6f, 0x6d, + 0x61, 0x69, 0x6e, 0x12, 0x15, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x53, 0x65, 0x74, 0x44, 0x6f, 0x6d, + 0x61, 0x69, 0x6e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x16, 0x2e, 0x67, 0x6f, 0x6f, + 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x45, 0x6d, 0x70, + 0x74, 0x79, 0x12, 0x30, 0x0a, 0x09, 0x47, 0x65, 0x74, 0x44, 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x12, 0x16, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, - 0x66, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x12, 0x37, 0x0a, 0x0d, 0x52, 0x65, 0x73, 0x65, 0x72, - 0x76, 0x65, 0x44, 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x12, 0x19, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x52, - 0x65, 0x73, 0x65, 0x72, 0x76, 0x65, 0x44, 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x52, 0x65, 0x71, 0x75, - 0x65, 0x73, 0x74, 0x1a, 0x0b, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x44, 0x6f, 0x6d, 0x61, 0x69, 0x6e, - 0x12, 0x30, 0x0a, 0x09, 0x47, 0x65, 0x74, 0x44, 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x12, 0x16, 0x2e, - 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, - 0x45, 0x6d, 0x70, 0x74, 0x79, 0x1a, 0x0b, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x44, 0x6f, 0x6d, 0x61, - 0x69, 0x6e, 0x12, 0x34, 0x0a, 0x0d, 0x52, 0x65, 0x6c, 0x65, 0x61, 0x73, 0x65, 0x44, 0x6f, 0x6d, - 0x61, 0x69, 0x6e, 0x12, 0x16, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, - 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x1a, 0x0b, 0x2e, 0x61, 0x70, - 0x69, 0x2e, 0x44, 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x12, 0x58, 0x0a, 0x13, 0x43, 0x72, 0x65, 0x61, - 0x74, 0x65, 0x44, 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x52, 0x65, 0x63, 0x6f, 0x72, 0x64, 0x73, 0x12, - 0x1f, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x44, 0x6f, 0x6d, 0x61, - 0x69, 0x6e, 0x52, 0x65, 0x63, 0x6f, 0x72, 0x64, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, - 0x1a, 0x20, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x44, 0x6f, 0x6d, - 0x61, 0x69, 0x6e, 0x52, 0x65, 0x63, 0x6f, 0x72, 0x64, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, - 0x73, 0x65, 0x42, 0x26, 0x5a, 0x24, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, - 0x2f, 0x70, 0x73, 0x76, 0x69, 0x64, 0x65, 0x72, 0x73, 0x6b, 0x69, 0x2f, 0x75, 0x6e, 0x63, 0x6c, - 0x6f, 0x75, 0x64, 0x2f, 0x61, 0x70, 0x69, 0x2f, 0x70, 0x62, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, - 0x6f, 0x33, + 0x66, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x1a, 0x0b, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x44, 0x6f, + 0x6d, 0x61, 0x69, 0x6e, 0x12, 0x34, 0x0a, 0x0d, 0x52, 0x65, 0x6c, 0x65, 0x61, 0x73, 0x65, 0x44, + 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x12, 0x16, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, + 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x1a, 0x0b, 0x2e, + 0x61, 0x70, 0x69, 0x2e, 0x44, 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x12, 0x58, 0x0a, 0x13, 0x43, 0x72, + 0x65, 0x61, 0x74, 0x65, 0x44, 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x52, 0x65, 0x63, 0x6f, 0x72, 0x64, + 0x73, 0x12, 0x1f, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x44, 0x6f, + 0x6d, 0x61, 0x69, 0x6e, 0x52, 0x65, 0x63, 0x6f, 0x72, 0x64, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, + 0x73, 0x74, 0x1a, 0x20, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x44, + 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x52, 0x65, 0x63, 0x6f, 0x72, 0x64, 0x73, 0x52, 0x65, 0x73, 0x70, + 0x6f, 0x6e, 0x73, 0x65, 0x42, 0x26, 0x5a, 0x24, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, + 0x6f, 0x6d, 0x2f, 0x70, 0x73, 0x76, 0x69, 0x64, 0x65, 0x72, 0x73, 0x6b, 0x69, 0x2f, 0x75, 0x6e, + 0x63, 0x6c, 0x6f, 0x75, 0x64, 0x2f, 0x61, 0x70, 0x69, 0x2f, 0x70, 0x62, 0x62, 0x06, 0x70, 0x72, + 0x6f, 0x74, 0x6f, 0x33, } var ( @@ -748,7 +815,7 @@ func file_api_pb_cluster_proto_rawDescGZIP() []byte { } var file_api_pb_cluster_proto_enumTypes = make([]protoimpl.EnumInfo, 2) -var file_api_pb_cluster_proto_msgTypes = make([]protoimpl.MessageInfo, 10) +var file_api_pb_cluster_proto_msgTypes = make([]protoimpl.MessageInfo, 11) var file_api_pb_cluster_proto_goTypes = []any{ (MachineMember_MembershipState)(0), // 0: api.MachineMember.MembershipState (DNSRecord_RecordType)(0), // 1: api.DNSRecord.RecordType @@ -759,40 +826,43 @@ var file_api_pb_cluster_proto_goTypes = []any{ (*RemoveMachineRequest)(nil), // 6: api.RemoveMachineRequest (*Domain)(nil), // 7: api.Domain (*ReserveDomainRequest)(nil), // 8: api.ReserveDomainRequest - (*CreateDomainRecordsRequest)(nil), // 9: api.CreateDomainRecordsRequest - (*CreateDomainRecordsResponse)(nil), // 10: api.CreateDomainRecordsResponse - (*DNSRecord)(nil), // 11: api.DNSRecord - (*NetworkConfig)(nil), // 12: api.NetworkConfig - (*IP)(nil), // 13: api.IP - (*MachineInfo)(nil), // 14: api.MachineInfo - (*emptypb.Empty)(nil), // 15: google.protobuf.Empty + (*SetDomainRequest)(nil), // 9: api.SetDomainRequest + (*CreateDomainRecordsRequest)(nil), // 10: api.CreateDomainRecordsRequest + (*CreateDomainRecordsResponse)(nil), // 11: api.CreateDomainRecordsResponse + (*DNSRecord)(nil), // 12: api.DNSRecord + (*NetworkConfig)(nil), // 13: api.NetworkConfig + (*IP)(nil), // 14: api.IP + (*MachineInfo)(nil), // 15: api.MachineInfo + (*emptypb.Empty)(nil), // 16: google.protobuf.Empty } var file_api_pb_cluster_proto_depIdxs = []int32{ - 12, // 0: api.AddMachineRequest.network:type_name -> api.NetworkConfig - 13, // 1: api.AddMachineRequest.public_ip:type_name -> api.IP - 14, // 2: api.AddMachineResponse.machine:type_name -> api.MachineInfo - 14, // 3: api.MachineMember.machine:type_name -> api.MachineInfo + 13, // 0: api.AddMachineRequest.network:type_name -> api.NetworkConfig + 14, // 1: api.AddMachineRequest.public_ip:type_name -> api.IP + 15, // 2: api.AddMachineResponse.machine:type_name -> api.MachineInfo + 15, // 3: api.MachineMember.machine:type_name -> api.MachineInfo 0, // 4: api.MachineMember.state:type_name -> api.MachineMember.MembershipState 4, // 5: api.ListMachinesResponse.machines:type_name -> api.MachineMember - 11, // 6: api.CreateDomainRecordsRequest.records:type_name -> api.DNSRecord - 11, // 7: api.CreateDomainRecordsResponse.records:type_name -> api.DNSRecord + 12, // 6: api.CreateDomainRecordsRequest.records:type_name -> api.DNSRecord + 12, // 7: api.CreateDomainRecordsResponse.records:type_name -> api.DNSRecord 1, // 8: api.DNSRecord.type:type_name -> api.DNSRecord.RecordType 2, // 9: api.Cluster.AddMachine:input_type -> api.AddMachineRequest - 15, // 10: api.Cluster.ListMachines:input_type -> google.protobuf.Empty + 16, // 10: api.Cluster.ListMachines:input_type -> google.protobuf.Empty 6, // 11: api.Cluster.RemoveMachine:input_type -> api.RemoveMachineRequest 8, // 12: api.Cluster.ReserveDomain:input_type -> api.ReserveDomainRequest - 15, // 13: api.Cluster.GetDomain:input_type -> google.protobuf.Empty - 15, // 14: api.Cluster.ReleaseDomain:input_type -> google.protobuf.Empty - 9, // 15: api.Cluster.CreateDomainRecords:input_type -> api.CreateDomainRecordsRequest - 3, // 16: api.Cluster.AddMachine:output_type -> api.AddMachineResponse - 5, // 17: api.Cluster.ListMachines:output_type -> api.ListMachinesResponse - 15, // 18: api.Cluster.RemoveMachine:output_type -> google.protobuf.Empty - 7, // 19: api.Cluster.ReserveDomain:output_type -> api.Domain - 7, // 20: api.Cluster.GetDomain:output_type -> api.Domain - 7, // 21: api.Cluster.ReleaseDomain:output_type -> api.Domain - 10, // 22: api.Cluster.CreateDomainRecords:output_type -> api.CreateDomainRecordsResponse - 16, // [16:23] is the sub-list for method output_type - 9, // [9:16] is the sub-list for method input_type + 9, // 13: api.Cluster.SetDomain:input_type -> api.SetDomainRequest + 16, // 14: api.Cluster.GetDomain:input_type -> google.protobuf.Empty + 16, // 15: api.Cluster.ReleaseDomain:input_type -> google.protobuf.Empty + 10, // 16: api.Cluster.CreateDomainRecords:input_type -> api.CreateDomainRecordsRequest + 3, // 17: api.Cluster.AddMachine:output_type -> api.AddMachineResponse + 5, // 18: api.Cluster.ListMachines:output_type -> api.ListMachinesResponse + 16, // 19: api.Cluster.RemoveMachine:output_type -> google.protobuf.Empty + 7, // 20: api.Cluster.ReserveDomain:output_type -> api.Domain + 16, // 21: api.Cluster.SetDomain:output_type -> google.protobuf.Empty + 7, // 22: api.Cluster.GetDomain:output_type -> api.Domain + 7, // 23: api.Cluster.ReleaseDomain:output_type -> api.Domain + 11, // 24: api.Cluster.CreateDomainRecords:output_type -> api.CreateDomainRecordsResponse + 17, // [17:25] is the sub-list for method output_type + 9, // [9:17] is the sub-list for method input_type 9, // [9:9] is the sub-list for extension type_name 9, // [9:9] is the sub-list for extension extendee 0, // [0:9] is the sub-list for field type_name @@ -891,7 +961,7 @@ func file_api_pb_cluster_proto_init() { } } file_api_pb_cluster_proto_msgTypes[7].Exporter = func(v any, i int) any { - switch v := v.(*CreateDomainRecordsRequest); i { + switch v := v.(*SetDomainRequest); i { case 0: return &v.state case 1: @@ -903,7 +973,7 @@ func file_api_pb_cluster_proto_init() { } } file_api_pb_cluster_proto_msgTypes[8].Exporter = func(v any, i int) any { - switch v := v.(*CreateDomainRecordsResponse); i { + switch v := v.(*CreateDomainRecordsRequest); i { case 0: return &v.state case 1: @@ -915,6 +985,18 @@ func file_api_pb_cluster_proto_init() { } } file_api_pb_cluster_proto_msgTypes[9].Exporter = func(v any, i int) any { + switch v := v.(*CreateDomainRecordsResponse); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_api_pb_cluster_proto_msgTypes[10].Exporter = func(v any, i int) any { switch v := v.(*DNSRecord); i { case 0: return &v.state @@ -927,13 +1009,14 @@ func file_api_pb_cluster_proto_init() { } } } + file_api_pb_cluster_proto_msgTypes[5].OneofWrappers = []any{} type x struct{} out := protoimpl.TypeBuilder{ File: protoimpl.DescBuilder{ GoPackagePath: reflect.TypeOf(x{}).PkgPath(), RawDescriptor: file_api_pb_cluster_proto_rawDesc, NumEnums: 2, - NumMessages: 10, + NumMessages: 11, NumExtensions: 0, NumServices: 1, }, diff --git a/api/pb/cluster.proto b/api/pb/cluster.proto index cb9ef95d..6956d578 100644 --- a/api/pb/cluster.proto +++ b/api/pb/cluster.proto @@ -14,6 +14,7 @@ service Cluster { rpc RemoveMachine(RemoveMachineRequest) returns (google.protobuf.Empty); rpc ReserveDomain(ReserveDomainRequest) returns (Domain); + rpc SetDomain(SetDomainRequest) returns (google.protobuf.Empty); rpc GetDomain(google.protobuf.Empty) returns (Domain); rpc ReleaseDomain(google.protobuf.Empty) returns (Domain); rpc CreateDomainRecords(CreateDomainRecordsRequest) returns (CreateDomainRecordsResponse); @@ -56,12 +57,20 @@ message RemoveMachineRequest { message Domain { string name = 1; + // Whether the domain is reserved in Uncloud DNS. Older daemons (<0.21) omit this field + // and only support reserved domains, so clients should treat absence as true. + optional bool reserved = 2; } message ReserveDomainRequest { string endpoint = 1; } +message SetDomainRequest { + // An externally managed domain name. Empty clears a manually set domain. + string name = 1; +} + message CreateDomainRecordsRequest { repeated DNSRecord records = 1; } diff --git a/api/pb/cluster_grpc.pb.go b/api/pb/cluster_grpc.pb.go index 8979a127..b7b1bfa0 100644 --- a/api/pb/cluster_grpc.pb.go +++ b/api/pb/cluster_grpc.pb.go @@ -24,6 +24,7 @@ const ( Cluster_ListMachines_FullMethodName = "/api.Cluster/ListMachines" Cluster_RemoveMachine_FullMethodName = "/api.Cluster/RemoveMachine" Cluster_ReserveDomain_FullMethodName = "/api.Cluster/ReserveDomain" + Cluster_SetDomain_FullMethodName = "/api.Cluster/SetDomain" Cluster_GetDomain_FullMethodName = "/api.Cluster/GetDomain" Cluster_ReleaseDomain_FullMethodName = "/api.Cluster/ReleaseDomain" Cluster_CreateDomainRecords_FullMethodName = "/api.Cluster/CreateDomainRecords" @@ -37,6 +38,7 @@ type ClusterClient interface { ListMachines(ctx context.Context, in *emptypb.Empty, opts ...grpc.CallOption) (*ListMachinesResponse, error) RemoveMachine(ctx context.Context, in *RemoveMachineRequest, opts ...grpc.CallOption) (*emptypb.Empty, error) ReserveDomain(ctx context.Context, in *ReserveDomainRequest, opts ...grpc.CallOption) (*Domain, error) + SetDomain(ctx context.Context, in *SetDomainRequest, opts ...grpc.CallOption) (*emptypb.Empty, error) GetDomain(ctx context.Context, in *emptypb.Empty, opts ...grpc.CallOption) (*Domain, error) ReleaseDomain(ctx context.Context, in *emptypb.Empty, opts ...grpc.CallOption) (*Domain, error) CreateDomainRecords(ctx context.Context, in *CreateDomainRecordsRequest, opts ...grpc.CallOption) (*CreateDomainRecordsResponse, error) @@ -90,6 +92,16 @@ func (c *clusterClient) ReserveDomain(ctx context.Context, in *ReserveDomainRequ return out, nil } +func (c *clusterClient) SetDomain(ctx context.Context, in *SetDomainRequest, opts ...grpc.CallOption) (*emptypb.Empty, error) { + cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) + out := new(emptypb.Empty) + err := c.cc.Invoke(ctx, Cluster_SetDomain_FullMethodName, in, out, cOpts...) + if err != nil { + return nil, err + } + return out, nil +} + func (c *clusterClient) GetDomain(ctx context.Context, in *emptypb.Empty, opts ...grpc.CallOption) (*Domain, error) { cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) out := new(Domain) @@ -128,6 +140,7 @@ type ClusterServer interface { ListMachines(context.Context, *emptypb.Empty) (*ListMachinesResponse, error) RemoveMachine(context.Context, *RemoveMachineRequest) (*emptypb.Empty, error) ReserveDomain(context.Context, *ReserveDomainRequest) (*Domain, error) + SetDomain(context.Context, *SetDomainRequest) (*emptypb.Empty, error) GetDomain(context.Context, *emptypb.Empty) (*Domain, error) ReleaseDomain(context.Context, *emptypb.Empty) (*Domain, error) CreateDomainRecords(context.Context, *CreateDomainRecordsRequest) (*CreateDomainRecordsResponse, error) @@ -153,6 +166,9 @@ func (UnimplementedClusterServer) RemoveMachine(context.Context, *RemoveMachineR func (UnimplementedClusterServer) ReserveDomain(context.Context, *ReserveDomainRequest) (*Domain, error) { return nil, status.Errorf(codes.Unimplemented, "method ReserveDomain not implemented") } +func (UnimplementedClusterServer) SetDomain(context.Context, *SetDomainRequest) (*emptypb.Empty, error) { + return nil, status.Errorf(codes.Unimplemented, "method SetDomain not implemented") +} func (UnimplementedClusterServer) GetDomain(context.Context, *emptypb.Empty) (*Domain, error) { return nil, status.Errorf(codes.Unimplemented, "method GetDomain not implemented") } @@ -255,6 +271,24 @@ func _Cluster_ReserveDomain_Handler(srv interface{}, ctx context.Context, dec fu return interceptor(ctx, in, info, handler) } +func _Cluster_SetDomain_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(SetDomainRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(ClusterServer).SetDomain(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: Cluster_SetDomain_FullMethodName, + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(ClusterServer).SetDomain(ctx, req.(*SetDomainRequest)) + } + return interceptor(ctx, in, info, handler) +} + func _Cluster_GetDomain_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { in := new(emptypb.Empty) if err := dec(in); err != nil { @@ -332,6 +366,10 @@ var Cluster_ServiceDesc = grpc.ServiceDesc{ MethodName: "ReserveDomain", Handler: _Cluster_ReserveDomain_Handler, }, + { + MethodName: "SetDomain", + Handler: _Cluster_SetDomain_Handler, + }, { MethodName: "GetDomain", Handler: _Cluster_GetDomain_Handler, diff --git a/cmd/uc/caddy/deploy.go b/cmd/uc/caddy/deploy.go index ebb3d2d3..02e5030a 100644 --- a/cmd/uc/caddy/deploy.go +++ b/cmd/uc/caddy/deploy.go @@ -202,6 +202,10 @@ func UpdateDomainRecords(ctx context.Context, clusterClient *client.Client, prog } return fmt.Errorf("get cluster domain: %w", err) } + if !domain.Reserved { + fmt.Printf("Skipping DNS records update as cluster domain '%s' is managed externally.\n", domain.Name) + return nil + } fmt.Println("Updating cluster domain records in Uncloud DNS to point to machines running caddy service...") // TODO: split the method into two: one to get the records and one to update them to ask for update confirmation. @@ -216,7 +220,7 @@ func UpdateDomainRecords(ctx context.Context, clusterClient *client.Client, prog if errors.Is(err, client.ErrNoReachableMachines) { fmt.Println() fmt.Printf("DNS records for domain '%s' could not be updated as there are no internet-reachable "+ - "machines running caddy containers.\n", domain) + "machines running caddy containers.\n", domain.Name) fmt.Println() fmt.Println("Possible solutions:") fmt.Println("- Ensure your machines have public IP addresses") diff --git a/cmd/uc/dns/release.go b/cmd/uc/dns/release.go index 6c8fb2da..8400cff9 100644 --- a/cmd/uc/dns/release.go +++ b/cmd/uc/dns/release.go @@ -37,6 +37,7 @@ func release(ctx context.Context, uncli *cli.CLI) error { if status.Convert(err).Code() == codes.NotFound { return errors.New("no domain reserved") } + return fmt.Errorf("release cluster domain: %w", err) } fmt.Printf("Released cluster domain: %s\n", domain.Name) diff --git a/cmd/uc/dns/reserve.go b/cmd/uc/dns/reserve.go index 7670a22f..794bb5e7 100644 --- a/cmd/uc/dns/reserve.go +++ b/cmd/uc/dns/reserve.go @@ -49,7 +49,8 @@ func reserve(ctx context.Context, uncli *cli.CLI, opts reserveOptions) error { domain, err := clusterClient.ReserveDomain(ctx, &pb.ReserveDomainRequest{Endpoint: opts.endpoint}) if err != nil { if status.Convert(err).Code() == codes.AlreadyExists { - return errors.New("domain already reserved") + return errors.New("cluster domain already configured, unset it with 'uc dns set \"\"' " + + "or release a reservation with 'uc dns release' first") } return err } diff --git a/cmd/uc/dns/root.go b/cmd/uc/dns/root.go index 187afafb..f7ef350f 100644 --- a/cmd/uc/dns/root.go +++ b/cmd/uc/dns/root.go @@ -7,16 +7,19 @@ import ( func NewRootCommand() *cobra.Command { cmd := &cobra.Command{ Use: "dns", - Short: "Manage cluster domain in Uncloud DNS.", - Long: "Manage cluster domain in Uncloud DNS.\n" + + Short: "Manage the cluster domain.", + Long: "Manage the cluster domain.\n" + "DNS commands allow you to reserve or release a unique 'xxxxxx.uncld.dev' domain for your " + "cluster. When reserved, Caddy service deployments will automatically update DNS records to route " + - "traffic to the services in the cluster.", + "traffic to the services in the cluster.\n\n" + + "EXPERIMENTAL: Use 'uc dns set' to configure an externally managed domain instead. " + + "Uncloud does not manage DNS records for manually set domains.", } cmd.AddCommand( NewReleaseCommand(), NewReserveCommand(), NewShowCommand(), + NewSetCommand(), ) return cmd } diff --git a/cmd/uc/dns/set.go b/cmd/uc/dns/set.go new file mode 100644 index 00000000..5047b18f --- /dev/null +++ b/cmd/uc/dns/set.go @@ -0,0 +1,69 @@ +package dns + +import ( + "context" + "errors" + "fmt" + + "github.com/miekg/dns" + "github.com/psviderski/uncloud/api/pb" + "github.com/psviderski/uncloud/internal/cli" + "github.com/psviderski/uncloud/internal/cli/tui" + "github.com/spf13/cobra" + "google.golang.org/grpc/codes" + "google.golang.org/grpc/status" +) + +func NewSetCommand() *cobra.Command { + cmd := &cobra.Command{ + Use: "set DOMAIN_NAME", + Args: cobra.ExactArgs(1), + Short: "Set or unset an externally managed cluster domain (EXPERIMENTAL).", + Long: "EXPERIMENTAL: Set the cluster domain used to generate ingress hostnames for services.\n" + + "Configure wildcard DNS records for this domain with your DNS provider. " + + "Uncloud will not create, verify, update, or delete external DNS records.\n" + + "Pass an empty string to unset a manually set domain. " + + "Use 'uc dns release' to release a domain reserved in Uncloud DNS. " + + "Setting or unsetting the domain does not change existing service hostnames.", + Example: " uc dns set apps.example.com\n uc dns set \"\"", + RunE: func(cmd *cobra.Command, args []string) error { + uncli := cmd.Context().Value("cli").(*cli.CLI) + return set(cmd.Context(), uncli, args[0]) + }, + } + + return cmd +} + +func set(ctx context.Context, uncli *cli.CLI, name string) error { + if name != "" { + if labels, ok := dns.IsDomainName(name); !ok || labels < 2 { + return fmt.Errorf("invalid cluster domain %q: must be a valid domain name with at least two labels", name) + } + } + tui.PrintWarning("Setting an externally managed cluster domain is experimental.") + + clusterClient, err := uncli.ConnectCluster(ctx) + if err != nil { + return fmt.Errorf("connect to cluster: %w", err) + } + defer clusterClient.Close() + + _, err = clusterClient.SetDomain(ctx, &pb.SetDomainRequest{Name: name}) + if err != nil { + if status.Convert(err).Code() == codes.AlreadyExists { + return errors.New("cluster domain already configured, unset it with 'uc dns set \"\"' or " + + "release a reservation with 'uc dns release' first") + } + return fmt.Errorf("set cluster domain: %w", err) + } + + if name == "" { + fmt.Println("Unset cluster domain. External DNS records have not been changed.") + return nil + } + fmt.Printf("Set cluster domain: %s\n", name) + fmt.Println("DNS is managed externally. " + + "Configure wildcard DNS records pointing to machines running the Caddy service.") + return nil +} diff --git a/cmd/uc/dns/show.go b/cmd/uc/dns/show.go index b9a1fc64..3f03073b 100644 --- a/cmd/uc/dns/show.go +++ b/cmd/uc/dns/show.go @@ -33,11 +33,11 @@ func show(ctx context.Context, uncli *cli.CLI) error { domain, err := clusterClient.GetDomain(ctx) if err != nil { if errors.Is(err, api.ErrNotFound) { - return errors.New("no domain reserved") + return errors.New("no cluster domain configured") } return err } - fmt.Println(domain) + fmt.Println(domain.Name) return nil } diff --git a/internal/machine/cluster/dns.go b/internal/machine/cluster/dns.go index c414ec84..2b16ef3a 100644 --- a/internal/machine/cluster/dns.go +++ b/internal/machine/cluster/dns.go @@ -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 } } diff --git a/pkg/api/client.go b/pkg/api/client.go index f8243f8a..8276dcf5 100644 --- a/pkg/api/client.go +++ b/pkg/api/client.go @@ -36,7 +36,7 @@ type ContainerClient interface { } type DNSClient interface { - GetDomain(ctx context.Context) (string, error) + GetDomain(ctx context.Context) (ClusterDomain, error) } type ImageClient interface { diff --git a/pkg/api/dns.go b/pkg/api/dns.go new file mode 100644 index 00000000..5f50f749 --- /dev/null +++ b/pkg/api/dns.go @@ -0,0 +1,8 @@ +package api + +// ClusterDomain is the domain name assigned to the cluster. +type ClusterDomain struct { + Name string + // Reserved reports whether the domain is reserved in Uncloud DNS or set and managed externally. + Reserved bool +} diff --git a/pkg/client/compose/deploy.go b/pkg/client/compose/deploy.go index 02bb6549..92d2b9ba 100644 --- a/pkg/client/compose/deploy.go +++ b/pkg/client/compose/deploy.go @@ -46,8 +46,8 @@ func NewDeploymentWithStrategy(ctx context.Context, cli Client, project *types.P return nil, fmt.Errorf("get cluster domain: %w", err) } resolver := &deploy.ServiceSpecResolver{ - // If the domain is not found (not reserved), an empty domain is used for the resolver. - ClusterDomain: domain, + // If no domain is configured, an empty domain is used for the resolver. + ClusterDomain: domain.Name, } return &Deployment{ diff --git a/pkg/client/deploy/deploy.go b/pkg/client/deploy/deploy.go index 73e3ea0d..4869c69e 100644 --- a/pkg/client/deploy/deploy.go +++ b/pkg/client/deploy/deploy.go @@ -306,8 +306,8 @@ func (d *Deployment) Plan(ctx context.Context) (ServicePlan, error) { return ServicePlan{}, fmt.Errorf("get cluster domain: %w", err) } specResolver := &ServiceSpecResolver{ - // If the domain is not found (not reserved), an empty domain is used for the resolver. - ClusterDomain: clusterDomain, + // If no domain is configured, an empty domain is used for the resolver. + ClusterDomain: clusterDomain.Name, } resolvedSpec, err := specResolver.Resolve(d.Spec) diff --git a/pkg/client/dns.go b/pkg/client/dns.go index 457a1a4f..0c13fe13 100644 --- a/pkg/client/dns.go +++ b/pkg/client/dns.go @@ -21,17 +21,21 @@ import ( "google.golang.org/grpc/status" ) -// GetDomain returns the cluster domain name or ErrNotFound if it hasn't been reserved yet. -func (cli *Client) GetDomain(ctx context.Context) (string, error) { +// GetDomain returns the cluster domain or api.ErrNotFound if none is configured. +func (cli *Client) GetDomain(ctx context.Context) (api.ClusterDomain, error) { domain, err := cli.ClusterClient.GetDomain(ctx, nil) if err != nil { - if status.Convert(err).Code() == codes.NotFound { - return "", api.ErrNotFound + if status.Code(err) == codes.NotFound { + return api.ClusterDomain{}, api.ErrNotFound } - return "", err + return api.ClusterDomain{}, err } - return domain.Name, nil + return api.ClusterDomain{ + Name: domain.Name, + // Older daemons (<0.21) omit Reserved and only support reserved domains. + Reserved: domain.Reserved == nil || domain.GetReserved(), + }, nil } var ErrNoReachableMachines = errors.New("no internet-reachable machines running service containers") @@ -39,8 +43,17 @@ var ErrNoReachableMachines = errors.New("no internet-reachable machines running // CreateIngressRecords verifies which machines running the specified service (typically Caddy) are reachable from // the internet, then creates DNS records for the cluster domain pointing to those machines. It tests each machine // by sending HTTP requests to their public IPs. Only machines that respond correctly with their machine ID are included -// in the resulting DNS configuration. Returns the created DNS records or an error. +// in the resulting DNS configuration. The domain must be reserved in Uncloud DNS. +// Returns the created DNS records or an error. func (cli *Client) CreateIngressRecords(ctx context.Context, serviceID string) ([]*pb.DNSRecord, error) { + domain, err := cli.GetDomain(ctx) + if err != nil { + return nil, fmt.Errorf("get cluster domain: %w", err) + } + if !domain.Reserved { + return nil, status.Error(codes.FailedPrecondition, "cluster domain is not reserved in Uncloud DNS") + } + svc, err := cli.InspectService(ctx, serviceID) if err != nil { return nil, fmt.Errorf("inspect service '%s': %w", serviceID, err) diff --git a/website/docs/9-cli-reference/uc.md b/website/docs/9-cli-reference/uc.md index feb32eee..3d8dc948 100644 --- a/website/docs/9-cli-reference/uc.md +++ b/website/docs/9-cli-reference/uc.md @@ -18,7 +18,7 @@ A CLI tool for managing Uncloud resources such as machines, services, and volume * [uc caddy](uc_caddy.md) - Manage Caddy reverse proxy service. * [uc ctx](uc_ctx.md) - Switch between different cluster contexts. Contains subcommands to manage contexts. * [uc deploy](uc_deploy.md) - Deploy services from a Compose file. -* [uc dns](uc_dns.md) - Manage cluster domain in Uncloud DNS. +* [uc dns](uc_dns.md) - Manage the cluster domain. * [uc exec](uc_exec.md) - Execute a command in a running service container. * [uc image](uc_image.md) - Manage images on machines in the cluster. * [uc images](uc_images.md) - List images on machines in the cluster. diff --git a/website/docs/9-cli-reference/uc_dns.md b/website/docs/9-cli-reference/uc_dns.md index 1bbabf0c..186b9c5d 100644 --- a/website/docs/9-cli-reference/uc_dns.md +++ b/website/docs/9-cli-reference/uc_dns.md @@ -1,12 +1,14 @@ # uc dns -Manage cluster domain in Uncloud DNS. +Manage the cluster domain. ## Synopsis -Manage cluster domain in Uncloud DNS. +Manage the cluster domain. DNS commands allow you to reserve or release a unique 'xxxxxx.uncld.dev' domain for your cluster. When reserved, Caddy service deployments will automatically update DNS records to route traffic to the services in the cluster. +EXPERIMENTAL: Use 'uc dns set' to configure an externally managed domain instead. Uncloud does not manage DNS records for manually set domains. + ## Options ``` @@ -27,5 +29,6 @@ DNS commands allow you to reserve or release a unique 'xxxxxx.uncld.dev' domain * [uc](uc.md) - A CLI tool for managing Uncloud resources such as machines, services, and volumes. * [uc dns release](uc_dns_release.md) - Release the reserved cluster domain. * [uc dns reserve](uc_dns_reserve.md) - Reserve a cluster domain in Uncloud DNS. +* [uc dns set](uc_dns_set.md) - Set or unset an externally managed cluster domain (EXPERIMENTAL). * [uc dns show](uc_dns_show.md) - Print the cluster domain name. diff --git a/website/docs/9-cli-reference/uc_dns_release.md b/website/docs/9-cli-reference/uc_dns_release.md index 455d0af0..e0b83664 100644 --- a/website/docs/9-cli-reference/uc_dns_release.md +++ b/website/docs/9-cli-reference/uc_dns_release.md @@ -23,5 +23,5 @@ uc dns release [flags] ## See also -* [uc dns](uc_dns.md) - Manage cluster domain in Uncloud DNS. +* [uc dns](uc_dns.md) - Manage the cluster domain. diff --git a/website/docs/9-cli-reference/uc_dns_reserve.md b/website/docs/9-cli-reference/uc_dns_reserve.md index 0f09e3f9..1462f78e 100644 --- a/website/docs/9-cli-reference/uc_dns_reserve.md +++ b/website/docs/9-cli-reference/uc_dns_reserve.md @@ -24,5 +24,5 @@ uc dns reserve [flags] ## See also -* [uc dns](uc_dns.md) - Manage cluster domain in Uncloud DNS. +* [uc dns](uc_dns.md) - Manage the cluster domain. diff --git a/website/docs/9-cli-reference/uc_dns_set.md b/website/docs/9-cli-reference/uc_dns_set.md new file mode 100644 index 00000000..f19323c8 --- /dev/null +++ b/website/docs/9-cli-reference/uc_dns_set.md @@ -0,0 +1,40 @@ +# uc dns set + +Set or unset an externally managed cluster domain (EXPERIMENTAL). + +## Synopsis + +EXPERIMENTAL: Set the cluster domain used to generate ingress hostnames for services. +Configure wildcard DNS records for this domain with your DNS provider. Uncloud will not create, verify, update, or delete external DNS records. +Pass an empty string to unset a manually set domain. Use 'uc dns release' to release a domain reserved in Uncloud DNS. Setting or unsetting the domain does not change existing service hostnames. + +``` +uc dns set DOMAIN_NAME [flags] +``` + +## Examples + +``` + uc dns set apps.example.com + uc dns set "" +``` + +## Options + +``` + -h, --help help for set +``` + +## Options inherited from parent commands + +``` + --connect string Connect to a remote cluster machine without using the Uncloud configuration file. [$UNCLOUD_CONNECT] + Format: [ssh://]user@host[:port], ssh+go://user@host[:port], tcp://host:port, or unix:///path/to/uncloud.sock + -c, --context string Name of the cluster context to use (default is the current context). [$UNCLOUD_CONTEXT] + --uncloud-config string Path to the Uncloud configuration file. [$UNCLOUD_CONFIG] (default "~/.config/uncloud/config.yaml") +``` + +## See also + +* [uc dns](uc_dns.md) - Manage the cluster domain. + diff --git a/website/docs/9-cli-reference/uc_dns_show.md b/website/docs/9-cli-reference/uc_dns_show.md index 9a7eaaf3..3e45553f 100644 --- a/website/docs/9-cli-reference/uc_dns_show.md +++ b/website/docs/9-cli-reference/uc_dns_show.md @@ -23,5 +23,5 @@ uc dns show [flags] ## See also -* [uc dns](uc_dns.md) - Manage cluster domain in Uncloud DNS. +* [uc dns](uc_dns.md) - Manage the cluster domain.