Skip to content

Commit ccf7556

Browse files
committed
effectiveOn and effectiveUntil both set
Cover cases where they are both set.
1 parent ca42853 commit ccf7556

File tree

2 files changed

+173
-44
lines changed

2 files changed

+173
-44
lines changed

policy/lib/volatile_config.rego

Lines changed: 83 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -27,15 +27,15 @@ import data.lib.time as time_lib
2727
# Get configurable warning threshold from rule_data (default defined in rule_data_defaults)
2828
warning_threshold_days := rule_data("volatile_config_warning_threshold_days")
2929

30+
# Nanoseconds per day constant
31+
_ns_per_day := 86400000000000
32+
3033
# Calculate days until a rule expires (returns integer days, can be negative if expired)
3134
days_until_expiration(rule) := days if {
32-
effective_until := object.get(rule, "effectiveUntil", "")
33-
effective_until != ""
34-
until_ns := _parse_date_safe(effective_until)
35-
until_ns != null
35+
until_ns := _get_effective_until_ns(rule)
3636
now_ns := time_lib.effective_current_time_ns
3737
diff_ns := until_ns - now_ns
38-
days := floor(diff_ns / (((24 * 60) * 60) * 1000000000))
38+
days := floor(diff_ns / _ns_per_day)
3939
}
4040

4141
# Check if rule applies to current image/component
@@ -89,75 +89,114 @@ is_rule_applicable(rule, context) if {
8989

9090
# Determine warning category - check for invalid dates first
9191
warning_category(rule) := "invalid" if {
92-
effective_on := object.get(rule, "effectiveOn", "")
93-
effective_on != ""
94-
_parse_date_safe(effective_on) == null
92+
_is_date_invalid(_get_effective_on(rule))
9593
}
9694

9795
warning_category(rule) := "invalid" if {
98-
effective_until := object.get(rule, "effectiveUntil", "")
99-
effective_until != ""
100-
_parse_date_safe(effective_until) == null
96+
_is_date_invalid(_get_effective_until(rule))
10197
}
10298

10399
# Pending: effectiveOn is in the future
104100
warning_category(rule) := "pending" if {
105-
effective_on := object.get(rule, "effectiveOn", "")
106-
effective_on != ""
107-
on_ns := _parse_date_safe(effective_on)
108-
on_ns != null
109-
now_ns := time_lib.effective_current_time_ns
110-
on_ns > now_ns
101+
_is_effective_on_in_future(rule)
102+
_is_effective_until_valid_or_empty(rule)
111103
}
112104

113105
# Expired: effectiveUntil is in the past
114106
warning_category(rule) := "expired" if {
115-
effective_until := object.get(rule, "effectiveUntil", "")
116-
effective_until != ""
117-
until_ns := _parse_date_safe(effective_until)
118-
until_ns != null
119-
now_ns := time_lib.effective_current_time_ns
120-
until_ns < now_ns
107+
_is_effective_until_expired(rule)
108+
_is_effective_on_valid_and_not_future(rule)
121109
}
122110

123111
# Expiring: effectiveUntil is within the warning threshold
124112
warning_category(rule) := "expiring" if {
125-
effective_until := object.get(rule, "effectiveUntil", "")
126-
effective_until != ""
127-
until_ns := _parse_date_safe(effective_until)
128-
until_ns != null
129-
now_ns := time_lib.effective_current_time_ns
130-
until_ns >= now_ns # Not yet expired
131-
days := days_until_expiration(rule)
132-
days <= warning_threshold_days
113+
_is_effective_until_expiring(rule)
114+
_is_effective_on_valid_and_not_future(rule)
133115
}
134116

135117
# No expiration: rule is active (effectiveOn in past or not set) but has no effectiveUntil
136118
warning_category(rule) := "no_expiration" if {
137-
# No effectiveUntil date set
138-
object.get(rule, "effectiveUntil", "") == ""
139-
140-
# And not pending (effectiveOn is in the past or not set)
141-
effective_on := object.get(rule, "effectiveOn", "")
142-
_is_active_or_unset(effective_on)
119+
_get_effective_until(rule) == ""
120+
_is_effective_on_active_or_unset(rule)
143121
}
144122

145-
# Helper: safely parse RFC3339 date, returns null on failure
123+
# =============================================================================
124+
# Helper functions for date extraction and validation
125+
# =============================================================================
126+
127+
# Extract effectiveOn date string from rule
128+
_get_effective_on(rule) := object.get(rule, "effectiveOn", "")
129+
130+
# Extract effectiveUntil date string from rule
131+
_get_effective_until(rule) := object.get(rule, "effectiveUntil", "")
132+
133+
# Safely parse RFC3339 date, undefined on failure
146134
_parse_date_safe(date_str) := ns if {
147135
date_str != ""
148136
ns := time.parse_rfc3339_ns(date_str)
149-
} else := null
137+
}
138+
139+
# Check if a date string is invalid (non-empty but unparseable)
140+
# Empty strings are considered valid (not set, not invalid)
141+
_is_date_invalid(date_str) if {
142+
date_str != ""
143+
not _parse_date_safe(date_str)
144+
}
145+
146+
# Get effectiveOn as nanoseconds, undefined if invalid or empty
147+
_get_effective_on_ns(rule) := _parse_date_safe(_get_effective_on(rule))
148+
149+
# Get effectiveUntil as nanoseconds, undefined if invalid or empty
150+
_get_effective_until_ns(rule) := _parse_date_safe(_get_effective_until(rule))
150151

