From 8c1d5c122e3045e6f91ea4b23aa7884da57ed0a9 Mon Sep 17 00:00:00 2001 From: Aditya Thebe Date: Mon, 7 Oct 2024 21:05:04 +0545 Subject: [PATCH] fix: attempt multiple time formats when parsing triggerAt --- pkg/controllers/canary_controller.go | 10 +++-- pkg/utils/utils.go | 20 +++++++++ pkg/utils/utils_test.go | 64 ++++++++++++++++++++++++++++ 3 files changed, 90 insertions(+), 4 deletions(-) create mode 100644 pkg/utils/utils_test.go diff --git a/pkg/controllers/canary_controller.go b/pkg/controllers/canary_controller.go index 8376639aa..1e24cc88c 100644 --- a/pkg/controllers/canary_controller.go +++ b/pkg/controllers/canary_controller.go @@ -18,9 +18,11 @@ package controllers import ( gocontext "context" + "fmt" "time" "github.com/flanksource/canary-checker/pkg/db" + "github.com/flanksource/canary-checker/pkg/utils" corev1 "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" @@ -121,12 +123,12 @@ func (r *CanaryReconciler) Reconcile(parentCtx gocontext.Context, req ctrl.Reque patch := client.MergeFrom(canaryForStatus.DeepCopy()) if val, ok := canary.Annotations["next-runtime"]; ok { - runAt, err := time.Parse(time.RFC3339, val) - if err != nil { - return ctrl.Result{}, err + runAt := utils.ParseTime(val) + if runAt == nil { + return ctrl.Result{}, fmt.Errorf("invalid next-runtime: %s", val) } - if err := canaryJobs.TriggerAt(ctx, *dbCanary, runAt); err != nil { + if err := canaryJobs.TriggerAt(ctx, *dbCanary, *runAt); err != nil { return ctrl.Result{Requeue: true, RequeueAfter: 2 * time.Minute}, err } diff --git a/pkg/utils/utils.go b/pkg/utils/utils.go index 8f00ed6fc..a737cab91 100644 --- a/pkg/utils/utils.go +++ b/pkg/utils/utils.go @@ -152,3 +152,23 @@ func FreePort() int { address := listener.Addr().(*net.TCPAddr) return address.Port } + +func ParseTime(t string) *time.Time { + formats := []string{ + time.RFC3339, + time.RFC3339Nano, + time.ANSIC, + time.DateTime, + "2006-01-02T15:04:05", // ISO8601 without timezone + "2006-01-02 15:04:05", // MySQL datetime format + } + + for _, format := range formats { + parsed, err := time.Parse(format, t) + if err == nil { + return &parsed + } + } + + return nil +} diff --git a/pkg/utils/utils_test.go b/pkg/utils/utils_test.go new file mode 100644 index 000000000..6d906e038 --- /dev/null +++ b/pkg/utils/utils_test.go @@ -0,0 +1,64 @@ +package utils + +import ( + "testing" + "time" + + "github.com/samber/lo" +) + +func TestParseTime(t *testing.T) { + testCases := []struct { + name string + input string + expected *time.Time + }{ + { + name: "RFC3339", + input: "2023-04-05T15:04:05Z", + expected: lo.ToPtr(time.Date(2023, 4, 5, 15, 4, 5, 0, time.UTC)), + }, + { + name: "RFC3339Nano", + input: "2023-04-05T15:04:05.999999999Z", + expected: lo.ToPtr(time.Date(2023, 4, 5, 15, 4, 5, 999999999, time.UTC)), + }, + { + name: "ISO8601 with timezone", + input: "2023-04-05T15:04:05+02:00", + expected: lo.ToPtr(time.Date(2023, 4, 5, 15, 4, 5, 0, time.FixedZone("", 2*60*60))), + }, + { + name: "ISO8601 without timezone", + input: "2023-04-05T15:04:05", + expected: lo.ToPtr(time.Date(2023, 4, 5, 15, 4, 5, 0, time.UTC)), + }, + { + name: "MySQL datetime format", + input: "2023-04-05 15:04:05", + expected: lo.ToPtr(time.Date(2023, 4, 5, 15, 4, 5, 0, time.UTC)), + }, + { + name: "Invalid format", + input: "not a valid time", + expected: nil, + }, + } + + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + result := ParseTime(tc.input) + if tc.expected == nil { + if result != nil { + t.Errorf("Expected nil, but got %v", result) + } + } else { + if result == nil { + t.Errorf("Expected %v, but got nil", tc.expected) + } else if !result.Equal(*tc.expected) { + t.Errorf("Expected %v, but got %v", tc.expected, result) + } + } + }) + } +}