1
0
Fork 0

Move checks out of the cli module

This commit is contained in:
Maurizio Porrato 2023-08-07 10:17:56 +01:00
parent f72700feb9
commit aefe6d2abe
9 changed files with 106 additions and 94 deletions

View File

View File

@ -0,0 +1,47 @@
from typing import Iterator, Tuple
from semver import Version
from .. import Bundle
from ..utils import lookup_dict
def check_operator_name(bundle: Bundle) -> Iterator[Tuple[str, str]]:
name = bundle.annotations.get("operators.operatorframework.io.bundle.package.v1")
if name is None:
yield "fail", "Bundle does not define the operator name in annotations.yaml"
return
if name != bundle.csv_operator_name:
yield "fail", f"Operator name from annotations.yaml ({name}) does not match the name defined in the CSV ({bundle.csv_operator_name})"
if name != bundle.operator_name:
yield "warn", f"Operator name from annotations.yaml ({name}) does not match the operator's directory name ({bundle.operator_name})"
def check_image(bundle: Bundle) -> Iterator[Tuple[str, str]]:
try:
container_image = lookup_dict(bundle.csv, "metadata.annotations.containerImage")
if container_image is None:
yield "fail", "CSV doesn't define .metadata.annotations.containerImage"
return
deployments = lookup_dict(bundle.csv, "spec.install.spec.deployments")
if deployments is None:
yield "fail", "CSV doesn't define .spec.install.spec.deployments"
return
for deployment in deployments:
containers = lookup_dict(deployment, "spec.template.spec.containers", [])
if any(container_image == x.get("image") for x in containers):
return
yield "fail", f"container image {container_image} not used by any deployment"
except Exception as e:
yield "fail", str(e)
def check_semver(bundle: Bundle) -> Iterator[Tuple[str, str]]:
try:
_ = Version.parse(bundle.operator_version)
except ValueError:
yield "warn", f"Version from filesystem ({bundle.operator_version}) is not valid semver"
try:
_ = Version.parse(bundle.csv_operator_version)
except ValueError:
yield "warn", f"Version from CSV ({bundle.csv_operator_version}) is not valid semver"

View File

@ -0,0 +1,19 @@
from typing import Iterator, Tuple
from .. import Operator
def check_upgrade(operator: Operator) -> Iterator[Tuple[str, str]]:
all_channels = operator.channels | {operator.default_channel} - {None}
for channel in sorted(all_channels):
try:
channel_bundles = operator.channel_bundles(channel)
channel_head = operator.head(channel)
graph = operator.update_graph(channel)
dangling_bundles = {
x for x in channel_bundles if x not in graph and x != channel_head
}
if dangling_bundles:
yield "fail", f"Channel {channel} has dangling bundles: {dangling_bundles}."
except Exception as e:
yield "fail", str(e)

View File

@ -5,7 +5,7 @@
import logging
from functools import cached_property, total_ordering
from pathlib import Path
from typing import Any, Dict, Iterator, List, Optional, Set, Union, Tuple
from typing import Any, Dict, Iterator, List, Optional, Set, Tuple, Union
from semver import Version
@ -110,9 +110,9 @@ class Bundle:
"""
:return: The operator the bundle belongs to
"""
if self._parent is not None:
return self._parent
return Operator(self._bundle_path.parent)
if self._parent is None:
self._parent = Operator(self._bundle_path.parent)
return self._parent
def load_metadata(self, filename: str) -> Dict[str, Any]:
"""
@ -268,9 +268,9 @@ class Operator:
@property
def repo(self) -> "Repo":
if self._parent is not None:
return self._parent
return Repo(self._operator_path.parent.parent)
if self._parent is None:
self._parent = Repo(self._operator_path.parent.parent)
return self._parent
def all_bundles(self) -> Iterator[Bundle]:
"""

View File

