Files
tiny-forge/internal/health/checker.go
T
alexei.dolgolyov eef60a4302 feat(docker-watcher): phase 6 - webhook handler
Secret UUID-based webhook endpoint for CI image push notifications.
Project/stage matching via glob patterns, auto-creation of unknown
projects from image inspection. Fix JSON response injection.
2026-03-27 21:56:18 +03:00

80 lines
2.0 KiB
Go

package health
import (
"context"
"fmt"
"net/http"
"time"
)
// DefaultRetries is the number of health check attempts before declaring failure.
const DefaultRetries = 3
// DefaultRetryInterval is the pause between health check retries.
const DefaultRetryInterval = 5 * time.Second
// DefaultTimeout is the HTTP timeout for a single health check attempt.
const DefaultTimeout = 10 * time.Second
// Checker performs HTTP health checks against a container endpoint.
type Checker struct {
httpClient *http.Client
retries int
retryInterval time.Duration
}
// New creates a Checker with default settings.
func New() *Checker {
return &Checker{
httpClient: &http.Client{
Timeout: DefaultTimeout,
},
retries: DefaultRetries,
retryInterval: DefaultRetryInterval,
}
}
// Check performs an HTTP GET health check against the given URL.
// It retries up to the configured number of times, waiting retryInterval between attempts.
// Returns nil on the first successful (2xx) response, or the last error encountered.
func (c *Checker) Check(ctx context.Context, url string) error {
var lastErr error
for attempt := 0; attempt < c.retries; attempt++ {
if attempt > 0 {
select {
case <-ctx.Done():
return fmt.Errorf("health check cancelled: %w", ctx.Err())
case <-time.After(c.retryInterval):
}
}
lastErr = c.doCheck(ctx, url)
if lastErr == nil {
return nil
}
}
return fmt.Errorf("health check failed after %d attempts: %w", c.retries, lastErr)
}
// doCheck performs a single HTTP GET health check.
func (c *Checker) doCheck(ctx context.Context, url string) error {
req, err := http.NewRequestWithContext(ctx, http.MethodGet, url, nil)
if err != nil {
return fmt.Errorf("create health check request: %w", err)
}
resp, err := c.httpClient.Do(req)
if err != nil {
return fmt.Errorf("health check request to %s: %w", url, err)
}
defer resp.Body.Close()
if resp.StatusCode < 200 || resp.StatusCode >= 300 {
return fmt.Errorf("health check %s returned status %d", url, resp.StatusCode)
}
return nil
}