Files
zeekayandHanzo Dev 1ab6e799c1 cd: the module is github.com/hanzoai/cd, where it is actually hosted
go.mod still declared `module github.com/hanzoai/deploy` after the repo was
renamed. A Go module path is not cosmetic — it is the resolvable location. So
`go get github.com/hanzoai/cd` could not work, and anything importing this
module had to spell a name that no longer exists at that address. The build log
of v3.6.0 is where it surfaced: the compiler was still emitting
github.com/hanzoai/deploy/cmd/cd/commands.

3541 import paths across 690 .go, 19 .proto, 4 .yaml, 3 .sh, 2 .mod, Procfile
and Makefile. Plus the 62 ghcr.io/hanzoai/deploy image references, moved to
ghcr.io/hanzoai/cd to match hanzo.yml's build target — a distinct string from
the module path, replaced separately so neither could clobber the other.

go mod tidy clean, CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build ./... clean.

Co-authored-by: Hanzo Dev <[email protected]>
2026-07-28 21:16:24 -07:00

136 lines
3.9 KiB
Go

package health
import (
"encoding/json"
"fmt"
autoscalingv1 "k8s.io/api/autoscaling/v1"
autoscalingv2 "k8s.io/api/autoscaling/v2"
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
"k8s.io/apimachinery/pkg/runtime"
"github.com/hanzoai/cd/gitops-engine/pkg/utils/kube"
)
var progressingStatus = &HealthStatus{
Status: HealthStatusProgressing,
Message: "Waiting to Autoscale",
}
type hpaCondition struct {
Type string
Reason string
Message string
Status string
}
func getHPAHealth(obj *unstructured.Unstructured) (*HealthStatus, error) {
gvk := obj.GroupVersionKind()
failedConversionMsg := "failed to convert unstructured HPA to typed: %v"
switch gvk {
case autoscalingv1.SchemeGroupVersion.WithKind(kube.HorizontalPodAutoscalerKind):
var hpa autoscalingv1.HorizontalPodAutoscaler
err := runtime.DefaultUnstructuredConverter.FromUnstructured(obj.Object, &hpa)
if err != nil {
return nil, fmt.Errorf(failedConversionMsg, err)
}
return getAutoScalingV1HPAHealth(&hpa)
case autoscalingv2.SchemeGroupVersion.WithKind(kube.HorizontalPodAutoscalerKind):
var hpa autoscalingv2.HorizontalPodAutoscaler
err := runtime.DefaultUnstructuredConverter.FromUnstructured(obj.Object, &hpa)
if err != nil {
return nil, fmt.Errorf(failedConversionMsg, err)
}
return getAutoScalingV2HPAHealth(&hpa)
default:
return nil, fmt.Errorf("unsupported HPA GVK: %s", gvk)
}
}
func getAutoScalingV2HPAHealth(hpa *autoscalingv2.HorizontalPodAutoscaler) (*HealthStatus, error) {
statusConditions := hpa.Status.Conditions
conditions := make([]hpaCondition, 0, len(statusConditions))
for _, statusCondition := range statusConditions {
conditions = append(conditions, hpaCondition{
Type: string(statusCondition.Type),
Reason: statusCondition.Reason,
Message: statusCondition.Message,
Status: string(statusCondition.Status),
})
}
return checkConditions(conditions, progressingStatus)
}
func getAutoScalingV1HPAHealth(hpa *autoscalingv1.HorizontalPodAutoscaler) (*HealthStatus, error) {
annotation, ok := hpa.GetAnnotations()["autoscaling.alpha.kubernetes.io/conditions"]
if !ok {
return progressingStatus, nil
}
var conditions []hpaCondition
err := json.Unmarshal([]byte(annotation), &conditions)
if err != nil {
failedMessage := "failed to convert conditions annotation to typed: %v"
return nil, fmt.Errorf(failedMessage, err)
}
if len(conditions) == 0 {
return progressingStatus, nil
}
return checkConditions(conditions, progressingStatus)
}
func checkConditions(conditions []hpaCondition, progressingStatus *HealthStatus) (*HealthStatus, error) {
for _, condition := range conditions {
if isDegraded(&condition) {
return &HealthStatus{
Status: HealthStatusDegraded,
Message: condition.Message,
}, nil
}
}
for _, condition := range conditions {
if isHealthy(&condition) {
return &HealthStatus{
Status: HealthStatusHealthy,
Message: condition.Message,
}, nil
}
}
return progressingStatus, nil
}
func isDegraded(condition *hpaCondition) bool {
degraded_states := []hpaCondition{
{Type: "AbleToScale", Reason: "FailedGetScale"},
{Type: "AbleToScale", Reason: "FailedUpdateScale"},
{Type: "ScalingActive", Reason: "FailedGetResourceMetric"},
{Type: "ScalingActive", Reason: "FailedGetObjectMetric"},
{Type: "ScalingActive", Reason: "FailedGetPodsMetric"},
{Type: "ScalingActive", Reason: "FailedGetExternalMetric"},
{Type: "ScalingActive", Reason: "FailedComputeMetricsReplicas"},
{Type: "ScalingActive", Reason: "InvalidSelector"},
}
for _, degraded_state := range degraded_states {
if condition.Type == degraded_state.Type && condition.Reason == degraded_state.Reason {
return true
}
}
return false
}
func isHealthy(condition *hpaCondition) bool {
healthyConditionTypes := []string{"AbleToScale", "ScalingLimited"}
for _, conditionType := range healthyConditionTypes {
if condition.Type == conditionType && condition.Status == "True" {
return true
}
}
return false
}