@ -5,13 +5,13 @@
import argparse
import logging
from itertools import chain
from inspect import getmembers, isfunction
from pathlib import Path
from typing import Union, Dict, Any, Iterator, Tuple
from typing import Union
from semver import Version
from operator_repo.classes import Bundle, Operator, Repo
from .checks import bundle as bundle_checks
from .checks import operator as operator_checks
from .classes import Bundle, Operator, Repo
def parse_target(repo: Repo, target: str) -> Union[Operator, Bundle]:
@ -69,98 +69,29 @@ def action_list(repo_path, *what: str, recursive: bool = False) -> None:
_list(parse_target(repo, target), recursive)
def lookup_dict(
data: Dict[str, Any], path: str, default: Any = None, separator: str = "."
) -> Any:
keys = path.split(separator)
subtree = data
for key in keys:
if key not in subtree:
return default
subtree = subtree[key]
return subtree
def do_check_bundle_operator_name(bundle: Bundle) -> Iterator[Tuple[str, str]]:
name = bundle.annotations.get("operators.operatorframework.io.bundle.package.v1")
if name is None:
yield "fail", "Bundle does not define the operator name in annotations.yaml"
return
if name != bundle.csv_operator_name:
yield "fail", f"Operator name from annotations.yaml ({name}) does not match the name defined in the CSV ({bundle.csv_operator_name})"
if name != bundle.operator_name:
yield "warn", f"Operator name from annotations.yaml ({name}) does not match the operator's directory name ({bundle.operator_name})"
def do_check_bundle_image(bundle: Bundle) -> Iterator[Tuple[str, str]]:
try:
container_image = lookup_dict(bundle.csv, "metadata.annotations.containerImage")
if container_image is None:
yield "fail", "CSV doesn't define .metadata.annotations.containerImage"
return
deployments = lookup_dict(bundle.csv, "spec.install.spec.deployments")
if deployments is None:
yield "fail", "CSV doesn't define .spec.install.spec.deployments"
return
for deployment in deployments:
containers = lookup_dict(deployment, "spec.template.spec.containers", [])
if any(container_image == x.get("image") for x in containers):
return
yield "fail", f"container image {container_image} not used by any deployment"
except Exception as e:
yield "fail", str(e)
def do_check_bundle_semver(bundle: Bundle) -> Iterator[Tuple[str, str]]:
try:
_ = Version.parse(bundle.operator_version)
except ValueError:
yield "warn", f"Version from filesystem ({bundle.operator_version}) is not valid semver"
try:
_ = Version.parse(bundle.csv_operator_version)
except ValueError:
yield "warn", f"Version from CSV ({bundle.csv_operator_version}) is not valid semver"
def action_check_bundle(bundle: Bundle) -> None:
for result, message in chain(
do_check_bundle_semver(bundle),
do_check_bundle_operator_name(bundle),
do_check_bundle_image(bundle),
):
print(f"{result.upper()}: {bundle}: {message}")
def do_check_operator_upgrade(operator: Operator) -> Iterator[Tuple[str, str]]:
all_channels = operator.channels | {operator.default_channel} - {None}
for channel in sorted(all_channels):
try:
channel_bundles = operator.channel_bundles(channel)
channel_head = operator.head(channel)
graph = operator.update_graph(channel)
dangling_bundles = {
x for x in channel_bundles if x not in graph and x != channel_head
}
if dangling_bundles:
yield "fail", f"Channel {channel} has dangling bundles: {dangling_bundles}."
except Exception as e:
yield "fail", str(e)
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}")
def action_check_operator(operator: Operator) -> None:
for result, message in do_check_operator_upgrade(operator):
print(f"{result.upper()}: {operator}: {message}")
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}")
def action_check(repo_path: Path, *what: str, recursive: bool = False) -> None:
repo = Repo(repo_path)
for target in [parse_target(repo, x) for x in what] or sorted(repo):
print(f"Checking {target}")
if isinstance(target, Operator):
action_check_operator(target)
if recursive:
for bundle in sorted(target):
print(f"Checking {bundle}")
action_check_bundle(bundle)
elif isinstance(target, Bundle):
action_check_bundle(target)

View File

@ -4,14 +4,14 @@
import logging
from pathlib import Path
from typing import Any
from typing import Any, Dict
import yaml
from yaml.composer import ComposerError
from operator_repo.exceptions import OperatorRepoException
from yaml.parser import ParserError
from .exceptions import OperatorRepoException
log = logging.getLogger(__name__)
@ -48,3 +48,15 @@ def _load_yaml_strict(path: Path) -> Any:
def load_yaml(path: Path) -> Any:
"""Same as _load_yaml_strict but tries both .yaml and .yml extensions"""
return _load_yaml_strict(_find_yaml(path))
def lookup_dict(
data: Dict[str, Any], path: str, default: Any = None, separator: str = "."
) -> Any:
keys = path.split(separator)
subtree = data
for key in keys:
if key not in subtree:
return default
subtree = subtree[key]
return subtree

View File

@ -1,6 +1,7 @@
from pathlib import Path
import pytest
from operator_repo import Bundle, Repo
from operator_repo.exceptions import InvalidBundleException, InvalidOperatorException
from tests import bundle_files, create_files

View File

@ -1,6 +1,7 @@
from pathlib import Path
import pytest
from operator_repo import Repo
from operator_repo.exceptions import InvalidRepoException
from tests import bundle_files, create_files

View File

@ -1,6 +1,7 @@
from pathlib import Path
import pytest
from operator_repo.utils import load_yaml
from tests import create_files