Skip to content

Commit 9df64f1

Browse files
committed
utils: improve bump-gns3.py error handling and usage text
Split download and MD5 computation into separate functions for clarity. Add proper error handling for HTTP 404 and network errors. Improve help text to mention appliances/ directory location. [skip ci] Signed-off-by: Joachim Wiberg <[email protected]>
1 parent 4b3ee0f commit 9df64f1

File tree

1 file changed

+55
-10
lines changed

1 file changed

+55
-10
lines changed

utils/bump-gns3.py

Lines changed: 55 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
import re
66
import shutil
77
import sys
8+
import urllib.error
89
import urllib.request
910
from datetime import datetime
1011

@@ -16,30 +17,63 @@ def check_version_format(ver):
1617
sys.exit("Error: version must be full form like 25.08.0")
1718
return ver
1819

19-
def compute_md5_and_size(url):
20-
md5 = hashlib.md5()
21-
size = 0
20+
def download_file(url):
21+
"""Download file from URL, yielding chunks.
22+
23+
Args:
24+
url: URL of the file to download
25+
26+
Yields:
27+
bytes: 1MB chunks of the file
28+
29+
Raises:
30+
urllib.error.HTTPError: If the URL returns an error (e.g., 404)
31+
urllib.error.URLError: If there's a network error
32+
"""
2233
with urllib.request.urlopen(url) as resp:
2334
while True:
2435
chunk = resp.read(1024 * 1024)
2536
if not chunk:
2637
break
27-
md5.update(chunk)
28-
size += len(chunk)
38+
yield chunk
39+
40+
def compute_md5_and_size(chunks):
41+
"""Compute MD5 hash and total size from data chunks.
42+
43+
Args:
44+
chunks: Iterable of byte chunks
45+
46+
Returns:
47+
tuple: (md5_hexdigest, size_in_bytes)
48+
"""
49+
md5 = hashlib.md5()
50+
size = 0
51+
for chunk in chunks:
52+
md5.update(chunk)
53+
size += len(chunk)
2954
return md5.hexdigest(), size
3055

3156
def main():
32-
parser = argparse.ArgumentParser(description="Add a new Infix version to a GNS3 appliance file")
57+
parser = argparse.ArgumentParser(
58+
description="Add a new Infix version to a GNS3 appliance file",
59+
epilog="The .gns3a appliance files are typically found in the appliances/ "
60+
"directory of the gns3-registry project."
61+
)
3362
parser.add_argument("version", help="Infix version (e.g. 25.08.0)")
34-
parser.add_argument("appliance", help="Path to appliance JSON (.gns3a)")
63+
parser.add_argument("appliance", help="Path to appliance JSON file (.gns3a)")
3564
args = parser.parse_args()
3665

3766
version = check_version_format(args.version)
3867
appliance_path = args.appliance
3968

4069
# Load JSON
41-
with open(appliance_path, "r", encoding="utf-8") as f:
42-
data = json.load(f)
70+
try:
71+
with open(appliance_path, "r", encoding="utf-8") as f:
72+
data = json.load(f)
73+
except FileNotFoundError:
74+
sys.exit(f"Error: Appliance file not found: {appliance_path}")
75+
except json.JSONDecodeError as e:
76+
sys.exit(f"Error: Invalid JSON in appliance file: {e}")
4377

4478
# Skip if version already exists
4579
for v in data.get("versions", []):
@@ -52,7 +86,18 @@ def main():
5286
url = f"{REPO}/releases/download/v{version}/{filename}"
5387

5488
print(f"Downloading {url} to compute MD5 and size...")
55-
md5sum, size = compute_md5_and_size(url)
89+
try:
90+
chunks = download_file(url)
91+
md5sum, size = compute_md5_and_size(chunks)
92+
except urllib.error.HTTPError as e:
93+
if e.code == 404:
94+
sys.exit(f"Error: Version {version} not found. "
95+
f"The release v{version} does not exist or the disk image is not available.\n"
96+
f"URL: {url}")
97+
else:
98+
sys.exit(f"Error: HTTP {e.code} when downloading: {e.reason}\nURL: {url}")
99+
except urllib.error.URLError as e:
100+
sys.exit(f"Error: Network error while downloading: {e.reason}\nURL: {url}")
56101

57102
# Add image entry
58103
image_entry = {

0 commit comments

Comments
 (0)