Skip to content

stuttgart-things/homerun2-scout

Repository files navigation

homerun2-scout

A Go microservice that periodically analyzes messages indexed in RediSearch and exposes aggregated analytics via a REST API. Part of the homerun2 platform.

Build & Test Docs

API Endpoints

Endpoint Method Auth Description
/health GET None Health check (returns version, commit, uptime)
/analytics/summary GET Bearer token Severity counts, total messages
/analytics/systems GET Bearer token Per-system message counts (top 20)
/analytics/alerts GET Bearer token Alert frequency, top alerting systems
/metrics GET None Prometheus metrics (Grafana integration)
Analytics summary
curl http://localhost:8080/analytics/summary \
  -H "Authorization: Bearer $AUTH_TOKEN"

Response:

{
  "totalMessages": 1542,
  "severityCounts": {
    "info": 1200,
    "warning": 250,
    "error": 80,
    "critical": 12
  },
  "timeWindow": "60s",
  "lastUpdated": "2026-03-11T12:00:00Z"
}
Systems breakdown
curl http://localhost:8080/analytics/systems \
  -H "Authorization: Bearer $AUTH_TOKEN"

Response:

{
  "systems": [
    {"system": "api-gateway", "count": 500},
    {"system": "worker-pool", "count": 320},
    {"system": "scheduler", "count": 180}
  ],
  "total": 3,
  "lastUpdated": "2026-03-11T12:00:00Z"
}
Alerts
curl http://localhost:8080/analytics/alerts \
  -H "Authorization: Bearer $AUTH_TOKEN"

Response:

{
  "totalAlerts": 92,
  "severityCounts": {
    "error": 80,
    "critical": 12
  },
  "topSystems": [
    {"system": "api-gateway", "count": 45},
    {"system": "worker-pool", "count": 30}
  ],
  "lastUpdated": "2026-03-11T12:00:00Z"
}

Deployment

Container image (ko / ghcr.io)

The container image is built with ko on top of cgr.dev/chainguard/static and published to GitHub Container Registry.

# Pull the image
docker pull ghcr.io/stuttgart-things/homerun2-scout:<tag>

# Run with Docker
docker run -p 8080:8080 \
  -e REDIS_ADDR=redis -e REDIS_PORT=6379 \
  -e REDISEARCH_INDEX=messages -e SCOUT_INTERVAL=60s \
  -e AUTH_TOKEN=mysecret \
  ghcr.io/stuttgart-things/homerun2-scout:<tag>
Deploy to Kubernetes with KCL

KCL manifests in kcl/ are the source of truth for Kubernetes deployment. The modular KCL modules cover: deploy.k, service.k, ingress.k, secret.k, configmap.k, serviceaccount.k, namespace.k, httproute.k, crd.k (ScoutProfile CRD), rbac.k (RBAC for ScoutProfile).

Render manifests locally:

# Render with kcl CLI
kcl run kcl/ -Y tests/kcl-deploy-profile.yaml

# Render via Dagger (non-interactive)
task render-manifests-quick
ScoutProfile — Kubernetes CR for business logic config

ScoutProfile is a Kubernetes Custom Resource (homerun2.stuttgart-things.com/v1alpha1) that holds the scout's runtime behaviour config (thresholds, retention, alerting), separate from deployment config.

Set SCOUT_PROFILE_NAME (KCL default: default) to activate it. At startup, the scout reads the named CR from its namespace and merges non-empty fields into the active config. If the CR is missing or unreachable, the scout starts normally with env var defaults — no crash.

apiVersion: homerun2.stuttgart-things.com/v1alpha1
kind: ScoutProfile
metadata:
  name: default
  namespace: homerun2
spec:
  scoutInterval: 60s
  retention:
    enabled: true
    ttl: 48h
  alerting:
    pitcherURL: https://homerun2-omni-pitcher.example.com/pitch
    errorThreshold: 50
    criticalThreshold: 10
    cooldown: 5m
kubectl apply -f tests/scout-profile-movie-scripts.yaml
Deploy Redis Stack (prerequisite)

Scout requires a Redis Stack instance with the RediSearch module loaded, and omni-pitcher configured with REDIS_SEARCH_INDEX for dual-write.

helmfile apply -f \
  git::https://github.com/stuttgart-things/helm.git@database/redis-stack.yaml.gotmpl \
  --state-values-set storageClass=openebs-hostpath \
  --state-values-set password="<REPLACE>" \
  --state-values-set namespace=homerun2

