2023-08-03 08:52:14 +00:00
|
|
|
#!/usr/bin/env python3
|
|
|
|
"""
|
|
|
|
CLI tool to handle operator repositories
|
|
|
|
"""
|
|
|
|
|
|
|
|
import argparse
|
|
|
|
import logging
|
2023-08-07 09:17:56 +00:00
|
|
|
from inspect import getmembers, isfunction
|
2023-08-03 08:52:14 +00:00
|
|
|
from pathlib import Path
|
2023-08-07 09:17:56 +00:00
|
|
|
from typing import Union
|
2023-08-03 08:52:14 +00:00
|
|
|
|
2023-08-07 09:17:56 +00:00
|
|
|
from .checks import bundle as bundle_checks
|
|
|
|
from .checks import operator as operator_checks
|
|
|
|
from .classes import Bundle, Operator, Repo
|
2023-08-03 08:52:14 +00:00
|
|
|
|
|
|
|
|
|
|
|
def parse_target(repo: Repo, target: str) -> Union[Operator, Bundle]:
|
|
|
|
if "/" in target:
|
|
|
|
operator_name, operator_version = target.split("/", 1)
|
|
|
|
return repo.operator(operator_name).bundle(operator_version)
|
|
|
|
return repo.operator(target)
|
|
|
|
|
|
|
|
|
|
|
|
def indent(depth: int) -> str:
|
|
|
|
return " " * depth
|
|
|
|
|
|
|
|
|
|
|
|
def _list(
|
|
|
|
target: Union[Repo, Operator, Bundle], recursive: bool = False, depth: int = 0
|
|
|
|
) -> None:
|
|
|
|
if isinstance(target, Repo):
|
|
|
|
print(indent(depth) + str(target))
|
|
|
|
for operator in target:
|
|
|
|
if recursive:
|
|
|
|
_list(operator, True, depth + 1)
|
|
|
|
else:
|
|
|
|
print(indent(depth + 1) + str(operator))
|
|
|
|
elif isinstance(target, Operator):
|
|
|
|
print(indent(depth) + str(target))
|
|
|
|
for bundle in target:
|
|
|
|
if recursive:
|
|
|
|
_list(bundle, True, depth + 1)
|
|
|
|
else:
|
|
|
|
print(indent(depth + 1) + str(bundle))
|
|
|
|
elif isinstance(target, Bundle):
|
|
|
|
print(indent(depth) + str(target))
|
|
|
|
csv_annotations = target.csv.get("metadata", {}).get("annotations", {})
|
|
|
|
info = [
|
|
|
|
("Description", csv_annotations.get("description", "")),
|
|
|
|
("Name", f"{target.csv_operator_name}.{target.csv_operator_version}"),
|
|
|
|
("Channels", ", ".join(target.channels)),
|
|
|
|
("Default channel", target.default_channel),
|
|
|
|
("Container image", csv_annotations.get("containerImage", "")),
|
2023-08-03 17:23:45 +00:00
|
|
|
("Replaces", target.csv.get("spec", {}).get("replaces", "")),
|
|
|
|
("Skips", target.csv.get("spec", {}).get("skips", [])),
|
2023-08-03 08:52:14 +00:00
|
|
|
]
|
|
|
|
max_width = max([len(key) for key, _ in info])
|
|
|
|
for key, value in info:
|
|
|
|
message = f"{key.ljust(max_width+1)}: {value}"
|
|
|
|
print(indent(depth + 1) + message)
|
|
|
|
|
|
|
|
|
|
|
|
def action_list(repo_path, *what: str, recursive: bool = False) -> None:
|
|
|
|
repo = Repo(repo_path)
|
|
|
|
if not what:
|
|
|
|
_list(repo, recursive)
|
|
|
|
else:
|
|
|
|
for target in what:
|
|
|
|
_list(parse_target(repo, target), recursive)
|
|
|
|
|
|
|
|
|
2023-08-03 17:23:45 +00:00
|
|
|
def action_check_bundle(bundle: Bundle) -> None:
|
2023-08-07 09:17:56 +00:00
|
|
|
print(f"Checking {bundle}")
|
|
|
|
for check_name, check in getmembers(bundle_checks, isfunction):
|
|
|
|
if check_name.startswith("check_"):
|
|
|
|
for result, message in check(bundle):
|
|
|
|
print(f"{result.upper()}: {bundle}: {message}")
|
2023-08-03 17:23:45 +00:00
|
|
|
|
|
|
|
|
|
|
|
def action_check_operator(operator: Operator) -> None:
|
2023-08-07 09:17:56 +00:00
|
|
|
print(f"Checking {operator}")
|
|
|
|
for check_name, check in getmembers(operator_checks, isfunction):
|
|
|
|
if check_name.startswith("check_"):
|
|
|
|
for result, message in check(operator):
|
|
|
|
print(f"{result.upper()}: {operator}: {message}")
|
2023-08-03 12:47:39 +00:00
|
|
|
|
|
|
|
|
2023-08-03 17:23:45 +00:00
|
|
|
def action_check(repo_path: Path, *what: str, recursive: bool = False) -> None:
|
2023-08-03 12:47:39 +00:00
|
|
|
repo = Repo(repo_path)
|
2023-08-03 17:23:45 +00:00
|
|
|
for target in [parse_target(repo, x) for x in what] or sorted(repo):
|
2023-08-03 12:47:39 +00:00
|
|
|
if isinstance(target, Operator):
|
2023-08-03 17:23:45 +00:00
|
|
|
action_check_operator(target)
|
|
|
|
if recursive:
|
|
|
|
for bundle in sorted(target):
|
|
|
|
action_check_bundle(bundle)
|
2023-08-03 12:47:39 +00:00
|
|
|
elif isinstance(target, Bundle):
|
2023-08-03 17:23:45 +00:00
|
|
|
action_check_bundle(target)
|
2023-08-03 08:52:14 +00:00
|
|
|
|
|
|
|
|
2023-08-08 08:06:11 +00:00
|
|
|
def action_check_list() -> None:
|
|
|
|
for check_type_name, check_type in (
|
|
|
|
("Operator", operator_checks),
|
|
|
|
("Bundle", bundle_checks),
|
|
|
|
):
|
|
|
|
print(f"{check_type_name} checks:")
|
|
|
|
for check_name, check in getmembers(check_type, isfunction):
|
|
|
|
if check_name.startswith("check_"):
|
|
|
|
display_name = check_name.removeprefix("check_")
|
|
|
|
print(f" - {display_name}: {check.__doc__}")
|
|
|
|
|
|
|
|
|
2023-08-03 08:52:14 +00:00
|
|
|
def main() -> None:
|
|
|
|
main_parser = argparse.ArgumentParser(
|
|
|
|
description="Operator repository manipulation tool",
|
|
|
|
)
|
|
|
|
main_parser.add_argument(
|
|
|
|
"-r", "--repo", help="path to the root of the operator repository", type=Path
|
|
|
|
)
|
|
|
|
main_parser.add_argument(
|
|
|
|
"-v", "--verbose", action="count", default=0, help="increase log verbosity"
|
|
|
|
)
|
|
|
|
main_subparsers = main_parser.add_subparsers(dest="action")
|
|
|
|
|
|
|
|
# list
|
|
|
|
list_parser = main_subparsers.add_parser(
|
2023-08-03 17:23:45 +00:00
|
|
|
"list", aliases=["ls"], help="list contents of repo, operators or bundles"
|
2023-08-03 08:52:14 +00:00
|
|
|
)
|
|
|
|
list_parser.add_argument(
|
|
|
|
"-R", "--recursive", action="store_true", help="descend the tree"
|
|
|
|
)
|
|
|
|
list_parser.add_argument(
|
|
|
|
"target",
|
|
|
|
nargs="*",
|
|
|
|
help="name of the repos or bundles to list; if omitted, list the contents of the repo",
|
|
|
|
)
|
|
|
|
|
|
|
|
# check_bundle
|
2023-08-03 12:47:39 +00:00
|
|
|
check_parser = main_subparsers.add_parser(
|
2023-08-03 17:23:45 +00:00
|
|
|
"check",
|
|
|
|
help="check validity of an operator or bundle",
|
|
|
|
)
|
2023-08-08 08:06:11 +00:00
|
|
|
check_parser.add_argument(
|
|
|
|
"--list", action="store_true", help="list available checks"
|
|
|
|
)
|
2023-08-03 17:23:45 +00:00
|
|
|
check_parser.add_argument(
|
|
|
|
"-R", "--recursive", action="store_true", help="descend the tree"
|
2023-08-03 08:52:14 +00:00
|
|
|
)
|
2023-08-03 12:47:39 +00:00
|
|
|
check_parser.add_argument(
|
2023-08-03 08:52:14 +00:00
|
|
|
"target",
|
|
|
|
nargs="*",
|
2023-08-03 12:47:39 +00:00
|
|
|
help="name of the operators or bundles to check",
|
2023-08-03 08:52:14 +00:00
|
|
|
)
|
|
|
|
|
|
|
|
args = main_parser.parse_args()
|
|
|
|
# print(args)
|
|
|
|
|
|
|
|
verbosity = {0: logging.ERROR, 1: logging.WARNING, 2: logging.INFO}
|
|
|
|
log = logging.getLogger(__package__)
|
|
|
|
log.setLevel(verbosity.get(args.verbose, logging.DEBUG))
|
|
|
|
handler = logging.StreamHandler()
|
|
|
|
handler.setFormatter(
|
|
|
|
logging.Formatter("%(asctime)s %(name)-12s %(levelname)-8s %(message)s")
|
|
|
|
)
|
|
|
|
log.addHandler(handler)
|
|
|
|
|
2023-08-03 17:23:45 +00:00
|
|
|
if args.action in ("list", "ls"):
|
2023-08-03 08:52:14 +00:00
|
|
|
action_list(args.repo or Path.cwd(), *args.target, recursive=args.recursive)
|
2023-08-03 12:47:39 +00:00
|
|
|
elif args.action == "check":
|
2023-08-08 08:06:11 +00:00
|
|
|
if args.list:
|
|
|
|
action_check_list()
|
|
|
|
else:
|
|
|
|
action_check(
|
|
|
|
args.repo or Path.cwd(), *args.target, recursive=args.recursive
|
|
|
|
)
|
2023-08-03 08:52:14 +00:00
|
|
|
else:
|
|
|
|
main_parser.print_help()
|
|
|
|
|
|
|
|
|
|
|
|
if __name__ == "__main__":
|
|
|
|
main()
|