@@ -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)
2828warning_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)
3134days_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
9191warning_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
9795warning_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
104100warning_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
114106warning_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
124112warning_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
136118warning_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:...")
0 commit comments