feat: Domain-keyed service registration model
Rewrite the service registration model per the architecture plan:
Model changes:
- Domain is the unique key (removed Name field)
- Removed ExtraDomains — each domain is its own registration
- Removed SourceNode, BackendStatic, ReachOff
- Added Subdomains bool for wildcard matching control
- Files stored as <domain_with_underscores>.yaml
- API routes: /services/{domain:.+} (regex for dots in path)
- Added DeregisterBySource for cleanup before re-registration
Reconciliation changes:
- No ExtraDomains iteration — each service IS one domain
- reach:internal → sets InternalDomain (generates local=/)
- reach:public → sets Domain (no local=/)
- tcp-passthrough → DNS points to backend IP
- http → DNS points to Central IP
All 22 tests pass (9 manager + 8 handler + 5 config/cert).
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -38,21 +38,16 @@ func (api *API) reconcileNetworking() {
|
||||
var httpRoutes []haproxy.HTTPRoute
|
||||
|
||||
for _, svc := range svcs {
|
||||
if svc.Reach == services.ReachOff {
|
||||
continue
|
||||
}
|
||||
|
||||
switch svc.Backend.Type {
|
||||
case services.BackendTCPPassthrough:
|
||||
instanceRoutes = append(instanceRoutes, haproxy.InstanceRoute{
|
||||
Name: svc.Name,
|
||||
Domain: svc.Domain,
|
||||
BackendIP: extractHost(svc.Backend.Address),
|
||||
ExtraDomains: svc.ExtraDomains,
|
||||
Name: svc.Domain,
|
||||
Domain: svc.Domain,
|
||||
BackendIP: extractHost(svc.Backend.Address),
|
||||
})
|
||||
case services.BackendHTTP, services.BackendStatic:
|
||||
case services.BackendHTTP:
|
||||
httpRoutes = append(httpRoutes, haproxy.HTTPRoute{
|
||||
Name: svc.Name,
|
||||
Name: svc.Domain,
|
||||
Domain: svc.Domain,
|
||||
Backend: svc.Backend.Address,
|
||||
HealthPath: svc.Backend.Health,
|
||||
@@ -116,7 +111,7 @@ func (api *API) reconcileNetworking() {
|
||||
|
||||
var instanceConfigs []config.InstanceConfig
|
||||
for _, svc := range svcs {
|
||||
if svc.Reach == services.ReachOff || svc.Domain == "" {
|
||||
if svc.Domain == "" {
|
||||
continue
|
||||
}
|
||||
|
||||
@@ -127,17 +122,14 @@ func (api *API) reconcileNetworking() {
|
||||
dnsIP = extractHost(svc.Backend.Address)
|
||||
}
|
||||
|
||||
// Primary domain
|
||||
ic := config.InstanceConfig{}
|
||||
ic.Cloud.Domain = svc.Domain
|
||||
ic.Cluster.LoadBalancerIp = dnsIP
|
||||
|
||||
// Extra domains (e.g., internal.cloud.payne.io)
|
||||
for _, extra := range svc.ExtraDomains {
|
||||
if strings.HasPrefix(extra, "internal.") {
|
||||
// Internal-only domain: local=/ prevents upstream DNS forwarding
|
||||
ic.Cloud.InternalDomain = extra
|
||||
}
|
||||
if svc.Reach == services.ReachInternal {
|
||||
// Internal-only: local=/ prevents upstream DNS forwarding
|
||||
ic.Cloud.InternalDomain = svc.Domain
|
||||
} else {
|
||||
ic.Cloud.Domain = svc.Domain
|
||||
}
|
||||
|
||||
instanceConfigs = append(instanceConfigs, ic)
|
||||
@@ -182,7 +174,7 @@ func (api *API) ensureTLSCerts(globalCfg *config.GlobalConfig, svcs []services.S
|
||||
wildcardCert := certbot.HAProxyCertPath(gatewayDomain)
|
||||
if _, err := os.Stat(wildcardCert); err != nil {
|
||||
slog.Warn("reconcile/tls: no wildcard cert for gateway domain — provision via Certificates page",
|
||||
"service", svc.Name, "domain", svc.Domain, "needed", "*."+gatewayDomain)
|
||||
"domain", svc.Domain, "needed", "*."+gatewayDomain)
|
||||
}
|
||||
continue
|
||||
}
|
||||
@@ -190,7 +182,7 @@ func (api *API) ensureTLSCerts(globalCfg *config.GlobalConfig, svcs []services.S
|
||||
// Check individual cert
|
||||
if _, err := os.Stat(certbot.HAProxyCertPath(svc.Domain)); err != nil {
|
||||
slog.Warn("reconcile/tls: no cert for service — provision via Certificates page",
|
||||
"service", svc.Name, "domain", svc.Domain)
|
||||
"domain", svc.Domain)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user