11
11
PROJECT_ROOT = pathlib .Path (__file__ ).parent .parent
12
12
13
13
14
- async def get_image_vcs_ref (image_url : str ) -> tuple [str , str | None ]:
14
+ async def get_image_vcs_ref (image_url : str , semaphore : asyncio . Semaphore ) -> tuple [str , str | None ]:
15
15
"""
16
16
Asynchronously inspects a container image's configuration using skopeo
17
17
and extracts the 'vcs-ref' label.
@@ -32,21 +32,29 @@ async def get_image_vcs_ref(image_url: str) -> tuple[str, str | None]:
32
32
33
33
logging .info (f"Starting config inspection for: { image_url } " )
34
34
35
+ stdout , stderr , returncode = None , None , None
35
36
try :
36
- # Create an asynchronous subprocess
37
- process = await asyncio .create_subprocess_exec (
38
- * command ,
39
- stdout = asyncio .subprocess .PIPE ,
40
- stderr = asyncio .subprocess .PIPE
41
- )
42
-
43
- # Wait for the command to complete and capture output
44
- stdout , stderr = await process .communicate ()
45
-
46
- # Check for errors
47
- if process .returncode != 0 :
48
- logging .error (f"Skopeo command failed for { image_url } with exit code { process .returncode } ." )
49
- logging .error (f"Stderr: { stderr .decode ().strip ()} " )
37
+ async with semaphore :
38
+ logging .info (f"Semaphore acquired, starting skopeo inspect for: { image_url } " )
39
+ # Create an asynchronous subprocess
40
+ process = await asyncio .create_subprocess_exec (
41
+ * command ,
42
+ stdout = asyncio .subprocess .PIPE ,
43
+ stderr = asyncio .subprocess .PIPE
44
+ )
45
+ # Wait for the command to complete and capture output
46
+ stdout , stderr = await process .communicate ()
47
+ returncode = process .returncode
48
+
49
+ # Process the results outside the semaphore block
50
+ if returncode != 0 :
51
+ logging .error (f"Skopeo command failed for { image_url } with exit code { returncode } ." )
52
+ if stderr :
53
+ logging .error (f"Stderr: { stderr .decode ().strip ()} " )
54
+ return image_url , None
55
+
56
+ if not stdout :
57
+ logging .error (f"Skopeo command returned success but stdout was empty for { image_url } ." )
50
58
return image_url , None
51
59
52
60
# Decode and parse the JSON output from stdout
@@ -67,7 +75,10 @@ async def get_image_vcs_ref(image_url: str) -> tuple[str, str | None]:
67
75
logging .error ("The 'skopeo' command was not found. Please ensure it is installed and in your PATH." )
68
76
return image_url , None
69
77
except json .JSONDecodeError :
78
+ # This error can now also happen if stdout is None or not valid JSON
70
79
logging .error (f"Failed to parse skopeo output as JSON for { image_url } ." )
80
+ if stdout :
81
+ logging .debug (f"Stdout from skopeo for { image_url } : { stdout .decode (errors = 'replace' )} " )
71
82
return image_url , None
72
83
except Exception as e :
73
84
logging .error (f"An unexpected error occurred while processing { image_url } : { e } " )
@@ -78,7 +89,8 @@ async def inspect(images_to_inspect: typing.Iterable[str]) -> list[tuple[str, st
78
89
"""
79
90
Main function to orchestrate the concurrent inspection of multiple images.
80
91
"""
81
- tasks = [get_image_vcs_ref (image ) for image in images_to_inspect ]
92
+ semaphore = asyncio .Semaphore (22 ) # Limit concurrent skopeo processes
93
+ tasks = [get_image_vcs_ref (image , semaphore ) for image in images_to_inspect ]
82
94
return await asyncio .gather (* tasks )
83
95
84
96
0 commit comments