feat: Extract Wild Central as standalone Go service
Extract the Central networking functionality from wild-cloud/api into a standalone service. Wild Central manages DNS (dnsmasq), gateway (HAProxy), firewall (nftables), VPN (WireGuard), TLS (certbot), security (CrowdSec), and DDNS — all the network appliance concerns. All Cloud-specific code (instances, clusters, nodes, apps, backups, operations, kubectl/talosctl tooling) has been removed. The API struct and route registration contain only Central endpoints. Tests updated to match the new API signature. Builds and all tests pass. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
120
internal/api/v1/middleware.go
Normal file
120
internal/api/v1/middleware.go
Normal file
@@ -0,0 +1,120 @@
|
||||
package v1
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"fmt"
|
||||
"log/slog"
|
||||
"net"
|
||||
"net/http"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/gorilla/mux"
|
||||
)
|
||||
|
||||
// statusResponseWriter wraps http.ResponseWriter to capture the status code.
|
||||
// Implements http.Hijacker so WebSocket upgrades work through the proxy.
|
||||
type statusResponseWriter struct {
|
||||
http.ResponseWriter
|
||||
status int
|
||||
}
|
||||
|
||||
func (w *statusResponseWriter) WriteHeader(code int) {
|
||||
w.status = code
|
||||
w.ResponseWriter.WriteHeader(code)
|
||||
}
|
||||
|
||||
func (w *statusResponseWriter) Hijack() (net.Conn, *bufio.ReadWriter, error) {
|
||||
if hj, ok := w.ResponseWriter.(http.Hijacker); ok {
|
||||
return hj.Hijack()
|
||||
}
|
||||
return nil, nil, fmt.Errorf("upstream ResponseWriter does not implement http.Hijacker")
|
||||
}
|
||||
|
||||
func (w *statusResponseWriter) Flush() {
|
||||
if fl, ok := w.ResponseWriter.(http.Flusher); ok {
|
||||
fl.Flush()
|
||||
}
|
||||
}
|
||||
|
||||
// RequestLoggingMiddleware logs method, path, status, and duration for each request.
|
||||
// Long-lived connections (SSE, WebSocket) are excluded.
|
||||
func RequestLoggingMiddleware(next http.Handler) http.Handler {
|
||||
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
path := r.URL.Path
|
||||
|
||||
// Skip SSE and WebSocket endpoints (long-lived connections)
|
||||
if strings.HasSuffix(path, "/events") || strings.HasSuffix(path, "/ws") || strings.HasSuffix(path, "/stream") {
|
||||
next.ServeHTTP(w, r)
|
||||
return
|
||||
}
|
||||
|
||||
start := time.Now()
|
||||
sw := &statusResponseWriter{ResponseWriter: w, status: http.StatusOK}
|
||||
next.ServeHTTP(sw, r)
|
||||
|
||||
attrs := []any{
|
||||
"status", sw.status,
|
||||
"method", r.Method,
|
||||
"path", path,
|
||||
"duration", time.Since(start),
|
||||
}
|
||||
|
||||
// Add route params if present
|
||||
vars := mux.Vars(r)
|
||||
if name := vars["name"]; name != "" {
|
||||
attrs = append(attrs, "instance", name)
|
||||
}
|
||||
if app := vars["app"]; app != "" {
|
||||
attrs = append(attrs, "app", app)
|
||||
}
|
||||
if node := vars["node"]; node != "" {
|
||||
attrs = append(attrs, "node", node)
|
||||
}
|
||||
|
||||
if sw.status >= 400 {
|
||||
slog.Error("request", attrs...)
|
||||
} else {
|
||||
slog.Info("request", attrs...)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// contextKey is a type for context keys to avoid collisions.
|
||||
type contextKey string
|
||||
|
||||
// Context keys for request values.
|
||||
const (
|
||||
InstanceNameKey contextKey = "instanceName"
|
||||
AppNameKey contextKey = "appName"
|
||||
NodeNameKey contextKey = "nodeName"
|
||||
)
|
||||
|
||||
|
||||
// GetInstanceName returns the instance name from request context.
|
||||
// Falls back to mux.Vars if not in context (for backward compatibility during migration).
|
||||
func GetInstanceName(r *http.Request) string {
|
||||
if name, ok := r.Context().Value(InstanceNameKey).(string); ok {
|
||||
return name
|
||||
}
|
||||
return mux.Vars(r)["name"]
|
||||
}
|
||||
|
||||
// GetAppName returns the app name from request context.
|
||||
// Falls back to mux.Vars if not in context.
|
||||
func GetAppName(r *http.Request) string {
|
||||
if name, ok := r.Context().Value(AppNameKey).(string); ok {
|
||||
return name
|
||||
}
|
||||
return mux.Vars(r)["app"]
|
||||
}
|
||||
|
||||
// GetNodeName returns the node name from request context.
|
||||
// Falls back to mux.Vars if not in context.
|
||||
func GetNodeName(r *http.Request) string {
|
||||
if name, ok := r.Context().Value(NodeNameKey).(string); ok {
|
||||
return name
|
||||
}
|
||||
return mux.Vars(r)["node"]
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user