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.
This commit is contained in:
@@ -0,0 +1,79 @@
|
||||
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
|
||||
}
|
||||
Reference in New Issue
Block a user