Skip to content

Commit

Permalink
feat: add namespace check & tests for certificates. (#14)
Browse files Browse the repository at this point in the history
* feat: certificate health check

* improve ingress health check

* add certificate test and remove go impl for certificate

* feat: add health check for namespace
  • Loading branch information
adityathebe authored Apr 4, 2024
1 parent 97e4bdc commit 45f9efd
Show file tree
Hide file tree
Showing 8 changed files with 191 additions and 68 deletions.
5 changes: 4 additions & 1 deletion pkg/health/health.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
}
Expand All @@ -161,6 +161,7 @@ func GetHealthCheckFunc(gvk schema.GroupVersionKind) func(obj *unstructured.Unst
// case APIServiceKind:
// return getAPIServiceHealth
// }

case "networking.k8s.io":
switch gvk.Kind {
case IngressKind:
Expand All @@ -174,6 +175,8 @@ func GetHealthCheckFunc(gvk schema.GroupVersionKind) func(obj *unstructured.Unst
return getPVCHealth
case PodKind:
return getPodHealth
case NamespaceKind:
return getNamespaceHealth
}
case "batch":
switch gvk.Kind {
Expand Down
2 changes: 1 addition & 1 deletion pkg/health/health_argo.go
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down
10 changes: 9 additions & 1 deletion pkg/health/health_ingress.go
Original file line number Diff line number Diff line change
@@ -1,11 +1,19 @@
package health

import (
"fmt"

"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
)

func getIngressHealth(obj *unstructured.Unstructured) (*HealthStatus, error) {
ingresses, _, _ := unstructured.NestedSlice(obj.Object, "status", "loadBalancer", "ingress")
ingresses, found, err := unstructured.NestedSlice(obj.Object, "status", "loadBalancer", "ingress")
if err != nil {
return &HealthStatus{Status: HealthStatusError, Message: fmt.Sprintf("failed to ingress status: %v", err)}, nil
} else if !found {
return &HealthStatus{Status: HealthStatusPending, Message: "ingress loadbalancer status not found"}, nil
}

health := HealthStatus{}
if len(ingresses) > 0 {
health.Status = HealthStatusHealthy
Expand Down
26 changes: 26 additions & 0 deletions pkg/health/health_namespace.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
package health

import (
"fmt"

v1 "k8s.io/api/core/v1"
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
"k8s.io/apimachinery/pkg/runtime"
)

func getNamespaceHealth(obj *unstructured.Unstructured) (*HealthStatus, error) {
var node v1.Namespace
if err := runtime.DefaultUnstructuredConverter.FromUnstructured(obj.Object, &node); err != nil {
return nil, fmt.Errorf("failed to convert unstructured Node to typed: %v", err)
}

if node.Status.Phase == v1.NamespaceActive {
return &HealthStatus{
Status: HealthStatusHealthy,
}, nil
}

return &HealthStatus{
Status: HealthStatusDeleting,
}, nil
}
141 changes: 76 additions & 65 deletions pkg/health/health_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,108 +2,119 @@
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 TestNamespace(t *testing.T) {
assertAppHealth(t, "./testdata/namespace.yaml", health.HealthStatusHealthy)
assertAppHealth(t, "./testdata/namespace-terminating.yaml", health.HealthStatusDeleting)
}

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) {
assert.Nil(t, getHealthStatus("./testdata/knative-service.yaml", 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) {
Expand Down Expand Up @@ -131,10 +142,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{}{
Expand All @@ -148,10 +159,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{}{
Expand All @@ -161,28 +172,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)
}
39 changes: 39 additions & 0 deletions pkg/health/testdata/certificate-healthy.yaml
Original file line number Diff line number Diff line change
@@ -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
18 changes: 18 additions & 0 deletions pkg/health/testdata/namespace-terminating.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
---
apiVersion: v1
kind: Namespace
metadata:
creationTimestamp: '2024-02-10T10:27:15Z'
labels:
kubernetes.io/metadata.name: media
kustomize.toolkit.fluxcd.io/name: flux-system
kustomize.toolkit.fluxcd.io/namespace: flux-system
kustomize.toolkit.fluxcd.io/prune: disabled
name: media
resourceVersion: '4518738'
uid: c1b2dc7f-7070-4ee5-9ae0-e43df5523cd8
spec:
finalizers:
- kubernetes
status:
phase: Terminating
18 changes: 18 additions & 0 deletions pkg/health/testdata/namespace.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
---
apiVersion: v1
kind: Namespace
metadata:
creationTimestamp: '2024-02-10T10:27:15Z'
labels:
kubernetes.io/metadata.name: media
kustomize.toolkit.fluxcd.io/name: flux-system
kustomize.toolkit.fluxcd.io/namespace: flux-system
kustomize.toolkit.fluxcd.io/prune: disabled
name: media
resourceVersion: '4518738'
uid: c1b2dc7f-7070-4ee5-9ae0-e43df5523cd8
spec:
finalizers:
- kubernetes
status:
phase: Active

0 comments on commit 45f9efd

Please sign in to comment.