// 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 }