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:
@@ -27,11 +27,11 @@ func (api *API) ServicesListAll(w http.ResponseWriter, r *http.Request) {
|
||||
})
|
||||
}
|
||||
|
||||
// ServicesGet retrieves a service by name.
|
||||
// ServicesGet retrieves a service by domain.
|
||||
func (api *API) ServicesGet(w http.ResponseWriter, r *http.Request) {
|
||||
name := mux.Vars(r)["name"]
|
||||
domain := mux.Vars(r)["domain"]
|
||||
|
||||
svc, err := api.services.Get(name)
|
||||
svc, err := api.services.Get(domain)
|
||||
if err != nil {
|
||||
respondError(w, http.StatusNotFound, fmt.Sprintf("Service not found: %v", err))
|
||||
return
|
||||
@@ -54,16 +54,16 @@ func (api *API) ServicesRegister(w http.ResponseWriter, r *http.Request) {
|
||||
}
|
||||
|
||||
// Return the stored version (has defaults applied)
|
||||
stored, _ := api.services.Get(svc.Name)
|
||||
stored, _ := api.services.Get(svc.Domain)
|
||||
respondJSON(w, http.StatusCreated, map[string]any{
|
||||
"message": fmt.Sprintf("Service %q registered", svc.Name),
|
||||
"message": fmt.Sprintf("Service %q registered", svc.Domain),
|
||||
"service": stored,
|
||||
})
|
||||
}
|
||||
|
||||
// ServicesUpdate applies partial updates to a service.
|
||||
func (api *API) ServicesUpdate(w http.ResponseWriter, r *http.Request) {
|
||||
name := mux.Vars(r)["name"]
|
||||
domain := mux.Vars(r)["domain"]
|
||||
|
||||
var updates map[string]any
|
||||
if err := json.NewDecoder(r.Body).Decode(&updates); err != nil {
|
||||
@@ -71,26 +71,44 @@ func (api *API) ServicesUpdate(w http.ResponseWriter, r *http.Request) {
|
||||
return
|
||||
}
|
||||
|
||||
if err := api.services.Update(name, updates); err != nil {
|
||||
if err := api.services.Update(domain, updates); err != nil {
|
||||
respondError(w, http.StatusNotFound, fmt.Sprintf("Failed to update service: %v", err))
|
||||
return
|
||||
}
|
||||
|
||||
svc, _ := api.services.Get(name)
|
||||
svc, _ := api.services.Get(domain)
|
||||
respondJSON(w, http.StatusOK, map[string]any{
|
||||
"message": fmt.Sprintf("Service %q updated", name),
|
||||
"message": fmt.Sprintf("Service %q updated", domain),
|
||||
"service": svc,
|
||||
})
|
||||
}
|
||||
|
||||
// ServicesDeregister removes a service registration.
|
||||
func (api *API) ServicesDeregister(w http.ResponseWriter, r *http.Request) {
|
||||
name := mux.Vars(r)["name"]
|
||||
domain := mux.Vars(r)["domain"]
|
||||
|
||||
if err := api.services.Deregister(name); err != nil {
|
||||
if err := api.services.Deregister(domain); err != nil {
|
||||
respondError(w, http.StatusNotFound, fmt.Sprintf("Failed to deregister service: %v", err))
|
||||
return
|
||||
}
|
||||
|
||||
respondMessage(w, http.StatusOK, fmt.Sprintf("Service %q deregistered", name))
|
||||
respondMessage(w, http.StatusOK, fmt.Sprintf("Service %q deregistered", domain))
|
||||
}
|
||||
|
||||
// ServicesDeregisterBySource removes all services from a source with a given backend.
|
||||
func (api *API) ServicesDeregisterBySource(w http.ResponseWriter, r *http.Request) {
|
||||
source := r.URL.Query().Get("source")
|
||||
backend := r.URL.Query().Get("backend")
|
||||
|
||||
if source == "" {
|
||||
respondError(w, http.StatusBadRequest, "source query parameter is required")
|
||||
return
|
||||
}
|
||||
|
||||
if err := api.services.DeregisterBySource(source, backend); err != nil {
|
||||
respondError(w, http.StatusInternalServerError, fmt.Sprintf("Failed to deregister: %v", err))
|
||||
return
|
||||
}
|
||||
|
||||
respondMessage(w, http.StatusOK, fmt.Sprintf("Services from %q deregistered", source))
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user