From 4212386e13cbc5a446c982f6985dcc6e7ee8344d Mon Sep 17 00:00:00 2001 From: Felipe Gehrke Date: Wed, 18 Jun 2025 14:57:49 -0300 Subject: [PATCH] changing ParseHumanDuration to support timestamp values too (#684) --- pkg/resources/common/duration.go | 11 ++++++++++- pkg/resources/common/duration_test.go | 8 ++++++-- pkg/resources/common/formatter.go | 18 +++++++++++++----- pkg/resources/virtual/virtual.go | 7 +++++-- 4 files changed, 34 insertions(+), 10 deletions(-) diff --git a/pkg/resources/common/duration.go b/pkg/resources/common/duration.go index b4f5c9f0..73706596 100644 --- a/pkg/resources/common/duration.go +++ b/pkg/resources/common/duration.go @@ -6,11 +6,20 @@ import ( "time" ) -func ParseHumanReadableDuration(s string) (time.Duration, error) { +// ParseTimestampOrHumanReadableDuration can do one of three things with an incoming string: +// 1. Recognize it's an absolute timestamp and calculate a relative `time.Duration` +// 2. Recognize it's a human-readable duration (like 3m) and convert to a relative `time.Duration` +// 3. Return an error because it doesn't recognize the input +func ParseTimestampOrHumanReadableDuration(s string) (time.Duration, error) { var total time.Duration var val int var unit byte + parsedTime, err := time.Parse(time.RFC3339, s) + if err == nil { + return time.Since(parsedTime), nil + } + r := strings.NewReader(s) for r.Len() > 0 { if _, err := fmt.Fscanf(r, "%d%c", &val, &unit); err != nil { diff --git a/pkg/resources/common/duration_test.go b/pkg/resources/common/duration_test.go index 6091cc31..698c4f5f 100644 --- a/pkg/resources/common/duration_test.go +++ b/pkg/resources/common/duration_test.go @@ -7,7 +7,7 @@ import ( "github.com/stretchr/testify/assert" ) -func TestParseHumanDuration(t *testing.T) { +func TestParseHumanReadableDuration(t *testing.T) { testCases := []struct { name string input string @@ -63,7 +63,7 @@ func TestParseHumanDuration(t *testing.T) { for _, tc := range testCases { t.Run(tc.name, func(t *testing.T) { - output, err := ParseHumanReadableDuration(tc.input) + output, err := ParseTimestampOrHumanReadableDuration(tc.input) if tc.expectedErr { assert.Error(t, err) } else { @@ -71,4 +71,8 @@ func TestParseHumanDuration(t *testing.T) { } }) } + + // Testing timestamp parsing separately + _, err := ParseTimestampOrHumanReadableDuration("2024-02-12T15:19:21Z") + assert.NoError(t, err) } diff --git a/pkg/resources/common/formatter.go b/pkg/resources/common/formatter.go index 8f9bd72d..03ffa633 100644 --- a/pkg/resources/common/formatter.go +++ b/pkg/resources/common/formatter.go @@ -247,29 +247,37 @@ func convertMetadataTimestampFields(request *types.APIRequest, gvk schema2.Group curValue, got, err := unstructured.NestedSlice(unstr.Object, "metadata", "fields") if err != nil { - logrus.Errorf("failed to get metadata.fields slice from unstr.Object: %s", err.Error()) + logrus.Warnf("failed to get metadata.fields slice from unstr.Object: %s", err.Error()) + return } if !got { - logrus.Debugf("couldn't find metadata.fields at unstr.Object") + logrus.Warnf("couldn't find metadata.fields at unstr.Object") return } timeValue, ok := curValue[index].(string) if !ok { - logrus.Debugf("time field isn't a string") + logrus.Warnf("time field isn't a string") return } + millis, err := strconv.ParseInt(timeValue, 10, 64) if err != nil { - logrus.Warnf("failed to convert timestamp value: %s", err.Error()) + logrus.Warnf("convert timestamp value: %s failed with error: %s", timeValue, err.Error()) return } timestamp := time.Unix(0, millis*int64(time.Millisecond)) dur := time.Since(timestamp) - curValue[index] = duration.HumanDuration(dur) + humanDuration := duration.HumanDuration(dur) + if humanDuration == "" { + logrus.Warnf("couldn't convert value %d into human duration for column %s", int64(dur), col.Name) + return + } + + curValue[index] = humanDuration if err := unstructured.SetNestedSlice(unstr.Object, curValue, "metadata", "fields"); err != nil { logrus.Errorf("failed to set value back to metadata.fields slice: %s", err.Error()) return diff --git a/pkg/resources/virtual/virtual.go b/pkg/resources/virtual/virtual.go index 99fa4c0c..da6a46f9 100644 --- a/pkg/resources/virtual/virtual.go +++ b/pkg/resources/virtual/virtual.go @@ -11,6 +11,7 @@ import ( "github.com/rancher/steve/pkg/resources/virtual/clusters" "github.com/rancher/steve/pkg/resources/virtual/common" "github.com/rancher/steve/pkg/resources/virtual/events" + "github.com/sirupsen/logrus" "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" "k8s.io/apimachinery/pkg/runtime/schema" "k8s.io/client-go/tools/cache" @@ -64,9 +65,11 @@ func (t *TransformBuilder) GetTransformFunc(gvk schema.GroupVersionKind, columns if !cast { return nil, fmt.Errorf("could not cast metadata.fields %d to string", index) } - duration, err := rescommon.ParseHumanReadableDuration(value) + + duration, err := rescommon.ParseTimestampOrHumanReadableDuration(value) if err != nil { - return nil, err + logrus.Errorf("parse timestamp %s, failed with error: %s", value, err) + return obj, nil } curValue[index] = fmt.Sprintf("%d", now.Add(-duration).UnixMilli())