feature(keyringctl): adding ci command to verify newly added certs
Currently only newly added certificates will be checked against the expectations as existing keys are not all fully compatible with those assumptions. New certificates are determined by using $CI_MERGE_REQUEST_DIFF_BASE_SHA as the base,
This commit is contained in:
parent
9733fbafd8
commit
a9e63edfa8
@ -21,6 +21,7 @@ from the provided data structure and to install it:
|
|||||||
Optional:
|
Optional:
|
||||||
* hopenpgp-tools (verify)
|
* hopenpgp-tools (verify)
|
||||||
* sq-keyring-linter (verify)
|
* sq-keyring-linter (verify)
|
||||||
|
* git (ci)
|
||||||
|
|
||||||
## Usage
|
## Usage
|
||||||
|
|
||||||
|
34
libkeyringctl/ci.py
Normal file
34
libkeyringctl/ci.py
Normal file
@ -0,0 +1,34 @@
|
|||||||
|
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
|
||||||
|
from os import environ
|
||||||
|
from pathlib import Path
|
||||||
|
from typing import List
|
||||||
|
|
||||||
|
from .git import git_changed_files
|
||||||
|
from .keyring import get_parent_cert_paths
|
||||||
|
from .keyring import verify
|
||||||
|
|
||||||
|
|
||||||
|
def ci(working_dir: Path, keyring_root: Path, project_root: Path) -> None:
|
||||||
|
"""Verify certificates against modern expectations using sq-keyring-linter and hokey
|
||||||
|
|
||||||
|
Currently only newly added certificates will be checked against the expectations as existing
|
||||||
|
keys are not all fully compatible with those assumptions.
|
||||||
|
New certificates are determined by using $CI_MERGE_REQUEST_DIFF_BASE_SHA as the base,
|
||||||
|
|
||||||
|
Parameters
|
||||||
|
----------
|
||||||
|
working_dir: A directory to use for temporary files
|
||||||
|
keyring_root: The keyring root directory to look up username shorthand sources
|
||||||
|
project_root: Path to the root of the git repository
|
||||||
|
"""
|
||||||
|
|
||||||
|
ci_merge_request_diff_base = environ.get("CI_MERGE_REQUEST_DIFF_BASE_SHA")
|
||||||
|
created, deleted, changed = git_changed_files(
|
||||||
|
git_path=project_root, base=f"{ci_merge_request_diff_base}", paths=[Path("keyring")]
|
||||||
|
)
|
||||||
|
|
||||||
|
added_certificates: List[Path] = list(get_parent_cert_paths(paths=created))
|
||||||
|
|
||||||
|
if added_certificates:
|
||||||
|
verify(working_dir=working_dir, keyring_root=keyring_root, sources=added_certificates)
|
@ -8,6 +8,7 @@ from pathlib import Path
|
|||||||
from tempfile import TemporaryDirectory
|
from tempfile import TemporaryDirectory
|
||||||
from tempfile import mkdtemp
|
from tempfile import mkdtemp
|
||||||
|
|
||||||
|
from .ci import ci
|
||||||
from .keyring import Username
|
from .keyring import Username
|
||||||
from .keyring import build
|
from .keyring import build
|
||||||
from .keyring import convert
|
from .keyring import convert
|
||||||
@ -114,6 +115,11 @@ verify_parser.add_argument(
|
|||||||
)
|
)
|
||||||
verify_parser.set_defaults(lint_hokey=True, lint_sq_keyring=True)
|
verify_parser.set_defaults(lint_hokey=True, lint_sq_keyring=True)
|
||||||
|
|
||||||
|
ci_parser = subcommands.add_parser(
|
||||||
|
"ci",
|
||||||
|
help="ci command to verify certain aspects and expectations in pipelines",
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
def main() -> None: # noqa: ignore=C901
|
def main() -> None: # noqa: ignore=C901
|
||||||
args = parser.parse_args()
|
args = parser.parse_args()
|
||||||
@ -123,6 +129,7 @@ def main() -> None: # noqa: ignore=C901
|
|||||||
|
|
||||||
# temporary working directory that gets auto cleaned
|
# temporary working directory that gets auto cleaned
|
||||||
with TemporaryDirectory(prefix="arch-keyringctl-") as tempdir:
|
with TemporaryDirectory(prefix="arch-keyringctl-") as tempdir:
|
||||||
|
project_root = Path(".").absolute()
|
||||||
keyring_root = Path("keyring").absolute()
|
keyring_root = Path("keyring").absolute()
|
||||||
working_dir = Path(tempdir)
|
working_dir = Path(tempdir)
|
||||||
debug(f"Working directory: {working_dir}")
|
debug(f"Working directory: {working_dir}")
|
||||||
@ -190,6 +197,8 @@ def main() -> None: # noqa: ignore=C901
|
|||||||
lint_hokey=args.lint_hokey,
|
lint_hokey=args.lint_hokey,
|
||||||
lint_sq_keyring=args.lint_sq_keyring,
|
lint_sq_keyring=args.lint_sq_keyring,
|
||||||
)
|
)
|
||||||
|
elif "ci" == args.subcommand:
|
||||||
|
ci(working_dir=working_dir, keyring_root=keyring_root, project_root=project_root)
|
||||||
else:
|
else:
|
||||||
parser.print_help()
|
parser.print_help()
|
||||||
|
|
||||||
|
55
libkeyringctl/git.py
Normal file
55
libkeyringctl/git.py
Normal file
@ -0,0 +1,55 @@
|
|||||||
|
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
|
||||||
|
from pathlib import Path
|
||||||
|
from typing import List
|
||||||
|
from typing import Optional
|
||||||
|
from typing import Tuple
|
||||||
|
|
||||||
|
from .util import system
|
||||||
|
|
||||||
|
|
||||||
|
def git_changed_files(
|
||||||
|
git_path: Optional[Path], base: Optional[str], paths: Optional[List[Path]] = None
|
||||||
|
) -> Tuple[List[Path], List[Path], List[Path]]:
|
||||||
|
"""Returns lists of created, deleted and changed files based on diff stats related to a base commit
|
||||||
|
and optional paths.
|
||||||
|
|
||||||
|
Parameters
|
||||||
|
----------
|
||||||
|
git_path: Path to the git repository, current directory by default
|
||||||
|
base: Optional base rev or current index by default
|
||||||
|
paths: Optional list of paths to take into account, unfiltered by default
|
||||||
|
|
||||||
|
Returns
|
||||||
|
-------
|
||||||
|
Lists of created, deleted and changed paths
|
||||||
|
"""
|
||||||
|
cmd = ["git"]
|
||||||
|
if git_path:
|
||||||
|
cmd += ["-C", str(git_path)]
|
||||||
|
cmd += ["--no-pager", "diff", "--color=never", "--summary", "--numstat"]
|
||||||
|
if base:
|
||||||
|
cmd += [base]
|
||||||
|
if paths:
|
||||||
|
cmd += ["--"]
|
||||||
|
cmd += [str(path) for path in paths]
|
||||||
|
|
||||||
|
result: str = system(cmd)
|
||||||
|
|
||||||
|
created: List[Path] = []
|
||||||
|
deleted: List[Path] = []
|
||||||
|
changed: List[Path] = []
|
||||||
|
|
||||||
|
for line in result.splitlines():
|
||||||
|
line = line.strip()
|
||||||
|
if line.startswith("create"):
|
||||||
|
created.append(Path(line.split(maxsplit=3)[3]))
|
||||||
|
continue
|
||||||
|
if line.startswith("delete"):
|
||||||
|
deleted.append(Path(line.split(maxsplit=3)[3]))
|
||||||
|
continue
|
||||||
|
changed.append(Path(line.split(maxsplit=2)[2]))
|
||||||
|
|
||||||
|
changed = [path for path in changed if path not in created and path not in deleted]
|
||||||
|
|
||||||
|
return created, deleted, changed
|
@ -57,7 +57,7 @@ def get_cert_paths(paths: Iterable[Path]) -> Set[Path]:
|
|||||||
|
|
||||||
Returns
|
Returns
|
||||||
-------
|
-------
|
||||||
The list of paths to certificates
|
A set of paths to certificates
|
||||||
"""
|
"""
|
||||||
|
|
||||||
# depth first search certificate paths
|
# depth first search certificate paths
|
||||||
@ -73,6 +73,31 @@ def get_cert_paths(paths: Iterable[Path]) -> Set[Path]:
|
|||||||
return cert_paths
|
return cert_paths
|
||||||
|
|
||||||
|
|
||||||
|
def get_parent_cert_paths(paths: Iterable[Path]) -> Set[Path]:
|
||||||
|
"""Walks a list of paths upwards and resolves all discovered parent certificate paths
|
||||||
|
|
||||||
|
Parameters
|
||||||
|
----------
|
||||||
|
paths: A list of paths to walk and resolve to certificate paths.
|
||||||
|
|
||||||
|
Returns
|
||||||
|
-------
|
||||||
|
A set of paths to certificates
|
||||||
|
"""
|
||||||
|
|
||||||
|
# depth first search certificate paths
|
||||||
|
cert_paths: Set[Path] = set()
|
||||||
|
visit: List[Path] = list(paths)
|
||||||
|
while visit:
|
||||||
|
node = visit.pop().parent
|
||||||
|
# this level contains a certificate, abort depth search
|
||||||
|
if "keyring" == node.parent.parent.parent.name:
|
||||||
|
cert_paths.add(node)
|
||||||
|
continue
|
||||||
|
visit.append(node)
|
||||||
|
return cert_paths
|
||||||
|
|
||||||
|
|
||||||
def transform_username_to_keyring_path(keyring_dir: Path, paths: List[Path]) -> None:
|
def transform_username_to_keyring_path(keyring_dir: Path, paths: List[Path]) -> None:
|
||||||
"""Mutates the input sources by transforming passed usernames to keyring paths
|
"""Mutates the input sources by transforming passed usernames to keyring paths
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user