diff --git a/pkg/health/health.go b/pkg/health/health.go index a1ce11a..513fb56 100644 --- a/pkg/health/health.go +++ b/pkg/health/health.go @@ -136,7 +136,7 @@ func GetHealthCheckFunc(gvk schema.GroupVersionKind) func(obj *unstructured.Unst case "argoproj.io": switch gvk.Kind { case "Workflow": - return getArgoWorkflowHealth + return GetArgoWorkflowHealth case "Application": return getArgoApplicationHealth } @@ -162,14 +162,6 @@ func GetHealthCheckFunc(gvk schema.GroupVersionKind) func(obj *unstructured.Unst // return getAPIServiceHealth // } - case "cert-manager.io": - switch gvk.Kind { - case CertificateKind: - return getCertificateHealth - case CertificateRequestKind: - return getCertificateRequestHealth - } - case "networking.k8s.io": switch gvk.Kind { case IngressKind: diff --git a/pkg/health/health_argo.go b/pkg/health/health_argo.go index e6969bb..97a8b85 100644 --- a/pkg/health/health_argo.go +++ b/pkg/health/health_argo.go @@ -26,7 +26,7 @@ type argoWorkflow struct { } } -func getArgoWorkflowHealth(obj *unstructured.Unstructured) (*HealthStatus, error) { +func GetArgoWorkflowHealth(obj *unstructured.Unstructured) (*HealthStatus, error) { var wf argoWorkflow err := runtime.DefaultUnstructuredConverter.FromUnstructured(obj.Object, &wf) if err != nil { diff --git a/pkg/health/health_certmanager.go b/pkg/health/health_certmanager.go deleted file mode 100644 index eed9ea8..0000000 --- a/pkg/health/health_certmanager.go +++ /dev/null @@ -1,101 +0,0 @@ -package health - -import ( - "fmt" - "time" - - "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" -) - -const ( - certificateExpiryWarningThreshold = 7 * 24 * time.Hour -) - -func getCertificateRequestHealth(obj *unstructured.Unstructured) (*HealthStatus, error) { - conditions, found, err := unstructured.NestedSlice(obj.Object, "status", "conditions") - if err != nil { - return nil, fmt.Errorf("failed to get conditions: %w", err) - } - - if !found || len(conditions) == 0 { - return &HealthStatus{ - Status: HealthStatusPending, - Message: "No conditions found", - }, nil - } - - var ( - lastCondition, _ = conditions[len(conditions)-1].(map[string]any) - status, _ = lastCondition["status"].(string) - conditionType, _ = lastCondition["type"].(string) - message, _ = lastCondition["message"].(string) - ) - if status == "True" && (conditionType == "Ready" || conditionType == "Approved") { - return &HealthStatus{ - Status: HealthStatusHealthy, - Message: message, - }, nil - } - - return &HealthStatus{ - Status: HealthStatusUnhealthy, - Message: fmt.Sprintf("status=%s conditionType=%s message=%s", status, conditionType, message), - }, nil -} - -func getCertificateHealth(obj *unstructured.Unstructured) (*HealthStatus, error) { - conditions, found, err := unstructured.NestedSlice(obj.Object, "status", "conditions") - if err != nil { - return nil, fmt.Errorf("failed to get conditions: %w", err) - } - - notAfter, found, err := unstructured.NestedString(obj.Object, "status", "notAfter") - if err != nil { - return nil, fmt.Errorf("failed to get notAfter: %w", err) - } - - if found { - expiryTime, err := time.Parse(time.RFC3339, notAfter) - if err != nil { - return nil, fmt.Errorf("failed to parse notAfter: %w", err) - } - - if expiryTime.Before(time.Now().Add(certificateExpiryWarningThreshold)) { - return &HealthStatus{ - Status: HealthStatusWarning, - Message: fmt.Sprintf("Certificate expiring soon: %s", expiryTime.Format(time.RFC3339)), - }, nil - } - } - - if !found || len(conditions) == 0 { - return &HealthStatus{ - Status: HealthStatusPending, - Message: "No conditions found", - }, nil - } - - lastCondition := conditions[len(conditions)-1] - cMap, _ := lastCondition.(map[string]any) - cType, ok := cMap["type"].(string) - if !ok || cType != "Ready" { - return &HealthStatus{ - Status: HealthStatusUnhealthy, - Message: "Ready condition not found", - }, nil - } - - cStatus, ok := cMap["status"].(string) - if !ok || cStatus != "True" { - return &HealthStatus{ - Status: HealthStatusUnhealthy, - Message: "Ready condition not True", - }, nil - } - - message, _ := cMap["message"].(string) - return &HealthStatus{ - Status: HealthStatusHealthy, - Message: message, - }, nil -} diff --git a/pkg/health/health_test.go b/pkg/health/health_test.go index 8c88361..15cef78 100644 --- a/pkg/health/health_test.go +++ b/pkg/health/health_test.go @@ -2,69 +2,75 @@ Package provides functionality that allows assessing the health state of a Kubernetes resource. */ -package health +package health_test import ( "os" "testing" + "github.com/flanksource/is-healthy/pkg/health" + "github.com/flanksource/is-healthy/pkg/lua" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" "sigs.k8s.io/yaml" ) -func assertAppHealth(t *testing.T, yamlPath string, expectedStatus HealthStatusCode) { +func assertAppHealth(t *testing.T, yamlPath string, expectedStatus health.HealthStatusCode) { health := getHealthStatus(yamlPath, t) assert.NotNil(t, health) assert.Equal(t, expectedStatus, health.Status) } -func getHealthStatus(yamlPath string, t *testing.T) *HealthStatus { +func getHealthStatus(yamlPath string, t *testing.T) *health.HealthStatus { yamlBytes, err := os.ReadFile(yamlPath) require.NoError(t, err) var obj unstructured.Unstructured err = yaml.Unmarshal(yamlBytes, &obj) require.NoError(t, err) - health, err := GetResourceHealth(&obj, nil) + health, err := health.GetResourceHealth(&obj, lua.ResourceHealthOverrides{}) require.NoError(t, err) return health } +func TestCertificate(t *testing.T) { + assertAppHealth(t, "./testdata/certificate-healthy.yaml", health.HealthStatusHealthy) +} + func TestDeploymentHealth(t *testing.T) { - assertAppHealth(t, "./testdata/nginx.yaml", HealthStatusHealthy) - assertAppHealth(t, "./testdata/deployment-progressing.yaml", HealthStatusProgressing) - assertAppHealth(t, "./testdata/deployment-suspended.yaml", HealthStatusSuspended) - assertAppHealth(t, "./testdata/deployment-degraded.yaml", HealthStatusDegraded) + assertAppHealth(t, "./testdata/nginx.yaml", health.HealthStatusHealthy) + assertAppHealth(t, "./testdata/deployment-progressing.yaml", health.HealthStatusProgressing) + assertAppHealth(t, "./testdata/deployment-suspended.yaml", health.HealthStatusSuspended) + assertAppHealth(t, "./testdata/deployment-degraded.yaml", health.HealthStatusDegraded) } func TestStatefulSetHealth(t *testing.T) { - assertAppHealth(t, "./testdata/statefulset.yaml", HealthStatusHealthy) + assertAppHealth(t, "./testdata/statefulset.yaml", health.HealthStatusHealthy) } func TestStatefulSetOnDeleteHealth(t *testing.T) { - assertAppHealth(t, "./testdata/statefulset-ondelete.yaml", HealthStatusHealthy) + assertAppHealth(t, "./testdata/statefulset-ondelete.yaml", health.HealthStatusHealthy) } func TestDaemonSetOnDeleteHealth(t *testing.T) { - assertAppHealth(t, "./testdata/daemonset-ondelete.yaml", HealthStatusHealthy) + assertAppHealth(t, "./testdata/daemonset-ondelete.yaml", health.HealthStatusHealthy) } func TestPVCHealth(t *testing.T) { - assertAppHealth(t, "./testdata/pvc-bound.yaml", HealthStatusHealthy) - assertAppHealth(t, "./testdata/pvc-pending.yaml", HealthStatusProgressing) + assertAppHealth(t, "./testdata/pvc-bound.yaml", health.HealthStatusHealthy) + assertAppHealth(t, "./testdata/pvc-pending.yaml", health.HealthStatusProgressing) } func TestServiceHealth(t *testing.T) { - assertAppHealth(t, "./testdata/svc-clusterip.yaml", HealthStatusHealthy) - assertAppHealth(t, "./testdata/svc-loadbalancer.yaml", HealthStatusHealthy) - assertAppHealth(t, "./testdata/svc-loadbalancer-unassigned.yaml", HealthStatusProgressing) - assertAppHealth(t, "./testdata/svc-loadbalancer-nonemptylist.yaml", HealthStatusHealthy) + assertAppHealth(t, "./testdata/svc-clusterip.yaml", health.HealthStatusHealthy) + assertAppHealth(t, "./testdata/svc-loadbalancer.yaml", health.HealthStatusHealthy) + assertAppHealth(t, "./testdata/svc-loadbalancer-unassigned.yaml", health.HealthStatusProgressing) + assertAppHealth(t, "./testdata/svc-loadbalancer-nonemptylist.yaml", health.HealthStatusHealthy) } func TestIngressHealth(t *testing.T) { - assertAppHealth(t, "./testdata/ingress.yaml", HealthStatusHealthy) - assertAppHealth(t, "./testdata/ingress-unassigned.yaml", HealthStatusProgressing) - assertAppHealth(t, "./testdata/ingress-nonemptylist.yaml", HealthStatusHealthy) + assertAppHealth(t, "./testdata/ingress.yaml", health.HealthStatusHealthy) + assertAppHealth(t, "./testdata/ingress-unassigned.yaml", health.HealthStatusProgressing) + assertAppHealth(t, "./testdata/ingress-nonemptylist.yaml", health.HealthStatusHealthy) } func TestCRD(t *testing.T) { @@ -72,38 +78,38 @@ func TestCRD(t *testing.T) { } func TestJob(t *testing.T) { - assertAppHealth(t, "./testdata/job-running.yaml", HealthStatusProgressing) - assertAppHealth(t, "./testdata/job-failed.yaml", HealthStatusDegraded) - assertAppHealth(t, "./testdata/job-succeeded.yaml", HealthStatusHealthy) - assertAppHealth(t, "./testdata/job-suspended.yaml", HealthStatusSuspended) + assertAppHealth(t, "./testdata/job-running.yaml", health.HealthStatusProgressing) + assertAppHealth(t, "./testdata/job-failed.yaml", health.HealthStatusDegraded) + assertAppHealth(t, "./testdata/job-succeeded.yaml", health.HealthStatusHealthy) + assertAppHealth(t, "./testdata/job-suspended.yaml", health.HealthStatusSuspended) } func TestHPA(t *testing.T) { - assertAppHealth(t, "./testdata/hpa-v2-healthy.yaml", HealthStatusHealthy) - assertAppHealth(t, "./testdata/hpa-v2-degraded.yaml", HealthStatusDegraded) - assertAppHealth(t, "./testdata/hpa-v2-progressing.yaml", HealthStatusProgressing) - assertAppHealth(t, "./testdata/hpa-v2beta2-healthy.yaml", HealthStatusHealthy) - assertAppHealth(t, "./testdata/hpa-v2beta1-healthy-disabled.yaml", HealthStatusHealthy) - assertAppHealth(t, "./testdata/hpa-v2beta1-healthy.yaml", HealthStatusHealthy) - assertAppHealth(t, "./testdata/hpa-v1-degraded.yaml", HealthStatusDegraded) - assertAppHealth(t, "./testdata/hpa-v1-healthy.yaml", HealthStatusHealthy) - assertAppHealth(t, "./testdata/hpa-v1-healthy-toofew.yaml", HealthStatusHealthy) - assertAppHealth(t, "./testdata/hpa-v1-progressing.yaml", HealthStatusProgressing) - assertAppHealth(t, "./testdata/hpa-v1-progressing-with-no-annotations.yaml", HealthStatusProgressing) + assertAppHealth(t, "./testdata/hpa-v2-healthy.yaml", health.HealthStatusHealthy) + assertAppHealth(t, "./testdata/hpa-v2-degraded.yaml", health.HealthStatusDegraded) + assertAppHealth(t, "./testdata/hpa-v2-progressing.yaml", health.HealthStatusProgressing) + assertAppHealth(t, "./testdata/hpa-v2beta2-healthy.yaml", health.HealthStatusHealthy) + assertAppHealth(t, "./testdata/hpa-v2beta1-healthy-disabled.yaml", health.HealthStatusHealthy) + assertAppHealth(t, "./testdata/hpa-v2beta1-healthy.yaml", health.HealthStatusHealthy) + assertAppHealth(t, "./testdata/hpa-v1-degraded.yaml", health.HealthStatusDegraded) + assertAppHealth(t, "./testdata/hpa-v1-healthy.yaml", health.HealthStatusHealthy) + assertAppHealth(t, "./testdata/hpa-v1-healthy-toofew.yaml", health.HealthStatusHealthy) + assertAppHealth(t, "./testdata/hpa-v1-progressing.yaml", health.HealthStatusProgressing) + assertAppHealth(t, "./testdata/hpa-v1-progressing-with-no-annotations.yaml", health.HealthStatusProgressing) } func TestPod(t *testing.T) { - assertAppHealth(t, "./testdata/pod-pending.yaml", HealthStatusProgressing) - assertAppHealth(t, "./testdata/pod-running-not-ready.yaml", HealthStatusProgressing) - assertAppHealth(t, "./testdata/pod-crashloop.yaml", HealthStatusDegraded) - assertAppHealth(t, "./testdata/pod-imagepullbackoff.yaml", HealthStatusDegraded) - assertAppHealth(t, "./testdata/pod-error.yaml", HealthStatusDegraded) - assertAppHealth(t, "./testdata/pod-running-restart-always.yaml", HealthStatusHealthy) - assertAppHealth(t, "./testdata/pod-running-restart-never.yaml", HealthStatusProgressing) - assertAppHealth(t, "./testdata/pod-running-restart-onfailure.yaml", HealthStatusProgressing) - assertAppHealth(t, "./testdata/pod-failed.yaml", HealthStatusDegraded) - assertAppHealth(t, "./testdata/pod-succeeded.yaml", HealthStatusHealthy) - assertAppHealth(t, "./testdata/pod-deletion.yaml", HealthStatusProgressing) + assertAppHealth(t, "./testdata/pod-pending.yaml", health.HealthStatusProgressing) + assertAppHealth(t, "./testdata/pod-running-not-ready.yaml", health.HealthStatusProgressing) + assertAppHealth(t, "./testdata/pod-crashloop.yaml", health.HealthStatusDegraded) + assertAppHealth(t, "./testdata/pod-imagepullbackoff.yaml", health.HealthStatusDegraded) + assertAppHealth(t, "./testdata/pod-error.yaml", health.HealthStatusDegraded) + assertAppHealth(t, "./testdata/pod-running-restart-always.yaml", health.HealthStatusHealthy) + assertAppHealth(t, "./testdata/pod-running-restart-never.yaml", health.HealthStatusProgressing) + assertAppHealth(t, "./testdata/pod-running-restart-onfailure.yaml", health.HealthStatusProgressing) + assertAppHealth(t, "./testdata/pod-failed.yaml", health.HealthStatusDegraded) + assertAppHealth(t, "./testdata/pod-succeeded.yaml", health.HealthStatusHealthy) + assertAppHealth(t, "./testdata/pod-deletion.yaml", health.HealthStatusProgressing) } func TestApplication(t *testing.T) { @@ -131,10 +137,10 @@ func TestGetArgoWorkflowHealth(t *testing.T) { }, } - health, err := getArgoWorkflowHealth(&sampleWorkflow) + argohealth, err := health.GetArgoWorkflowHealth(&sampleWorkflow) require.NoError(t, err) - assert.Equal(t, HealthStatusProgressing, health.Status) - assert.Equal(t, "This node is running", health.Message) + assert.Equal(t, health.HealthStatusProgressing, argohealth.Status) + assert.Equal(t, "This node is running", argohealth.Message) sampleWorkflow = unstructured.Unstructured{Object: map[string]interface{}{ "spec": map[string]interface{}{ @@ -148,10 +154,10 @@ func TestGetArgoWorkflowHealth(t *testing.T) { }, } - health, err = getArgoWorkflowHealth(&sampleWorkflow) + argohealth, err = health.GetArgoWorkflowHealth(&sampleWorkflow) require.NoError(t, err) - assert.Equal(t, HealthStatusHealthy, health.Status) - assert.Equal(t, "This node is has succeeded", health.Message) + assert.Equal(t, health.HealthStatusHealthy, argohealth.Status) + assert.Equal(t, "This node is has succeeded", argohealth.Message) sampleWorkflow = unstructured.Unstructured{Object: map[string]interface{}{ "spec": map[string]interface{}{ @@ -161,28 +167,28 @@ func TestGetArgoWorkflowHealth(t *testing.T) { }, } - health, err = getArgoWorkflowHealth(&sampleWorkflow) + argohealth, err = health.GetArgoWorkflowHealth(&sampleWorkflow) require.NoError(t, err) - assert.Equal(t, HealthStatusProgressing, health.Status) - assert.Equal(t, "", health.Message) + assert.Equal(t, health.HealthStatusProgressing, argohealth.Status) + assert.Equal(t, "", argohealth.Message) } func TestArgoApplication(t *testing.T) { - assertAppHealth(t, "./testdata/argo-application-healthy.yaml", HealthStatusHealthy) - assertAppHealth(t, "./testdata/argo-application-missing.yaml", HealthStatusMissing) + assertAppHealth(t, "./testdata/argo-application-healthy.yaml", health.HealthStatusHealthy) + assertAppHealth(t, "./testdata/argo-application-missing.yaml", health.HealthStatusMissing) } func TestFluxResources(t *testing.T) { - assertAppHealth(t, "./testdata/flux-kustomization-healthy.yaml", HealthStatusHealthy) - assertAppHealth(t, "./testdata/flux-kustomization-unhealthy.yaml", HealthStatusDegraded) + assertAppHealth(t, "./testdata/flux-kustomization-healthy.yaml", health.HealthStatusHealthy) + assertAppHealth(t, "./testdata/flux-kustomization-unhealthy.yaml", health.HealthStatusDegraded) - assertAppHealth(t, "./testdata/flux-helmrelease-healthy.yaml", HealthStatusHealthy) - assertAppHealth(t, "./testdata/flux-helmrelease-unhealthy.yaml", HealthStatusDegraded) + assertAppHealth(t, "./testdata/flux-helmrelease-healthy.yaml", health.HealthStatusHealthy) + assertAppHealth(t, "./testdata/flux-helmrelease-unhealthy.yaml", health.HealthStatusDegraded) - assertAppHealth(t, "./testdata/flux-helmrepository-healthy.yaml", HealthStatusHealthy) - assertAppHealth(t, "./testdata/flux-helmrepository-unhealthy.yaml", HealthStatusDegraded) + assertAppHealth(t, "./testdata/flux-helmrepository-healthy.yaml", health.HealthStatusHealthy) + assertAppHealth(t, "./testdata/flux-helmrepository-unhealthy.yaml", health.HealthStatusDegraded) - assertAppHealth(t, "./testdata/flux-gitrepository-healthy.yaml", HealthStatusHealthy) - assertAppHealth(t, "./testdata/flux-gitrepository-unhealthy.yaml", HealthStatusDegraded) + assertAppHealth(t, "./testdata/flux-gitrepository-healthy.yaml", health.HealthStatusHealthy) + assertAppHealth(t, "./testdata/flux-gitrepository-unhealthy.yaml", health.HealthStatusDegraded) } diff --git a/pkg/health/testdata/certificate-healthy.yaml b/pkg/health/testdata/certificate-healthy.yaml new file mode 100644 index 0000000..c6c2b90 --- /dev/null +++ b/pkg/health/testdata/certificate-healthy.yaml @@ -0,0 +1,39 @@ +apiVersion: cert-manager.io/v1alpha2 +kind: Certificate +metadata: + creationTimestamp: "2019-02-15T18:17:06Z" + generation: 1 + name: test-cert + namespace: argocd + resourceVersion: "68337322" + selfLink: /apis/cert-manager.io/v1alpha2/namespaces/argocd/certificates/test-cert + uid: e6cfba50-314d-11e9-be3f-42010a800011 +spec: + acme: + config: + - domains: + - cd.apps.argoproj.io + http01: + ingress: http01 + commonName: cd.apps.argoproj.io + dnsNames: + - cd.apps.argoproj.io + issuerRef: + kind: Issuer + name: argo-cd-issuer + secretName: test-secret +status: + acme: + order: + url: https://acme-v02.api.letsencrypt.org/acme/order/45250083/316944902 + conditions: + - lastTransitionTime: "2019-02-15T18:21:10Z" + message: Order validated + reason: OrderValidated + status: "False" + type: ValidateFailed + - lastTransitionTime: null + message: Certificate issued successfully + reason: CertIssued + status: "True" + type: Ready diff --git a/pkg/health/utils.go b/pkg/health/utils.go index d0406d7..70b0323 100644 --- a/pkg/health/utils.go +++ b/pkg/health/utils.go @@ -8,8 +8,6 @@ import ( ) const ( - CertificateKind = "Certificate" - CertificateRequestKind = "CertificateRequest" SecretKind = "Secret" ServiceKind = "Service" ServiceAccountKind = "ServiceAccount"