151-
# Helper: check if effectiveOn is active (in the past) or not set
152-
_is_active_or_unset(effective_on) if {
153-
effective_on == ""
152+
# Check if effectiveOn is in the future
153+
_is_effective_on_in_future(rule) if {
154+
on_ns := _get_effective_on_ns(rule)
155+
now_ns := time_lib.effective_current_time_ns
156+
on_ns > now_ns
157+
}
158+
159+
# Check if effectiveOn is active (in the past) or not set
160+
_is_effective_on_active_or_unset(rule) if {
161+
_get_effective_on(rule) == ""
162+
} else if {
163+
on_ns := _get_effective_on_ns(rule)
164+
now_ns := time_lib.effective_current_time_ns
165+
on_ns <= now_ns
166+
}
167+
168+
# Check if effectiveOn is valid (if set) and not in the future
169+
_is_effective_on_valid_and_not_future(rule) if {
170+
_get_effective_on(rule) == ""
154171
} else if {
155-
on_ns := _parse_date_safe(effective_on)
156-
on_ns != null
172+
on_ns := _get_effective_on_ns(rule)
157173
now_ns := time_lib.effective_current_time_ns
158174
on_ns <= now_ns
159175
}
160176

177+
# Check if effectiveUntil is valid (if set) or empty
178+
_is_effective_until_valid_or_empty(rule) if {
179+
_get_effective_until(rule) == ""
180+
} else if {
181+
_get_effective_until_ns(rule)
182+
}
183+
184+
# Check if effectiveUntil is expired (in the past)
185+
_is_effective_until_expired(rule) if {
186+
until_ns := _get_effective_until_ns(rule)
187+
now_ns := time_lib.effective_current_time_ns
188+
until_ns < now_ns
189+
}
190+
191+
# Check if effectiveUntil is expiring (within warning threshold)
192+
_is_effective_until_expiring(rule) if {
193+
until_ns := _get_effective_until_ns(rule)
194+
now_ns := time_lib.effective_current_time_ns
195+
until_ns >= now_ns
196+
days := days_until_expiration(rule)
197+
days <= warning_threshold_days
198+
}
199+
161200
# Helper: check if imageUrl matches the image reference
162201
# imageUrl is a URL pattern without tag (e.g., "quay.io/redhat/myimage")
163202
# image_ref may include tag and/or digest (e.g., "quay.io/redhat/myimage:v1@sha256:...")

policy/lib/volatile_config_test.rego

Lines changed: 90 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -245,6 +245,44 @@ test_warning_category_invalid_effective_until if {
245245
lib.assert_equal(lib.warning_category(rule), "invalid") with time_lib.effective_current_time_ns as _now_ns
246246
}
247247

248+
test_warning_category_empty_strings_not_invalid if {
249+
# Empty strings should not be considered invalid (they're just not set)
250+
rule := {"value": "some.rule", "effectiveOn": "", "effectiveUntil": ""}
251+
252+
# Should not be "invalid" - empty strings are valid (not set)
253+
not lib.warning_category(rule) == "invalid" with time_lib.effective_current_time_ns as _now_ns
254+
}
255+
256+
test_warning_category_invalid_on_valid_until if {
257+
# effectiveOn invalid, effectiveUntil valid - should be "invalid"
258+
rule := {
259+
"value": "some.rule",
260+
"effectiveOn": "not-a-date",
261+
"effectiveUntil": "2024-06-25T12:00:00Z",
262+
}
263+
lib.assert_equal(lib.warning_category(rule), "invalid") with time_lib.effective_current_time_ns as _now_ns
264+
}
265+
266+
test_warning_category_valid_on_invalid_until if {
267+
# effectiveOn valid, effectiveUntil invalid - should be "invalid"
268+
rule := {
269+
"value": "some.rule",
270+
"effectiveOn": "2024-06-01T12:00:00Z",
271+
"effectiveUntil": "not-a-date",
272+
}
273+
lib.assert_equal(lib.warning_category(rule), "invalid") with time_lib.effective_current_time_ns as _now_ns
274+
}
275+
276+
test_warning_category_both_dates_invalid if {
277+
# Both dates invalid - should be "invalid"
278+
rule := {
279+
"value": "some.rule",
280+
"effectiveOn": "not-a-date",
281+
"effectiveUntil": "also-not-a-date",
282+
}
283+
lib.assert_equal(lib.warning_category(rule), "invalid") with time_lib.effective_current_time_ns as _now_ns
284+
}
285+
248286
# =============================================================================
249287
# warning_category tests - pending
250288
# =============================================================================
@@ -345,3 +383,55 @@ test_warning_category_with_both_dates_active_and_expiring if {
345383
}
346384
lib.assert_equal(lib.warning_category(rule), "expiring") with time_lib.effective_current_time_ns as _now_ns
347385
}
386+
387+
test_warning_category_pending_with_expired_until if {
388+
# effectiveOn in future (pending takes precedence), effectiveUntil in past
389+
rule := {
390+
"value": "some.rule",
391+
"effectiveOn": "2024-07-15T12:00:00Z",
392+
"effectiveUntil": "2024-06-05T12:00:00Z",
393+
}
394+
lib.assert_equal(lib.warning_category(rule), "pending") with time_lib.effective_current_time_ns as _now_ns
395+
}
396+
397+
test_warning_category_pending_with_expiring_until if {
398+
# effectiveOn in future (pending takes precedence), effectiveUntil expiring
399+
rule := {
400+
"value": "some.rule",
401+
"effectiveOn": "2024-07-15T12:00:00Z",
402+
"effectiveUntil": "2024-06-25T12:00:00Z",
403+
}
404+
lib.assert_equal(lib.warning_category(rule), "pending") with time_lib.effective_current_time_ns as _now_ns
405+
}
406+
407+
test_warning_category_pending_with_future_until_beyond_threshold if {
408+
# effectiveOn in future (pending takes precedence), effectiveUntil beyond threshold
409+
rule := {
410+
"value": "some.rule",
411+
"effectiveOn": "2024-07-15T12:00:00Z",
412+
"effectiveUntil": "2024-08-14T12:00:00Z",
413+
}
414+
lib.assert_equal(lib.warning_category(rule), "pending") with time_lib.effective_current_time_ns as _now_ns
415+
}
416+
417+
test_warning_category_active_with_expired_until if {
418+
# effectiveOn in past, effectiveUntil in past
419+
rule := {
420+
"value": "some.rule",
421+
"effectiveOn": "2024-06-01T12:00:00Z",
422+
"effectiveUntil": "2024-06-05T12:00:00Z",
423+
}
424+
lib.assert_equal(lib.warning_category(rule), "expired") with time_lib.effective_current_time_ns as _now_ns
425+
}
426+
427+
test_warning_category_active_with_future_until_beyond_threshold if {
428+
# effectiveOn in past, effectiveUntil beyond threshold (no warning)
429+
rule := {
430+
"value": "some.rule",
431+
"effectiveOn": "2024-06-01T12:00:00Z",
432+
"effectiveUntil": "2024-08-14T12:00:00Z",
433+
}
434+
435+
# Should not produce a category (no warning needed)
436+
not lib.warning_category(rule) with time_lib.effective_current_time_ns as _now_ns
437+
}

0 commit comments

Comments
 (0)