Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions doc/rpms-2.0.rst
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,10 @@ Changes from 1.1
* ``path`` field replaced by a ``location`` object
* Location objects include ``url``, ``size``, ``checksum``, and ``local_path`` fields
* ``sigkey`` and ``category`` fields are preserved alongside the location
* Optional ``sigkeys`` list for RPM v6 packages with multiple signatures.
When ``sigkeys`` is provided to ``Rpms.add()``, ``sigkey`` is derived from
``sigkeys[0]``. This derivation only happens at ``add()`` time; modifying
``sigkeys`` directly on the entry dict afterwards will not update ``sigkey``.
* URLs may be HTTPS URLs, OCI registry references, or relative paths
* Checksums use ``algorithm:hexdigest`` format (e.g., ``sha256:abc123...``)
* ``force_version`` parameter added to ``serialize()`` for version control
Expand Down Expand Up @@ -60,6 +64,7 @@ in order to read and diff rpms.json files easily.
"local_path": <str> # relative path for v1.x filesystem layout
},
"sigkey": <str|null>, # sigkey ID; null for unsigned RPMs
"sigkeys": [<str>, ...],# (optional) list of signing key IDs; for RPM v6 multiple signatures
"category": <str> # binary, debug, source
}
}
Expand Down Expand Up @@ -99,6 +104,7 @@ Bash and kernel RPMs in Fedora 41::
"local_path": "Server/x86_64/os/Packages/b/bash-5.2.26-3.fc41.x86_64.rpm"
},
"sigkey": "a15b79cc",
"sigkeys": ["a15b79cc", "1234567890abcdef1234567890abcdef12345678"],
"category": "binary"
}
},
Expand Down
62 changes: 57 additions & 5 deletions productmd/rpms.py
Original file line number Diff line number Diff line change
Expand Up @@ -163,6 +163,9 @@ def _serialize_v2(self, data):
v2_entry["sigkey"] = rpm_data.get("sigkey")
v2_entry["category"] = rpm_data.get("category")

# Include sigkeys (RPM v6 multiple signatures)
v2_entry["sigkeys"] = rpm_data.get("sigkeys", [])

v2_rpms[variant][arch][srpm_nevra][rpm_nevra] = v2_entry

data["payload"]["rpms"] = v2_rpms
Expand Down Expand Up @@ -235,12 +238,15 @@ def _deserialize_v2(self, data):
"category": rpm_data.get("category"),
}

# Preserve sigkeys for RPM v6 multiple signatures
entry["sigkeys"] = rpm_data.get("sigkeys", [])

# Preserve full location for round-trip fidelity
entry["_location"] = loc

self.rpms[variant][arch][srpm_nevra][rpm_nevra] = entry

def add(self, variant, arch, nevra, path, sigkey, category, srpm_nevra=None, location=None):
def add(self, variant, arch, nevra, path, sigkey, category, srpm_nevra=None, location=None, sigkeys=None):
"""
Map RPM to to variant and arch.

Expand All @@ -252,7 +258,8 @@ def add(self, variant, arch, nevra, path, sigkey, category, srpm_nevra=None, loc
:type nevra: str
:param path: relative path to the RPM file
:type path: str
:param sigkey: sigkey hash
:param sigkey: sigkey hash. When *sigkeys* is provided, this is
derived from ``sigkeys[0]`` and should not be set explicitly.
:type sigkey: str or None
:param category: RPM category, one of binary, debug, source
:type category: str
Expand All @@ -265,6 +272,13 @@ def add(self, variant, arch, nevra, path, sigkey, category, srpm_nevra=None, loc
and *path* is not explicitly set, *path* defaults to
``location.local_path``.
:type location: :class:`~productmd.location.Location` or None
:param sigkeys: list of signing key identifiers for RPM v6
packages with multiple signatures. Each value must be a hex
string. Only included in v2.0 serialization. When provided,
*sigkey* is derived from ``sigkeys[0]``. This derivation
only happens at ``add()`` time; modifying *sigkeys* directly
on the entry dict afterwards will not update *sigkey*.
:type sigkeys: list of str or None
"""

# When location is provided without an explicit path, derive path
Expand Down Expand Up @@ -301,8 +315,25 @@ def add(self, variant, arch, nevra, path, sigkey, category, srpm_nevra=None, loc
if (category == "source") != (nevra_dict["arch"] in ("src", "nosrc")):
raise ValueError("Invalid category/arch combination: %s/%s" % (category, nevra))

if sigkey is not None:
sigkey = sigkey.lower()
if sigkeys is not None:
if not isinstance(sigkeys, list):
raise TypeError("'sigkeys' must be a list, got: %s" % type(sigkeys))
validated = []
for key in sigkeys:
if not isinstance(key, str) or not key:
raise ValueError("Each sigkey must be a non-empty string, got: %r" % key)
try:
int(key, 16)
except ValueError:
raise ValueError("Each sigkey must be a hex string, got: %r" % key)
validated.append(key.lower())
sigkeys = validated
# Derive sigkey from sigkeys[0] for v1.x backward compatibility
sigkey = sigkeys[0] if sigkeys else None
else:
sigkeys = []
if sigkey is not None:
sigkey = sigkey.lower()

if srpm_nevra:
srpm_nevra, _ = self._check_nevra(srpm_nevra)
Expand All @@ -312,7 +343,7 @@ def add(self, variant, arch, nevra, path, sigkey, category, srpm_nevra=None, loc
arches = self.rpms.setdefault(variant, {})
srpms = arches.setdefault(arch, {})
rpms = srpms.setdefault(srpm_nevra, {})
entry = {"sigkey": sigkey, "path": path, "category": category}
entry = {"sigkey": sigkey, "path": path, "category": category, "sigkeys": sigkeys}
if location is not None:
entry["_location"] = location
rpms[nevra] = entry
Expand All @@ -337,3 +368,24 @@ def get_location(self, variant, arch, srpm_nevra, rpm_nevra):
except KeyError:
return None
return entry.get("_location")

def get_sigkeys(self, variant, arch, srpm_nevra, rpm_nevra):
"""
Return the list of signing key identifiers for an RPM entry.

:param variant: compose variant UID
:type variant: str
:param arch: compose architecture
:type arch: str
:param srpm_nevra: name-epoch:version-release.arch of the SRPM
:type srpm_nevra: str
:param rpm_nevra: name-epoch:version-release.arch of the RPM
:type rpm_nevra: str
:return: list of signing key identifiers, empty if not found or not set
:rtype: list of str
"""
try:
entry = self.rpms[variant][arch][srpm_nevra][rpm_nevra]
except KeyError:
return []
return entry.get("sigkeys", [])
1 change: 1 addition & 0 deletions tests/compose-v2/metadata/rpms.json
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@
"local_path": "Server/x86_64/os/Packages/b/bash-5.2.26-3.fc41.x86_64.rpm"
},
"sigkey": "a15b79cc",
"sigkeys": ["a15b79cc", "1234567890abcdef1234567890abcdef12345678"],
"category": "binary"
},
"bash-doc-0:5.2.26-3.fc41.x86_64": {
Expand Down
Loading
Loading