Verified Commit 9c6c15c5 authored by David Runge's avatar David Runge
Browse files

Simplify locating of package signatures with repod-file

repod/cli/argparse.py:
Add flag (`-s`/ `--with-signature`) for signature collection to
`repod-file package inspect` and `repod-file package import`.

repod/cli/cli.py:
Change `repod_file_package()` to more easily collect package signatures
by using `args.with_signature`.

tests/cli/test_cli.py:
Adapt tests to changes in `repod_file_package()` behavior.

repod/files/package.py:
Change `Package.from_file()` to fail if a signature file does not exist
or does not match the package name.

tests/files/test_package.py:
Adapt tests to changes in `Package.from_file()` behavior.

docs/repod/tooling.rst:
Adapt documentation to the changes in the CLI options of `repod-file
package inspect` and `repod-file package import`.
parent 733706cb
Pipeline #21756 passed with stages
in 13 minutes and 5 seconds
......@@ -31,12 +31,13 @@ To get the entire metadata collected by repod, use:
.. code:: sh
repod-file package inspect <package> <signature>
repod-file package inspect <package>
.. note::
The signature file is optional, but if present should be added to the call to
``repod-file`` as it provides the additional ``pgpsig`` data for the output.
By default package signature files are not considered. To enforce the
locating and use of accompanying signature files, use the ``-s``/
``--with-signature`` flag.
The output of ``repod-file package inspect`` can be modified by using the
``-p``/ ``--pretty`` option (for pretty printing the JSON output).
......@@ -60,15 +61,13 @@ splitting|) need to be consumed at once.
.. code:: sh
repod-file package import <package> <signature> <repo>
repod-file package import <package> <repo>
.. note::
All packages must either be provided with or without their accompanying
signatures. If signatures are present the packages and signatures are to be
provided in tuples of two (the first is always the package, the second always
the signature). The last parameter is always considered as the output
directory.
By default package signature files are not considered. To enforce the
locating and use of accompanying signature files, use the ``-s``/
``--with-signature`` flag.
The output of the above command may be displayed using the ``-d``/
``--dry-run`` flag (nothing is written to the output directory in this case).
......
......@@ -51,16 +51,19 @@ class ArgParseFactory:
"file",
nargs="+",
type=cls.string_to_file_path,
help=(
"package files and signatures "
"(the first file is always regarded as the package, the second as the signature)"
),
help="package files",
)
mutual_exclusive_inspect = package_inspect_parser.add_mutually_exclusive_group()
mutual_exclusive_inspect.add_argument("-B", "--buildinfo", action="store_true", help="only inspect .BUILDINFO")
mutual_exclusive_inspect.add_argument("-M", "--mtree", action="store_true", help="only inspect .MTREE")
mutual_exclusive_inspect.add_argument("-P", "--pkginfo", action="store_true", help="only inspect .PKGINFO")
package_inspect_parser.add_argument("-p", "--pretty", action="store_true", help="pretty print output")
package_inspect_parser.add_argument(
"-s",
"--with-signature",
action="store_true",
help="locate and use a signature file for each provided package file",
)
package_import_parser = package_subcommands.add_parser(
name="import",
......@@ -70,10 +73,7 @@ class ArgParseFactory:
"file",
nargs="+",
type=cls.string_to_file_path,
help=(
"package files and signatures "
"(the first file is always regarded as the package, the second as the signature)"
),
help="package files",
)
package_import_parser.add_argument(
"repo",
......@@ -87,6 +87,12 @@ class ArgParseFactory:
help="only show output, but do not write output to file",
)
package_import_parser.add_argument("-p", "--pretty", action="store_true", help="pretty print output")
package_import_parser.add_argument(
"-s",
"--with-signature",
action="store_true",
help="locate and use a signature file for each provided package file",
)
management = subcommands.add_parser(name="management", help="interact with management repositories")
management_subcommands = management.add_subparsers(dest="management")
......
import asyncio
from argparse import Namespace
from itertools import pairwise
from logging import DEBUG, INFO, WARNING, StreamHandler, debug, getLogger
from pathlib import Path
from sys import stdout
......@@ -30,30 +29,34 @@ def repod_file_package(args: Namespace) -> None:
pretty = ORJSON_OPTION if hasattr(args, "pretty") and args.pretty else 0
match args.package:
case "inspect":
model = asyncio.run(
Package.from_file(package=args.file[0], signature=args.file[1] if len(args.file) == 2 else None)
)
for package_path in args.file:
model = asyncio.run(
Package.from_file(
package=package_path,
signature=Path(str(package_path) + ".sig") if args.with_signature else None,
)
)
if args.buildinfo:
print(dumps(model.buildinfo.dict(), option=pretty).decode("utf-8")) # type: ignore[attr-defined]
elif args.mtree:
print(dumps(model.mtree.dict(), option=pretty).decode("utf-8")) # type: ignore[attr-defined]
elif args.pkginfo:
print(dumps(model.pkginfo.dict(), option=pretty).decode("utf-8")) # type: ignore[attr-defined]
else:
print(dumps(model.dict(), option=pretty).decode("utf-8"))
if args.buildinfo:
print(dumps(model.buildinfo.dict(), option=pretty).decode("utf-8")) # type: ignore[attr-defined]
elif args.mtree:
print(dumps(model.mtree.dict(), option=pretty).decode("utf-8")) # type: ignore[attr-defined]
elif args.pkginfo:
print(dumps(model.pkginfo.dict(), option=pretty).decode("utf-8")) # type: ignore[attr-defined]
else:
print(dumps(model.dict(), option=pretty).decode("utf-8"))
case "import":
packages: List[Package] = []
if any(file.suffix == ".sig" for file in args.file):
if len(args.file) % 2 != 0:
raise RuntimeError(
"If signatures for packages are provided, they need to be provided for all of them!"
for package_path in args.file:
packages.append(
asyncio.run(
Package.from_file(
package=package_path,
signature=Path(str(package_path) + ".sig") if args.with_signature else None,
)
)
for package_file, signature in pairwise(args.file):
packages.append(asyncio.run(Package.from_file(package=package_file, signature=signature)))
else:
for package_file in args.file:
packages.append(asyncio.run(Package.from_file(package=package_file)))
)
outputpackagebase = OutputPackageBase.from_package(packages=packages)
if args.dry_run:
......
......@@ -44,6 +44,12 @@ class Package(BaseModel):
signature: Optional[Path]
The optional path to a signature file for package
Raises
------
RepoManagementFileError
If the signature file does not match the package file.
If the signature file does not exist.
Returns
-------
Package
......@@ -57,6 +63,13 @@ class Package(BaseModel):
if signature:
debug(f"Opening signature file {signature} for reading...")
if not Path(str(package) + ".sig") == signature:
raise RepoManagementFileError(
f"The signature file for {package} should be {str(package) + '.sig'}, but {signature} is provided!"
)
if not signature.exists():
raise RepoManagementFileError(f"The signature file {signature} does not exist!")
with open(signature, "rb") as signature_file:
pgpsig = b64encode(signature_file.read()).decode("utf-8")
debug(f"Created pgpsig: {pgpsig}")
......
......@@ -12,19 +12,49 @@ from repod.cli import cli
@mark.parametrize(
"args, only_package, dup_package, expectation",
"args, expectation",
[
(Namespace(package="inspect", buildinfo=False, mtree=False, pkginfo=False), True, False, does_not_raise()),
(Namespace(package="inspect", buildinfo=True, mtree=False, pkginfo=False), True, False, does_not_raise()),
(Namespace(package="inspect", buildinfo=False, mtree=True, pkginfo=False), True, False, does_not_raise()),
(Namespace(package="inspect", buildinfo=False, mtree=False, pkginfo=True), True, False, does_not_raise()),
(Namespace(package="import", dry_run=True), True, False, does_not_raise()),
(Namespace(package="import", dry_run=False), True, False, does_not_raise()),
(Namespace(package="import", dry_run=True), False, False, does_not_raise()),
(Namespace(package="import", dry_run=False), False, False, does_not_raise()),
(Namespace(package="import", dry_run=True), False, True, raises(RuntimeError)),
(Namespace(package="import", dry_run=False), False, True, raises(RuntimeError)),
(Namespace(package="foo"), True, False, raises(RuntimeError)),
(
Namespace(package="inspect", buildinfo=False, mtree=False, pkginfo=False, with_signature=False),
does_not_raise(),
),
(
Namespace(package="inspect", buildinfo=True, mtree=False, pkginfo=False, with_signature=False),
does_not_raise(),
),
(
Namespace(package="inspect", buildinfo=False, mtree=True, pkginfo=False, with_signature=False),
does_not_raise(),
),
(
Namespace(package="inspect", buildinfo=False, mtree=False, pkginfo=True, with_signature=False),
does_not_raise(),
),
(
Namespace(package="inspect", buildinfo=False, mtree=False, pkginfo=False, with_signature=True),
does_not_raise(),
),
(
Namespace(package="inspect", buildinfo=True, mtree=False, pkginfo=False, with_signature=True),
does_not_raise(),
),
(
Namespace(package="inspect", buildinfo=False, mtree=True, pkginfo=False, with_signature=True),
does_not_raise(),
),
(
Namespace(package="inspect", buildinfo=False, mtree=False, pkginfo=True, with_signature=True),
does_not_raise(),
),
(Namespace(package="import", dry_run=True, with_signature=False), does_not_raise()),
(Namespace(package="import", dry_run=False, with_signature=False), does_not_raise()),
(Namespace(package="import", dry_run=True, with_signature=False), does_not_raise()),
(Namespace(package="import", dry_run=False, with_signature=False), does_not_raise()),
(Namespace(package="import", dry_run=True, with_signature=True), does_not_raise()),
(Namespace(package="import", dry_run=False, with_signature=True), does_not_raise()),
(Namespace(package="import", dry_run=True, with_signature=True), does_not_raise()),
(Namespace(package="import", dry_run=False, with_signature=True), does_not_raise()),
(Namespace(package="foo"), raises(RuntimeError)),
],
)
def test_repod_file_package(
......@@ -32,19 +62,12 @@ def test_repod_file_package(
default_package_file: Tuple[Path, ...],
tmp_path: Path,
args: Namespace,
only_package: bool,
dup_package: bool,
expectation: ContextManager[str],
) -> None:
caplog.set_level(DEBUG)
if args.package in ["inspect", "import"]:
if only_package:
args.file = [default_package_file[0]]
else:
args.file = [default_package_file[0], default_package_file[1]]
if dup_package:
args.file += [default_package_file[0]]
args.file = [default_package_file[0]]
if args.package == "import":
args.repo = tmp_path
......
from contextlib import nullcontext as does_not_raise
from logging import DEBUG
from pathlib import Path
from typing import Tuple
from typing import ContextManager, Tuple
from pytest import LogCaptureFixture, mark, raises
......@@ -9,26 +10,41 @@ from repod.files import package
@mark.parametrize(
"add_sig",
"add_sig, valid_sig_name, sig_exists, expectation",
[
(True),
(False),
(True, True, True, does_not_raise()),
(True, False, True, raises(RepoManagementFileError)),
(True, True, False, raises(RepoManagementFileError)),
(False, True, True, does_not_raise()),
],
)
async def test_package_from_file(
add_sig: bool,
valid_sig_name: bool,
sig_exists: bool,
expectation: ContextManager[str],
caplog: LogCaptureFixture,
default_package_file: Tuple[Path, ...],
default_sync_db_file: Tuple[Path],
) -> None:
caplog.set_level(DEBUG)
assert isinstance(
await package.Package.from_file(
package=default_package_file[0],
signature=default_package_file[1] if add_sig else None,
),
package.PackageV1,
)
signature = default_package_file[1]
if not sig_exists:
signature.unlink()
if not valid_sig_name:
signature = Path("foo")
with expectation:
assert isinstance(
await package.Package.from_file(
package=default_package_file[0],
signature=signature if add_sig else None,
),
package.PackageV1,
)
with raises(RepoManagementFileError):
await package.Package.from_file(package=default_sync_db_file[0])
......
Supports Markdown
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment