Skip to content

Commit

Permalink
Adding CRD health customization and tests for getCustomResourceDefini…
Browse files Browse the repository at this point in the history
…tionHealth

Signed-off-by: Almo Elda <[email protected]>
  • Loading branch information
almoelda committed Dec 29, 2024
1 parent 54992bf commit 1f22c4c
Show file tree
Hide file tree
Showing 8 changed files with 388 additions and 0 deletions.
5 changes: 5 additions & 0 deletions pkg/health/health.go
Original file line number Diff line number Diff line change
Expand Up @@ -116,6 +116,11 @@ func GetHealthCheckFunc(gvk schema.GroupVersionKind) func(obj *unstructured.Unst
case kube.IngressKind:
return getIngressHealth
}
case "apiextensions.k8s.io":
switch gvk.Kind {
case kube.CustomResourceDefinitionKind:
return getCustomResourceDefinitionHealth
}
case "argoproj.io":
switch gvk.Kind {
case "Workflow":
Expand Down
99 changes: 99 additions & 0 deletions pkg/health/health_customresourcedefinition.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
package health

import (
"fmt"

"github.com/argoproj/gitops-engine/pkg/utils/kube"
apiextensionsv1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1"
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
"k8s.io/apimachinery/pkg/runtime"
)

func getCustomResourceDefinitionHealth(obj *unstructured.Unstructured) (*HealthStatus, error) {
gvk := obj.GroupVersionKind()
switch gvk {
case apiextensionsv1.SchemeGroupVersion.WithKind(kube.CustomResourceDefinitionKind):
var crd apiextensionsv1.CustomResourceDefinition
err := runtime.DefaultUnstructuredConverter.FromUnstructured(obj.Object, &crd)
if err != nil {
return nil, fmt.Errorf("failed to convert unstructured CustomResourceDefinition to typed: %v", err)
}
return getApiExtenstionsV1CustomResourceDefinitionHealth(&crd)
default:
return nil, fmt.Errorf("unsupported CustomResourceDefinition GVK: %s", gvk)
}
}

func getApiExtenstionsV1CustomResourceDefinitionHealth(crd *apiextensionsv1.CustomResourceDefinition) (*HealthStatus, error) {

if crd.Status.Conditions == nil || crd.Status.Conditions != nil && len(crd.Status.Conditions) == 0 {
return &HealthStatus{
Status: HealthStatusProgressing,
Message: "Status conditions not found",
}, nil
}

var (
isEstablished bool
isTerminating bool
namesNotAccepted bool
hasViolations bool
conditionMsg string
)

// Check conditions
for _, condition := range crd.Status.Conditions {
switch condition.Type {
case apiextensionsv1.Terminating:
if condition.Status == apiextensionsv1.ConditionTrue {
isTerminating = true
conditionMsg = condition.Message
}
case apiextensionsv1.NamesAccepted:
if condition.Status == apiextensionsv1.ConditionFalse {
namesNotAccepted = true
conditionMsg = condition.Message
}
case apiextensionsv1.Established:
if condition.Status == apiextensionsv1.ConditionTrue {
isEstablished = true
} else {
conditionMsg = condition.Message
}
case apiextensionsv1.NonStructuralSchema:
if condition.Status == apiextensionsv1.ConditionTrue {
hasViolations = true
conditionMsg = condition.Message
}
}
}

// Return appropriate health status
switch {
case isTerminating:
return &HealthStatus{
Status: HealthStatusProgressing,
Message: fmt.Sprintf("CRD is being terminated: %s", conditionMsg),
}, nil
case namesNotAccepted:
return &HealthStatus{
Status: HealthStatusDegraded,
Message: fmt.Sprintf("CRD names have not been accepted: %s", conditionMsg),
}, nil
case !isEstablished:
return &HealthStatus{
Status: HealthStatusDegraded,
Message: fmt.Sprintf("CRD is not established: %s", conditionMsg),
}, nil
case hasViolations:
return &HealthStatus{
Status: HealthStatusDegraded,
Message: fmt.Sprintf("Schema violations found: %s", conditionMsg),
}, nil
default:
return &HealthStatus{
Status: HealthStatusHealthy,
Message: "CRD is healthy",
}, nil
}
}
8 changes: 8 additions & 0 deletions pkg/health/health_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -118,6 +118,14 @@ func TestAPIService(t *testing.T) {
assertAppHealth(t, "./testdata/apiservice-v1beta1-false.yaml", HealthStatusProgressing)
}

func TestCustomResourceDefinitionHealth(t *testing.T) {
assertAppHealth(t, "./testdata/crd-v1-healthy.yaml", HealthStatusHealthy)
assertAppHealth(t, "./testdata/crd-v1-non-structual-degraded.yaml", HealthStatusDegraded)
assertAppHealth(t, "./testdata/crd-v1-not-established-degraded.yaml", HealthStatusDegraded)
assertAppHealth(t, "./testdata/crd-v1-terminating-progressing.yaml", HealthStatusProgressing)
assertAppHealth(t, "./testdata/crd-v1-no-conditions-progressing.yaml", HealthStatusProgressing)
}

func TestGetArgoWorkflowHealth(t *testing.T) {
sampleWorkflow := unstructured.Unstructured{Object: map[string]interface{}{
"spec": map[string]interface{}{
Expand Down
56 changes: 56 additions & 0 deletions pkg/health/testdata/crd-v1-healthy.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
apiVersion: apiextensions.k8s.io/v1
kind: CustomResourceDefinition
metadata:
name: examples.example.io
spec:
conversion:
strategy: None
group: example.io
names:
kind: Example
listKind: ExampleList
plural: examples
shortNames:
- ex
singular: example
preserveUnknownFields: true
scope: Namespaced
versions:
- additionalPrinterColumns:
- description: >-
CreationTimestamp is a timestamp representing the server time when
this object was created. It is not guaranteed to be set in
happens-before order across separate operations. Clients may not set
this value. It is represented in RFC3339 form and is in UTC.
Populated by the system. Read-only. Null for lists. More info:
https://git.k8s.io/community/contributors/devel/api-conventions.md#metadata
jsonPath: .metadata.creationTimestamp
name: Age
type: date
name: v1alpha1
served: true
storage: true
subresources: {}
status:
acceptedNames:
kind: Example
listKind: ExampleList
plural: examples
shortNames:
- ex
singular: example
conditions:
- lastTransitionTime: '2024-05-19T23:35:28Z'
message: no conflicts found
reason: NoConflicts
status: 'True'
type: NamesAccepted
- lastTransitionTime: '2024-05-19T23:35:28Z'
message: the initial names have been accepted
reason: InitialNamesAccepted
status: 'True'
type: Established
storedVersions:
- v1alpha1
46 changes: 46 additions & 0 deletions pkg/health/testdata/crd-v1-no-conditions-progressing.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
apiVersion: apiextensions.k8s.io/v1
kind: CustomResourceDefinition
metadata:
name: examples.example.io
spec:
conversion:
strategy: None
group: example.io
names:
kind: Example
listKind: ExampleList
plural: examples
shortNames:
- ex
singular: example
preserveUnknownFields: true
scope: Namespaced
versions:
- additionalPrinterColumns:
- description: >-
CreationTimestamp is a timestamp representing the server time when
this object was created. It is not guaranteed to be set in
happens-before order across separate operations. Clients may not set
this value. It is represented in RFC3339 form and is in UTC.
Populated by the system. Read-only. Null for lists. More info:
https://git.k8s.io/community/contributors/devel/api-conventions.md#metadata
jsonPath: .metadata.creationTimestamp
name: Age
type: date
name: v1alpha1
served: true
storage: true
subresources: {}
status:
acceptedNames:
kind: Example
listKind: ExampleList
plural: examples
shortNames:
- ex
singular: example
conditions: []
storedVersions:
- v1alpha1
61 changes: 61 additions & 0 deletions pkg/health/testdata/crd-v1-non-structual-degraded.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
apiVersion: apiextensions.k8s.io/v1
kind: CustomResourceDefinition
metadata:
name: examples.example.io
spec:
conversion:
strategy: None
group: example.io
names:
kind: Example
listKind: ExampleList
plural: examples
shortNames:
- ex
singular: example
preserveUnknownFields: true
scope: Namespaced
versions:
- additionalPrinterColumns:
- description: >-
CreationTimestamp is a timestamp representing the server time when
this object was created. It is not guaranteed to be set in
happens-before order across separate operations. Clients may not set
this value. It is represented in RFC3339 form and is in UTC.
Populated by the system. Read-only. Null for lists. More info:
https://git.k8s.io/community/contributors/devel/api-conventions.md#metadata
jsonPath: .metadata.creationTimestamp
name: Age
type: date
name: v1alpha1
served: true
storage: true
subresources: {}
status:
acceptedNames:
kind: Example
listKind: ExampleList
plural: examples
shortNames:
- ex
singular: example
conditions:
- lastTransitionTime: '2024-05-19T23:35:28Z'
message: no conflicts found
reason: NoConflicts
status: 'True'
type: NamesAccepted
- lastTransitionTime: '2024-05-19T23:35:28Z'
message: the initial names have been accepted
reason: InitialNamesAccepted
status: 'True'
type: Established
- lastTransitionTime: '2024-10-26T19:44:57Z'
message: 'spec.preserveUnknownFields: Invalid value: true: must be false'
reason: Violations
status: 'True'
type: NonStructuralSchema
storedVersions:
- v1alpha1
56 changes: 56 additions & 0 deletions pkg/health/testdata/crd-v1-not-established-degraded.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
apiVersion: apiextensions.k8s.io/v1
kind: CustomResourceDefinition
metadata:
name: examples.example.io
spec:
conversion:
strategy: None
group: example.io
names:
kind: Example
listKind: ExampleList
plural: examples
shortNames:
- ex
singular: example
preserveUnknownFields: true
scope: Namespaced
versions:
- additionalPrinterColumns:
- description: >-
CreationTimestamp is a timestamp representing the server time when
this object was created. It is not guaranteed to be set in
happens-before order across separate operations. Clients may not set
this value. It is represented in RFC3339 form and is in UTC.
Populated by the system. Read-only. Null for lists. More info:
https://git.k8s.io/community/contributors/devel/api-conventions.md#metadata
jsonPath: .metadata.creationTimestamp
name: Age
type: date
name: v1alpha1
served: true
storage: true
subresources: {}
status:
acceptedNames:
kind: Example
listKind: ExampleList
plural: examples
shortNames:
- ex
singular: example
conditions:
- lastTransitionTime: '2024-05-19T23:35:28Z'
message: no conflicts found
reason: NoConflicts
status: 'False'
type: NamesAccepted
- lastTransitionTime: '2024-05-19T23:35:28Z'
message: the initial names have been accepted
reason: InitialNamesAccepted
status: 'False'
type: Established
storedVersions:
- v1alpha1
Loading

0 comments on commit 1f22c4c

Please sign in to comment.