Development

Run locally

1. Forward Redis from the Kubernetes cluster:

kubectl port-forward -n homerun2 svc/redis-stack 6379:6379

2. Set environment variables and run:

export REDIS_ADDR=localhost
export REDIS_PORT=6379
export REDISEARCH_INDEX=messages
export SCOUT_INTERVAL=10s
export AUTH_TOKEN=test
export LOG_FORMAT=text

go run .

The service starts on port 8080. If Redis is not reachable, it logs a warning and retries on each aggregation cycle — it will not crash.

3. Test the endpoints:

# Health (no auth)
curl http://localhost:8080/health

# Analytics (Bearer token required)
curl -H "Authorization: Bearer test" http://localhost:8080/analytics/summary
curl -H "Authorization: Bearer test" http://localhost:8080/analytics/systems
curl -H "Authorization: Bearer test" http://localhost:8080/analytics/alerts

# Prometheus metrics (no auth)
curl http://localhost:8080/metrics
Project structure
main.go                    # Entrypoint, routing, aggregator, graceful shutdown
internal/
  aggregator/              # Periodic FT.AGGREGATE queries, result caching
  alerter/                 # Threshold alerting via omni-pitcher
  banner/                  # Startup banner (lipgloss)
  config/                  # Env-based config loading, slog setup
  handlers/                # HTTP handlers (analytics, health)
  metrics/                 # Prometheus metrics registration
  middleware/              # Auth (Bearer token), request logging
  models/                  # Analytics response structs
  profile/                 # ScoutProfile CRD loader and merge logic
  retention/               # Periodic cleanup of expired JSON docs + Redis Stream trimming
kcl/                       # Kubernetes manifests (modular KCL)
dagger/                    # CI functions (Lint, Build, Test, Scan)
tests/                     # Deploy profiles and sample CRs
.ko.yaml                   # ko build configuration
Taskfile.yaml              # Task runner
Configuration reference
Variable Description Default
PORT HTTP server port 8080
REDIS_ADDR Redis server address localhost
REDIS_PORT Redis server port 6379
REDIS_PASSWORD Redis password (empty)
REDISEARCH_INDEX RediSearch index name messages
SCOUT_INTERVAL Aggregation interval (Go duration) 60s
SCOUT_PROFILE_NAME ScoutProfile CR name to load at startup (empty)
SCOUT_RETENTION_TTL Retention TTL — prunes expired JSON documents and trims the Redis Stream via XTRIM MINID 48h
SCOUT_RETENTION_ENABLED Enable/disable retention cleaner true
AUTH_TOKEN Bearer token for API auth (empty = no auth)
LOG_FORMAT Log format: json or text json
LOG_LEVEL Log level: debug, info, warn, error info
ALERT_PITCHER_URL omni-pitcher /pitch endpoint (empty)
ALERT_PITCHER_TOKEN Bearer token for omni-pitcher (empty)
ALERT_ERROR_THRESHOLD Error count threshold to trigger alert 0
ALERT_CRITICAL_THRESHOLD Critical count threshold to trigger alert 0
ALERT_COOLDOWN Minimum time between alerts 5m

When SCOUT_PROFILE_NAME is set, the corresponding ScoutProfile CR overrides these env var values at startup. See ScoutProfile docs for the full schema.

CI/CD and release process

Releases are fully automated via GitHub Actions and semantic-release.

Workflow chain on merge to main:

  1. Build, Push & Scan Container Image — builds with ko, pushes to ghcr.io, scans with Trivy
  2. Release (triggered on successful image build) — semantic-release creates a GitHub release, stages image, pushes kustomize OCI artifact

Trigger a release manually:

task trigger-release

Branch naming convention:

  • fix/<issue-number>-<short-description> — bug fixes (patch)
  • feat/<issue-number>-<short-description> — new features (minor)
  • test/<issue-number>-<short-description> — test-only changes (no release)

Testing

Unit tests

Unit tests run without Redis:

go test ./...
Integration tests (Dagger + Redis)

Integration tests spin up a Redis service via Dagger:

task build-test-binary
Lint
task lint
Build and scan container image
task build-scan-image-ko

Links

License

See LICENSE file.

About

homerun2-scout — periodic analytics service for RediSearch data

Topics

Resources

Stars

Watchers

Forks

Packages

 
 
 

Contributors