From 3e77a871e6c801d6eeb8cb1436d0f852a108813b Mon Sep 17 00:00:00 2001 From: Kyr Shatskyy Date: Thu, 14 Aug 2025 22:43:03 +0200 Subject: [PATCH] discover: add FedoraHandler Signed-off-by: Kyr Shatskyy --- downburst/discover.py | 111 +++++++++++++++++++++++++++++++++++++++++- 1 file changed, 110 insertions(+), 1 deletion(-) diff --git a/downburst/discover.py b/downburst/discover.py index 43d04d9..f97e0dc 100644 --- a/downburst/discover.py +++ b/downburst/discover.py @@ -57,6 +57,30 @@ def handle_starttag(self, tag, attrs): self.urls.append(val) +class FedoraImageParser(HTMLParser): + def __init__(self): + self.urls = [] + HTMLParser.__init__(self) + + def handle_starttag(self, tag, attrs): + if tag == 'a': + for key, val in attrs: + if key == 'href' and val.endswith('.qcow2') and 'Cloud-Base-Generic' in val: + self.urls.append(val) + + +class FedoraChecksumFileParser(HTMLParser): + def __init__(self): + self.urls = [] + HTMLParser.__init__(self) + + def handle_starttag(self, tag, attrs): + if tag == 'a': + for key, val in attrs: + if key == 'href' and val.endswith('-CHECKSUM'): + self.urls.append(val) + + class ReleaseParser(HTMLParser): def __init__(self): self.dirs = [] @@ -150,6 +174,23 @@ def handle_starttag(self, tag, attrs): continue self.versions.append(f'{ver}.stream') +class FedoraVersionParser(HTMLParser): + def __init__(self): + self.versions = [] + HTMLParser.__init__(self) + def handle_starttag(self, tag, attrs): + if tag == 'a': + r = re.compile(r'^([0-9]{1,2})/') + for key, val in attrs: + if key == 'href': + res = r.search(val) + if res: + ver = res.group(1) + # Skip everything before 41 version + if int(ver) < 41: + continue + self.versions.append(f'{ver}') + class DistroHandler: def get_releases(self) -> dict[str, str]: log.error(f"Method 'get_releases' is undefined for class {self.__class__.__name__}") @@ -314,6 +355,74 @@ def __call__(self, distroversion, arch): return {'url': url, 'serial': serial, 'checksum': sha256, 'hash_function': 'sha256'} + +class FedoraHandler(DistroHandler): + URL="https://download.fedoraproject.org" + + def get_releases(self) -> dict[str, str]: + url = f"{self.URL}/pub/fedora/linux/releases/" + log.debug(f"Lookup for Fedora releases by url {url}") + r = requests.get(url) + r.raise_for_status() + parser = FedoraVersionParser() + parser.feed(r.content.decode()) + parser.close() + log.debug(f"Fedora versions: {parser.versions}") + return {v:None for v in parser.versions} + + def get_sha256(self, base_url, filename): + r = requests.get(base_url) + r.raise_for_status() + parser = FedoraChecksumFileParser() + parser.feed(r.content.decode()) + parser.close() + if len(parser.urls) < 1: + raise RuntimeError(f"Unable to find checksum file by {base_url}") + log.debug(f"Checksum files found: {parser.urls}") + url = f"{base_url}/{parser.urls[0]}" + r = requests.get(url) + parser = re.compile(r"SHA256\s+\((.*\.qcow2)\)\s+=\s+([a-f0-9]+)$") + for line in r.content.decode().strip().split("\n"): + found = parser.search(line) + if found: + if found.group(1) == filename: + return found.group(2) + raise NameError('SHA-256 checksums not found for file ' + filename + + ' at ' + url) + + def get_latest_release_image(self, url): + r = requests.get(url) + r.raise_for_status() + parser = FedoraImageParser() + parser.feed(r.content.decode()) + parser.close() + r = re.compile(r"Cloud-Base-Generic-[0-9]+-([0-9]+\.[0-9]+)\.") + for href in sorted(parser.urls, reverse=True): + res = r.search(href) + if res: + serial=res.group(1) + return href, serial + + raise NameError('Image not found on server at ' + url) + + def __call__(self, release, arch): + if arch == "amd64": + arch = "x86_64" + if arch == "arm64": + arch = "aarch64" + base_url = self.URL + f"/pub/fedora/linux/releases/{release}/Cloud/{arch}/images" + filename, serial = self.get_latest_release_image(base_url) + log.debug(f"Found image for release '{release}': {filename} ({serial})") + sha256 = self.get_sha256(base_url, filename) + url = base_url + '/' + filename + return { + 'url': url, + 'serial': serial.rstrip('.0'), + 'checksum': sha256, + 'hash_function': 'sha256' + } + + class CentOSHandler(DistroHandler): URL="https://cloud.centos.org" @@ -384,7 +493,6 @@ def __call__(self, distroversion, arch): } - class AlmaHandler(DistroHandler): URL="https://repo.almalinux.org" @@ -583,6 +691,7 @@ def __call__(self, release, arch): 'ubuntu': UbuntuHandler(), 'opensuse': OpenSUSEHandler(), 'alma': AlmaHandler(), + 'fedora': FedoraHandler(), 'rocky': RockyHandler(), 'centos': CentOSHandler(), }