2727
2828description:
2929 - Reads the YAML produced by the env_op_images pulled-images report role task.
30- - "Parses CRI-O journal lines for C(msg=\"Pulled image: ...@sha256:...\")."
30+ - "Parses CRI-O journal lines for both C(msg=\"Trying to access\") and
31+ C(msg=\"Pulled image: ...@sha256:...\") entries."
32+ - "CRI-O always reports the canonical source registry in C(Pulled image)
33+ lines even when the image was fetched from a mirror. The
34+ C(Trying to access) lines show which registries CRI-O actually
35+ contacted. This module uses the last C(Trying to access) URI for
36+ a given digest as the authoritative pull-source evidence."
3137 - Adds per-row verification fields using trusted mirror domains from
3238 C(summary.mirror_rules).
3339 - When images carry a C(node) field, evidence is matched against the
105111 returned: always
106112nodes_with_evidence:
107113 description: >-
108- Node names that had at least one C(Pulled image) log entry.
114+ Node names that had at least one C(Trying to access) or
115+ C(Pulled image) log entry.
109116 type: list
110117 elements: str
111118 returned: always
118125import yaml
119126from ansible .module_utils .basic import AnsibleModule
120127
121- LOG_PATTERN = re .compile (
122- r'msg="Pulled image: (?P<actual_uri>[^@\s]+)@(?P<id>sha256:[a-f0-9]+)"'
128+ PULLED_PATTERN = re .compile (
129+ r'msg="Pulled image: (?P<pulled_uri>[^@\s]+)@(?P<id>sha256:[a-f0-9]+)"'
130+ )
131+ TRYING_PATTERN = re .compile (
132+ r'msg="Trying to access \\"(?P<tried_uri>[^@\s]+)@(?P<id>sha256:[a-f0-9]+)\\"'
123133)
124134SHA256_PATTERN = re .compile (r"sha256:[a-f0-9]+" )
125135
@@ -138,24 +148,58 @@ def _domain_from_uri(uri):
138148 return uri .split ("/" )[0 ].strip ()
139149
140150
141- def _apply_evidence (img , actual_uri , evidence_node , trusted_mirrors ):
142- """Set common verification fields on an image row that has log evidence."""
143- actual_domain = _domain_from_uri (actual_uri )
144- img ["node_verified_image_origin" ] = (
145- "mirror" if actual_domain in trusted_mirrors else "source"
146- )
147- img ["log_evidence_uri" ] = actual_uri
151+ def _apply_evidence (img , evidence , evidence_node , trusted_mirrors ):
152+ """Set verification fields on an image row that has log evidence.
153+
154+ The *evidence* dict carries ``tried_uri`` (from "Trying to access") and/or
155+ ``pulled_uri`` (from "Pulled image"). The tried URI is authoritative for
156+ determining origin because CRI-O always reports the canonical source name
157+ in "Pulled image" even when it fetched from a mirror.
158+
159+ When only ``tried_uri`` exists without a ``pulled_uri``, the pull was
160+ attempted but never confirmed successful -- origin is ``pull_failed``.
161+ """
162+ tried_uri = evidence .get ("tried_uri" )
163+ pulled_uri = evidence .get ("pulled_uri" )
164+
165+ if tried_uri and not pulled_uri :
166+ img ["node_verified_image_origin" ] = "pull_failed"
167+ img ["verification_reason" ] = "CRI-O tried registries but pull never succeeded"
168+ img ["image_fetched_from" ] = None
169+ img ["image_canonical_name" ] = None
170+ img ["log_evidence_node" ] = evidence_node
171+ return None
172+
173+ authoritative_uri = tried_uri or pulled_uri
174+ actual_domain = _domain_from_uri (authoritative_uri )
175+ origin = "mirror" if actual_domain in trusted_mirrors else "source"
176+ img ["node_verified_image_origin" ] = origin
177+
178+ if tried_uri :
179+ img ["verification_reason" ] = "CRI-O contacted {0} and pull succeeded" .format (
180+ origin
181+ )
182+ else :
183+ img ["verification_reason" ] = (
184+ "Pull confirmed via Pulled image log only"
185+ " (no registry contact attempt logged)"
186+ )
187+
188+ img ["image_fetched_from" ] = tried_uri
189+ img ["image_canonical_name" ] = pulled_uri
148190 img ["log_evidence_node" ] = evidence_node
149- return actual_domain
150191
151192
152193def _collect_log_evidence (paths , module ):
153194 """Parse CRI-O logs into per-node and global evidence dicts.
154195
196+ Each digest maps to ``{"tried_uri": ..., "pulled_uri": ...}``.
197+ "Trying to access" is the authoritative source for which registry was
198+ contacted; "Pulled image" always carries the canonical name.
199+
155200 Returns:
156- per_node: ``{node_name: {sha256_digest: pull_uri}}``
157- global_evidence: ``{sha256_digest: (pull_uri, node_name)}``
158- (last writer wins across nodes for the global dict)
201+ per_node: ``{node_name: {digest: {"tried_uri": str|None, "pulled_uri": str|None}}}``
202+ global_evidence: ``{digest: (evidence_dict, node_name)}``
159203 """
160204 per_node = {}
161205 global_evidence = {}
@@ -165,12 +209,33 @@ def _collect_log_evidence(paths, module):
165209 try :
166210 with open (path , "r" ) as f :
167211 for line in f :
168- match = LOG_PATTERN .search (line )
169- if match :
170- digest = match .group ("id" )
171- uri = match .group ("actual_uri" )
172- node_ev [digest ] = uri
173- global_evidence [digest ] = (uri , node )
212+ trying = TRYING_PATTERN .search (line )
213+ if trying :
214+ digest = trying .group ("id" )
215+ node_entry = node_ev .setdefault (
216+ digest , {"tried_uri" : None , "pulled_uri" : None }
217+ )
218+ node_entry ["tried_uri" ] = trying .group ("tried_uri" )
219+ global_entry = global_evidence .setdefault (
220+ digest , [{"tried_uri" : None , "pulled_uri" : None }, node ]
221+ )
222+ global_entry [0 ]["tried_uri" ] = trying .group ("tried_uri" )
223+ global_entry [1 ] = node
224+ continue
225+
226+ pulled = PULLED_PATTERN .search (line )
227+ if pulled :
228+ digest = pulled .group ("id" )
229+ node_entry = node_ev .setdefault (
230+ digest , {"tried_uri" : None , "pulled_uri" : None }
231+ )
232+ node_entry ["pulled_uri" ] = pulled .group ("pulled_uri" )
233+ global_entry = global_evidence .setdefault (
234+ digest , [{"tried_uri" : None , "pulled_uri" : None }, node ]
235+ )
236+ global_entry [0 ]["pulled_uri" ] = pulled .group ("pulled_uri" )
237+ if global_entry [1 ] is None :
238+ global_entry [1 ] = node
174239 except IOError as exc :
175240 module .fail_json (
176241 msg = "Cannot read CRI-O log file {0}: {1}" .format (path , str (exc ))
@@ -251,16 +316,18 @@ def run_module():
251316 )
252317
253318 if node_local_hit :
254- actual_uri = per_node_evidence [img_node ][img_sha ]
255- _apply_evidence (img , actual_uri , img_node , trusted_mirrors )
319+ evidence = per_node_evidence [img_node ][img_sha ]
320+ _apply_evidence (img , evidence , img_node , trusted_mirrors )
256321 elif img_sha in global_evidence :
257- actual_uri , evidence_node = global_evidence [img_sha ]
258- _apply_evidence (img , actual_uri , evidence_node , trusted_mirrors )
322+ evidence , evidence_node = global_evidence [img_sha ]
323+ _apply_evidence (img , evidence , evidence_node , trusted_mirrors )
259324 if img_node :
260325 cross_node_entries += 1
261326 else :
262327 img ["node_verified_image_origin" ] = "cached/unknown"
263- img ["log_evidence_uri" ] = None
328+ img ["verification_reason" ] = "No CRI-O log evidence found for this digest"
329+ img ["image_fetched_from" ] = None
330+ img ["image_canonical_name" ] = None
264331 img ["log_evidence_node" ] = None
265332
266333 nodes_with_evidence = sorted (n for n , ev in per_node_evidence .items () if ev )
0 commit comments