From 3f221401a6464adda117d68409d2b22e753f6586 Mon Sep 17 00:00:00 2001 From: vg006 Date: Tue, 11 Aug 2026 18:11:43 +0530 Subject: [PATCH 1/4] refac: Update harbor package and health check Signed-off-by: vg006 --- cmd/groundcontrol/server/main.go | 4 +- internal/groundcontrol/harbor/health.go | 64 +++++++++++++++ internal/groundcontrol/harborhealth/check.go | 85 -------------------- internal/groundcontrol/harborhealth/types.go | 29 ------- 4 files changed, 66 insertions(+), 116 deletions(-) create mode 100644 internal/groundcontrol/harbor/health.go delete mode 100644 internal/groundcontrol/harborhealth/check.go delete mode 100644 internal/groundcontrol/harborhealth/types.go diff --git a/cmd/groundcontrol/server/main.go b/cmd/groundcontrol/server/main.go index 0c84a5cd2..54235c3f8 100644 --- a/cmd/groundcontrol/server/main.go +++ b/cmd/groundcontrol/server/main.go @@ -12,7 +12,7 @@ import ( "time" "github.com/container-registry/harbor-satellite/internal/env" - "github.com/container-registry/harbor-satellite/internal/groundcontrol/harborhealth" + "github.com/container-registry/harbor-satellite/internal/groundcontrol/harbor" "github.com/container-registry/harbor-satellite/internal/groundcontrol/migrator" "github.com/container-registry/harbor-satellite/internal/groundcontrol/server" "github.com/joho/godotenv" @@ -25,7 +25,7 @@ func main() { log.Fatalf("failed to load environment: %v", err) } - err := harborhealth.CheckHealth() + err := harbor.CheckHealth() if err != nil { log.Fatalf("health check failed: %v", err) } diff --git a/internal/groundcontrol/harbor/health.go b/internal/groundcontrol/harbor/health.go new file mode 100644 index 000000000..cb3112647 --- /dev/null +++ b/internal/groundcontrol/harbor/health.go @@ -0,0 +1,64 @@ +package harbor + +import ( + "context" + "fmt" + "log" + "time" + + "github.com/container-registry/harbor-satellite/internal/env" + "github.com/goharbor/go-client/pkg/sdk/v2.0/client/health" + "github.com/goharbor/go-client/pkg/sdk/v2.0/models" +) + +const defaultHealthCheckTimeout = 5 * time.Second + +var ignoredHealthComponents = map[string]struct{}{ + "portal": {}, + "trivy": {}, + "registryctl": {}, + "jobservice": {}, +} + +// CheckHealth verifies that all required Harbor components are healthy. +func CheckHealth() error { + if env.GC.Harbor.SkipHealthCheck { + log.Println("WARNING: Harbor health check skipped (SKIP_HARBOR_HEALTH_CHECK=true)") + return nil + } + + client := GetClient() + + params := health.NewGetHealthParamsWithTimeout(defaultHealthCheckTimeout) + response, err := client.Health.GetHealth(context.Background(), params) + if err != nil { + return fmt.Errorf("failed to get Harbor health: %w", err) + } + if response == nil || response.Payload == nil { + return fmt.Errorf("failed to get Harbor health: empty response") + } + + unhealthy := getUnhealthyComponents(response.Payload.Components, ignoredHealthComponents) + if len(unhealthy) > 0 { + return fmt.Errorf("unhealthy components: %v", unhealthy) + } + + return nil +} + +func getUnhealthyComponents(components []*models.ComponentHealthStatus, ignored map[string]struct{}) []string { + var unhealthy []string + for _, component := range components { + if component == nil { + continue + } + if _, ignore := ignored[component.Name]; ignore { + continue + } + if component.Status != "healthy" { + unhealthy = append(unhealthy, component.Name, component.Error) + } + } + + return unhealthy +} diff --git a/internal/groundcontrol/harborhealth/check.go b/internal/groundcontrol/harborhealth/check.go deleted file mode 100644 index d70d5bf50..000000000 --- a/internal/groundcontrol/harborhealth/check.go +++ /dev/null @@ -1,85 +0,0 @@ -package harborhealth - -import ( - "encoding/json" - "fmt" - "log" - "net/http" - "net/url" - "time" - - "github.com/container-registry/harbor-satellite/internal/env" -) - -type config struct { - HarborURL string - Timeout time.Duration - SkipComponents map[string]struct{} -} - -func defaultConfig() *config { - return &config{ - HarborURL: env.GC.Harbor.URL, - Timeout: 5 * time.Second, - SkipComponents: map[string]struct{}{ - "portal": {}, - "trivy": {}, - "registryctl": {}, - "jobservice": {}, - }, - } -} - -func CheckHealth() error { - cfg := env.GC - // Allow skipping health check for development/testing - if cfg.Harbor.SkipHealthCheck { - log.Println("WARNING: Harbor health check skipped (SKIP_HARBOR_HEALTH_CHECK=true)") - return nil - } - - config := defaultConfig() - return checkhealth(config) -} - -func checkhealth(config *config) error { - parsed, err := url.ParseRequestURI(config.HarborURL) - if err != nil { - return fmt.Errorf("invalid URL format: %w", err) - } - - if parsed.Scheme != "http" && parsed.Scheme != "https" { - return fmt.Errorf("unsupported URL scheme: %s (must be http or https)", parsed.Scheme) - } - - client := &http.Client{ - Timeout: config.Timeout, - } - - resp, err := client.Get(config.HarborURL + "/api/v2.0/health") - if err != nil { - return fmt.Errorf("failed to call API: %w", err) - } - - defer func() { - if err := resp.Body.Close(); err != nil { - log.Printf("failed to close response body: %v", err) - } - }() - - if resp.StatusCode != http.StatusOK { - return fmt.Errorf("unexpected HTTP status: %s", resp.Status) - } - - var health HealthResponse - if err := json.NewDecoder(resp.Body).Decode(&health); err != nil { - return fmt.Errorf("failed to parse response: %w", err) - } - - unhealthyComponents := health.GetUnhealthyComponents(config.SkipComponents) - - if len(unhealthyComponents) > 0 { - return fmt.Errorf("unhealthy components: %v", unhealthyComponents) - } - return nil -} diff --git a/internal/groundcontrol/harborhealth/types.go b/internal/groundcontrol/harborhealth/types.go deleted file mode 100644 index 9a467bfaa..000000000 --- a/internal/groundcontrol/harborhealth/types.go +++ /dev/null @@ -1,29 +0,0 @@ -package harborhealth - -type Component struct { - Name string `json:"name"` - Status string `json:"status"` - Error string `json:"error,omitempty"` -} - -func (c *Component) IsHealthy() bool { - return c.Status == "healthy" -} - -type HealthResponse struct { - Components []Component `json:"components"` - Status string `json:"status"` -} - -func (h *HealthResponse) GetUnhealthyComponents(skip map[string]struct{}) []string { - var unhealthy []string - for _, c := range h.Components { - if _, ignore := skip[c.Name]; ignore { - continue - } - if !c.IsHealthy() { - unhealthy = append(unhealthy, c.Name, c.Error) - } - } - return unhealthy -} From a120a6f3b4abef63e8d50ad488b2c70a25453cc5 Mon Sep 17 00:00:00 2001 From: vg006 Date: Wed, 12 Aug 2026 21:25:11 +0530 Subject: [PATCH 2/4] fix: Use separate client for health checks Signed-off-by: vg006 --- internal/groundcontrol/harbor/health.go | 20 +++++++++++++++++--- 1 file changed, 17 insertions(+), 3 deletions(-) diff --git a/internal/groundcontrol/harbor/health.go b/internal/groundcontrol/harbor/health.go index cb3112647..9fa74995f 100644 --- a/internal/groundcontrol/harbor/health.go +++ b/internal/groundcontrol/harbor/health.go @@ -4,9 +4,11 @@ import ( "context" "fmt" "log" + "net/url" "time" "github.com/container-registry/harbor-satellite/internal/env" + v2client "github.com/goharbor/go-client/pkg/sdk/v2.0/client" "github.com/goharbor/go-client/pkg/sdk/v2.0/client/health" "github.com/goharbor/go-client/pkg/sdk/v2.0/models" ) @@ -27,10 +29,18 @@ func CheckHealth() error { return nil } - client := GetClient() + harborURL, err := url.Parse(env.GC.Harbor.URL) + if err != nil { + return fmt.Errorf("parse Harbor URL: %w", err) + } + + client := v2client.New(v2client.Config{URL: harborURL}) + return checkHealth(client.Health) +} +func checkHealth(client health.API) error { params := health.NewGetHealthParamsWithTimeout(defaultHealthCheckTimeout) - response, err := client.Health.GetHealth(context.Background(), params) + response, err := client.GetHealth(context.Background(), params) if err != nil { return fmt.Errorf("failed to get Harbor health: %w", err) } @@ -56,7 +66,11 @@ func getUnhealthyComponents(components []*models.ComponentHealthStatus, ignored continue } if component.Status != "healthy" { - unhealthy = append(unhealthy, component.Name, component.Error) + entry := component.Name + if component.Error != "" { + entry += ": " + component.Error + } + unhealthy = append(unhealthy, entry) } } From fd58092b8659b3ee65b7ad889a8a253c6a88e9d9 Mon Sep 17 00:00:00 2001 From: vg006 Date: Wed, 12 Aug 2026 22:20:05 +0530 Subject: [PATCH 3/4] fix: Replace sdk/v2.0/New with NewClientSet Signed-off-by: vg006 --- internal/groundcontrol/harbor/health.go | 11 ++++------- 1 file changed, 4 insertions(+), 7 deletions(-) diff --git a/internal/groundcontrol/harbor/health.go b/internal/groundcontrol/harbor/health.go index 9fa74995f..aeaf1c7b9 100644 --- a/internal/groundcontrol/harbor/health.go +++ b/internal/groundcontrol/harbor/health.go @@ -4,11 +4,10 @@ import ( "context" "fmt" "log" - "net/url" "time" "github.com/container-registry/harbor-satellite/internal/env" - v2client "github.com/goharbor/go-client/pkg/sdk/v2.0/client" + "github.com/goharbor/go-client/pkg/harbor" "github.com/goharbor/go-client/pkg/sdk/v2.0/client/health" "github.com/goharbor/go-client/pkg/sdk/v2.0/models" ) @@ -29,13 +28,11 @@ func CheckHealth() error { return nil } - harborURL, err := url.Parse(env.GC.Harbor.URL) + client, err := harbor.NewClientSet(&harbor.ClientSetConfig{URL: env.GC.Harbor.URL}) if err != nil { - return fmt.Errorf("parse Harbor URL: %w", err) + return fmt.Errorf("create Harbor client: %w", err) } - - client := v2client.New(v2client.Config{URL: harborURL}) - return checkHealth(client.Health) + return checkHealth(client.V2().Health) } func checkHealth(client health.API) error { From 09e9513ed92a45de31836f766b26c46abddd24be Mon Sep 17 00:00:00 2001 From: vg006 Date: Wed, 12 Aug 2026 22:47:13 +0530 Subject: [PATCH 4/4] fix: Reuse v2client with BasePath set Signed-off-by: vg006 --- internal/groundcontrol/harbor/health.go | 18 +++++++++++++++--- 1 file changed, 15 insertions(+), 3 deletions(-) diff --git a/internal/groundcontrol/harbor/health.go b/internal/groundcontrol/harbor/health.go index aeaf1c7b9..fd9d72389 100644 --- a/internal/groundcontrol/harbor/health.go +++ b/internal/groundcontrol/harbor/health.go @@ -4,10 +4,11 @@ import ( "context" "fmt" "log" + "net/url" "time" "github.com/container-registry/harbor-satellite/internal/env" - "github.com/goharbor/go-client/pkg/harbor" + v2client "github.com/goharbor/go-client/pkg/sdk/v2.0/client" "github.com/goharbor/go-client/pkg/sdk/v2.0/client/health" "github.com/goharbor/go-client/pkg/sdk/v2.0/models" ) @@ -28,11 +29,22 @@ func CheckHealth() error { return nil } - client, err := harbor.NewClientSet(&harbor.ClientSetConfig{URL: env.GC.Harbor.URL}) + client, err := newHealthClient(env.GC.Harbor.URL) if err != nil { return fmt.Errorf("create Harbor client: %w", err) } - return checkHealth(client.V2().Health) + return checkHealth(client.Health) +} + +func newHealthClient(rawURL string) (*v2client.HarborAPI, error) { + harborURL, err := url.Parse(rawURL) + if err != nil { + return nil, err + } + + harborURL.Path = v2client.DefaultBasePath + + return v2client.New(v2client.Config{URL: harborURL}), nil } func checkHealth(client health.API) error {