Files
homelab-frontend/internal/server/health.go
T

91 lines
2.4 KiB
Go
Raw Normal View History

2026-08-19 20:52:13 -07:00
package server
import (
"net/http"
"sync"
)
// HealthChecker provides health and readiness check information.
type HealthChecker struct {
mu sync.RWMutex
configValid bool
jwksHasFetched bool
authEnabled bool
}
// NewHealthChecker creates a new health checker instance.
func NewHealthChecker(configValid bool, authEnabled bool) *HealthChecker {
return &HealthChecker{
configValid: configValid,
jwksHasFetched: false,
authEnabled: authEnabled,
}
}
// MarkJWKSFetched marks that JWKS has been fetched successfully.
func (hc *HealthChecker) MarkJWKSFetched() {
hc.mu.Lock()
defer hc.mu.Unlock()
hc.jwksHasFetched = true
}
// IsReady checks if the server is ready to serve traffic.
// It returns true if:
// - Configuration is valid
// - If auth is enabled, JWKS has been fetched at least once
func (hc *HealthChecker) IsReady() bool {
hc.mu.RLock()
defer hc.mu.RUnlock()
if !hc.configValid {
return false
}
// If auth is enabled, we must have fetched JWKS at least once
if hc.authEnabled && !hc.jwksHasFetched {
return false
}
return true
}
// IsAlive returns true if the process is running.
// This is always true since if it weren't, we wouldn't be running this code.
func (hc *HealthChecker) IsAlive() bool {
return true
}
// LivenessHandler returns a handler for the /healthz endpoint.
// It returns 200 whenever the process is alive.
// It performs no network I/O.
func LivenessHandler(hc *HealthChecker) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
if !hc.IsAlive() {
w.WriteHeader(http.StatusServiceUnavailable)
return
}
w.Header().Set("Content-Type", "application/json")
w.WriteHeader(http.StatusOK)
w.Write([]byte(`{"status":"alive"}`))
}
}
// ReadinessHandler returns a handler for the /readyz endpoint.
// It returns 200 only when configuration is valid and, if auth is enabled,
// JWKS has been fetched at least once.
// It returns non-2xx status while configuration is invalid or JWKS has never
// been fetched.
func ReadinessHandler(hc *HealthChecker) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
if !hc.IsReady() {
w.Header().Set("Content-Type", "application/json")
w.WriteHeader(http.StatusServiceUnavailable)
w.Write([]byte(`{"status":"not_ready"}`))
return
}
w.Header().Set("Content-Type", "application/json")
w.WriteHeader(http.StatusOK)
w.Write([]byte(`{"status":"ready"}`))
}
}