From 5c09dfc80e606d860a081e22805b31ca9ee1b2e5 Mon Sep 17 00:00:00 2001 From: Joakim Olsson Date: Fri, 9 Oct 2026 09:19:40 +0200 Subject: [PATCH 1/3] fix: evaluate schedules in the cronjob's spec.timeZone Schedules were always evaluated in UTC, so a cronjob with timeZone set (e.g. 0 7 * * * Europe/Stockholm) was reported as not running every day. Embed tzdata since the image is built from scratch. Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_01C8SW6vsbkrTKjLNatQ8JTg --- main.go | 15 +++++++++++-- main_test.go | 63 +++++++++++++++++++++++++++++++++++++++++++++++++++- 2 files changed, 75 insertions(+), 3 deletions(-) diff --git a/main.go b/main.go index 270f686..88682fa 100644 --- a/main.go +++ b/main.go @@ -8,6 +8,7 @@ import ( "os/signal" "syscall" "time" + _ "time/tzdata" // the image is FROM scratch, so embed zoneinfo for spec.timeZone "github.com/alecthomas/kingpin/v2" "github.com/multiplay/go-slack/chat" @@ -19,8 +20,10 @@ import ( "k8s.io/client-go/rest" ) -var checkFunc = doCheck -var exitFunc = os.Exit +var ( + checkFunc = doCheck + exitFunc = os.Exit +) func main() { slackUrl := kingpin.Flag("slack-url", "The Slack Webhook URL").Envar("SLACK_URL").Required().String() @@ -68,6 +71,14 @@ func doCheck(client Client, slackUrl string, ic chan os.Signal, sleepTime time.D if c.Status.LastScheduleTime != nil { since = *c.Status.LastScheduleTime } + // The schedule is evaluated in the location of since, so use the cronjob's time zone (UTC if unset) + if c.Spec.TimeZone != nil { + loc, err := time.LoadLocation(*c.Spec.TimeZone) + if err != nil { + return fmt.Errorf("error loading time zone of %s/%s (%s): %w", c.Namespace, c.Name, *c.Spec.TimeZone, err) + } + since = v1.NewTime(since.In(loc)) + } schedule, err := parser.Parse(c.Spec.Schedule) if err != nil { return fmt.Errorf("error parsing schedule of %s/%s (%s): %w", c.Namespace, c.Name, c.Spec.Schedule, err) diff --git a/main_test.go b/main_test.go index 4b80e7c..deb8a53 100644 --- a/main_test.go +++ b/main_test.go @@ -106,6 +106,7 @@ func Test_doCheck(t *testing.T) { slackResponse string wantErr bool wantOut []string + wantNotOut []string }{ { name: "error getting cronjobs", @@ -181,6 +182,57 @@ func Test_doCheck(t *testing.T) { }, wantErr: true, }, + { + name: "invalid time zone", + args: args{ + client: &brokenClient{ + batchApi: &batchApi{ + cronApi: &cronApi{ + listFn: func(_ context.Context, _ v1.ListOptions) (*cronjobv1.CronJobList, error) { + return &cronjobv1.CronJobList{ + Items: []cronjobv1.CronJob{ + { + Spec: cronjobv1.CronJobSpec{Schedule: "* * * * *", TimeZone: strP("Nowhere/Special")}, + }, + }, + }, nil + }, + }, + }, + }, + }, + wantErr: true, + }, + { + name: "schedule evaluated in cronjob time zone", + args: args{ + client: &brokenClient{ + batchApi: &batchApi{ + cronApi: &cronApi{ + listFn: func(_ context.Context, _ v1.ListOptions) (*cronjobv1.CronJobList, error) { + // Ran 6h ago at its daily local time; read as UTC the next run would be 30 minutes ago + last := time.Now().Add(-6 * time.Hour).Truncate(time.Minute) + local := last.In(time.FixedZone("IST", 5*3600+1800)) // Asia/Kolkata has no DST + return &cronjobv1.CronJobList{ + Items: []cronjobv1.CronJob{ + { + ObjectMeta: v1.ObjectMeta{Name: "some-name", Namespace: "some-ns"}, + Spec: cronjobv1.CronJobSpec{Schedule: fmt.Sprintf("%d %d * * *", local.Minute(), local.Hour()), TimeZone: strP("Asia/Kolkata")}, + Status: cronjobv1.CronJobStatus{LastScheduleTime: &v1.Time{Time: last}}, + }, + }, + }, nil + }, + }, + }, + }, + }, + timeout: 100 * time.Millisecond, + slackResponse: `{"ok": true}`, + wantErr: false, + wantOut: []string{"Checking some-ns/some-name since"}, + wantNotOut: []string{"was not scheduled"}, + }, { name: "only correctly running cronjobs", args: args{ @@ -308,6 +360,11 @@ func Test_doCheck(t *testing.T) { if err := doCheck(tt.args.client, fmt.Sprintf("http://%s", baseURL), ic, 10*time.Millisecond, buff); (err != nil) != tt.wantErr { t.Errorf("doCheck() error = %v, wantErr %v", err, tt.wantErr) } + for _, o := range tt.wantNotOut { + if strings.Contains(buff.String(), o) { + t.Errorf("doCheck() got %s, did not want %s", buff.String(), o) + } + } if len(tt.wantOut) > 0 { for _, o := range tt.wantOut { if !strings.Contains(buff.String(), o) { @@ -404,7 +461,7 @@ func (b batchApi) CronJobs(namespace string) batchv1.CronJobInterface { } func (b batchApi) Jobs(namespace string) batchv1.JobInterface { - //TODO implement me + // TODO implement me panic("implement me") } @@ -463,3 +520,7 @@ var _ batchv1.CronJobInterface = &cronApi{} func boolP(b bool) *bool { return &b } + +func strP(s string) *string { + return &s +} -- 2.54.0 From 485261b462589b4bdc0012bd379a4eb9fc7e9b97 Mon Sep 17 00:00:00 2001 From: Joakim Olsson Date: Fri, 9 Oct 2026 09:22:57 +0200 Subject: [PATCH 2/3] fix(deps): update golang.org/x/net to v0.60.0 Fixes GO-2026-6603, GO-2026-6610, GO-2026-6611, GO-2026-6612 and GO-2026-6617 reported by govulncheck. Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_01C8SW6vsbkrTKjLNatQ8JTg --- go.mod | 8 ++++---- go.sum | 16 ++++++++-------- 2 files changed, 12 insertions(+), 12 deletions(-) diff --git a/go.mod b/go.mod index 4712318..cc64000 100644 --- a/go.mod +++ b/go.mod @@ -44,11 +44,11 @@ require ( github.com/xhit/go-str2duration/v2 v2.1.0 // indirect go.yaml.in/yaml/v2 v2.4.4 // indirect go.yaml.in/yaml/v3 v3.0.4 // indirect - golang.org/x/net v0.57.0 // indirect + golang.org/x/net v0.60.0 // indirect golang.org/x/oauth2 v0.36.0 // indirect - golang.org/x/sys v0.47.0 // indirect - golang.org/x/term v0.45.0 // indirect - golang.org/x/text v0.40.0 // indirect + golang.org/x/sys v0.48.0 // indirect + golang.org/x/term v0.46.0 // indirect + golang.org/x/text v0.42.0 // indirect golang.org/x/time v0.15.0 // indirect google.golang.org/protobuf v1.36.12-0.20260120151049-f2248ac996af // indirect gopkg.in/evanphx/json-patch.v4 v4.13.0 // indirect diff --git a/go.sum b/go.sum index 54fce3b..a04f372 100644 --- a/go.sum +++ b/go.sum @@ -98,16 +98,16 @@ go.yaml.in/yaml/v2 v2.4.4 h1:tuyd0P+2Ont/d6e2rl3be67goVK4R6deVxCUX5vyPaQ= go.yaml.in/yaml/v2 v2.4.4/go.mod h1:gMZqIpDtDqOfM0uNfy0SkpRhvUryYH0Z6wdMYcacYXQ= go.yaml.in/yaml/v3 v3.0.4 h1:tfq32ie2Jv2UxXFdLJdh3jXuOzWiL1fo0bu/FbuKpbc= go.yaml.in/yaml/v3 v3.0.4/go.mod h1:DhzuOOF2ATzADvBadXxruRBLzYTpT36CKvDb3+aBEFg= -golang.org/x/net v0.57.0 h1:K5+3DljvIuDG9/Jv9rvyMywYNFCQ9RSUY6OOTTkT+tE= -golang.org/x/net v0.57.0/go.mod h1:KpXc8iv+r3XplLAG/f7Jsf9RPszJzdR0f58q9vGOuEU= +golang.org/x/net v0.60.0 h1:79p50tfZlm0J9YfoDsSi639qSXNGVwEzOPLCxM2FsYU= +golang.org/x/net v0.60.0/go.mod h1:2DA/G1UfVbCpQPeWTmMPGY7Cs2PkBkwu743bVX5PIVg= golang.org/x/oauth2 v0.36.0 h1:peZ/1z27fi9hUOFCAZaHyrpWG5lwe0RJEEEeH0ThlIs= golang.org/x/oauth2 v0.36.0/go.mod h1:YDBUJMTkDnJS+A4BP4eZBjCqtokkg1hODuPjwiGPO7Q= -golang.org/x/sys v0.47.0 h1:o7XGOvZQCADBQQ4Y7VNq2dRWQR7JmOUW8Kxx4ZsNgWs= -golang.org/x/sys v0.47.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw= -golang.org/x/term v0.45.0 h1:NwWyBmoJCbfTHpxrWoZ9C6/VxOf7ic219I8xZZFdrf0= -golang.org/x/term v0.45.0/go.mod h1:9aqxs0blBcrm/n0L9QW0aRVD+ktan8ssZromtqJC43w= -golang.org/x/text v0.40.0 h1:Ub2Z6/xjgF1WrYQz2nuITOEegKFtiIy+rieRJ5lHZKs= -golang.org/x/text v0.40.0/go.mod h1:hpnzDAfGV753zIKo+wk3u1bVKCGPbrnF7+7LBF/UHVY= +golang.org/x/sys v0.48.0 h1:bbX/i/6MgT9BVLM9RT1thmxL04yeTAhbEz4SyadbXoo= +golang.org/x/sys v0.48.0/go.mod h1:hNLxWAXmnKAxqDtdwIYC4bM9oQPEecfsnNMuSxOs3og= +golang.org/x/term v0.46.0 h1:3+OXuTbaKDgwk8jTi3aSLHRlmWqHEUDUtxnbFigO4YE= +golang.org/x/term v0.46.0/go.mod h1:+K02xbkittuwc0Am4abfA3Fc+XRGXkvBXNO88NCXPoc= +golang.org/x/text v0.42.0 h1:JbOZXgfeCPU9gacVtYliJqOhD+zhrEqK4LfdpmlUZqI= +golang.org/x/text v0.42.0/go.mod h1:ojzP1Z+2QtioaF8DTtO8K5q7JWVVYwZKenzujK0Zd0E= golang.org/x/time v0.15.0 h1:bbrp8t3bGUeFOx08pvsMYRTCVSMk89u4tKbNOZbp88U= golang.org/x/time v0.15.0/go.mod h1:Y4YMaQmXwGQZoFaVFk4YpCt4FLQMYKZe9oeV/f4MSno= google.golang.org/protobuf v1.36.12-0.20260120151049-f2248ac996af h1:+5/Sw3GsDNlEmu7TfklWKPdQ0Ykja5VEmq2i817+jbI= -- 2.54.0 From 722b60debe115b75611949889857df52a4cbdbb8 Mon Sep 17 00:00:00 2001 From: Joakim Olsson Date: Fri, 9 Oct 2026 09:44:32 +0200 Subject: [PATCH 3/3] fix(deps): update go toolchain directive to v1.27.2 Fixes stdlib advisories GO-2026-6603, 6605, 6607, 6608, 6610, 6611, 6613 and 6617 (same change as Renovate PR #427). Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_01C8SW6vsbkrTKjLNatQ8JTg --- go.mod | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/go.mod b/go.mod index cc64000..051f096 100644 --- a/go.mod +++ b/go.mod @@ -2,7 +2,7 @@ module gitlab.com/unboundsoftware/cron-checker go 1.26.0 -toolchain go1.27.1 +toolchain go1.27.2 require ( github.com/alecthomas/kingpin/v2 v2.4.0 -- 2.54.0