7
7
import re
8
8
import subprocess
9
9
import sys
10
+ import time
10
11
import typing
12
+ import urllib .error
11
13
import urllib .request
12
14
from pathlib import Path ,PurePosixPath ,PureWindowsPath
13
15
@@ -161,6 +163,21 @@ def get_externals() -> list[str]:
161
163
return externals
162
164
163
165
166
+ def download_with_retries (download_location :str ,
167
+ max_retries :int = 5 ,
168
+ base_delay :float = 2.0 )-> typing .Any :
169
+ """Download a file with exponential backoff retry."""
170
+ for attempt in range (max_retries ):
171
+ try :
172
+ resp = urllib .request .urlopen (download_location )
173
+ except urllib .error .URLError as ex :
174
+ if attempt == max_retries :
175
+ raise ex
176
+ time .sleep (base_delay ** attempt )
177
+ else :
178
+ return resp
179
+
180
+
164
181
def check_sbom_packages (sbom_data :dict [str ,typing .Any ])-> None :
165
182
"""Make a bunch of assertions about the SBOM package data to ensure it's consistent."""
166
183
@@ -175,7 +192,7 @@ def check_sbom_packages(sbom_data: dict[str, typing.Any]) -> None:
175
192
# and that the download URL is valid.
176
193
if "checksums" not in package or "CI" in os .environ :
177
194
download_location = package ["downloadLocation" ]
178
- resp = urllib . request . urlopen (download_location )
195
+ resp = download_with_retries (download_location )
179
196
error_if (resp .status != 200 ,f"Couldn't access URL:{ download_location } '" )
180
197
181
198
package ["checksums" ]= [{