feat(dns): allow setting an externally managed cluster domain

This commit is contained in:
Pasha Sviderski committed 2026-09-25 16:20:54 +10:00
1 parent b28c458029
commit 9ee3ca15eb
21 files changed
+233 -136

No files matched your search

+78 -62
View File
@@ -392,6 +392,9 @@ type Domain struct {
unknownFields protoimpl.UnknownFields unknownFields protoimpl.UnknownFields
Name string `protobuf:"bytes,1,opt,name=name,proto3" json:"name,omitempty"` 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() { func (x *Domain) Reset() {
@@ -433,6 +436,13 @@ func (x *Domain) GetName() string {
return "" return ""
} }
func (x *Domain) GetReserved() bool {
if x != nil && x.Reserved != nil {
return *x.Reserved
}
return false
}
type ReserveDomainRequest struct { type ReserveDomainRequest struct {
state protoimpl.MessageState state protoimpl.MessageState
sizeCache protoimpl.SizeCache sizeCache protoimpl.SizeCache
@@ -485,6 +495,7 @@ type SetDomainRequest struct {
sizeCache protoimpl.SizeCache sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields 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"` Name string `protobuf:"bytes,1,opt,name=name,proto3" json:"name,omitempty"`
} }
@@ -723,68 +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, 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, 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, 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, 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, 0x09, 0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x1f, 0x0a, 0x08, 0x72, 0x65, 0x73, 0x65, 0x72,
0x76, 0x65, 0x44, 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x76, 0x65, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x08, 0x48, 0x00, 0x52, 0x08, 0x72, 0x65, 0x73,
0x1a, 0x0a, 0x08, 0x65, 0x6e, 0x64, 0x70, 0x6f, 0x69, 0x6e, 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, 0x65, 0x72, 0x76, 0x65, 0x64, 0x88, 0x01, 0x01, 0x42, 0x0b, 0x0a, 0x09, 0x5f, 0x72, 0x65, 0x73,
0x09, 0x52, 0x08, 0x65, 0x6e, 0x64, 0x70, 0x6f, 0x69, 0x6e, 0x74, 0x22, 0x26, 0x0a, 0x10, 0x53, 0x65, 0x72, 0x76, 0x65, 0x64, 0x22, 0x32, 0x0a, 0x14, 0x52, 0x65, 0x73, 0x65, 0x72, 0x76, 0x65,
0x65, 0x74, 0x44, 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x44, 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x1a, 0x0a,
0x12, 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x6e, 0x08, 0x65, 0x6e, 0x64, 0x70, 0x6f, 0x69, 0x6e, 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52,
0x61, 0x6d, 0x65, 0x22, 0x46, 0x0a, 0x1a, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x44, 0x6f, 0x6d, 0x08, 0x65, 0x6e, 0x64, 0x70, 0x6f, 0x69, 0x6e, 0x74, 0x22, 0x26, 0x0a, 0x10, 0x53, 0x65, 0x74,
0x61, 0x69, 0x6e, 0x52, 0x65, 0x63, 0x6f, 0x72, 0x64, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x44, 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x12, 0x0a,
0x74, 0x12, 0x28, 0x0a, 0x07, 0x72, 0x65, 0x63, 0x6f, 0x72, 0x64, 0x73, 0x18, 0x01, 0x20, 0x03, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x6e, 0x61, 0x6d,
0x28, 0x0b, 0x32, 0x0e, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x44, 0x4e, 0x53, 0x52, 0x65, 0x63, 0x6f, 0x65, 0x22, 0x46, 0x0a, 0x1a, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x44, 0x6f, 0x6d, 0x61, 0x69,
0x72, 0x64, 0x52, 0x07, 0x72, 0x65, 0x63, 0x6f, 0x72, 0x64, 0x73, 0x22, 0x47, 0x0a, 0x1b, 0x43, 0x6e, 0x52, 0x65, 0x63, 0x6f, 0x72, 0x64, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12,
0x72, 0x65, 0x61, 0x74, 0x65, 0x44, 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x52, 0x65, 0x63, 0x6f, 0x72, 0x28, 0x0a, 0x07, 0x72, 0x65, 0x63, 0x6f, 0x72, 0x64, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b,
0x64, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x28, 0x0a, 0x07, 0x72, 0x65, 0x32, 0x0e, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x44, 0x4e, 0x53, 0x52, 0x65, 0x63, 0x6f, 0x72, 0x64,
0x63, 0x6f, 0x72, 0x64, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x0e, 0x2e, 0x61, 0x70, 0x52, 0x07, 0x72, 0x65, 0x63, 0x6f, 0x72, 0x64, 0x73, 0x22, 0x47, 0x0a, 0x1b, 0x43, 0x72, 0x65,
0x69, 0x2e, 0x44, 0x4e, 0x53, 0x52, 0x65, 0x63, 0x6f, 0x72, 0x64, 0x52, 0x07, 0x72, 0x65, 0x63, 0x61, 0x74, 0x65, 0x44, 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x52, 0x65, 0x63, 0x6f, 0x72, 0x64, 0x73,
0x6f, 0x72, 0x64, 0x73, 0x22, 0x96, 0x01, 0x0a, 0x09, 0x44, 0x4e, 0x53, 0x52, 0x65, 0x63, 0x6f, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x28, 0x0a, 0x07, 0x72, 0x65, 0x63, 0x6f,
0x72, 0x64, 0x12, 0x12, 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x72, 0x64, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x0e, 0x2e, 0x61, 0x70, 0x69, 0x2e,
0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x2d, 0x0a, 0x04, 0x74, 0x79, 0x70, 0x65, 0x18, 0x02, 0x44, 0x4e, 0x53, 0x52, 0x65, 0x63, 0x6f, 0x72, 0x64, 0x52, 0x07, 0x72, 0x65, 0x63, 0x6f, 0x72,
0x20, 0x01, 0x28, 0x0e, 0x32, 0x19, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x44, 0x4e, 0x53, 0x52, 0x65, 0x64, 0x73, 0x22, 0x96, 0x01, 0x0a, 0x09, 0x44, 0x4e, 0x53, 0x52, 0x65, 0x63, 0x6f, 0x72, 0x64,
0x63, 0x6f, 0x72, 0x64, 0x2e, 0x52, 0x65, 0x63, 0x6f, 0x72, 0x64, 0x54, 0x79, 0x70, 0x65, 0x52, 0x12, 0x12, 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04,
0x04, 0x74, 0x79, 0x70, 0x65, 0x12, 0x16, 0x0a, 0x06, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x73, 0x18, 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x2d, 0x0a, 0x04, 0x74, 0x79, 0x70, 0x65, 0x18, 0x02, 0x20, 0x01,
0x03, 0x20, 0x03, 0x28, 0x09, 0x52, 0x06, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x73, 0x22, 0x2e, 0x0a, 0x28, 0x0e, 0x32, 0x19, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x44, 0x4e, 0x53, 0x52, 0x65, 0x63, 0x6f,
0x0a, 0x52, 0x65, 0x63, 0x6f, 0x72, 0x64, 0x54, 0x79, 0x70, 0x65, 0x12, 0x0f, 0x0a, 0x0b, 0x55, 0x72, 0x64, 0x2e, 0x52, 0x65, 0x63, 0x6f, 0x72, 0x64, 0x54, 0x79, 0x70, 0x65, 0x52, 0x04, 0x74,
0x4e, 0x53, 0x50, 0x45, 0x43, 0x49, 0x46, 0x49, 0x45, 0x44, 0x10, 0x00, 0x12, 0x05, 0x0a, 0x01, 0x79, 0x70, 0x65, 0x12, 0x16, 0x0a, 0x06, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x73, 0x18, 0x03, 0x20,
0x41, 0x10, 0x01, 0x12, 0x08, 0x0a, 0x04, 0x41, 0x41, 0x41, 0x41, 0x10, 0x02, 0x32, 0xfb, 0x03, 0x03, 0x28, 0x09, 0x52, 0x06, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x73, 0x22, 0x2e, 0x0a, 0x0a, 0x52,
0x0a, 0x07, 0x43, 0x6c, 0x75, 0x73, 0x74, 0x65, 0x72, 0x12, 0x3d, 0x0a, 0x0a, 0x41, 0x64, 0x64, 0x65, 0x63, 0x6f, 0x72, 0x64, 0x54, 0x79, 0x70, 0x65, 0x12, 0x0f, 0x0a, 0x0b, 0x55, 0x4e, 0x53,
0x4d, 0x61, 0x63, 0x68, 0x69, 0x6e, 0x65, 0x12, 0x16, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x41, 0x64, 0x50, 0x45, 0x43, 0x49, 0x46, 0x49, 0x45, 0x44, 0x10, 0x00, 0x12, 0x05, 0x0a, 0x01, 0x41, 0x10,
0x64, 0x4d, 0x61, 0x63, 0x68, 0x69, 0x6e, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x01, 0x12, 0x08, 0x0a, 0x04, 0x41, 0x41, 0x41, 0x41, 0x10, 0x02, 0x32, 0x86, 0x04, 0x0a, 0x07,
0x17, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x41, 0x64, 0x64, 0x4d, 0x61, 0x63, 0x68, 0x69, 0x6e, 0x65, 0x43, 0x6c, 0x75, 0x73, 0x74, 0x65, 0x72, 0x12, 0x3d, 0x0a, 0x0a, 0x41, 0x64, 0x64, 0x4d, 0x61,
0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x41, 0x0a, 0x0c, 0x4c, 0x69, 0x73, 0x74, 0x63, 0x68, 0x69, 0x6e, 0x65, 0x12, 0x16, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x41, 0x64, 0x64, 0x4d,
0x4d, 0x61, 0x63, 0x68, 0x69, 0x6e, 0x65, 0x73, 0x12, 0x16, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x61, 0x63, 0x68, 0x69, 0x6e, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x17, 0x2e,
0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x61, 0x70, 0x69, 0x2e, 0x41, 0x64, 0x64, 0x4d, 0x61, 0x63, 0x68, 0x69, 0x6e, 0x65, 0x52, 0x65,
0x1a, 0x19, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x4d, 0x61, 0x63, 0x68, 0x69, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x41, 0x0a, 0x0c, 0x4c, 0x69, 0x73, 0x74, 0x4d, 0x61,
0x6e, 0x65, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x42, 0x0a, 0x0d, 0x52, 0x63, 0x68, 0x69, 0x6e, 0x65, 0x73, 0x12, 0x16, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e,
0x65, 0x6d, 0x6f, 0x76, 0x65, 0x4d, 0x61, 0x63, 0x68, 0x69, 0x6e, 0x65, 0x12, 0x19, 0x2e, 0x61, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x1a, 0x19,
0x70, 0x69, 0x2e, 0x52, 0x65, 0x6d, 0x6f, 0x76, 0x65, 0x4d, 0x61, 0x63, 0x68, 0x69, 0x6e, 0x65, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x4d, 0x61, 0x63, 0x68, 0x69, 0x6e, 0x65,
0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x16, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x42, 0x0a, 0x0d, 0x52, 0x65, 0x6d,
0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x12, 0x6f, 0x76, 0x65, 0x4d, 0x61, 0x63, 0x68, 0x69, 0x6e, 0x65, 0x12, 0x19, 0x2e, 0x61, 0x70, 0x69,
0x37, 0x0a, 0x0d, 0x52, 0x65, 0x73, 0x65, 0x72, 0x76, 0x65, 0x44, 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x2e, 0x52, 0x65, 0x6d, 0x6f, 0x76, 0x65, 0x4d, 0x61, 0x63, 0x68, 0x69, 0x6e, 0x65, 0x52, 0x65,
0x12, 0x19, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x52, 0x65, 0x73, 0x65, 0x72, 0x76, 0x65, 0x44, 0x6f, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x16, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70,
0x6d, 0x61, 0x69, 0x6e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x0b, 0x2e, 0x61, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x12, 0x37, 0x0a,
0x69, 0x2e, 0x44, 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x12, 0x2f, 0x0a, 0x09, 0x53, 0x65, 0x74, 0x44, 0x0d, 0x52, 0x65, 0x73, 0x65, 0x72, 0x76, 0x65, 0x44, 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x12, 0x19,
0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x12, 0x15, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x53, 0x65, 0x74, 0x44, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x52, 0x65, 0x73, 0x65, 0x72, 0x76, 0x65, 0x44, 0x6f, 0x6d, 0x61,
0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x0b, 0x2e, 0x61, 0x69, 0x6e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x0b, 0x2e, 0x61, 0x70, 0x69, 0x2e,
0x70, 0x69, 0x2e, 0x44, 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x12, 0x30, 0x0a, 0x09, 0x47, 0x65, 0x74, 0x44, 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x12, 0x3a, 0x0a, 0x09, 0x53, 0x65, 0x74, 0x44, 0x6f, 0x6d,
0x44, 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x12, 0x16, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x61, 0x69, 0x6e, 0x12, 0x15, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x53, 0x65, 0x74, 0x44, 0x6f, 0x6d,
0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x1a, 0x0b, 0x61, 0x69, 0x6e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x16, 0x2e, 0x67, 0x6f, 0x6f,
0x2e, 0x61, 0x70, 0x69, 0x2e, 0x44, 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x12, 0x34, 0x0a, 0x0d, 0x52, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x45, 0x6d, 0x70,
0x65, 0x6c, 0x65, 0x61, 0x73, 0x65, 0x44, 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x12, 0x16, 0x2e, 0x67, 0x74, 0x79, 0x12, 0x30, 0x0a, 0x09, 0x47, 0x65, 0x74, 0x44, 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x12,
0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x45, 0x16, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75,
0x6d, 0x70, 0x74, 0x79, 0x1a, 0x0b, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x44, 0x6f, 0x6d, 0x61, 0x69, 0x66, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x1a, 0x0b, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x44, 0x6f,
0x6e, 0x12, 0x58, 0x0a, 0x13, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x44, 0x6f, 0x6d, 0x61, 0x69, 0x6d, 0x61, 0x69, 0x6e, 0x12, 0x34, 0x0a, 0x0d, 0x52, 0x65, 0x6c, 0x65, 0x61, 0x73, 0x65, 0x44,
0x6e, 0x52, 0x65, 0x63, 0x6f, 0x72, 0x64, 0x73, 0x12, 0x1f, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x43, 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x12, 0x16, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70,
0x72, 0x65, 0x61, 0x74, 0x65, 0x44, 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x52, 0x65, 0x63, 0x6f, 0x72, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x1a, 0x0b, 0x2e,
0x64, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x20, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x44, 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x12, 0x58, 0x0a, 0x13, 0x43, 0x72,
0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x44, 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x52, 0x65, 0x63, 0x6f, 0x65, 0x61, 0x74, 0x65, 0x44, 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x52, 0x65, 0x63, 0x6f, 0x72, 0x64,
0x72, 0x64, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x42, 0x26, 0x5a, 0x24, 0x67, 0x73, 0x12, 0x1f, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x44, 0x6f,
0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x70, 0x73, 0x76, 0x69, 0x64, 0x65, 0x6d, 0x61, 0x69, 0x6e, 0x52, 0x65, 0x63, 0x6f, 0x72, 0x64, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65,
0x72, 0x73, 0x6b, 0x69, 0x2f, 0x75, 0x6e, 0x63, 0x6c, 0x6f, 0x75, 0x64, 0x2f, 0x61, 0x70, 0x69, 0x73, 0x74, 0x1a, 0x20, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x44,
0x2f, 0x70, 0x62, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, 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 ( var (
@@ -842,7 +857,7 @@ var file_api_pb_cluster_proto_depIdxs = []int32{
5, // 18: api.Cluster.ListMachines:output_type -> api.ListMachinesResponse 5, // 18: api.Cluster.ListMachines:output_type -> api.ListMachinesResponse
16, // 19: api.Cluster.RemoveMachine:output_type -> google.protobuf.Empty 16, // 19: api.Cluster.RemoveMachine:output_type -> google.protobuf.Empty
7, // 20: api.Cluster.ReserveDomain:output_type -> api.Domain 7, // 20: api.Cluster.ReserveDomain:output_type -> api.Domain
7, // 21: api.Cluster.SetDomain:output_type -> api.Domain 16, // 21: api.Cluster.SetDomain:output_type -> google.protobuf.Empty
7, // 22: api.Cluster.GetDomain:output_type -> api.Domain 7, // 22: api.Cluster.GetDomain:output_type -> api.Domain
7, // 23: api.Cluster.ReleaseDomain:output_type -> api.Domain 7, // 23: api.Cluster.ReleaseDomain:output_type -> api.Domain
11, // 24: api.Cluster.CreateDomainRecords:output_type -> api.CreateDomainRecordsResponse 11, // 24: api.Cluster.CreateDomainRecords:output_type -> api.CreateDomainRecordsResponse
@@ -994,6 +1009,7 @@ func file_api_pb_cluster_proto_init() {
} }
} }
} }
file_api_pb_cluster_proto_msgTypes[5].OneofWrappers = []any{}
type x struct{} type x struct{}
out := protoimpl.TypeBuilder{ out := protoimpl.TypeBuilder{
File: protoimpl.DescBuilder{ File: protoimpl.DescBuilder{
+5 -1
View File
@@ -14,7 +14,7 @@ service Cluster {
rpc RemoveMachine(RemoveMachineRequest) returns (google.protobuf.Empty); rpc RemoveMachine(RemoveMachineRequest) returns (google.protobuf.Empty);
rpc ReserveDomain(ReserveDomainRequest) returns (Domain); rpc ReserveDomain(ReserveDomainRequest) returns (Domain);
rpc SetDomain(SetDomainRequest) returns (Domain); rpc SetDomain(SetDomainRequest) returns (google.protobuf.Empty);
rpc GetDomain(google.protobuf.Empty) returns (Domain); rpc GetDomain(google.protobuf.Empty) returns (Domain);
rpc ReleaseDomain(google.protobuf.Empty) returns (Domain); rpc ReleaseDomain(google.protobuf.Empty) returns (Domain);
rpc CreateDomainRecords(CreateDomainRecordsRequest) returns (CreateDomainRecordsResponse); rpc CreateDomainRecords(CreateDomainRecordsRequest) returns (CreateDomainRecordsResponse);
@@ -57,6 +57,9 @@ message RemoveMachineRequest {
message Domain { message Domain {
string name = 1; 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 { message ReserveDomainRequest {
@@ -64,6 +67,7 @@ message ReserveDomainRequest {
} }
message SetDomainRequest { message SetDomainRequest {
// An externally managed domain name. Empty clears a manually set domain.
string name = 1; string name = 1;
} }
+5 -5
View File
@@ -38,7 +38,7 @@ type ClusterClient interface {
ListMachines(ctx context.Context, in *emptypb.Empty, opts ...grpc.CallOption) (*ListMachinesResponse, error) ListMachines(ctx context.Context, in *emptypb.Empty, opts ...grpc.CallOption) (*ListMachinesResponse, error)
RemoveMachine(ctx context.Context, in *RemoveMachineRequest, opts ...grpc.CallOption) (*emptypb.Empty, error) RemoveMachine(ctx context.Context, in *RemoveMachineRequest, opts ...grpc.CallOption) (*emptypb.Empty, error)
ReserveDomain(ctx context.Context, in *ReserveDomainRequest, opts ...grpc.CallOption) (*Domain, error) ReserveDomain(ctx context.Context, in *ReserveDomainRequest, opts ...grpc.CallOption) (*Domain, error)
SetDomain(ctx context.Context, in *SetDomainRequest, 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) GetDomain(ctx context.Context, in *emptypb.Empty, opts ...grpc.CallOption) (*Domain, error)
ReleaseDomain(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) CreateDomainRecords(ctx context.Context, in *CreateDomainRecordsRequest, opts ...grpc.CallOption) (*CreateDomainRecordsResponse, error)
@@ -92,9 +92,9 @@ func (c *clusterClient) ReserveDomain(ctx context.Context, in *ReserveDomainRequ
return out, nil return out, nil
} }
func (c *clusterClient) SetDomain(ctx context.Context, in *SetDomainRequest, opts ...grpc.CallOption) (*Domain, error) { func (c *clusterClient) SetDomain(ctx context.Context, in *SetDomainRequest, opts ...grpc.CallOption) (*emptypb.Empty, error) {
cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)
out := new(Domain) out := new(emptypb.Empty)
err := c.cc.Invoke(ctx, Cluster_SetDomain_FullMethodName, in, out, cOpts...) err := c.cc.Invoke(ctx, Cluster_SetDomain_FullMethodName, in, out, cOpts...)
if err != nil { if err != nil {
return nil, err return nil, err
@@ -140,7 +140,7 @@ type ClusterServer interface {
ListMachines(context.Context, *emptypb.Empty) (*ListMachinesResponse, error) ListMachines(context.Context, *emptypb.Empty) (*ListMachinesResponse, error)
RemoveMachine(context.Context, *RemoveMachineRequest) (*emptypb.Empty, error) RemoveMachine(context.Context, *RemoveMachineRequest) (*emptypb.Empty, error)
ReserveDomain(context.Context, *ReserveDomainRequest) (*Domain, error) ReserveDomain(context.Context, *ReserveDomainRequest) (*Domain, error)
SetDomain(context.Context, *SetDomainRequest) (*Domain, error) SetDomain(context.Context, *SetDomainRequest) (*emptypb.Empty, error)
GetDomain(context.Context, *emptypb.Empty) (*Domain, error) GetDomain(context.Context, *emptypb.Empty) (*Domain, error)
ReleaseDomain(context.Context, *emptypb.Empty) (*Domain, error) ReleaseDomain(context.Context, *emptypb.Empty) (*Domain, error)
CreateDomainRecords(context.Context, *CreateDomainRecordsRequest) (*CreateDomainRecordsResponse, error) CreateDomainRecords(context.Context, *CreateDomainRecordsRequest) (*CreateDomainRecordsResponse, error)
@@ -166,7 +166,7 @@ func (UnimplementedClusterServer) RemoveMachine(context.Context, *RemoveMachineR
func (UnimplementedClusterServer) ReserveDomain(context.Context, *ReserveDomainRequest) (*Domain, error) { func (UnimplementedClusterServer) ReserveDomain(context.Context, *ReserveDomainRequest) (*Domain, error) {
return nil, status.Errorf(codes.Unimplemented, "method ReserveDomain not implemented") return nil, status.Errorf(codes.Unimplemented, "method ReserveDomain not implemented")
} }
func (UnimplementedClusterServer) SetDomain(context.Context, *SetDomainRequest) (*Domain, error) { func (UnimplementedClusterServer) SetDomain(context.Context, *SetDomainRequest) (*emptypb.Empty, error) {
return nil, status.Errorf(codes.Unimplemented, "method SetDomain not implemented") return nil, status.Errorf(codes.Unimplemented, "method SetDomain not implemented")
} }
func (UnimplementedClusterServer) GetDomain(context.Context, *emptypb.Empty) (*Domain, error) { func (UnimplementedClusterServer) GetDomain(context.Context, *emptypb.Empty) (*Domain, error) {
+5 -1
View File
@@ -202,6 +202,10 @@ func UpdateDomainRecords(ctx context.Context, clusterClient *client.Client, prog
} }
return fmt.Errorf("get cluster domain: %w", err) 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...") 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. // 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) { if errors.Is(err, client.ErrNoReachableMachines) {
fmt.Println() fmt.Println()
fmt.Printf("DNS records for domain '%s' could not be updated as there are no internet-reachable "+ 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()
fmt.Println("Possible solutions:") fmt.Println("Possible solutions:")
fmt.Println("- Ensure your machines have public IP addresses") fmt.Println("- Ensure your machines have public IP addresses")
+1
View File
@@ -37,6 +37,7 @@ func release(ctx context.Context, uncli *cli.CLI) error {
if status.Convert(err).Code() == codes.NotFound { if status.Convert(err).Code() == codes.NotFound {
return errors.New("no domain reserved") return errors.New("no domain reserved")
} }
return fmt.Errorf("release cluster domain: %w", err)
} }
fmt.Printf("Released cluster domain: %s\n", domain.Name) fmt.Printf("Released cluster domain: %s\n", domain.Name)
+2 -1
View File
@@ -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}) domain, err := clusterClient.ReserveDomain(ctx, &pb.ReserveDomainRequest{Endpoint: opts.endpoint})
if err != nil { if err != nil {
if status.Convert(err).Code() == codes.AlreadyExists { 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 return err
} }
+5 -3
View File
@@ -7,11 +7,13 @@ import (
func NewRootCommand() *cobra.Command { func NewRootCommand() *cobra.Command {
cmd := &cobra.Command{ cmd := &cobra.Command{
Use: "dns", Use: "dns",
Short: "Manage cluster domain in Uncloud DNS.", Short: "Manage the cluster domain.",
Long: "Manage cluster domain in Uncloud DNS.\n" + Long: "Manage the cluster domain.\n" +
"DNS commands allow you to reserve or release a unique 'xxxxxx.uncld.dev' domain for your " + "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 " + "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( cmd.AddCommand(
NewReleaseCommand(), NewReleaseCommand(),
+25 -13
View File
@@ -8,6 +8,7 @@ import (
"github.com/miekg/dns" "github.com/miekg/dns"
"github.com/psviderski/uncloud/api/pb" "github.com/psviderski/uncloud/api/pb"
"github.com/psviderski/uncloud/internal/cli" "github.com/psviderski/uncloud/internal/cli"
"github.com/psviderski/uncloud/internal/cli/tui"
"github.com/spf13/cobra" "github.com/spf13/cobra"
"google.golang.org/grpc/codes" "google.golang.org/grpc/codes"
"google.golang.org/grpc/status" "google.golang.org/grpc/status"
@@ -17,9 +18,14 @@ func NewSetCommand() *cobra.Command {
cmd := &cobra.Command{ cmd := &cobra.Command{
Use: "set DOMAIN_NAME", Use: "set DOMAIN_NAME",
Args: cobra.ExactArgs(1), Args: cobra.ExactArgs(1),
Short: "Set a cluster domain directly in the cluster.", Short: "Set or unset an externally managed cluster domain (EXPERIMENTAL).",
Long: "Set a cluster domain directly in the cluster, bypassing Uncloud DNS. " + Long: "EXPERIMENTAL: Set the cluster domain used to generate ingress hostnames for services.\n" +
"This assumes the DNS is externally set up.", "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 { RunE: func(cmd *cobra.Command, args []string) error {
uncli := cmd.Context().Value("cli").(*cli.CLI) uncli := cmd.Context().Value("cli").(*cli.CLI)
return set(cmd.Context(), uncli, args[0]) return set(cmd.Context(), uncli, args[0])
@@ -30,10 +36,12 @@ func NewSetCommand() *cobra.Command {
} }
func set(ctx context.Context, uncli *cli.CLI, name string) error { func set(ctx context.Context, uncli *cli.CLI, name string) error {
labels, ok := dns.IsDomainName(name) if name != "" {
if !ok || labels < 3 { if labels, ok := dns.IsDomainName(name); !ok || labels < 2 {
return fmt.Errorf("domain '%s' is not a valid name", name) 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) clusterClient, err := uncli.ConnectCluster(ctx)
if err != nil { if err != nil {
@@ -41,17 +49,21 @@ func set(ctx context.Context, uncli *cli.CLI, name string) error {
} }
defer clusterClient.Close() defer clusterClient.Close()
domain, err := clusterClient.SetDomain(ctx, &pb.SetDomainRequest{Name: name}) _, err = clusterClient.SetDomain(ctx, &pb.SetDomainRequest{Name: name})
if err != nil { if err != nil {
if status.Convert(err).Code() == codes.AlreadyExists { 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 return fmt.Errorf("set cluster domain: %w", err)
} }
fmt.Printf("Set cluster domain: %s\n", domain.Name) if name == "" {
fmt.Println("Unset cluster domain. External DNS records have not been changed.")
// No need to update anything as the domain records pointing to this cluster should be wildcard, so all return nil
// names already exist. }
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 return nil
} }
+2 -2
View File
@@ -33,11 +33,11 @@ func show(ctx context.Context, uncli *cli.CLI) error {
domain, err := clusterClient.GetDomain(ctx) domain, err := clusterClient.GetDomain(ctx)
if err != nil { if err != nil {
if errors.Is(err, api.ErrNotFound) { if errors.Is(err, api.ErrNotFound) {
return errors.New("no domain reserved") return errors.New("no cluster domain configured")
} }
return err return err
} }
fmt.Println(domain) fmt.Println(domain.Name)
return nil return nil
} }
+51 -26
View File
@@ -4,20 +4,24 @@ import (
"context" "context"
"encoding/json" "encoding/json"
"errors" "errors"
"strings"
"github.com/miekg/dns"
"github.com/psviderski/uncloud/api/pb" "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" "github.com/psviderski/uncloud/internal/machine/store"
"google.golang.org/grpc/codes" "google.golang.org/grpc/codes"
"google.golang.org/grpc/status" "google.golang.org/grpc/status"
"google.golang.org/protobuf/proto"
"google.golang.org/protobuf/types/known/emptypb" "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" const uncloudDNSKey = "uncloud_dns"
type uncloudDNSDomain struct { type uncloudDNSDomain struct {
// Endpoint is the API endpoint of the Uncloud DNS service where the domain is reserved. // 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 Endpoint string
Name string Name string
// TODO: encrypt the token in the store. // 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 { 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 { } else {
if s := status.Convert(err); s.Code() != codes.NotFound { if s := status.Convert(err); s.Code() != codes.NotFound {
return nil, err return nil, err
} }
} }
dnsClient := dns.NewClient() dnsClient := undns.NewClient()
name, token, err := dnsClient.ReserveDomain(req.Endpoint) name, token, err := dnsClient.ReserveDomain(req.Endpoint)
if err != nil { if err != nil {
return nil, status.Error(codes.Internal, err.Error()) 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 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) { 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 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) { func (c *Cluster) storedDomain(ctx context.Context) (uncloudDNSDomain, error) {
@@ -103,33 +107,54 @@ func (c *Cluster) ReleaseDomain(ctx context.Context, _ *emptypb.Empty) (*pb.Doma
if err != nil { if err != nil {
return nil, err 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 { if err = c.store.Delete(ctx, uncloudDNSKey); err != nil {
return nil, status.Errorf(codes.Internal, "delete domain from store: %v", err) return nil, status.Errorf(codes.Internal, "delete domain from store: %v", err)
} }
// TODO: implement and call Uncloud DNS endpoint to release/delete the domain. // 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) (*pb.Domain, error) { func (c *Cluster) SetDomain(ctx context.Context, req *pb.SetDomainRequest) (*emptypb.Empty, error) {
if err := c.checkReady(); err != nil { if err := c.checkReady(); err != nil {
return nil, err return nil, err
} }
if _, err := c.storedDomain(ctx); err == nil { name := req.GetName()
return nil, status.Errorf(codes.AlreadyExists, "domain already reserved") if name != "" {
} else { if labels, ok := dns.IsDomainName(name); !ok || labels < 2 {
if s := status.Convert(err); s.Code() != codes.NotFound { return nil, status.Errorf(codes.InvalidArgument,
return nil, err "invalid cluster domain '%s': must be a valid domain name with at least two labels", name)
} }
} }
name = strings.ToLower(strings.TrimSuffix(name, "."))
domain := uncloudDNSDomain{ existing, err := c.storedDomain(ctx)
Endpoint: "", if err != nil && status.Code(err) != codes.NotFound {
Name: req.Name, return nil, err
Token: "",
} }
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) domainJSON, err := json.Marshal(domain)
if err != nil { if err != nil {
return nil, status.Errorf(codes.Internal, "marshal set domain for store: %v", err) return nil, status.Errorf(codes.Internal, "marshal set domain for store: %v", err)
@@ -138,7 +163,7 @@ func (c *Cluster) SetDomain(ctx context.Context, req *pb.SetDomainRequest) (*pb.
return nil, status.Errorf(codes.Internal, "store set domain: %v", err) return nil, status.Errorf(codes.Internal, "store set domain: %v", err)
} }
return &pb.Domain{Name: req.Name}, nil return &emptypb.Empty{}, nil
} }
func (c *Cluster) CreateDomainRecords( func (c *Cluster) CreateDomainRecords(
@@ -153,16 +178,16 @@ func (c *Cluster) CreateDomainRecords(
return nil, err return nil, err
} }
if domain.Endpoint == "" { // uc dns set has set a name if domain.Endpoint == "" {
return &pb.CreateDomainRecordsResponse{}, nil return nil, status.Error(codes.FailedPrecondition, "cluster domain is not reserved in Uncloud DNS")
} }
dnsClient := dns.NewClient() dnsClient := undns.NewClient()
recordsReq := make([]dns.RecordRequest, len(req.Records)) recordsReq := make([]undns.RecordRequest, len(req.Records))
for i, r := range req.Records { for i, r := range req.Records {
recordsReq[i] = dns.RecordRequest{ recordsReq[i] = undns.RecordRequest{
Name: r.Name, Name: r.Name,
Type: dns.RecordType(r.Type.String()), Type: undns.RecordType(r.Type.String()),
Values: r.Values, Values: r.Values,
} }
} }
@@ -182,9 +207,9 @@ func (c *Cluster) CreateDomainRecords(
} }
switch r.Type { switch r.Type {
case dns.RecordTypeA: case undns.RecordTypeA:
resp.Records[i].Type = pb.DNSRecord_A resp.Records[i].Type = pb.DNSRecord_A
case dns.RecordTypeAAAA: case undns.RecordTypeAAAA:
resp.Records[i].Type = pb.DNSRecord_AAAA resp.Records[i].Type = pb.DNSRecord_AAAA
} }
} }
+1 -1
View File
@@ -36,7 +36,7 @@ type ContainerClient interface {
} }
type DNSClient interface { type DNSClient interface {
GetDomain(ctx context.Context) (string, error) GetDomain(ctx context.Context) (ClusterDomain, error)
} }
type ImageClient interface { type ImageClient interface {
+8
View File
@@ -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
}
+2 -2
View File
@@ -46,8 +46,8 @@ func NewDeploymentWithStrategy(ctx context.Context, cli Client, project *types.P
return nil, fmt.Errorf("get cluster domain: %w", err) return nil, fmt.Errorf("get cluster domain: %w", err)
} }
resolver := &deploy.ServiceSpecResolver{ resolver := &deploy.ServiceSpecResolver{
// If the domain is not found (not reserved), an empty domain is used for the resolver. // If no domain is configured, an empty domain is used for the resolver.
ClusterDomain: domain, ClusterDomain: domain.Name,
} }
return &Deployment{ return &Deployment{
+2 -2
View File
@@ -306,8 +306,8 @@ func (d *Deployment) Plan(ctx context.Context) (ServicePlan, error) {
return ServicePlan{}, fmt.Errorf("get cluster domain: %w", err) return ServicePlan{}, fmt.Errorf("get cluster domain: %w", err)
} }
specResolver := &ServiceSpecResolver{ specResolver := &ServiceSpecResolver{
// If the domain is not found (not reserved), an empty domain is used for the resolver. // If no domain is configured, an empty domain is used for the resolver.
ClusterDomain: clusterDomain, ClusterDomain: clusterDomain.Name,
} }
resolvedSpec, err := specResolver.Resolve(d.Spec) resolvedSpec, err := specResolver.Resolve(d.Spec)
+20 -7
View File
@@ -21,17 +21,21 @@ import (
"google.golang.org/grpc/status" "google.golang.org/grpc/status"
) )
// GetDomain returns the cluster domain name or ErrNotFound if it hasn't been reserved yet. // GetDomain returns the cluster domain or api.ErrNotFound if none is configured.
func (cli *Client) GetDomain(ctx context.Context) (string, error) { func (cli *Client) GetDomain(ctx context.Context) (api.ClusterDomain, error) {
domain, err := cli.ClusterClient.GetDomain(ctx, nil) domain, err := cli.ClusterClient.GetDomain(ctx, nil)
if err != nil { if err != nil {
if status.Convert(err).Code() == codes.NotFound { if status.Code(err) == codes.NotFound {
return "", api.ErrNotFound 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") 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 // 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 // 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 // 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) { 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) svc, err := cli.InspectService(ctx, serviceID)
if err != nil { if err != nil {
return nil, fmt.Errorf("inspect service '%s': %w", serviceID, err) return nil, fmt.Errorf("inspect service '%s': %w", serviceID, err)
+1 -1
View File
@@ -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 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 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 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 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 image](uc_image.md) - Manage images on machines in the cluster.
* [uc images](uc_images.md) - List images on machines in the cluster. * [uc images](uc_images.md) - List images on machines in the cluster.
+5 -3
View File
@@ -1,12 +1,14 @@
# uc dns # uc dns
Manage cluster domain in Uncloud DNS. Manage the cluster domain.
## Synopsis ## 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. 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 ## Options
``` ```
@@ -27,6 +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](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 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 reserve](uc_dns_reserve.md) - Reserve a cluster domain in Uncloud DNS.
* [uc dns set](uc_dns_set.md) - Set a cluster domain directly in the cluster. * [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. * [uc dns show](uc_dns_show.md) - Print the cluster domain name.
@@ -23,5 +23,5 @@ uc dns release [flags]
## See also ## See also
* [uc dns](uc_dns.md) - Manage cluster domain in Uncloud DNS. * [uc dns](uc_dns.md) - Manage the cluster domain.
@@ -24,5 +24,5 @@ uc dns reserve [flags]
## See also ## See also
* [uc dns](uc_dns.md) - Manage cluster domain in Uncloud DNS. * [uc dns](uc_dns.md) - Manage the cluster domain.
+12 -3
View File
@@ -1,15 +1,24 @@
# uc dns set # uc dns set
Set a cluster domain directly in the cluster. Set or unset an externally managed cluster domain (EXPERIMENTAL).
## Synopsis ## Synopsis
Set a cluster domain directly in the cluster, bypassing Uncloud DNS. This assumes the DNS is externally set up. 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] uc dns set DOMAIN_NAME [flags]
``` ```
## Examples
```
uc dns set apps.example.com
uc dns set ""
```
## Options ## Options
``` ```
@@ -27,5 +36,5 @@ uc dns set DOMAIN_NAME [flags]
## See also ## See also
* [uc dns](uc_dns.md) - Manage cluster domain in Uncloud DNS. * [uc dns](uc_dns.md) - Manage the cluster domain.
+1 -1
View File
@@ -23,5 +23,5 @@ uc dns show [flags]
## See also ## See also
* [uc dns](uc_dns.md) - Manage cluster domain in Uncloud DNS. * [uc dns](uc_dns.md) - Manage the cluster domain.