Skip to content

Commit

Permalink
feat: add LastUpdated time
Browse files Browse the repository at this point in the history
  • Loading branch information
moshloop committed Dec 1, 2024
1 parent 17a7fc5 commit 665a9c2
Show file tree
Hide file tree
Showing 7 changed files with 110 additions and 3 deletions.
98 changes: 95 additions & 3 deletions pkg/health/health.go
Original file line number Diff line number Diff line change
Expand Up @@ -173,6 +173,96 @@ func GetHealthByConfigType(configType string, obj map[string]any, states ...stri
}
}

func max(a, b time.Time) time.Time {
if a.After(b) {
return a
}
return b
}

func GetLastUpdatedTime(obj *unstructured.Unstructured) *time.Time {

lastUpdated := obj.GetCreationTimestamp().Time

// Check annotations
if annotations := obj.GetAnnotations(); annotations != nil {
if lastApplied, ok := annotations["kubectl.kubernetes.io/last-applied-configuration"]; ok {
if t, err := time.Parse(time.RFC3339, lastApplied); err == nil {
lastUpdated = max(lastUpdated, t)
}
}
}

possibleStatus := [][]string{
{"lastUpdateTime"},
{"startTime"},
{"lastSyncTime"},
{"reconciledAt"},
{"startedAt"},
{"deployedAt"},
{"finishedAt"},
{"lastTransitionTime"},
{"observedAt"},
{"operationState", "startedAt"},
{"operationState", "finishedAt"},
}

if status, ok := obj.Object["status"].(map[string]interface{}); ok {
for _, key := range possibleStatus {
if value, ok, _ := unstructured.NestedString(status, key...); ok {
if t, err := time.Parse(time.RFC3339, value); err == nil {
lastUpdated = max(lastUpdated, t)
}
}
}

// Check conditions
if conditions, found, _ := unstructured.NestedSlice(status, "conditions"); found {
for _, c := range conditions {
condition, ok := c.(map[string]interface{})
if !ok {
continue
}

for _, k := range []string{"lastProbeTime", "lastTransitionTime", "lastUpdateTime"} {
if lastTransitionTime, exists, _ := unstructured.NestedString(condition, k); exists {
if t, err := time.Parse(time.RFC3339, lastTransitionTime); err == nil {
lastUpdated = max(lastUpdated, t)
}
}
}
}
}

if containerStatuses, ok, _ := unstructured.NestedSlice(status, "containerStatuses"); ok {
for _, c := range containerStatuses {
containerStatus, ok := c.(map[string]interface{})
if !ok {
continue
}

for _, k := range [][]string{
{"state", "running", "startedAt"},
{"state", "running", "finishedAt"},
{"state", "terminated", "finishedAt"},
{"lastState", "terminated", "finishedAt"},
{"operationState", "startedAt"},
{"operationState", "finishedAt"},
} {
if at, exists, _ := unstructured.NestedString(containerStatus, k...); exists {
if t, err := time.Parse(time.RFC3339, at); err == nil {
lastUpdated = max(lastUpdated, t)
}
}
}
}
}

}

return &lastUpdated
}

// GetResourceHealth returns the health of a k8s resource
func GetResourceHealth(
obj *unstructured.Unstructured,
Expand All @@ -182,9 +272,10 @@ func GetResourceHealth(
time.Since(obj.GetDeletionTimestamp().Time) > time.Hour {
terminatingFor := time.Since(obj.GetDeletionTimestamp().Time)
return &HealthStatus{
Status: "TerminatingStalled",
Health: HealthWarning,
Message: fmt.Sprintf("terminating for %v", duration.ShortHumanDuration(terminatingFor.Truncate(time.Hour))),
Status: "TerminatingStalled",
LastUpdated: GetLastUpdatedTime(obj),
Health: HealthWarning,
Message: fmt.Sprintf("terminating for %v", duration.ShortHumanDuration(terminatingFor.Truncate(time.Hour))),
}, nil
}

Expand Down Expand Up @@ -243,6 +334,7 @@ func GetResourceHealth(
health.Status = HealthStatusTerminating
health.Ready = false
}
health.LastUpdated = GetLastUpdatedTime(obj)

return health, err
}
Expand Down
8 changes: 8 additions & 0 deletions pkg/health/health_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,14 @@ func testFixture(t *testing.T, yamlPath string) {
if v, ok := obj.GetAnnotations()["expected-ready"]; ok {
assert.Equal(t, v == "true", hr.Ready)
}

if v, ok := obj.GetAnnotations()["expected-last-update"]; ok {
if hr.LastUpdated == nil {
assert.Fail(t, "expected last update but got nil")
} else {
assert.Equal(t, v, hr.LastUpdated.Format(time.RFC3339))
}
}
})
}

Expand Down
1 change: 1 addition & 0 deletions pkg/health/testdata/Kubernetes/Application/unhealthy.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ metadata:
name: snipe-it-helm
namespace: argo-apps
annotations:
expected-last-update: 2024-10-08T13:50:17Z
argocd.argoproj.io/refresh: normal
argocd.argoproj.io/sync-wave: "-5"
argocd.argoproj.io/tracking-id: argo-apps_snipe-it-wrapper:argoproj.io/Application:argo-apps/snipe-it-helm
Expand Down
1 change: 1 addition & 0 deletions pkg/health/testdata/Kubernetes/Job/job-failed.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ metadata:
annotations:
expected-status: BackoffLimitExceeded
expected-health: unhealthy
expected-last-update: 2018-12-02T08:09:27Z
resourceVersion: "46534173"
selfLink: /apis/batch/v1/namespaces/argoci-workflows/jobs/fail
uid: 95052288-f609-11e8-aa53-42010a80021b
Expand Down
1 change: 1 addition & 0 deletions pkg/health/testdata/Kubernetes/Pod/crashloopbackoff.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ metadata:
annotations:
expected-status: CrashLoopBackOff
expected-health: unhealthy
expected-last-update: 2018-12-02T09:20:25Z
resourceVersion: "151454"
selfLink: /api/v1/namespaces/argocd/pods/my-pod
uid: 63674389-f613-11e8-a057-fe5f49266390
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ metadata:
annotations:
expected-status: OOMKilled
expected-health: unhealthy
expected-last-update: "@now-5m"
expected-message: system has run out of memory, restarted 9 times
creationTimestamp: 2024-11-20T06:57:31Z
spec:
Expand Down
3 changes: 3 additions & 0 deletions pkg/health/utils.go
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,9 @@ type HealthStatus struct {
Status HealthStatusCode `json:"status,omitempty" protobuf:"bytes,1,opt,name=status"`
// Message is a human-readable informational message describing the health status
Message string `json:"message,omitempty" protobuf:"bytes,2,opt,name=message"`
// LastUpdated is the time this resource as last updated, detected by inspecting all
// of the relevant status timestamps
LastUpdated *time.Time `json:"lastUpdated,omitempty"`

order int `json:"-" yaml:"-"`
}
Expand Down

0 comments on commit 665a9c2

Please sign in to comment.