Refactor architecture: extract reconciler, add interfaces, reduce complexity, improve test coverage
Architecture: - Extract reconcileNetworking into internal/reconcile package with 7 consumer-side interfaces - Add locked modifyState helper to fix state.yaml read-modify-write race condition - Extract CrowdSec and VPN handler groups with interfaces documenting dependency surface - Replace raw map[string]any YAML manipulation with typed AddDHCPStaticLease/RemoveDHCPStaticLease - Extract Cloudflare API functions into cfClient struct, eliminating repeated auth boilerplate Complexity reduction: - haproxy.GenerateWithOpts: 50 → 6 (extracted 8 focused helpers) - reconcile.Reconcile: 42 → 11 (extracted buildRoutes, buildDNSEntries, writeHAProxyConfig) - AutheliaUpdateConfig: 25 → 10 (extracted enableAuthelia/disableAuthelia) Test coverage improvements: - reconcile: 4.5% → 56.8% (stub-based tests for route building, DNS entries, orchestration) - dnsfilter: 10.7% → 45.6% (Manager.Compile, AddList, ToggleList, custom entries) - config: 67.3% → 89.8% (DHCP static lease mutation tests)
This commit is contained in:
@@ -24,35 +24,37 @@ import (
|
||||
"github.com/wild-cloud/wild-central/internal/ddns"
|
||||
"github.com/wild-cloud/wild-central/internal/dnsfilter"
|
||||
"github.com/wild-cloud/wild-central/internal/dnsmasq"
|
||||
"github.com/wild-cloud/wild-central/internal/domains"
|
||||
"github.com/wild-cloud/wild-central/internal/haproxy"
|
||||
"github.com/wild-cloud/wild-central/internal/network"
|
||||
"github.com/wild-cloud/wild-central/internal/nftables"
|
||||
"github.com/wild-cloud/wild-central/internal/reconcile"
|
||||
"github.com/wild-cloud/wild-central/internal/secrets"
|
||||
"github.com/wild-cloud/wild-central/internal/domains"
|
||||
"github.com/wild-cloud/wild-central/internal/sse"
|
||||
"github.com/wild-cloud/wild-central/internal/wireguard"
|
||||
)
|
||||
|
||||
// API holds all dependencies for Central API handlers
|
||||
type API struct {
|
||||
dataDir string
|
||||
version string
|
||||
allowedOrigins []string
|
||||
ctx gocontext.Context // parent context for restartable goroutines
|
||||
secrets *secrets.Manager
|
||||
dnsmasq *dnsmasq.ConfigGenerator
|
||||
haproxy *haproxy.Manager
|
||||
nftables *nftables.Manager
|
||||
ddns *ddns.Runner
|
||||
crowdsec *crowdsec.Manager
|
||||
vpn *wireguard.Manager
|
||||
certbot *certbot.Manager
|
||||
authelia *authelia.Manager // Authelia authentication service manager
|
||||
dnsFilter *dnsfilter.Manager // DNS filtering manager
|
||||
dnsFilterRunner *dnsfilter.Runner // DNS filter background update runner
|
||||
domains *domains.Manager // Domain registration manager
|
||||
sseManager *sse.Manager // SSE manager for real-time events
|
||||
port int // Running API port (for config-driven routes)
|
||||
dataDir string
|
||||
version string
|
||||
allowedOrigins []string
|
||||
ctx gocontext.Context // parent context for restartable goroutines
|
||||
reconciler *reconcile.Reconciler
|
||||
secrets *secrets.Manager
|
||||
dnsmasq *dnsmasq.ConfigGenerator
|
||||
haproxy *haproxy.Manager
|
||||
nftables *nftables.Manager
|
||||
ddns *ddns.Runner
|
||||
crowdsec *crowdsec.Manager
|
||||
vpn *wireguard.Manager
|
||||
certbot *certbot.Manager
|
||||
authelia *authelia.Manager // Authelia authentication service manager
|
||||
dnsFilter *dnsfilter.Manager // DNS filtering manager
|
||||
dnsFilterRunner *dnsfilter.Runner // DNS filter background update runner
|
||||
domains *domains.Manager // Domain registration manager
|
||||
sseManager *sse.Manager // SSE manager for real-time events
|
||||
port int // Running API port (for config-driven routes)
|
||||
}
|
||||
|
||||
// NewAPI creates a new Central API handler with all dependencies
|
||||
@@ -77,8 +79,8 @@ func NewAPI(dataDir, version string, allowedOrigins []string) (*API, error) {
|
||||
crowdsec: crowdsec.NewManager(),
|
||||
vpn: wireguard.NewManager(dataDir, vpnConfigPath),
|
||||
certbot: certbot.NewManager(""),
|
||||
authelia: authelia.NewManager(filepath.Join(dataDir, "authelia")),
|
||||
dnsFilter: dnsfilter.NewManager(filepath.Join(dataDir, "dns-filter")),
|
||||
authelia: authelia.NewManager(filepath.Join(dataDir, "authelia")),
|
||||
dnsFilter: dnsfilter.NewManager(filepath.Join(dataDir, "dns-filter")),
|
||||
domains: domains.NewManager(dataDir),
|
||||
sseManager: sseManager,
|
||||
}
|
||||
@@ -89,9 +91,22 @@ func NewAPI(dataDir, version string, allowedOrigins []string) (*API, error) {
|
||||
filterDir := envOrDefault("WILD_CENTRAL_FILTER_DIR", "/var/lib/wild-central/dns-filter")
|
||||
api.dnsFilter.SetHostsFilePath(filepath.Join(filterDir, "blocked.conf"))
|
||||
|
||||
// Build the reconciler — the core domain→config→daemon pipeline
|
||||
api.reconciler = &reconcile.Reconciler{
|
||||
Domains: api.domains,
|
||||
HAProxy: api.haproxy,
|
||||
DNS: api.dnsmasq,
|
||||
Auth: api.authelia,
|
||||
DDNS: api.ddns,
|
||||
DNSFilter: api.dnsFilter,
|
||||
SSE: sseManager,
|
||||
StatePath: filepath.Join(dataDir, "state.yaml"),
|
||||
GenerateAutheliaConfig: api.generateAutheliaConfig,
|
||||
}
|
||||
|
||||
// Wire up domain registration reconciliation: when domains change,
|
||||
// regenerate dnsmasq DNS entries and HAProxy routes.
|
||||
api.domains.SetReconcileFn(api.reconcileNetworking)
|
||||
api.domains.SetReconcileFn(api.reconciler.Reconcile)
|
||||
|
||||
return api, nil
|
||||
}
|
||||
@@ -259,15 +274,16 @@ func (api *API) RegisterRoutes(r *mux.Router) {
|
||||
r.HandleFunc("/api/v1/ddns/trigger", api.DDNSTrigger).Methods("POST")
|
||||
|
||||
// WireGuard VPN management
|
||||
r.HandleFunc("/api/v1/vpn/status", api.VpnStatus).Methods("GET")
|
||||
r.HandleFunc("/api/v1/vpn/config", api.VpnGetConfig).Methods("GET")
|
||||
r.HandleFunc("/api/v1/vpn/config", api.VpnUpdateConfig).Methods("PUT")
|
||||
r.HandleFunc("/api/v1/vpn/keygen", api.VpnGenerateKeypair).Methods("POST")
|
||||
r.HandleFunc("/api/v1/vpn/apply", api.VpnApply).Methods("POST")
|
||||
r.HandleFunc("/api/v1/vpn/peers", api.VpnListPeers).Methods("GET")
|
||||
r.HandleFunc("/api/v1/vpn/peers", api.VpnAddPeer).Methods("POST")
|
||||
r.HandleFunc("/api/v1/vpn/peers/{id}/config", api.VpnGetPeerConfig).Methods("GET")
|
||||
r.HandleFunc("/api/v1/vpn/peers/{id}", api.VpnDeletePeer).Methods("DELETE")
|
||||
vpn := &vpnHandlers{vpn: api.vpn, statePath: api.statePath(), syncNftables: api.syncNftablesOnly}
|
||||
r.HandleFunc("/api/v1/vpn/status", vpn.Status).Methods("GET")
|
||||
r.HandleFunc("/api/v1/vpn/config", vpn.GetConfig).Methods("GET")
|
||||
r.HandleFunc("/api/v1/vpn/config", vpn.UpdateConfig).Methods("PUT")
|
||||
r.HandleFunc("/api/v1/vpn/keygen", vpn.GenerateKeypair).Methods("POST")
|
||||
r.HandleFunc("/api/v1/vpn/apply", vpn.Apply).Methods("POST")
|
||||
r.HandleFunc("/api/v1/vpn/peers", vpn.ListPeers).Methods("GET")
|
||||
r.HandleFunc("/api/v1/vpn/peers", vpn.AddPeer).Methods("POST")
|
||||
r.HandleFunc("/api/v1/vpn/peers/{id}/config", vpn.GetPeerConfig).Methods("GET")
|
||||
r.HandleFunc("/api/v1/vpn/peers/{id}", vpn.DeletePeer).Methods("DELETE")
|
||||
|
||||
// TLS certificate management
|
||||
r.HandleFunc("/api/v1/cert/status", api.CertStatus).Methods("GET")
|
||||
@@ -275,19 +291,20 @@ func (api *API) RegisterRoutes(r *mux.Router) {
|
||||
r.HandleFunc("/api/v1/cert/renew", api.CertRenew).Methods("POST")
|
||||
|
||||
// CrowdSec LAPI management
|
||||
r.HandleFunc("/api/v1/crowdsec/status", api.CrowdSecStatus).Methods("GET")
|
||||
r.HandleFunc("/api/v1/crowdsec/summary", api.CrowdSecGetSummary).Methods("GET")
|
||||
r.HandleFunc("/api/v1/crowdsec/alerts", api.CrowdSecGetAlerts).Methods("GET")
|
||||
r.HandleFunc("/api/v1/crowdsec/decisions", api.CrowdSecGetDecisions).Methods("GET")
|
||||
r.HandleFunc("/api/v1/crowdsec/decisions", api.CrowdSecAddDecision).Methods("POST")
|
||||
r.HandleFunc("/api/v1/crowdsec/decisions/{id}", api.CrowdSecDeleteDecision).Methods("DELETE")
|
||||
r.HandleFunc("/api/v1/crowdsec/decisions/ip/{ip}", api.CrowdSecDeleteDecisionByIP).Methods("DELETE")
|
||||
r.HandleFunc("/api/v1/crowdsec/machines", api.CrowdSecGetMachines).Methods("GET")
|
||||
r.HandleFunc("/api/v1/crowdsec/machines", api.CrowdSecAddMachine).Methods("POST")
|
||||
r.HandleFunc("/api/v1/crowdsec/machines/{name}", api.CrowdSecDeleteMachine).Methods("DELETE")
|
||||
r.HandleFunc("/api/v1/crowdsec/bouncers", api.CrowdSecGetBouncers).Methods("GET")
|
||||
r.HandleFunc("/api/v1/crowdsec/bouncers", api.CrowdSecAddBouncer).Methods("POST")
|
||||
r.HandleFunc("/api/v1/crowdsec/bouncers/{name}", api.CrowdSecDeleteBouncer).Methods("DELETE")
|
||||
csec := &crowdsecHandlers{crowdsec: api.crowdsec}
|
||||
r.HandleFunc("/api/v1/crowdsec/status", csec.Status).Methods("GET")
|
||||
r.HandleFunc("/api/v1/crowdsec/summary", csec.GetSummary).Methods("GET")
|
||||
r.HandleFunc("/api/v1/crowdsec/alerts", csec.GetAlerts).Methods("GET")
|
||||
r.HandleFunc("/api/v1/crowdsec/decisions", csec.GetDecisions).Methods("GET")
|
||||
r.HandleFunc("/api/v1/crowdsec/decisions", csec.AddDecision).Methods("POST")
|
||||
r.HandleFunc("/api/v1/crowdsec/decisions/{id}", csec.DeleteDecision).Methods("DELETE")
|
||||
r.HandleFunc("/api/v1/crowdsec/decisions/ip/{ip}", csec.DeleteDecisionByIP).Methods("DELETE")
|
||||
r.HandleFunc("/api/v1/crowdsec/machines", csec.GetMachines).Methods("GET")
|
||||
r.HandleFunc("/api/v1/crowdsec/machines", csec.AddMachine).Methods("POST")
|
||||
r.HandleFunc("/api/v1/crowdsec/machines/{name}", csec.DeleteMachine).Methods("DELETE")
|
||||
r.HandleFunc("/api/v1/crowdsec/bouncers", csec.GetBouncers).Methods("GET")
|
||||
r.HandleFunc("/api/v1/crowdsec/bouncers", csec.AddBouncer).Methods("POST")
|
||||
r.HandleFunc("/api/v1/crowdsec/bouncers/{name}", csec.DeleteBouncer).Methods("DELETE")
|
||||
|
||||
// Cloudflare management
|
||||
r.HandleFunc("/api/v1/cloudflare/verify", api.CloudflareVerify).Methods("GET")
|
||||
@@ -399,8 +416,8 @@ func (api *API) StatusHandler(w http.ResponseWriter, r *http.Request, startTime
|
||||
// getDaemonStatus checks whether each managed daemon is active and gets its version.
|
||||
func getDaemonStatus() map[string]map[string]any {
|
||||
type daemonInfo struct {
|
||||
unit string
|
||||
verCmd []string // command to get version
|
||||
unit string
|
||||
verCmd []string // command to get version
|
||||
verParse func(string) string // extract version from output
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user