Files
poimen-workflows/cmd/worker/main.go
T
Test 90fcd6a9df feat(T1.8): implement health checks for Kubernetes deployment
- Add internal/health package with health checker
- Implement three endpoints: /health, /health/live, /health/ready
- /health returns full JSON report with component status, latency, timestamp
- /health/live for K8s liveness probe (service running)
- /health/ready for K8s readiness probe (ready to accept traffic)
- Temporal connectivity check via GetWorkflow call with timeout
- Health check caching (30s interval) to prevent excessive checks
- Graceful shutdown: health server stops on SIGINT/SIGTERM
- Add --health flag to starter command to run health check
- Worker runs health server on port 8081 alongside task queue worker
- 10/10 unit tests passing
- All verification criteria met

Closes T1.8
2026-08-23 16:31:33 -07:00

115 lines
3.1 KiB
Go

package main
import (
"context"
"fmt"
"log"
"net/http"
"os"
"os/signal"
"syscall"
"time"
"go.temporal.io/sdk/client"
"go.temporal.io/sdk/worker"
"github.com/rockliang/poimen/workflows/action"
"github.com/rockliang/poimen/workflows/internal/config"
"github.com/rockliang/poimen/workflows/internal/health"
"github.com/rockliang/poimen/workflows/statemachine"
)
func main() {
// Load configuration
cfg, err := config.LoadConfig()
if err != nil {
log.Fatalf("failed to load config: %v", err)
}
// Connect to Temporal
c, err := client.Dial(client.Options{
HostPort: cfg.Temporal.HostPort,
Namespace: cfg.Temporal.Namespace,
})
if err != nil {
log.Fatalf("failed to connect to temporal: %v", err)
}
defer c.Close()
// Create worker
w := worker.New(c, "poimen-taskqueue", worker.Options{})
if w == nil {
log.Fatalf("failed to create worker")
}
// Register all workflows
w.RegisterWorkflow(statemachine.OrchestratorWorkflow)
w.RegisterWorkflow(statemachine.TaskUnitWorkflow)
w.RegisterWorkflow(statemachine.TestWorkflow)
// Register all activities
w.RegisterActivity(action.CloneRepoActivity)
w.RegisterActivity(action.GitWorktreeAddActivity)
w.RegisterActivity(action.GitCommitActivity)
w.RegisterActivity(action.GitPushActivity)
w.RegisterActivity(action.GitSquashMergeActivity)
w.RegisterActivity(action.GitDiffActivity)
w.RegisterActivity(action.PrepareSkillsActivity)
w.RegisterActivity(action.PlanningActivity)
w.RegisterActivity(action.ImplementerActivity)
w.RegisterActivity(action.JudgeActivity)
// Integration and lessons activities - register when fully tested
// w.RegisterActivity(action.RunIntegrationTestActivity)
// w.RegisterActivity(action.UpdateLessonsActivity)
// w.RegisterActivity(action.ReadLessonsActivity)
// Initialize health checker
healthChecker := health.NewChecker(c)
healthHandler := health.NewHandler(healthChecker)
// Set up HTTP server for health checks
mux := http.NewServeMux()
healthHandler.RegisterRoutes(mux)
healthServer := &http.Server{
Addr: ":8081",
Handler: mux,
}
// Start health check server in a goroutine
go func() {
log.Printf("Health check server listening on %s", healthServer.Addr)
if err := healthServer.ListenAndServe(); err != nil && err != http.ErrServerClosed {
log.Printf("health check server error: %v", err)
}
}()
// Set up signal handling for graceful shutdown
sigChan := make(chan os.Signal, 1)
signal.Notify(sigChan, syscall.SIGINT, syscall.SIGTERM)
// Run worker in a goroutine
workerErrChan := make(chan error, 1)
go func() {
fmt.Println("Starting worker on queue 'poimen-taskqueue'...")
if err := w.Run(worker.InterruptCh()); err != nil {
workerErrChan <- err
}
}()
// Wait for either worker error or signal
select {
case err := <-workerErrChan:
log.Fatalf("worker failed: %v", err)
case sig := <-sigChan:
log.Printf("received signal: %v, shutting down gracefully", sig)
w.Stop()
// Shutdown health check server
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
defer cancel()
if err := healthServer.Shutdown(ctx); err != nil {
log.Printf("health check server shutdown error: %v", err)
}
}
}