feat: expose signed platform health status
This commit is contained in:
@@ -0,0 +1,135 @@
|
||||
package diagnostics
|
||||
|
||||
import (
|
||||
"context"
|
||||
"crypto/sha256"
|
||||
"encoding/hex"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"regexp"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"data-terminal-cloud/internal/identity"
|
||||
|
||||
"github.com/google/uuid"
|
||||
"github.com/jackc/pgx/v5"
|
||||
)
|
||||
|
||||
const (
|
||||
PlatformHealthSchema = "platform-server-health-v1"
|
||||
platformHealthMaxAge = 15 * time.Minute
|
||||
platformHealthFuture = 2 * time.Minute
|
||||
)
|
||||
|
||||
var platformHealthCheckName = regexp.MustCompile(`^[a-z][a-z0-9_.-]{1,79}$`)
|
||||
|
||||
type PlatformHealthCheck struct {
|
||||
Name string `json:"name"`
|
||||
Status string `json:"status"`
|
||||
Value string `json:"value"`
|
||||
}
|
||||
|
||||
type PlatformHealthInput struct {
|
||||
Schema string `json:"schema"`
|
||||
Status string `json:"status"`
|
||||
ObservedAt time.Time `json:"observed_at"`
|
||||
Checks []PlatformHealthCheck `json:"checks"`
|
||||
}
|
||||
|
||||
type PlatformHealthSnapshot struct {
|
||||
ID string `json:"id"`
|
||||
Schema string `json:"schema"`
|
||||
Status string `json:"status"`
|
||||
ObservedAt time.Time `json:"observed_at"`
|
||||
ReceivedAt time.Time `json:"received_at"`
|
||||
Checks []PlatformHealthCheck `json:"checks"`
|
||||
}
|
||||
|
||||
type PlatformHealthReceipt struct {
|
||||
ID string `json:"id"`
|
||||
Status string `json:"status"`
|
||||
Duplicate bool `json:"duplicate"`
|
||||
}
|
||||
|
||||
func (s *Service) IngestPlatformHealth(ctx context.Context, input PlatformHealthInput, rawBody []byte, now time.Time) (PlatformHealthReceipt, error) {
|
||||
if !validPlatformHealth(input, now) || len(rawBody) == 0 || len(rawBody) > 64*1024 {
|
||||
return PlatformHealthReceipt{}, ErrInvalid
|
||||
}
|
||||
checks, err := json.Marshal(input.Checks)
|
||||
if err != nil {
|
||||
return PlatformHealthReceipt{}, err
|
||||
}
|
||||
digestBytes := sha256.Sum256(rawBody)
|
||||
digest := hex.EncodeToString(digestBytes[:])
|
||||
publicID := uuid.NewString()
|
||||
var storedID, storedStatus string
|
||||
err = s.pool.QueryRow(ctx, `
|
||||
INSERT INTO platform_health_snapshot(public_id,schema_name,status,observed_at,checks,body_digest)
|
||||
VALUES($1::uuid,$2,$3,$4,$5::jsonb,$6)
|
||||
ON CONFLICT(body_digest) DO UPDATE SET body_digest=EXCLUDED.body_digest
|
||||
RETURNING public_id::text,status`, publicID, input.Schema, input.Status, input.ObservedAt, checks, digest).Scan(&storedID, &storedStatus)
|
||||
if err != nil {
|
||||
return PlatformHealthReceipt{}, err
|
||||
}
|
||||
_, _ = s.pool.Exec(ctx, `DELETE FROM platform_health_snapshot WHERE received_at < now()-interval '30 days'`)
|
||||
return PlatformHealthReceipt{ID: storedID, Status: storedStatus, Duplicate: storedID != publicID}, nil
|
||||
}
|
||||
|
||||
func (s *Service) GetLatestPlatformHealth(ctx context.Context, principal identity.Principal, tenantPublicID string) (PlatformHealthSnapshot, error) {
|
||||
if _, err := s.resolveActor(ctx, principal, tenantPublicID, "ops_viewer", "ops_operator", "ops_approver", "ops_admin"); err != nil {
|
||||
return PlatformHealthSnapshot{}, err
|
||||
}
|
||||
var item PlatformHealthSnapshot
|
||||
var checks []byte
|
||||
err := s.pool.QueryRow(ctx, `
|
||||
SELECT public_id::text,schema_name,status,observed_at,received_at,checks
|
||||
FROM platform_health_snapshot ORDER BY observed_at DESC,id DESC LIMIT 1`).Scan(
|
||||
&item.ID, &item.Schema, &item.Status, &item.ObservedAt, &item.ReceivedAt, &checks,
|
||||
)
|
||||
if errors.Is(err, pgx.ErrNoRows) {
|
||||
return PlatformHealthSnapshot{}, ErrNotFound
|
||||
}
|
||||
if err != nil {
|
||||
return PlatformHealthSnapshot{}, err
|
||||
}
|
||||
if err = json.Unmarshal(checks, &item.Checks); err != nil {
|
||||
return PlatformHealthSnapshot{}, err
|
||||
}
|
||||
return item, nil
|
||||
}
|
||||
|
||||
func validPlatformHealth(input PlatformHealthInput, now time.Time) bool {
|
||||
if input.Schema != PlatformHealthSchema || !platformHealthStatus(input.Status) || input.ObservedAt.IsZero() ||
|
||||
input.ObservedAt.Before(now.Add(-platformHealthMaxAge)) || input.ObservedAt.After(now.Add(platformHealthFuture)) ||
|
||||
len(input.Checks) == 0 || len(input.Checks) > 64 {
|
||||
return false
|
||||
}
|
||||
seen := make(map[string]struct{}, len(input.Checks))
|
||||
critical, warning := 0, 0
|
||||
for _, check := range input.Checks {
|
||||
if !platformHealthCheckName.MatchString(check.Name) || !platformHealthStatus(check.Status) || len([]rune(check.Value)) > 256 || strings.ContainsAny(check.Value, "\r\n\x00") {
|
||||
return false
|
||||
}
|
||||
if _, exists := seen[check.Name]; exists {
|
||||
return false
|
||||
}
|
||||
seen[check.Name] = struct{}{}
|
||||
if check.Status == "critical" {
|
||||
critical++
|
||||
} else if check.Status == "warning" {
|
||||
warning++
|
||||
}
|
||||
}
|
||||
expected := "healthy"
|
||||
if critical > 0 {
|
||||
expected = "critical"
|
||||
} else if warning > 0 {
|
||||
expected = "warning"
|
||||
}
|
||||
return input.Status == expected
|
||||
}
|
||||
|
||||
func platformHealthStatus(value string) bool {
|
||||
return value == "healthy" || value == "warning" || value == "critical"
|
||||
}
|
||||
Reference in New Issue
Block a user