Skip to content

Commit c98c5c0

Browse files
authored
✨ enhance: update garbage collection logic to use schedule-based thresholds and add tests for schedule parsing (#1437)
1 parent 4c83f88 commit c98c5c0

6 files changed

Lines changed: 84 additions & 9 deletions

File tree

controllers/k8s_scan/deployment_handler.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -614,7 +614,7 @@ func (n *DeploymentHandler) performGarbageCollection(ctx context.Context, manage
614614
ManagedBy: managedBy,
615615
PlatformRuntime: "k8s-cluster",
616616
DateFilter: &mondooclient.DateFilter{
617-
Timestamp: time.Now().Add(-mondoo.GCOlderThan()).Format(time.RFC3339),
617+
Timestamp: time.Now().Add(-mondoo.GCOlderThan(n.Mondoo.Spec.KubernetesResources.Schedule)).Format(time.RFC3339),
618618
Comparison: mondooclient.Comparison_LESS_THAN,
619619
Field: mondooclient.DateFilterField_FILTER_LAST_UPDATED,
620620
},

controllers/nodes/deployment_handler.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -441,7 +441,7 @@ func (n *DeploymentHandler) performGarbageCollection(ctx context.Context, manage
441441
req := &mondooclient.DeleteAssetsRequest{
442442
ManagedBy: managedBy,
443443
DateFilter: &mondooclient.DateFilter{
444-
Timestamp: time.Now().Add(-mondoo.GCOlderThan()).Format(time.RFC3339),
444+
Timestamp: time.Now().Add(-mondoo.GCOlderThan(n.Mondoo.Spec.Nodes.Schedule)).Format(time.RFC3339),
445445
Comparison: mondooclient.Comparison_LESS_THAN,
446446
Field: mondooclient.DateFilterField_FILTER_LAST_UPDATED,
447447
},

go.mod

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ require (
2323

2424
require (
2525
github.com/hashicorp/vault-client-go v0.4.3
26+
github.com/robfig/cron/v3 v3.0.1
2627
go.mondoo.com/mql/v13 v13.1.1
2728
go.mondoo.com/mql/v13/providers/k8s v0.0.0-00010101000000-000000000000
2829
)

go.sum

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -547,6 +547,8 @@ github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec h1:W09IVJc94
547547
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo=
548548
github.com/rivo/uniseg v0.4.7 h1:WUdvkW8uEhrYfLC4ZzdpI2ztxP1I582+49Oc5Mq64VQ=
549549
github.com/rivo/uniseg v0.4.7/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88=
550+
github.com/robfig/cron/v3 v3.0.1 h1:WdRxkvbJztn8LMz/QEvLN5sBU+xKpSqwwUO1Pjr4qDs=
551+
github.com/robfig/cron/v3 v3.0.1/go.mod h1:eQICP3HwyT7UooqI/z+Ov+PtYAWygg1TEWWzGIFLtro=
550552
github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs=
551553
github.com/rogpeppe/go-internal v1.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0tI/otEQ=
552554
github.com/rogpeppe/go-internal v1.14.1/go.mod h1:MaRKkUm5W0goXpeCfT7UZI6fk/L7L7so1lCWt35ZSgc=

pkg/utils/mondoo/gc.go

Lines changed: 33 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ import (
1111
"time"
1212

1313
"github.com/go-logr/logr"
14+
"github.com/robfig/cron/v3"
1415
corev1 "k8s.io/api/core/v1"
1516
"sigs.k8s.io/controller-runtime/pkg/client"
1617

@@ -19,7 +20,10 @@ import (
1920
"go.mondoo.com/mondoo-operator/pkg/constants"
2021
)
2122

22-
const defaultGCOlderThan = 2 * time.Hour
23+
const (
24+
defaultGCOlderThan = 2 * time.Hour
25+
gcMultiplier = 2
26+
)
2327

2428
// ManagedByLabel returns the ManagedBy value for assets owned by this operator instance.
2529
// The cluster UID uniquely identifies which operator instance manages the assets.
@@ -30,19 +34,41 @@ func ManagedByLabel(clusterUID string) string {
3034
return "mondoo-operator-" + clusterUID
3135
}
3236

33-
// GCOlderThan returns the duration threshold for garbage collection.
34-
// It defaults to 2h but can be overridden via the MONDOO_GC_OLDER_THAN env var
35-
// (accepts any value parseable by time.ParseDuration, e.g. "5m", "30s").
36-
func GCOlderThan() time.Duration {
37+
// GCOlderThan returns the duration threshold for garbage collection based on
38+
// the scan schedule. It computes 2x the interval between consecutive cron runs
39+
// so that assets are only GC'd after missing at least one full scan cycle.
40+
// The MONDOO_GC_OLDER_THAN env var overrides the computed value.
41+
func GCOlderThan(schedule string) time.Duration {
3742
if v := os.Getenv("MONDOO_GC_OLDER_THAN"); v != "" {
3843
d, err := time.ParseDuration(v)
3944
if err != nil {
40-
fmt.Fprintf(os.Stderr, "WARNING: invalid MONDOO_GC_OLDER_THAN=%q, using default %s: %v\n", v, defaultGCOlderThan, err)
45+
fmt.Fprintf(os.Stderr, "WARNING: invalid MONDOO_GC_OLDER_THAN=%q, falling back to schedule-based computation: %v\n", v, err)
4146
} else {
4247
return d
4348
}
4449
}
45-
return defaultGCOlderThan
50+
return gcOlderThanFromSchedule(schedule)
51+
}
52+
53+
// gcOlderThanFromSchedule parses a cron schedule and returns 2x the interval
54+
// between consecutive runs. Falls back to defaultGCOlderThan if parsing fails.
55+
func gcOlderThanFromSchedule(schedule string) time.Duration {
56+
if schedule == "" {
57+
return defaultGCOlderThan
58+
}
59+
60+
sched, err := cron.ParseStandard(schedule)
61+
if err != nil {
62+
fmt.Fprintf(os.Stderr, "WARNING: failed to parse cron schedule %q, using default %s: %v\n", schedule, defaultGCOlderThan, err)
63+
return defaultGCOlderThan
64+
}
65+
66+
now := time.Now()
67+
next1 := sched.Next(now)
68+
next2 := sched.Next(next1)
69+
interval := next2.Sub(next1)
70+
71+
return time.Duration(gcMultiplier) * interval
4672
}
4773

4874
// DeleteStaleAssets builds a Mondoo API client from the operator's credentials and

pkg/utils/mondoo/gc_test.go

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,10 +5,56 @@ package mondoo
55

66
import (
77
"testing"
8+
"time"
89

910
"github.com/stretchr/testify/assert"
1011
)
1112

13+
func TestGCOlderThanFromSchedule(t *testing.T) {
14+
tests := []struct {
15+
name string
16+
schedule string
17+
expect time.Duration
18+
}{
19+
{
20+
name: "hourly schedule",
21+
schedule: "30 * * * *",
22+
expect: 2 * time.Hour,
23+
},
24+
{
25+
name: "every 6 hours",
26+
schedule: "0 */6 * * *",
27+
expect: 12 * time.Hour,
28+
},
29+
{
30+
name: "daily schedule",
31+
schedule: "0 2 * * *",
32+
expect: 48 * time.Hour,
33+
},
34+
{
35+
name: "every 15 minutes",
36+
schedule: "*/15 * * * *",
37+
expect: 30 * time.Minute,
38+
},
39+
{
40+
name: "empty schedule falls back to default",
41+
schedule: "",
42+
expect: defaultGCOlderThan,
43+
},
44+
{
45+
name: "invalid schedule falls back to default",
46+
schedule: "not-a-cron",
47+
expect: defaultGCOlderThan,
48+
},
49+
}
50+
51+
for _, tt := range tests {
52+
t.Run(tt.name, func(t *testing.T) {
53+
assert.Equal(t, tt.expect, gcOlderThanFromSchedule(tt.schedule))
54+
})
55+
}
56+
}
57+
1258
func TestSpaceMrnFromServiceAccountMrn(t *testing.T) {
1359
tests := []struct {
1460
name string

0 commit comments

Comments
 (0)