Bootstrap copied BFF and local accounts with public Sendico contracts (#34)
This commit is contained in:
1 parent
67dc5b5b1e
commit
7062672a87
556 files changed
+42291
-777
No files matched your search
@@ -0,0 +1,18 @@
|
||||
package routers
|
||||
|
||||
import (
|
||||
"git.sendico.io/tech/sxchnge/bff/pkg/api/routers/health"
|
||||
"git.sendico.io/tech/sxchnge/bff/pkg/api/routers/internal/healthimp"
|
||||
"git.sendico.io/tech/sxchnge/bff/pkg/mlogger"
|
||||
"github.com/go-chi/chi/v5"
|
||||
)
|
||||
|
||||
type Health interface {
|
||||
SetStatus(status health.ServiceStatus)
|
||||
SetStatusWithReason(status health.ServiceStatus, reason string)
|
||||
Finish()
|
||||
}
|
||||
|
||||
func NewHealthRouter(logger mlogger.Logger, router chi.Router, endpoint string) (Health, error) {
|
||||
return healthimp.NewRouter(logger, router, endpoint), nil
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
package health
|
||||
|
||||
type ServiceStatus string
|
||||
|
||||
const (
|
||||
SSCreated ServiceStatus = "created"
|
||||
SSStarting ServiceStatus = "starting"
|
||||
SSRunning ServiceStatus = "ok"
|
||||
SSTerminating ServiceStatus = "deactivating"
|
||||
)
|
||||
@@ -0,0 +1,76 @@
|
||||
package healthimp
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"strings"
|
||||
|
||||
"git.sendico.io/tech/sxchnge/bff/pkg/api/routers/health"
|
||||
"git.sendico.io/tech/sxchnge/bff/pkg/mlogger"
|
||||
"github.com/go-chi/chi/v5"
|
||||
"go.uber.org/zap"
|
||||
)
|
||||
|
||||
type Router struct {
|
||||
logger mlogger.Logger
|
||||
status *Status
|
||||
}
|
||||
|
||||
func (hr *Router) SetStatus(status health.ServiceStatus) {
|
||||
hr.SetStatusWithReason(status, defaultReason(status))
|
||||
}
|
||||
|
||||
func (hr *Router) SetStatusWithReason(status health.ServiceStatus, reason string) {
|
||||
previous, changed := hr.status.transition(status)
|
||||
if !changed {
|
||||
return
|
||||
}
|
||||
reason = strings.TrimSpace(reason)
|
||||
if reason == "" {
|
||||
reason = defaultReason(status)
|
||||
}
|
||||
hr.logger.Info("Health status changed",
|
||||
zap.String("previous_status", string(previous)),
|
||||
zap.String("status", string(status)),
|
||||
zap.String("reason", reason),
|
||||
)
|
||||
}
|
||||
|
||||
func (hr *Router) Finish() {
|
||||
hr.status.Finish()
|
||||
hr.logger.Debug("Stopped")
|
||||
}
|
||||
|
||||
func (hr *Router) handle(w http.ResponseWriter, r *http.Request) {
|
||||
hr.status.healthHandler()(w, r)
|
||||
}
|
||||
|
||||
func NewRouter(logger mlogger.Logger, router chi.Router, endpoint string) *Router {
|
||||
hr := Router{
|
||||
logger: logger.Named("health_check"),
|
||||
}
|
||||
hr.status = StatusHandler(hr.logger)
|
||||
|
||||
logger.Debug("Installing healthcheck middleware...")
|
||||
router.Group(func(r chi.Router) {
|
||||
ep := endpoint + "/health"
|
||||
r.Get(ep, hr.handle)
|
||||
logger.Info("Health handler installed", zap.String("endpoint", ep))
|
||||
})
|
||||
|
||||
return &hr
|
||||
}
|
||||
|
||||
func defaultReason(status health.ServiceStatus) string {
|
||||
switch status {
|
||||
case health.SSCreated:
|
||||
return "service created"
|
||||
case health.SSStarting:
|
||||
return "service is starting"
|
||||
case health.SSRunning:
|
||||
return "service is ready"
|
||||
case health.SSTerminating:
|
||||
return "service is shutting down"
|
||||
default:
|
||||
return "service health status changed"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,53 @@
|
||||
package healthimp
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"git.sendico.io/tech/sxchnge/bff/pkg/api/routers/health"
|
||||
"github.com/go-chi/chi/v5"
|
||||
"go.uber.org/zap"
|
||||
"go.uber.org/zap/zaptest/observer"
|
||||
)
|
||||
|
||||
func TestSetStatusLogsOnlyTransitionsWithReason(t *testing.T) {
|
||||
core, observed := observer.New(zap.InfoLevel)
|
||||
router := NewRouter(zap.New(core), chi.NewRouter(), "")
|
||||
|
||||
router.SetStatusWithReason(health.SSStarting, "observability endpoint initialized")
|
||||
router.SetStatusWithReason(health.SSStarting, "duplicate readiness report")
|
||||
router.SetStatusWithReason(health.SSRunning, "all configured ingestion drivers are ready")
|
||||
router.SetStatus(health.SSRunning)
|
||||
|
||||
entries := observed.FilterMessage("Health status changed").All()
|
||||
if len(entries) != 2 {
|
||||
t.Fatalf("health transition log count = %d, want 2", len(entries))
|
||||
}
|
||||
assertTransition(t, entries[0].ContextMap(), "created", "starting", "observability endpoint initialized")
|
||||
assertTransition(t, entries[1].ContextMap(), "starting", "ok", "all configured ingestion drivers are ready")
|
||||
}
|
||||
|
||||
func TestSetStatusUsesNonEmptyDefaultReason(t *testing.T) {
|
||||
core, observed := observer.New(zap.InfoLevel)
|
||||
router := NewRouter(zap.New(core), chi.NewRouter(), "")
|
||||
|
||||
router.SetStatusWithReason(health.SSTerminating, " ")
|
||||
|
||||
entries := observed.FilterMessage("Health status changed").All()
|
||||
if len(entries) != 1 {
|
||||
t.Fatalf("health transition log count = %d, want 1", len(entries))
|
||||
}
|
||||
assertTransition(t, entries[0].ContextMap(), "created", "deactivating", "service is shutting down")
|
||||
}
|
||||
|
||||
func assertTransition(t *testing.T, fields map[string]any, previous, status, reason string) {
|
||||
t.Helper()
|
||||
for key, want := range map[string]string{
|
||||
"previous_status": previous,
|
||||
"status": status,
|
||||
"reason": reason,
|
||||
} {
|
||||
if got, ok := fields[key].(string); !ok || got != want {
|
||||
t.Fatalf("%s = %v, want %q", key, fields[key], want)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,50 @@
|
||||
package healthimp
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"sync"
|
||||
|
||||
"git.sendico.io/tech/sxchnge/bff/pkg/api/http/response"
|
||||
"git.sendico.io/tech/sxchnge/bff/pkg/api/routers/health"
|
||||
"git.sendico.io/tech/sxchnge/bff/pkg/mlogger"
|
||||
)
|
||||
|
||||
type Status struct {
|
||||
logger mlogger.Logger
|
||||
mu sync.RWMutex
|
||||
status health.ServiceStatus
|
||||
}
|
||||
|
||||
func (hs *Status) healthHandler() http.HandlerFunc {
|
||||
hs.mu.RLock()
|
||||
status := hs.status
|
||||
hs.mu.RUnlock()
|
||||
return response.Ok(hs.logger, struct {
|
||||
Status health.ServiceStatus `json:"status"`
|
||||
}{
|
||||
status,
|
||||
})
|
||||
}
|
||||
|
||||
func (hr *Status) Finish() {
|
||||
hr.logger.Info("Finished")
|
||||
}
|
||||
|
||||
func (hs *Status) transition(status health.ServiceStatus) (health.ServiceStatus, bool) {
|
||||
hs.mu.Lock()
|
||||
defer hs.mu.Unlock()
|
||||
previous := hs.status
|
||||
if previous == status {
|
||||
return previous, false
|
||||
}
|
||||
hs.status = status
|
||||
return previous, true
|
||||
}
|
||||
|
||||
func StatusHandler(logger mlogger.Logger) *Status {
|
||||
hs := Status{
|
||||
status: health.SSCreated,
|
||||
logger: logger.Named("status"),
|
||||
}
|
||||
return &hs
|
||||
}
|
||||
Reference in new issue
Block a user