Files
argo-cd/gitops-engine/pkg/health/health.go
2026-02-12 09:29:40 -05:00

153 lines
4.3 KiB
Go

package health
import (
"fmt"
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
"k8s.io/apimachinery/pkg/runtime/schema"
"github.com/argoproj/argo-cd/gitops-engine/pkg/sync/hook"
"github.com/argoproj/argo-cd/gitops-engine/pkg/utils/kube"
)
// Represents resource health status
type HealthStatusCode string
const (
// Indicates that health assessment failed and actual health status is unknown
HealthStatusUnknown HealthStatusCode = "Unknown"
// Progressing health status means that resource is not healthy but still have a chance to reach healthy state
HealthStatusProgressing HealthStatusCode = "Progressing"
// Resource is 100% healthy
HealthStatusHealthy HealthStatusCode = "Healthy"
// Assigned to resources that are suspended or paused. The typical example is a
// [suspended](https://kubernetes.io/docs/tasks/job/automated-tasks-with-cron-jobs/#suspend) CronJob.
HealthStatusSuspended HealthStatusCode = "Suspended"
// Degrade status is used if resource status indicates failure or resource could not reach healthy state
// within some timeout.
HealthStatusDegraded HealthStatusCode = "Degraded"
// Indicates that resource is missing in the cluster.
HealthStatusMissing HealthStatusCode = "Missing"
)
// Implements custom health assessment that overrides built-in assessment
type HealthOverride interface {
GetResourceHealth(obj *unstructured.Unstructured) (*HealthStatus, error)
}
// Holds health assessment results
type HealthStatus struct {
Status HealthStatusCode `json:"status,omitempty"`
Message string `json:"message,omitempty"`
}
// healthOrder is a list of health codes in order of most healthy to least healthy
var healthOrder = []HealthStatusCode{
HealthStatusHealthy,
HealthStatusSuspended,
HealthStatusProgressing,
HealthStatusMissing,
HealthStatusDegraded,
HealthStatusUnknown,
}
// IsWorse returns whether or not the new health status code is a worse condition than the current
func IsWorse(current, new HealthStatusCode) bool {
currentIndex := 0
newIndex := 0
for i, code := range healthOrder {
if current == code {
currentIndex = i
}
if new == code {
newIndex = i
}
}
return newIndex > currentIndex
}
// GetResourceHealth returns the health of a k8s resource
func GetResourceHealth(obj *unstructured.Unstructured, healthOverride HealthOverride) (health *HealthStatus, err error) {
if obj.GetDeletionTimestamp() != nil && !hook.HasHookFinalizer(obj) {
return &HealthStatus{
Status: HealthStatusProgressing,
Message: "Pending deletion",
}, nil
}
if healthOverride != nil {
health, err := healthOverride.GetResourceHealth(obj)
if err != nil {
health = &HealthStatus{
Status: HealthStatusUnknown,
Message: err.Error(),
}
return health, fmt.Errorf("failed to get resource health for %s/%s: %w", obj.GetNamespace(), obj.GetName(), err)
}
if health != nil {
return health, nil
}
}
if healthCheck := GetHealthCheckFunc(obj.GroupVersionKind()); healthCheck != nil {
if health, err = healthCheck(obj); err != nil {
health = &HealthStatus{
Status: HealthStatusUnknown,
Message: err.Error(),
}
}
}
return health, err
}
// GetHealthCheckFunc returns built-in health check function or nil if health check is not supported
func GetHealthCheckFunc(gvk schema.GroupVersionKind) func(obj *unstructured.Unstructured) (*HealthStatus, error) {
switch gvk.Group {
case "apps":
switch gvk.Kind {
case kube.DeploymentKind:
return getDeploymentHealth
case kube.StatefulSetKind:
return getStatefulSetHealth
case kube.ReplicaSetKind:
return getReplicaSetHealth
case kube.DaemonSetKind:
return getDaemonSetHealth
}
case "extensions":
if gvk.Kind == kube.IngressKind {
return getIngressHealth
}
case "argoproj.io":
if gvk.Kind == "Workflow" {
return getArgoWorkflowHealth
}
case "apiregistration.k8s.io":
if gvk.Kind == kube.APIServiceKind {
return getAPIServiceHealth
}
case "networking.k8s.io":
if gvk.Kind == kube.IngressKind {
return getIngressHealth
}
case "":
switch gvk.Kind {
case kube.ServiceKind:
return getServiceHealth
case kube.PersistentVolumeClaimKind:
return getPVCHealth
case kube.PodKind:
return getPodHealth
}
case "batch":
if gvk.Kind == kube.JobKind {
return getJobHealth
}
case "autoscaling":
if gvk.Kind == kube.HorizontalPodAutoscalerKind {
return getHPAHealth
}
}
return nil
}