diff --git a/docs/index.rst b/docs/index.rst
index 779a841..5be4c64 100644
--- a/docs/index.rst
+++ b/docs/index.rst
@@ -16,6 +16,7 @@ It supports:
- yum repositories (``repodata/repomd.xml``)
- pulp file repositories (``PULP_MANIFEST``)
+- kickstart tree repositories\* (``treeinfo``, ``repodata/repomd.xml``, ``extra_files.json``)
``repo-autoindex`` provides similar functionality to traditional server-generated
directory indexes such as httpd's
@@ -28,6 +29,10 @@ a few key differences:
library to integrate with exotic scenarios such as repositories generated on demand
or not stored within a traditional filesystem.
+\* ``repo-autoindex`` supports kickstart tree repositories satisfying certain conditions:
+
+- The kickstart repo contains exactly one yum repo
+- The yum repo is located in the root of the kickstart tree repo, at exactly ``.``
Reference: CLI
--------------
diff --git a/repo_autoindex/_impl/api.py b/repo_autoindex/_impl/api.py
index b094190..4954e54 100644
--- a/repo_autoindex/_impl/api.py
+++ b/repo_autoindex/_impl/api.py
@@ -8,9 +8,10 @@ import aiohttp
from .base import Fetcher, GeneratedIndex, Repo, ContentError, FetcherError
from .yum import YumRepo
from .pulp import PulpFileRepo
+from .kickstart import KickstartRepo
LOG = logging.getLogger("repo-autoindex")
-REPO_TYPES: list[Type[Repo]] = [YumRepo, PulpFileRepo]
+REPO_TYPES: list[Type[Repo]] = [KickstartRepo, YumRepo, PulpFileRepo]
def http_fetcher(session: aiohttp.ClientSession) -> Fetcher:
@@ -133,6 +134,7 @@ async def autoindex(
index_href_suffix=index_href_suffix
):
yield page
+ break
except FetcherError as exc:
# FetcherErrors are unwrapped to propagate whatever was the original error
assert exc.__cause__
diff --git a/repo_autoindex/_impl/kickstart.py b/repo_autoindex/_impl/kickstart.py
new file mode 100644
index 0000000..df2572e
--- /dev/null
+++ b/repo_autoindex/_impl/kickstart.py
@@ -0,0 +1,235 @@
+from typing import Optional, Type
+from collections.abc import AsyncGenerator
+import logging
+import configparser
+import json
+import os
+
+from .base import Repo, GeneratedIndex, Fetcher, IndexEntry, ICON_OPTICAL, ICON_QCOW
+from .template import TemplateContext
+from .tree import treeify
+from .yum import YumRepo
+
+LOG = logging.getLogger("repo-autoindex")
+
+
+class KickstartRepo(YumRepo):
+ def __init__(
+ self,
+ base_url: str,
+ repomd_xml: str,
+ extra_files: str,
+ treeinfo: str,
+ fetcher: Fetcher,
+ ):
+ super().__init__(base_url, repomd_xml, fetcher)
+ self.base_url = base_url
+ self.fetcher = fetcher
+ self.entry_point_content = repomd_xml
+ self.extra_files_content = extra_files
+ self.treeinfo_content = treeinfo
+
+ async def render_index(
+ self, index_href_suffix: str
+ ) -> AsyncGenerator[GeneratedIndex, None]:
+ all_entries: list[IndexEntry] = []
+
+ # Parse the treeinfo entry point
+ LOG.debug("treeinfo: %s", self.treeinfo_content)
+ all_entries.extend(await self._treeinfo_entries())
+
+ # Parse the extra_files.json entry point
+ #
+ # Legacy kickstart tree repositories do not contain extra_files.json files.
+ # If the repo does not contain an extra_files.json, do not attempt to process it.
+ if self.extra_files_content:
+ LOG.debug("extra_files.json: %s", self.extra_files_content)
+ all_entries.extend(await self._extra_files_entries())
+
+ # Parse the yum repo embedded in the kickstart repo
+ LOG.debug("repomd.xml: %s", self.entry_point_content)
+ all_entries.extend(await super()._repodata_entries())
+ all_entries.extend(await super()._package_entries())
+
+ ctx = TemplateContext()
+ nodes = [treeify(all_entries, index_href_suffix=index_href_suffix)]
+ while nodes:
+ node = nodes.pop()
+ yield GeneratedIndex(
+ content=ctx.render_index(index_entries=node.entries),
+ relative_dir=node.relative_dir,
+ )
+ nodes.extend(node.children)
+
+ async def _treeinfo_entries(self) -> list[IndexEntry]:
+ """
+ A treeinfo file might look like this:
+
+ [checksums]
+ images/boot.iso = sha256:f6be6ec48a4a610e25d591dcf98e1777c4274ed58c583fa64d0aea5b3ecffb18
+ images/efiboot.img = sha256:94d5500c4ba266ce77b06aa955d9041eea22129737badc6af56c283dcaec1c29
+ images/install.img = sha256:46171146377610cfa0deae157bbcc4ea146b3995c9b0c58d9f261ce404468abe
+ images/pxeboot/initrd.img = sha256:e0cd3966097c175d3aaf406a7f8c094374c69504c7be8f08d8084ab9a8812796
+ images/pxeboot/vmlinuz = sha256:370db9a3943d4f46dc079dbaeb7e0cc3910dca069f7eede66d3d7d0d5177f684
+
+ [general]
+ ; WARNING.0 = This section provides compatibility with pre-productmd treeinfos.
+ ; WARNING.1 = Read productmd documentation for details about new format.
+ arch = x86_64
+ family = Red Hat Enterprise Linux
+ name = Red Hat Enterprise Linux 8.0.0
+ packagedir = Packages
+ platforms = x86_64,xen
+ repository = .
+ timestamp = 1554367044
+ variant = BaseOS
+ variants = BaseOS
+ version = 8.0.0
+
+ [header]
+ type = productmd.treeinfo
+ version = 1.2
+
+ [images-x86_64]
+ boot.iso = images/boot.iso
+ efiboot.img = images/efiboot.img
+ initrd = images/pxeboot/initrd.img
+ kernel = images/pxeboot/vmlinuz
+
+ [images-xen]
+ initrd = images/pxeboot/initrd.img
+ kernel = images/pxeboot/vmlinuz
+
+ [release]
+ name = Red Hat Enterprise Linux
+ short = RHEL
+ version = 8.0.0
+
+ [stage2]
+ mainimage = images/install.img
+
+ [tree]
+ arch = x86_64
+ build_timestamp = 1554367044
+ platforms = x86_64,xen
+ variants = BaseOS
+
+ [variant-BaseOS]
+ id = BaseOS
+ name = BaseOS
+ packages = Packages
+ repository = .
+ type = variant
+ uid = BaseOS
+ """
+ out: list[IndexEntry] = [
+ IndexEntry(
+ href="treeinfo",
+ text="treeinfo",
+ size=str(len(self.treeinfo_content)),
+ ),
+ ]
+
+ treeinfo = configparser.ConfigParser()
+ treeinfo.read_string(self.treeinfo_content)
+
+ for image in treeinfo["checksums"]:
+ entry = IndexEntry(
+ href=image,
+ text=os.path.basename(image),
+ )
+ if entry.href.endswith(".iso") or entry.href.endswith(".img"):
+ entry.icon = ICON_OPTICAL
+ out.append(entry)
+ return out
+
+ async def _extra_files_entries(self) -> list[IndexEntry]:
+ """
+ An extra_files.json file might look like this:
+
+ {
+ "data": [
+ {
+ "checksums": {
+ "md5": "feb4d252ee63634debea654b446e830b",
+ "sha1": "a73fad5aeb5642d1b2108885010c4e7a547a1204",
+ "sha256": "c4117d0e325cde392981626edbd1484c751f0216689a171e4b7547e8800acc21"
+ },
+ "file": "RPM-GPG-KEY-redhat-release",
+ "size": 5134
+ },
+ {
+ "checksums": {
+ "md5": "3c24137e12ece142a27bbf825c256936",
+ "sha1": "a72daf8585b41529269cdffcca3a0b3d4e2f21cd",
+ "sha256": "3f8644b35db4197e7689d0a034bdef2039d92e330e6b22217abfa6b86a1fc0fa"
+ },
+ "file": "RPM-GPG-KEY-redhat-beta",
+ "size": 1669
+ },
+ {
+ "checksums": {
+ "md5": "b234ee4d69f5fce4486a80fdaf4a4263",
+ "sha1": "4cc77b90af91e615a64ae04893fdffa7939db84c",
+ "sha256": "8177f97513213526df2cf6184d8ff986c675afb514d4e68a404010521b880643"
+ },
+ "file": "GPL",
+ "size": 18092
+ },
+ {
+ "checksums": {
+ "md5": "0c53898068810a989fa59ca0656bdf24",
+ "sha1": "42d51858642b8a0d10fdf09050266395544ea556",
+ "sha256": "8f833ce3fbcbcb82e47687a890c043332c88350ddabd606201556e14aaf8fcd9"
+ },
+ "file": "EULA",
+ "size": 8154
+ }
+ ],
+ "header": {
+ "version": "1.0"
+ }
+ }
+ """
+ out: list[IndexEntry] = [
+ IndexEntry(
+ href="extra_files.json",
+ text="extra_files.json",
+ size=str(len(self.extra_files_content)),
+ ),
+ ]
+
+ extra_files = json.loads(self.extra_files_content)
+ for extra_file in extra_files["data"]:
+ entry = IndexEntry(
+ href=extra_file["file"],
+ text=extra_file["file"],
+ size=extra_file["size"],
+ )
+ out.append(entry)
+ return out
+
+ @classmethod
+ async def probe(
+ cls: Type["KickstartRepo"], fetcher: Fetcher, url: str
+ ) -> Optional["KickstartRepo"]:
+ treeinfo_url = f"{url}/treeinfo"
+ treeinfo_content = await fetcher(treeinfo_url)
+ extra_files_url = f"{url}/extra_files.json"
+ extra_files_content = await fetcher(extra_files_url) or ""
+ repomd_xml_url = f"{url}/repodata/repomd.xml"
+ repomd_xml = await fetcher(repomd_xml_url)
+
+ # Modern versions of kickstart repositories (RHEL-8, 9) contain three entry points:
+ # treeinfo, extra_files.json, and repomd.xml. repo-autoindex requires that a kickstart
+ # repo contains a treeinfo file and exactly one yum repo located in the root of the
+ # kickstart tree repo.
+ #
+ # Legacy kickstart tree repositories do not contain an extra_files.json file. When
+ # repo-autoindex encounters a legacy kickstart tree repository, it will attempt to
+ # produce a repo index. The repo index produced by repo-autoindex will not contain the
+ # files commonly included in extra_files.json (EULA, GPL, GPG keys).
+ if treeinfo_content is None or repomd_xml is None:
+ return None
+
+ return cls(url, repomd_xml, extra_files_content, treeinfo_content, fetcher)
diff --git a/repo_autoindex/_impl/yum.py b/repo_autoindex/_impl/yum.py
index 5d1a9b7..96dc935 100644
--- a/repo_autoindex/_impl/yum.py
+++ b/repo_autoindex/_impl/yum.py
@@ -128,13 +128,13 @@ class YumRepo(Repo):
LOG.debug("repomd.xml: %s", self.entry_point_content)
entries = []
- entries.extend(await self.__repodata_entries())
- entries.extend(await self.__package_entries())
+ entries.extend(await self._repodata_entries())
+ entries.extend(await self._package_entries())
for page in self.__render_entries(entries, index_href_suffix):
yield page
- async def __repodata_entries(self) -> list[IndexEntry]:
+ async def _repodata_entries(self) -> list[IndexEntry]:
out = []
# There's always an entry for repomd.xml itself...
@@ -189,7 +189,7 @@ class YumRepo(Repo):
return out
- async def __package_entries(self) -> list[IndexEntry]:
+ async def _package_entries(self) -> list[IndexEntry]:
primary_nodes = list(
pulldom_elements(
diff --git a/tests/sample_kickstart_repo/Packages/w/walrus-5.21-1.noarch.rpm b/tests/sample_kickstart_repo/Packages/w/walrus-5.21-1.noarch.rpm
new file mode 100644
index 0000000..1e833d9
Binary files /dev/null and b/tests/sample_kickstart_repo/Packages/w/walrus-5.21-1.noarch.rpm differ
diff --git a/tests/sample_kickstart_repo/extra_files.json b/tests/sample_kickstart_repo/extra_files.json
new file mode 100644
index 0000000..83fcba7
--- /dev/null
+++ b/tests/sample_kickstart_repo/extra_files.json
@@ -0,0 +1,43 @@
+{
+ "data": [
+ {
+ "checksums": {
+ "md5": "feb4d252ee63634debea654b446e830b",
+ "sha1": "a73fad5aeb5642d1b2108885010c4e7a547a1204",
+ "sha256": "c4117d0e325cde392981626edbd1484c751f0216689a171e4b7547e8800acc21"
+ },
+ "file": "RPM-GPG-KEY-redhat-release",
+ "size": 5134
+ },
+ {
+ "checksums": {
+ "md5": "3c24137e12ece142a27bbf825c256936",
+ "sha1": "a72daf8585b41529269cdffcca3a0b3d4e2f21cd",
+ "sha256": "3f8644b35db4197e7689d0a034bdef2039d92e330e6b22217abfa6b86a1fc0fa"
+ },
+ "file": "RPM-GPG-KEY-redhat-beta",
+ "size": 1669
+ },
+ {
+ "checksums": {
+ "md5": "b234ee4d69f5fce4486a80fdaf4a4263",
+ "sha1": "4cc77b90af91e615a64ae04893fdffa7939db84c",
+ "sha256": "8177f97513213526df2cf6184d8ff986c675afb514d4e68a404010521b880643"
+ },
+ "file": "GPL",
+ "size": 18092
+ },
+ {
+ "checksums": {
+ "md5": "0c53898068810a989fa59ca0656bdf24",
+ "sha1": "42d51858642b8a0d10fdf09050266395544ea556",
+ "sha256": "8f833ce3fbcbcb82e47687a890c043332c88350ddabd606201556e14aaf8fcd9"
+ },
+ "file": "EULA",
+ "size": 8154
+ }
+ ],
+ "header": {
+ "version": "1.0"
+ }
+}
diff --git a/tests/sample_kickstart_repo/images/boot.iso b/tests/sample_kickstart_repo/images/boot.iso
new file mode 100644
index 0000000..e69de29
diff --git a/tests/sample_kickstart_repo/images/efiboot.img b/tests/sample_kickstart_repo/images/efiboot.img
new file mode 100644
index 0000000..e69de29
diff --git a/tests/sample_kickstart_repo/images/install.img b/tests/sample_kickstart_repo/images/install.img
new file mode 100644
index 0000000..e69de29
diff --git a/tests/sample_kickstart_repo/images/pxeboot/initrd.img b/tests/sample_kickstart_repo/images/pxeboot/initrd.img
new file mode 100644
index 0000000..e69de29
diff --git a/tests/sample_kickstart_repo/images/pxeboot/vmlinuz b/tests/sample_kickstart_repo/images/pxeboot/vmlinuz
new file mode 100644
index 0000000..e69de29
diff --git a/tests/sample_kickstart_repo/repodata/33795fed0c0144a7fe732a9ded8d7529940e4a1384ad654e7214266648c37f0b-filelists.xml.gz b/tests/sample_kickstart_repo/repodata/33795fed0c0144a7fe732a9ded8d7529940e4a1384ad654e7214266648c37f0b-filelists.xml.gz
new file mode 100644
index 0000000..77d3cec
Binary files /dev/null and b/tests/sample_kickstart_repo/repodata/33795fed0c0144a7fe732a9ded8d7529940e4a1384ad654e7214266648c37f0b-filelists.xml.gz differ
diff --git a/tests/sample_kickstart_repo/repodata/3a7a286e13883d497b2e3c7029ceb7c372ff2529bbfa22d0c890285ce6aa3129-primary.xml.gz b/tests/sample_kickstart_repo/repodata/3a7a286e13883d497b2e3c7029ceb7c372ff2529bbfa22d0c890285ce6aa3129-primary.xml.gz
new file mode 100644
index 0000000..9455510
Binary files /dev/null and b/tests/sample_kickstart_repo/repodata/3a7a286e13883d497b2e3c7029ceb7c372ff2529bbfa22d0c890285ce6aa3129-primary.xml.gz differ
diff --git a/tests/sample_kickstart_repo/repodata/834b12e38d809c4b5afd1a7c03ad48c0e15e3d28420987132d7d2c176127b9db-other.xml.gz b/tests/sample_kickstart_repo/repodata/834b12e38d809c4b5afd1a7c03ad48c0e15e3d28420987132d7d2c176127b9db-other.xml.gz
new file mode 100644
index 0000000..1c896ca
Binary files /dev/null and b/tests/sample_kickstart_repo/repodata/834b12e38d809c4b5afd1a7c03ad48c0e15e3d28420987132d7d2c176127b9db-other.xml.gz differ
diff --git a/tests/sample_kickstart_repo/repodata/repomd.xml b/tests/sample_kickstart_repo/repodata/repomd.xml
new file mode 100644
index 0000000..3dd3bd0
--- /dev/null
+++ b/tests/sample_kickstart_repo/repodata/repomd.xml
@@ -0,0 +1,28 @@
+
+
+ 1659419679
+
+ 3a7a286e13883d497b2e3c7029ceb7c372ff2529bbfa22d0c890285ce6aa3129
+ ad4149ec99b72282ab4891ea5d224db02cc3d7e0ad5c1bdaba56c21cbd4ab132
+
+ 1659419679
+ 598
+ 1127
+
+
+ 33795fed0c0144a7fe732a9ded8d7529940e4a1384ad654e7214266648c37f0b
+ ae8aa2cca2e1eba056ed56a66da2b1f6cdb142e465a13bb55f603c7481239e39
+
+ 1659419679
+ 243
+ 320
+
+
+ 834b12e38d809c4b5afd1a7c03ad48c0e15e3d28420987132d7d2c176127b9db
+ ee1c6e87c3b7ebfa2e85d9b56e245ef097a0d928794da75ab63d43ac5593d9d0
+
+ 1659419679
+ 229
+ 285
+
+
diff --git a/tests/sample_kickstart_repo/treeinfo b/tests/sample_kickstart_repo/treeinfo
new file mode 100644
index 0000000..708c381
--- /dev/null
+++ b/tests/sample_kickstart_repo/treeinfo
@@ -0,0 +1,56 @@
+[checksums]
+images/boot.iso = sha256:f6be6ec48a4a610e25d591dcf98e1777c4274ed58c583fa64d0aea5b3ecffb18
+images/efiboot.img = sha256:94d5500c4ba266ce77b06aa955d9041eea22129737badc6af56c283dcaec1c29
+images/install.img = sha256:46171146377610cfa0deae157bbcc4ea146b3995c9b0c58d9f261ce404468abe
+images/pxeboot/initrd.img = sha256:e0cd3966097c175d3aaf406a7f8c094374c69504c7be8f08d8084ab9a8812796
+images/pxeboot/vmlinuz = sha256:370db9a3943d4f46dc079dbaeb7e0cc3910dca069f7eede66d3d7d0d5177f684
+
+[general]
+; WARNING.0 = This section provides compatibility with pre-productmd treeinfos.
+; WARNING.1 = Read productmd documentation for details about new format.
+arch = x86_64
+family = Red Hat Enterprise Linux
+name = Red Hat Enterprise Linux 8.0.0
+packagedir = Packages
+platforms = x86_64,xen
+repository = .
+timestamp = 1554367044
+variant = BaseOS
+variants = BaseOS
+version = 8.0.0
+
+[header]
+type = productmd.treeinfo
+version = 1.2
+
+[images-x86_64]
+boot.iso = images/boot.iso
+efiboot.img = images/efiboot.img
+initrd = images/pxeboot/initrd.img
+kernel = images/pxeboot/vmlinuz
+
+[images-xen]
+initrd = images/pxeboot/initrd.img
+kernel = images/pxeboot/vmlinuz
+
+[release]
+name = Red Hat Enterprise Linux
+short = RHEL
+version = 8.0.0
+
+[stage2]
+mainimage = images/install.img
+
+[tree]
+arch = x86_64
+build_timestamp = 1554367044
+platforms = x86_64,xen
+variants = BaseOS
+
+[variant-BaseOS]
+id = BaseOS
+name = BaseOS
+packages = Packages
+repository = .
+type = variant
+uid = BaseOS
diff --git a/tests/test_kickstart_render_typical.py b/tests/test_kickstart_render_typical.py
new file mode 100644
index 0000000..e771960
--- /dev/null
+++ b/tests/test_kickstart_render_typical.py
@@ -0,0 +1,576 @@
+from typing import Optional
+import textwrap
+
+from repo_autoindex import autoindex
+from repo_autoindex._impl.base import GeneratedIndex
+
+REPOMD_XML = textwrap.dedent(
+ """
+
+
+ 1657165688
+
+ d4888f04f95ac067af4d997d35c6d345cbe398563d777d017a3634c9ed6148cf
+ 6fc4eddd4e9de89246efba3815b8a9dec9dfe168e4fd3104cc792dff908a0f62
+
+ 1657165688
+ 2932
+ 16585
+
+
+ 284769ec79daa9e0a3b0129bb6260cc6271c90c4fe02b43dfa7cdf7635fb803f
+ 72f89223c8b0f6c7a2ee6ed7fbd16ee0bb395ca68260038bb3895265af84c29f
+
+ 1657165688
+ 4621
+ 36911
+
+
+ 36c2195bbee0c39ee080969abc6fd59d943c3471114cfd43c6e776ac20d7ed21
+ 39f52cf295db14e863abcd7b2eede8e6c5e39ac9b2f194349459d29cd492c90f
+
+ 1657165688
+ 1408
+ 8432
+
+
+ 55e6bfd00e889c5c1f9a3c9fb35a660158bc5d975ae082d434f3cf81cc2c0c21
+ b2692c49d1d98d68e764e29108d8a81a3dfd9e04fa7665115853a029396d118d
+
+ 1657165688
+ 7609
+ 114688
+ 10
+
+
+ de63a509812c37f7736fcef0b79e9c55dfe67a2d77006f74fdc442935103e9e6
+ 40eb5d53fe547c98d470813256c9bfc8a239b13697d8eb824a1485c9e186a0e3
+
+ 1657165688
+ 10323
+ 65536
+ 10
+
+
+ 9aa39b62df200cb3784dea24092d0c1c686afff0cd0990c2ec7a61afe8896e1c
+ 3e5cefb10ce805b827e12ca3b4839bba873dc9403fd92b60a364bf6f312bd972
+
+ 1657165688
+ 2758
+ 32768
+ 10
+
+
+"""
+).strip()
+
+PRIMARY_XML = textwrap.dedent(
+ """
+
+
+
+ wireplumber
+ x86_64
+
+ 539a773f3f39a7b2b5f971bdd0063f7d4201aab00920f380962e935356dc4d3a
+ A modular session/policy manager for PipeWire
+ WirePlumber is a modular session/policy manager for PipeWire and a
+GObject-based high-level library that wraps PipeWire's API, providing
+convenience for writing the daemon's modules as well as external tools for
+managing PipeWire.
+ Fedora Project
+ https://pipewire.pages.freedesktop.org/wireplumber/
+
+
+
+
+ MIT
+ Fedora Project
+ Unspecified
+ buildvm-x86-27.iad2.fedoraproject.org
+ wireplumber-0.4.10-1.fc36.src.rpm
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ /etc/wireplumber
+ /etc/wireplumber/bluetooth.lua.d
+ /etc/wireplumber/common
+ /etc/wireplumber/main.lua.d
+ /etc/wireplumber/policy.lua.d
+ /usr/bin/wireplumber
+ /usr/bin/wpctl
+ /usr/bin/wpexec
+
+
+
+ wireplumber-libs
+ x86_64
+
+ 1f0d373bd1b8af6b4b7baab1c89e4820aa8cd8691f51fca4fccac9785fe715ea
+ Libraries for WirePlumber clients
+ This package contains the runtime libraries for any application that wishes
+to interface with WirePlumber.
+ Fedora Project
+ https://pipewire.pages.freedesktop.org/wireplumber/
+
+
+
+
+ MIT
+ Fedora Project
+ Unspecified
+ buildvm-x86-27.iad2.fedoraproject.org
+ wireplumber-0.4.10-1.fc36.src.rpm
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ xfce4-panel
+ x86_64
+
+ 1eecad127499d557f9d97562a1c65d9c881f3f63431546007a9ed714997b909c
+ Next generation panel for Xfce
+ This package includes the panel for the Xfce desktop environment.
+ Fedora Project
+ http://www.xfce.org/
+
+
+
+
+ GPLv2+ and LGPLv2+
+ Fedora Project
+ Unspecified
+ buildvm-x86-16.iad2.fedoraproject.org
+ xfce4-panel-4.16.4-1.fc36.src.rpm
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ /etc/xdg/xfce4/panel/default.xml
+ /usr/bin/xfce4-panel
+ /usr/bin/xfce4-popup-applicationsmenu
+ /usr/bin/xfce4-popup-directorymenu
+ /usr/bin/xfce4-popup-windowmenu
+
+
+
+ xfce4-power-manager
+ x86_64
+
+ 48697b6e83646e702d83523acd4a25df546129a1a11f3fbb81724c30d58e9c21
+ Power management for the Xfce desktop environment
+ Xfce Power Manager uses the information and facilities provided by HAL to
+display icons and handle user callbacks in an interactive Xfce session.
+Xfce Power Preferences allows authorised users to set policy and change
+preferences.
+ Fedora Project
+ http://goodies.xfce.org/projects/applications/xfce4-power-manager
+
+
+
+
+ GPLv2+
+ Fedora Project
+ Unspecified
+ buildvm-x86-21.iad2.fedoraproject.org
+ xfce4-power-manager-4.16.0-5.fc36.src.rpm
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ /etc/xdg/autostart/xfce4-power-manager.desktop
+ /etc/xdg/xfce4/xfconf/xfce-perchannel-xml/xfce4-power-manager.xml
+ /usr/bin/xfce4-power-manager
+ /usr/bin/xfce4-power-manager-settings
+ /usr/sbin/xfce4-pm-helper
+ /usr/sbin/xfpm-power-backlight-helper
+
+
+
+ xfce4-terminal
+ x86_64
+
+ 6b6d0d941c16988b4c68ae473f1af141dedafe691922c0c88f6f3ef82baeef79
+ Terminal Emulator for the Xfce Desktop environment
+ Xfce4-terminal is a lightweight and easy to use terminal emulator application
+with many advanced features including drop down, tabs, unlimited scrolling,
+full colors, fonts, transparent backgrounds, and more.
+ Fedora Project
+ http://docs.xfce.org/apps/terminal/start
+
+
+
+
+ GPLv2+
+ Fedora Project
+ Unspecified
+ buildvm-x86-15.iad2.fedoraproject.org
+ xfce4-terminal-1.0.3-1.fc36.src.rpm
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ /usr/bin/xfce4-terminal
+
+
+
+"""
+).strip()
+
+TREEINFO = """[checksums]
+images/boot.iso = sha256:f6be6ec48a4a610e25d591dcf98e1777c4274ed58c583fa64d0aea5b3ecffb18
+images/efiboot.img = sha256:94d5500c4ba266ce77b06aa955d9041eea22129737badc6af56c283dcaec1c29
+images/install.img = sha256:46171146377610cfa0deae157bbcc4ea146b3995c9b0c58d9f261ce404468abe
+images/pxeboot/initrd.img = sha256:e0cd3966097c175d3aaf406a7f8c094374c69504c7be8f08d8084ab9a8812796
+images/pxeboot/vmlinuz = sha256:370db9a3943d4f46dc079dbaeb7e0cc3910dca069f7eede66d3d7d0d5177f684
+
+[general]
+; WARNING.0 = This section provides compatibility with pre-productmd treeinfos.
+; WARNING.1 = Read productmd documentation for details about new format.
+arch = x86_64
+family = Red Hat Enterprise Linux
+name = Red Hat Enterprise Linux 8.0.0
+packagedir = Packages
+platforms = x86_64,xen
+repository = .
+timestamp = 1554367044
+variant = BaseOS
+variants = BaseOS
+version = 8.0.0
+
+[header]
+type = productmd.treeinfo
+version = 1.2
+
+[images-x86_64]
+boot.iso = images/boot.iso
+efiboot.img = images/efiboot.img
+initrd = images/pxeboot/initrd.img
+kernel = images/pxeboot/vmlinuz
+
+[images-xen]
+initrd = images/pxeboot/initrd.img
+kernel = images/pxeboot/vmlinuz
+
+[release]
+name = Red Hat Enterprise Linux
+short = RHEL
+version = 8.0.0
+
+[stage2]
+mainimage = images/install.img
+
+[tree]
+arch = x86_64
+build_timestamp = 1554367044
+platforms = x86_64,xen
+variants = BaseOS
+
+[variant-BaseOS]
+id = BaseOS
+name = BaseOS
+packages = Packages
+repository = .
+type = variant
+uid = BaseOS"""
+
+EXTRA_FILES_JSON = """{
+ "data": [
+ {
+ "checksums": {
+ "md5": "feb4d252ee63634debea654b446e830b",
+ "sha1": "a73fad5aeb5642d1b2108885010c4e7a547a1204",
+ "sha256": "c4117d0e325cde392981626edbd1484c751f0216689a171e4b7547e8800acc21"
+ },
+ "file": "RPM-GPG-KEY-redhat-release",
+ "size": 5134
+ },
+ {
+ "checksums": {
+ "md5": "3c24137e12ece142a27bbf825c256936",
+ "sha1": "a72daf8585b41529269cdffcca3a0b3d4e2f21cd",
+ "sha256": "3f8644b35db4197e7689d0a034bdef2039d92e330e6b22217abfa6b86a1fc0fa"
+ },
+ "file": "RPM-GPG-KEY-redhat-beta",
+ "size": 1669
+ },
+ {
+ "checksums": {
+ "md5": "b234ee4d69f5fce4486a80fdaf4a4263",
+ "sha1": "4cc77b90af91e615a64ae04893fdffa7939db84c",
+ "sha256": "8177f97513213526df2cf6184d8ff986c675afb514d4e68a404010521b880643"
+ },
+ "file": "GPL",
+ "size": 18092
+ },
+ {
+ "checksums": {
+ "md5": "0c53898068810a989fa59ca0656bdf24",
+ "sha1": "42d51858642b8a0d10fdf09050266395544ea556",
+ "sha256": "8f833ce3fbcbcb82e47687a890c043332c88350ddabd606201556e14aaf8fcd9"
+ },
+ "file": "EULA",
+ "size": 8154
+ }
+ ],
+ "header": {
+ "version": "1.0"
+ }
+}"""
+
+
+class StaticFetcher:
+ def __init__(self):
+ self.content: dict[str, str] = {}
+
+ async def __call__(self, url: str) -> Optional[str]:
+ return self.content.get(url)
+
+
+async def test_typical_index():
+ fetcher = StaticFetcher()
+
+ fetcher.content["https://example.com/repodata/repomd.xml"] = REPOMD_XML
+ fetcher.content[
+ "https://example.com/repodata/d4888f04f95ac067af4d997d35c6d345cbe398563d777d017a3634c9ed6148cf-primary.xml.gz"
+ ] = PRIMARY_XML
+ fetcher.content["https://example.com/treeinfo"] = TREEINFO
+ fetcher.content["https://example.com/extra_files.json"] = EXTRA_FILES_JSON
+
+ entries: list[GeneratedIndex] = []
+ async for entry in autoindex("https://example.com", fetcher=fetcher):
+ print(f"Found one entry: {entry.relative_dir}")
+ entries.append(entry)
+
+ # It should generate some entries
+ assert entries
+
+ entries.sort(key=lambda e: e.relative_dir)
+
+ # First check that the directory structure was reproduced.
+ assert [e.relative_dir for e in entries] == [
+ "",
+ "images",
+ "images/pxeboot",
+ "packages",
+ "packages/w",
+ "packages/x",
+ "repodata",
+ ]
+
+ by_relative_dir: dict[str, GeneratedIndex] = {}
+ for entry in entries:
+ by_relative_dir[entry.relative_dir] = entry
+
+ # Sanity check a few links expected to appear in each.
+ assert '' in by_relative_dir[""].content
+ assert '' in by_relative_dir[""].content
+
+ assert '' in by_relative_dir["packages"].content
+ assert '' in by_relative_dir["packages"].content
+
+ assert '' in by_relative_dir[""].content
+ assert '' in by_relative_dir["images"].content
+
+ assert (
+ ''
+ in by_relative_dir["repodata"].content
+ )
+
+ assert (
+ ''
+ in by_relative_dir["packages/w"].content
+ )
+ assert (
+ ''
+ in by_relative_dir["packages/x"].content
+ )
+
+ assert '' in by_relative_dir[""].content
+
+ assert '' in by_relative_dir[""].content
+
+ assert '' in by_relative_dir[""].content
+
+ assert '' in by_relative_dir[""].content
+
+ assert '' in by_relative_dir[""].content
+
+ assert '' in by_relative_dir[""].content
+
+ assert '' in by_relative_dir["images"].content
+
+ assert '' in by_relative_dir["images"].content
+
+ assert '' in by_relative_dir["images/pxeboot"].content