Composable monorepo CI fixes: - Add empty go.sum.tmpl files for pkg, service, worker, and cli components - Fix Dockerfile.tmpl glob patterns (COPY go.work.sum* is invalid in Kaniko) - Add deps step to CI that runs go work sync and go mod tidy before builds - Fix scalar-go dependency version (v0.1.2 doesn't exist, use v0.13.0) Health endpoint improvements: - Add registry health check (zot OCI /v2/ endpoint) - Add health metrics for CI, registry, and Git - Add /health/ci endpoint for Woodpecker health Visual verification scaffolding: - Add Playwright pod and scripts ConfigMap - Add vision.md and implementation breakdown plan Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
75 lines
1.8 KiB
Go
75 lines
1.8 KiB
Go
// Package zot provides a client for checking zot container registry health.
|
|
package zot
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"net/http"
|
|
"time"
|
|
|
|
"github.com/orchard9/rdev/internal/domain"
|
|
)
|
|
|
|
// Client checks zot registry health via the OCI /v2/ endpoint.
|
|
type Client struct {
|
|
url string
|
|
httpClient *http.Client
|
|
}
|
|
|
|
// NewClient creates a new zot health checker.
|
|
// The URL should be the registry base URL (e.g., "https://registry.threesix.ai").
|
|
func NewClient(url string) *Client {
|
|
return &Client{
|
|
url: url,
|
|
httpClient: &http.Client{
|
|
Timeout: 5 * time.Second,
|
|
},
|
|
}
|
|
}
|
|
|
|
// Check returns the health status of the registry.
|
|
// A 200 or 401 response indicates the registry is healthy (401 means auth required but registry is up).
|
|
func (c *Client) Check(ctx context.Context) domain.RegistryStatus {
|
|
start := time.Now()
|
|
|
|
req, err := http.NewRequestWithContext(ctx, http.MethodGet, c.url+"/v2/", nil)
|
|
if err != nil {
|
|
return domain.RegistryStatus{
|
|
Healthy: false,
|
|
URL: c.url,
|
|
Error: fmt.Sprintf("failed to create request: %v", err),
|
|
LastChecked: time.Now().UTC(),
|
|
}
|
|
}
|
|
|
|
resp, err := c.httpClient.Do(req)
|
|
latency := time.Since(start)
|
|
|
|
if err != nil {
|
|
return domain.RegistryStatus{
|
|
Healthy: false,
|
|
URL: c.url,
|
|
Latency: latency.String(),
|
|
Error: fmt.Sprintf("connection error: %v", err),
|
|
LastChecked: time.Now().UTC(),
|
|
}
|
|
}
|
|
defer func() { _ = resp.Body.Close() }()
|
|
|
|
// 200 = healthy, 401 = healthy but requires auth
|
|
healthy := resp.StatusCode == http.StatusOK || resp.StatusCode == http.StatusUnauthorized
|
|
|
|
status := domain.RegistryStatus{
|
|
Healthy: healthy,
|
|
URL: c.url,
|
|
Latency: latency.String(),
|
|
LastChecked: time.Now().UTC(),
|
|
}
|
|
|
|
if !healthy {
|
|
status.Error = fmt.Sprintf("unexpected status code: %d", resp.StatusCode)
|
|
}
|
|
|
|
return status
|
|
}
|