diff --git a/.gitignore b/.gitignore index b1ce0f97b..978316a91 100644 --- a/.gitignore +++ b/.gitignore @@ -149,3 +149,4 @@ ansible/vault-password* .pdm-python +integration-tests-config.yaml diff --git a/ansible/roles/config_ocp_cluster/tasks/main.yml b/ansible/roles/config_ocp_cluster/tasks/main.yml index e82fe9120..794e44db8 100644 --- a/ansible/roles/config_ocp_cluster/tasks/main.yml +++ b/ansible/roles/config_ocp_cluster/tasks/main.yml @@ -20,6 +20,7 @@ - chat ansible.builtin.include_vars: file: ../../vaults/pipelinerun-listener/secret-vars.yml + no_log: true - name: Include Chat trigger tags: diff --git a/integration-tests-config-sample.yaml b/integration-tests-config-sample.yaml new file mode 100644 index 000000000..223c2781b --- /dev/null +++ b/integration-tests-config-sample.yaml @@ -0,0 +1,22 @@ +--- + +operator_repository: + # The GitHub repository hosting the operators for integration tests + url: https://github.com/foo/operators-integration-tests + token: secret123 +contributor_repository: + # The GitHub repository hosting where to fork the integration tests repo and submit the PR from + url: https://github.com/bar/operators-integration-tests-fork + token: secret456 + ssh_key: ~/.ssh/id_rsa_alt +bundle_registry: &quay + # The container registry where the bundle and index images will be pushed to + base_ref: quay.io/foo + username: foo + password: secret789 +test_registry: *quay + # The container registry where to push the operator-pipeline image +iib: + # The iib instance to use to manipulate indices + url: https://iib.stage.engineering.redhat.com + keytab: /tmp/keytab diff --git a/operator-pipeline-images/operatorcert/entrypoints/integration_tests.py b/operator-pipeline-images/operatorcert/entrypoints/integration_tests.py new file mode 100644 index 000000000..0566db9f0 --- /dev/null +++ b/operator-pipeline-images/operatorcert/entrypoints/integration_tests.py @@ -0,0 +1,64 @@ +""" +Integration tests for the operator-pipelines project +""" + +import argparse +import logging +import sys +from pathlib import Path + +from operatorcert.integration.runner import run_integration_tests + +LOGGER = logging.getLogger("operator-cert") + + +def parse_args() -> argparse.Namespace: + """ + Parse command line arguments + + Returns: + Parsed arguments + """ + parser = argparse.ArgumentParser(description="Run integration tests") + parser.add_argument("--verbose", "-v", action="store_true", help="Verbose output") + parser.add_argument( + "--image", "-i", help="Skip image build and use alternate container image" + ) + parser.add_argument( + "directory", type=Path, help="operator-pipelines project directory" + ) + parser.add_argument("config_file", type=Path, help="Path to the yaml config file") + + return parser.parse_args() + + +def setup_logging(verbose: bool) -> None: + """ + Set up the logging configuration for the application. + + Args: + verbose (bool): If True, set the logging level to DEBUG; otherwise, set it to INFO. + + This function configures the logging format and level for the application, allowing for + detailed debug messages when verbose mode is enabled. + """ + + logging.basicConfig( + format="%(asctime)s %(name)-12s %(levelname)-8s %(message)s", + level=logging.DEBUG if verbose else logging.INFO, + ) + + +def main() -> int: + """ + Main function for integration tests runner + """ + args = parse_args() + setup_logging(args.verbose) + + # Logic + return run_integration_tests(args.directory, args.config_file, args.image) + + +if __name__ == "__main__": # pragma: no cover + sys.exit(main()) diff --git a/operator-pipeline-images/operatorcert/integration/__init__.py b/operator-pipeline-images/operatorcert/integration/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/operator-pipeline-images/operatorcert/integration/config.py b/operator-pipeline-images/operatorcert/integration/config.py new file mode 100644 index 000000000..c43978294 --- /dev/null +++ b/operator-pipeline-images/operatorcert/integration/config.py @@ -0,0 +1,66 @@ +""" +Schema of the integration tests configuration file +""" + +from pathlib import Path +from typing import Optional, Type, TypeVar + +from pydantic import BaseModel +from yaml import safe_load + + +class GitHubRepoConfig(BaseModel): + """ + A GitHub repository + """ + + url: str + token: Optional[str] = None + ssh_key: Optional[Path] = None + + +class ContainerRegistryConfig(BaseModel): + """ + A container registry + """ + + base_ref: str + username: Optional[str] = None + password: Optional[str] = None + + +class IIBConfig(BaseModel): + """ + An IIB API endpoint + """ + + url: str + keytab: Path + + +C = TypeVar("C", bound="Config") + + +class Config(BaseModel): + """ + Root configuration object + """ + + operator_repository: GitHubRepoConfig + contributor_repository: GitHubRepoConfig + bundle_registry: ContainerRegistryConfig + test_registry: ContainerRegistryConfig + iib: IIBConfig + + @classmethod + def from_yaml(cls: Type[C], path: Path) -> C: + """ + Parse a yaml configuration file + + Args: + path: path to the configuration file + + Returns: + the parsed configuration object + """ + return cls(**safe_load(path.read_text())) diff --git a/operator-pipeline-images/operatorcert/integration/external_tools.py b/operator-pipeline-images/operatorcert/integration/external_tools.py new file mode 100644 index 000000000..2c27e43aa --- /dev/null +++ b/operator-pipeline-images/operatorcert/integration/external_tools.py @@ -0,0 +1,197 @@ +""" +Utility classes to run external tools +""" + +import base64 +import json +import logging +import subprocess +import tempfile +from os import PathLike +from pathlib import Path +from typing import Mapping, Optional, Sequence, TypeAlias + +LOGGER = logging.getLogger("operator-cert") + + +CommandArg: TypeAlias = str | PathLike[str] + + +class Secret(str): + """ + A string with sensitive content that should not be logged + """ + + +def run( + *cmd: CommandArg, + cwd: Optional[CommandArg] = None, + env: Optional[Mapping[str, str]] = None, +) -> None: + """ + Execute an external command + + Args: + *cmd: The command and its arguments + cwd: Directory to run the command in, by default current working directory + env: Environment variables, if None the current environment is used + + Raises: + subprocess.CalledProcessError when the called process exits with a + non-zero status; the process' stdout and stderr can be obtained + from the exception object + """ + LOGGER.debug("Running %s from %s", cmd, cwd or Path.cwd()) + subprocess.run( + cmd, + stdout=subprocess.PIPE, + stderr=subprocess.STDOUT, + check=True, + cwd=cwd, + env=env, + ) + + +class Ansible: + """ + Utility class to interact with Ansible + """ + + def __init__(self, path: Optional[Path]) -> None: + """ + Initialize the Ansible instance + + Args: + path: The working directory for the ansible commands; + It must contain an ansible.cfg file + """ + self.path = (path or Path.cwd()).absolute() + # Simple sanity check to ensure the directory actually contains + # a copy of the operator-pipelines project + ansible_cfg_file = self.path / "ansible.cfg" + if not ansible_cfg_file.exists(): + raise FileNotFoundError(f"ansible.cfg not found in {self.path}") + + def playbook_path(self, playbook_name: str) -> Path: + """ + Return the path to the playbook file with the given name; this is specific + to the operator-pipelines project + """ + playbook_dir = self.path / "ansible" / "playbooks" + for ext in ("yml", "yaml"): + playbook_path = playbook_dir / f"{playbook_name}.{ext}" + if playbook_path.exists(): + return playbook_path + raise FileNotFoundError(f"Playbook {playbook_name} not found in {playbook_dir}") + + def run_playbook( + self, playbook: str, *extra_args: CommandArg, **extra_vars: str | Secret + ) -> None: + """ + Run an ansible playbook + + Args: + playbook: The name of the playbook to execute + *extra_args: Additional arguments for the ansible playbook + **extra_vars: Extra variables to pass to the playbook + """ + command: list[CommandArg] = ["ansible-playbook", self.playbook_path(playbook)] + command.extend(extra_args) + secrets = {} + for k, v in extra_vars.items(): + if isinstance(v, Secret): + # Avoid adding secrets to the command line + secrets[k] = str(v) + else: + command.extend(["-e", f"{k}={v}"]) + with tempfile.NamedTemporaryFile( + mode="w", + encoding="utf-8", + suffix=".json", + delete=True, + delete_on_close=False, + ) as tmp: + if secrets: + json.dump(secrets, tmp) + command.extend(["-e", f"@{tmp.name}"]) + tmp.close() + run(*command, cwd=self.path) + + +class Podman: + """ + Utility class to interact with Podman. + """ + + def __init__(self, auth: Optional[Mapping[str, tuple[str, str]]] = None): + """ + Initialize the Podman instance + + Args: + auth: The authentication credentials for registries + """ + self._auth = { + "auths": { + registry: { + "auth": base64.b64encode( + f"{username}:{password}".encode("utf-8") + ).decode("ascii") + } + for registry, (username, password) in (auth or {}).items() + if username and password + } + } + + def _run(self, *args: CommandArg) -> None: + """ + Run a podman subcommand + + Args: + *args: The podman subcommand and its arguments + """ + command: list[CommandArg] = ["podman"] + command.extend(args) + with tempfile.NamedTemporaryFile( + mode="w", + encoding="utf-8", + suffix=".json", + delete=True, + delete_on_close=False, + ) as tmp: + json.dump(self._auth, tmp) + tmp.close() + LOGGER.debug("Using podman auth file: %s", tmp.name) + run(*command, env={"REGISTRY_AUTH_FILE": tmp.name}) + + def build( + self, + context: CommandArg, + image: str, + containerfile: Optional[CommandArg] = None, + extra_args: Optional[Sequence[CommandArg]] = None, + ) -> None: + """ + Build an image + + Args: + context: Directory to build the image from + image: The name of the image to build + containerfile: The path to the container configuration file, + if not specified it will be inferred by podman + extra_args: Additional arguments for the podman build command + """ + command: list[CommandArg] = ["build", "-t", image, context] + if containerfile: + command.extend(["-f", containerfile]) + if extra_args: + command.extend(extra_args) + self._run(*command) + + def push(self, image: str) -> None: + """ + Push an image to a registry. + + Args: + image: The name of the image to push. + """ + self._run("push", image) diff --git a/operator-pipeline-images/operatorcert/integration/runner.py b/operator-pipeline-images/operatorcert/integration/runner.py new file mode 100644 index 000000000..bf5faa21a --- /dev/null +++ b/operator-pipeline-images/operatorcert/integration/runner.py @@ -0,0 +1,132 @@ +""" +Core module for the integration test framework +""" + +import logging +import subprocess +from datetime import datetime +from pathlib import Path +from typing import Mapping, Optional + +import openshift_client as oc + +from operatorcert.integration.config import Config +from operatorcert.integration.external_tools import Ansible, Podman, Secret + +LOGGER = logging.getLogger("operator-cert") + + +def _build_and_push_image( + project_dir: Path, image: str, auth: Optional[Mapping[str, tuple[str, str]]] = None +) -> None: + """ + Build the operator-pipelines container image and push it to the registry + + Args: + project_dir: base directory of the operator-pipeline project + image: tagged reference to the image on the destination container registry + auth: registry authentication information + """ + podman = Podman(auth) + LOGGER.info("Building image %s", image) + podman.build( + project_dir, + image, + project_dir / "operator-pipeline-images" / "Dockerfile", + # enforce a two days lifetime on the image pushed to quay + ["--label", "quay.expires-after=48h"], + ) + LOGGER.info("Pushing image %s", image) + podman.push(image) + + +def _deploy_pipelines(project_dir: Path, namespace: str, image: str) -> None: + """ + Deploy the operator-pipelines tekton components to the given namespace on + an openshift cluster; the current user must be already authenticated to the + openshift cluster + + Args: + project_dir: base directory of the operator-pipeline project + namespace: namespace to deploy to + image: reference to the operator-pipelines container image to use + """ + ansible = Ansible(project_dir) + LOGGER.info("Deploying pipelines to namespace %s", namespace) + ansible.run_playbook( + "deploy", + "-vv", + "--skip-tags", + "ci,import-index-images", + "--vault-password-file", + "ansible/vault-password", + oc_namespace=namespace, + operator_pipeline_image_pull_spec=image, + suffix="inttest", + ocp_token=Secret(oc.get_auth_token()), + env="stage", + branch=namespace, + ) + + +def run_integration_tests( + project_dir: Path, config_path: Path, image: Optional[str] = None +) -> int: + """ + Run the integration tests + + Args: + project_dir: base directory of the operator-pipeline project + config_path: path to yaml config file containing the integration tests parameters + image: tagged reference to the image on the destination container registry + + Returns: + 0 on success, 1 on failure + """ + if not config_path.exists(): + LOGGER.fatal("Config file %s does not exist", config_path) + return 1 + if not project_dir.exists(): + LOGGER.fatal("Directory %s does not exist", config_path) + return 1 + oc_context = oc.get_config_context() + if oc_context is None: + LOGGER.fatal( + "A valid connection to an OpenShift cluster is required: " + "please log into the cluster using the `oc login` command" + ) + return 1 + LOGGER.debug("Connected to %s", oc_context.strip()) + cfg = Config.from_yaml(config_path) + test_id = datetime.now().strftime("%Y%m%d%H%M%S") + namespace = f"inttest-{test_id}" + try: + if image: + tagged_image_name = image + else: + image_name = f"{cfg.test_registry.base_ref}/operator-pipeline-images" + tagged_image_name = f"{image_name}:{test_id}" + auth = ( + {image_name: (cfg.test_registry.username, cfg.test_registry.password)} + if cfg.test_registry.username and cfg.test_registry.password + else None + ) + _build_and_push_image( + project_dir, + tagged_image_name, + auth=auth, + ) + _deploy_pipelines(project_dir, namespace, tagged_image_name) + except subprocess.CalledProcessError as e: + LOGGER.error("Command %s failed:", e.cmd) + for line in e.stdout.decode("utf-8").splitlines(): + LOGGER.error("%s", line.rstrip()) + return 1 + except Exception as e: # pylint: disable=broad-except + LOGGER.error("%s", e) + return 1 + finally: + LOGGER.debug("Cleaning up namespace %s", namespace) + oc.delete_project(namespace, ignore_not_found=True) + LOGGER.info("Integration tests completed successfully") + return 0 diff --git a/operator-pipeline-images/tests/entrypoints/test_integration_tests.py b/operator-pipeline-images/tests/entrypoints/test_integration_tests.py new file mode 100644 index 000000000..f656879fc --- /dev/null +++ b/operator-pipeline-images/tests/entrypoints/test_integration_tests.py @@ -0,0 +1,55 @@ +import logging +from argparse import Namespace +from pathlib import Path +from unittest.mock import ANY, MagicMock, patch + +from operatorcert.entrypoints.integration_tests import ( + main, + parse_args, + setup_logging, +) + + +def test_parse_args() -> None: + args = [ + "integration-tests", + "-v", + "-i", + "quay.io/foo/bar:latest", + "/foo", + "/bar/config.yaml", + ] + with patch("sys.argv", args): + result = parse_args() + assert result.verbose == True + assert result.image == "quay.io/foo/bar:latest" + assert result.directory == Path("/foo") + assert result.config_file == Path("/bar/config.yaml") + + +@patch("operatorcert.entrypoints.integration_tests.logging.basicConfig") +def test_setup_logging(mock_basicconfig: MagicMock) -> None: + setup_logging(True) + mock_basicconfig.assert_called_once_with(format=ANY, level=logging.DEBUG) + + +@patch("operatorcert.entrypoints.integration_tests.parse_args") +@patch("operatorcert.entrypoints.integration_tests.setup_logging") +@patch("operatorcert.entrypoints.integration_tests.run_integration_tests") +def test_main( + mock_run_integration_tests: MagicMock, + mock_setup_logging: MagicMock, + mock_parse_args: MagicMock, +) -> None: + mock_parse_args.return_value = Namespace( + verbose=True, + image="quay.io/foo/bar:latest", + directory=Path("/foo"), + config_file=Path("/bar/config.yaml"), + ) + main() + mock_parse_args.assert_called_once() + mock_setup_logging.assert_called_once_with(True) + mock_run_integration_tests.assert_called_once_with( + Path("/foo"), Path("/bar/config.yaml"), "quay.io/foo/bar:latest" + ) diff --git a/operator-pipeline-images/tests/integration/__init__.py b/operator-pipeline-images/tests/integration/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/operator-pipeline-images/tests/integration/conftest.py b/operator-pipeline-images/tests/integration/conftest.py new file mode 100644 index 000000000..9ee99b2ae --- /dev/null +++ b/operator-pipeline-images/tests/integration/conftest.py @@ -0,0 +1,60 @@ +from pathlib import Path + +import pytest + + +@pytest.fixture +def operator_pipelines_path(tmp_path: Path) -> Path: + ansible_cfg = tmp_path / "ansible.cfg" + ansible_cfg.write_text( + """ +[defaults] +roles_path=./ansible/roles +inventory=./ansible/inventory/local +""" + ) + roles_dir = tmp_path / "ansible" / "roles" + inventory_dir = tmp_path / "ansible" / "inventory" + playbooks_dir = tmp_path / "ansible" / "playbooks" + roles_dir.mkdir(mode=0o755, parents=True, exist_ok=True) + inventory_dir.mkdir(mode=0o755, parents=True, exist_ok=True) + playbooks_dir.mkdir(mode=0o755, parents=True, exist_ok=True) + inventory = inventory_dir / "local" + inventory.write_text("localhost ansible_connection=local\n") + site_playbook = playbooks_dir / "site.yml" + site_playbook.write_text( + """ +--- +- hosts: localhost + tasks: [] +""" + ) + return tmp_path + + +@pytest.fixture +def integration_tests_config_file(tmp_path: Path) -> Path: + sample_config = """ +--- +operator_repository: + url: https://github.com/foo/bar + token: asdfg +contributor_repository: + url: https://github.com/foo/baz + token: something + ssh_key: ~/.ssh/id_rsa_alt +bundle_registry: + base_ref: quay.io/user1 + username: user1 + password: secret1 +test_registry: + base_ref: quay.io/user2 + username: user2 + password: secret2 +iib: + url: https://example.com/iib + keytab: /tmp/keytab +""" + cfg_file = tmp_path / "config.yaml" + cfg_file.write_text(sample_config) + return cfg_file diff --git a/operator-pipeline-images/tests/integration/test_config.py b/operator-pipeline-images/tests/integration/test_config.py new file mode 100644 index 000000000..5904a32ed --- /dev/null +++ b/operator-pipeline-images/tests/integration/test_config.py @@ -0,0 +1,20 @@ +from pathlib import Path + +from operatorcert.integration.config import Config + + +def test_Config(integration_tests_config_file: Path) -> None: + cfg = Config.from_yaml(integration_tests_config_file) + assert cfg.operator_repository.url == "https://github.com/foo/bar" + assert cfg.operator_repository.token == "asdfg" + assert cfg.contributor_repository.url == "https://github.com/foo/baz" + assert cfg.contributor_repository.token == "something" + assert cfg.contributor_repository.ssh_key == Path("~/.ssh/id_rsa_alt") + assert cfg.bundle_registry.base_ref == "quay.io/user1" + assert cfg.bundle_registry.username == "user1" + assert cfg.bundle_registry.password == "secret1" + assert cfg.test_registry.base_ref == "quay.io/user2" + assert cfg.test_registry.username == "user2" + assert cfg.test_registry.password == "secret2" + assert cfg.iib.url == "https://example.com/iib" + assert cfg.iib.keytab == Path("/tmp/keytab") diff --git a/operator-pipeline-images/tests/integration/test_external_tools.py b/operator-pipeline-images/tests/integration/test_external_tools.py new file mode 100644 index 000000000..a136a3966 --- /dev/null +++ b/operator-pipeline-images/tests/integration/test_external_tools.py @@ -0,0 +1,94 @@ +import subprocess +from pathlib import Path +from unittest.mock import ANY, MagicMock, patch + +import pytest +from operatorcert.integration.external_tools import Ansible, Podman, Secret, run + + +def test_Secret() -> None: + secret = Secret("password") + assert secret == "password" + assert type(secret) != str + + +@patch("subprocess.run") +def test_run(mock_run: MagicMock) -> None: + cmd = ("find", Path("/"), "-type", "f", "-empty") + run(*cmd, cwd=Path("/foo"), env={"FOO": "bar"}) + mock_run.assert_called_once_with( + cmd, + stdout=subprocess.PIPE, + stderr=subprocess.STDOUT, + check=True, + cwd=Path("/foo"), + env={"FOO": "bar"}, + ) + + +def test_Ansible___init__(operator_pipelines_path: Path) -> None: + ansible = Ansible(operator_pipelines_path) + assert ansible.path.resolve(strict=True) == operator_pipelines_path.resolve( + strict=True + ) + + +def test_Ansible___init___fail(tmp_path: Path) -> None: + with pytest.raises(FileNotFoundError): + _ = Ansible(tmp_path) + + +def test_Ansible_playbook_dir(operator_pipelines_path: Path) -> None: + ansible = Ansible(operator_pipelines_path) + assert ( + ansible.playbook_path("site") + == operator_pipelines_path / "ansible" / "playbooks" / "site.yml" + ) + with pytest.raises(FileNotFoundError): + _ = ansible.playbook_path("nonexistent") + + +@patch("operatorcert.integration.external_tools.run") +def test_Ansible_run_playbook( + mock_run: MagicMock, operator_pipelines_path: Path +) -> None: + ansible = Ansible(operator_pipelines_path) + ansible.run_playbook("site", "-vv", var1="foo", var2=Secret("bar")) + mock_run.assert_called_once() + assert len(mock_run.mock_calls[0].args) == 7 + assert mock_run.mock_calls[0].args[:6] == ( + "ansible-playbook", + operator_pipelines_path / "ansible" / "playbooks" / "site.yml", + "-vv", + "-e", + "var1=foo", + "-e", + ) + assert mock_run.mock_calls[0].args[6].startswith("@") + assert mock_run.mock_calls[0].kwargs == {"cwd": operator_pipelines_path} + + +@patch("operatorcert.integration.external_tools.run") +def test_Podman_build(mock_run: MagicMock) -> None: + podman = Podman() + podman.build(Path("/foo"), "quay.io/foo/bar", Path("/foo/Dockerfile"), ["-q"]) + mock_run.assert_called_once_with( + "podman", + "build", + "-t", + "quay.io/foo/bar", + Path("/foo"), + "-f", + Path("/foo/Dockerfile"), + "-q", + env=ANY, + ) + + +@patch("operatorcert.integration.external_tools.run") +def test_Podman_push(mock_run: MagicMock) -> None: + podman = Podman(auth={"quay.io/foo": ("username", "password")}) + podman.push("quay.io/foo/bar") + mock_run.assert_called_once() + assert mock_run.mock_calls[0].args == ("podman", "push", "quay.io/foo/bar") + assert "REGISTRY_AUTH_FILE" in mock_run.mock_calls[0].kwargs["env"] diff --git a/operator-pipeline-images/tests/integration/test_runner.py b/operator-pipeline-images/tests/integration/test_runner.py new file mode 100644 index 000000000..622161ece --- /dev/null +++ b/operator-pipeline-images/tests/integration/test_runner.py @@ -0,0 +1,138 @@ +import subprocess +from datetime import datetime +from pathlib import Path +from unittest.mock import MagicMock, patch + +from operatorcert.integration.runner import ( + _build_and_push_image, + _deploy_pipelines, + run_integration_tests, +) + + +@patch("operatorcert.integration.external_tools.Podman.build") +@patch("operatorcert.integration.external_tools.Podman.push") +def test__build_and_push_image(mock_push: MagicMock, mock_build: MagicMock) -> None: + _build_and_push_image( + Path("/foo"), + "quay.io/foo/bar:latest", + auth={"quay.io/foo": ("username", "password")}, + ) + mock_build.assert_called_once() + mock_push.assert_called_once() + + +@patch("operatorcert.integration.external_tools.Ansible.run_playbook") +@patch("openshift_client.get_auth_token") +def test__deploy_pipelines( + mock_get_auth_token: MagicMock, + mock_run_playbook: MagicMock, + operator_pipelines_path: Path, +) -> None: + mock_get_auth_token.return_value = "secret" + _deploy_pipelines(operator_pipelines_path, "namespace", "quay.io/foo/bar:latest") + mock_run_playbook.assert_called_once() + mock_get_auth_token.assert_called_once() + + +@patch("operatorcert.integration.runner._build_and_push_image") +@patch("operatorcert.integration.runner._deploy_pipelines") +@patch("openshift_client.delete_project") +@patch("openshift_client.get_config_context") +@patch("operatorcert.integration.runner.datetime") +def test_run_integration_tests( + mock_datetime: MagicMock, + mock_get_config_context: MagicMock, + mock_delete_project: MagicMock, + mock_deploy_pipelines: MagicMock, + mock_build_and_push_image: MagicMock, + operator_pipelines_path: Path, + integration_tests_config_file: Path, +) -> None: + mock_datetime.now.return_value = datetime(2024, 11, 25, 12, 34, 56) + mock_get_config_context.return_value = ( + "default/api-my-openshift-cluster:6443/user\n" + ) + + # Non-existent directory: fail + assert ( + run_integration_tests(Path("/doesnotexist"), integration_tests_config_file) == 1 + ) + mock_build_and_push_image.assert_not_called() + mock_deploy_pipelines.assert_not_called() + mock_build_and_push_image.reset_mock() + mock_deploy_pipelines.reset_mock() + + # Non-existent config file: fail + assert run_integration_tests(operator_pipelines_path, Path("/doesnotexist")) == 1 + mock_build_and_push_image.assert_not_called() + mock_deploy_pipelines.assert_not_called() + mock_build_and_push_image.reset_mock() + mock_deploy_pipelines.reset_mock() + + # Regular run - no image specified + assert ( + run_integration_tests(operator_pipelines_path, integration_tests_config_file) + == 0 + ) + mock_build_and_push_image.assert_called_once_with( + operator_pipelines_path, + "quay.io/user2/operator-pipeline-images:20241125123456", + auth={"quay.io/user2/operator-pipeline-images": ("user2", "secret2")}, + ) + mock_deploy_pipelines.assert_called_once_with( + operator_pipelines_path, + "inttest-20241125123456", + "quay.io/user2/operator-pipeline-images:20241125123456", + ) + mock_build_and_push_image.reset_mock() + mock_deploy_pipelines.reset_mock() + + # Regular run - custom image specified + assert ( + run_integration_tests( + operator_pipelines_path, + integration_tests_config_file, + "quay.io/foo/bar:latest", + ) + == 0 + ) + mock_build_and_push_image.assert_not_called() + mock_deploy_pipelines.assert_called_once_with( + operator_pipelines_path, + "inttest-20241125123456", + "quay.io/foo/bar:latest", + ) + mock_build_and_push_image.reset_mock() + mock_deploy_pipelines.reset_mock() + + # Ansible playbook failure - fail + mock_deploy_pipelines.side_effect = subprocess.CalledProcessError( + 1, ["ansible-playbook"], output=b"failed", stderr=b"" + ) + assert ( + run_integration_tests(operator_pipelines_path, integration_tests_config_file) + == 1 + ) + mock_build_and_push_image.reset_mock() + mock_deploy_pipelines.reset_mock() + mock_build_and_push_image.reset_mock() + mock_deploy_pipelines.reset_mock() + + # Other unexpected failures - fail + mock_deploy_pipelines.side_effect = Exception() + assert ( + run_integration_tests(operator_pipelines_path, integration_tests_config_file) + == 1 + ) + mock_build_and_push_image.reset_mock() + mock_deploy_pipelines.reset_mock() + + # Not logged into openshift - fail + mock_get_config_context.return_value = None + assert ( + run_integration_tests(operator_pipelines_path, integration_tests_config_file) + == 1 + ) + mock_build_and_push_image.assert_not_called() + mock_deploy_pipelines.assert_not_called() diff --git a/pdm.lock b/pdm.lock index bae5a149f..59e2b057a 100644 --- a/pdm.lock +++ b/pdm.lock @@ -5,7 +5,7 @@ groups = ["default", "operatorcert-dev", "tox"] strategy = ["inherit_metadata"] lock_version = "4.5.0" -content_hash = "sha256:e6831d82bb17e5360d6e40db83b947de25b4da5f3ca661923875e02f6fd923e4" +content_hash = "sha256:a385bde57b243f82841b52066f918192d661f4b83315cc1f6462b9d6f25ca3cb" [[metadata.targets]] requires_python = ">=3.10" @@ -15,7 +15,7 @@ name = "annotated-types" version = "0.7.0" requires_python = ">=3.8" summary = "Reusable constraint types to use with typing.Annotated" -groups = ["operatorcert-dev"] +groups = ["default", "operatorcert-dev"] dependencies = [ "typing-extensions>=4.0.0; python_version < \"3.9\"", ] @@ -24,6 +24,72 @@ files = [ {file = "annotated_types-0.7.0.tar.gz", hash = "sha256:aff07c09a53a08bc8cfccb9c85b05f1aa9a2a6f23728d790723543408344ce89"}, ] +[[package]] +name = "ansible-compat" +version = "24.10.0" +requires_python = ">=3.9" +summary = "Ansible compatibility goodies" +groups = ["operatorcert-dev"] +dependencies = [ + "PyYAML", + "ansible-core>=2.14", + "jsonschema>=4.6.0", + "packaging", + "subprocess-tee>=0.4.1", + "typing-extensions>=4.5.0; python_version < \"3.10\"", +] +files = [ + {file = "ansible_compat-24.10.0-py3-none-any.whl", hash = "sha256:0415bcd82f7d84e5b344c03439154c1f16576809dc3a523b81178354c86ae5a1"}, + {file = "ansible_compat-24.10.0.tar.gz", hash = "sha256:0ad873e0dae8b2de79bc33ced813d6c92c716c4d7b82f9a4693e1fd57f43776e"}, +] + +[[package]] +name = "ansible-core" +version = "2.17.6" +requires_python = ">=3.10" +summary = "Radically simple IT automation" +groups = ["operatorcert-dev"] +dependencies = [ + "PyYAML>=5.1", + "cryptography", + "jinja2>=3.0.0", + "packaging", + "resolvelib<1.1.0,>=0.5.3", +] +files = [ + {file = "ansible_core-2.17.6-py3-none-any.whl", hash = "sha256:dab09cd49fe7e17003e13188ce1ab52a0a6e5b88bc4bf29ff101cfdcb2862395"}, + {file = "ansible_core-2.17.6.tar.gz", hash = "sha256:3e53970b7cebfe2adb39b711c1e2f8bbfcbedac828da51dc0357a19070638e95"}, +] + +[[package]] +name = "ansible-lint" +version = "24.10.0" +requires_python = ">=3.10" +summary = "Checks playbooks for practices and behavior that could potentially be improved" +groups = ["operatorcert-dev"] +dependencies = [ + "ansible-compat>=24.10.0", + "ansible-core>=2.13.0", + "black>=24.3.0", + "filelock>=3.3.0", + "importlib-metadata", + "jsonschema>=4.10.0", + "packaging>=21.3", + "pathspec>=0.10.3", + "pyyaml>=5.4.1", + "rich>=12.0.0", + "ruamel-yaml>=0.18.5", + "subprocess-tee>=0.4.1", + "wcmatch>=8.1.2; python_version < \"3.12\"", + "wcmatch>=8.5.0; python_version >= \"3.12\"", + "will-not-work-on-windows-try-from-wsl-instead; platform_system == \"Windows\"", + "yamllint>=1.30.0", +] +files = [ + {file = "ansible_lint-24.10.0-py3-none-any.whl", hash = "sha256:1c4f4c3a79cc983edab9deb09cc898285cbee30658112d66f72cc9f67932d616"}, + {file = "ansible_lint-24.10.0.tar.gz", hash = "sha256:59b396bcd9be734f9ae40796e9660c267ae85f1f13e47811fd8abdc6aaffea5c"}, +] + [[package]] name = "argcomplete" version = "3.5.0" @@ -49,6 +115,20 @@ files = [ {file = "astroid-3.2.4.tar.gz", hash = "sha256:0e14202810b30da1b735827f78f5157be2bbd4a7a59b7707ca0bfc2fb4c0063a"}, ] +[[package]] +name = "attrs" +version = "24.2.0" +requires_python = ">=3.7" +summary = "Classes Without Boilerplate" +groups = ["operatorcert-dev"] +dependencies = [ + "importlib-metadata; python_version < \"3.8\"", +] +files = [ + {file = "attrs-24.2.0-py3-none-any.whl", hash = "sha256:81921eb96de3191c8258c199618104dd27ac608d9366f5e35d011eae1867ede2"}, + {file = "attrs-24.2.0.tar.gz", hash = "sha256:5cfb1b9148b5b086569baec03f20d7b6bf3bcacc9a42bebf87ffaaca362f6346"}, +] + [[package]] name = "authlib" version = "1.3.2" @@ -112,12 +192,23 @@ files = [ {file = "black-24.8.0.tar.gz", hash = "sha256:2500945420b6784c38b9ee885af039f5e7471ef284ab03fa35ecdde4688cd83f"}, ] +[[package]] +name = "bracex" +version = "2.5.post1" +requires_python = ">=3.8" +summary = "Bash style brace expander." +groups = ["operatorcert-dev"] +files = [ + {file = "bracex-2.5.post1-py3-none-any.whl", hash = "sha256:13e5732fec27828d6af308628285ad358047cec36801598368cb28bc631dbaf6"}, + {file = "bracex-2.5.post1.tar.gz", hash = "sha256:12c50952415bfa773d2d9ccb8e79651b8cdb1f31a42f6091b804f6ba2b4a66b6"}, +] + [[package]] name = "cachetools" version = "5.5.0" requires_python = ">=3.7" summary = "Extensible memoizing collections and decorators" -groups = ["tox"] +groups = ["operatorcert-dev", "tox"] files = [ {file = "cachetools-5.5.0-py3-none-any.whl", hash = "sha256:02134e8439cdc2ffb62023ce1debca2944c3f289d66bb17ead3ab3dede74b292"}, {file = "cachetools-5.5.0.tar.gz", hash = "sha256:2cc24fb4cbe39633fb7badd9db9ca6295d766d9c2995f245725a46715d050f2a"}, @@ -519,6 +610,16 @@ files = [ {file = "dparse-0.6.4b0.tar.gz", hash = "sha256:f8d49b41a527f3d16a269f854e6665245b325e50e41d2c213810cb984553e5c8"}, ] +[[package]] +name = "durationpy" +version = "0.9" +summary = "Module for converting between datetime.timedelta and Go's Duration strings." +groups = ["operatorcert-dev"] +files = [ + {file = "durationpy-0.9-py3-none-any.whl", hash = "sha256:e65359a7af5cedad07fb77a2dd3f390f8eb0b74cb845589fa6c057086834dd38"}, + {file = "durationpy-0.9.tar.gz", hash = "sha256:fd3feb0a69a0057d582ef643c355c40d2fa1c942191f914d12203b1a01ac722a"}, +] + [[package]] name = "exceptiongroup" version = "1.2.2" @@ -536,7 +637,7 @@ name = "filelock" version = "3.16.1" requires_python = ">=3.8" summary = "A platform independent file lock." -groups = ["tox"] +groups = ["operatorcert-dev", "tox"] files = [ {file = "filelock-3.16.1-py3-none-any.whl", hash = "sha256:2082e5703d51fbf98ea75855d9d5527e33d8ff23099bec374a134febee6946b0"}, {file = "filelock-3.16.1.tar.gz", hash = "sha256:c249fbfcd5db47e5e2d6d62198e565475ee65e4831e2561c8e313fa7eb961435"}, @@ -582,6 +683,22 @@ files = [ {file = "giturlparse-0.12.0.tar.gz", hash = "sha256:c0fff7c21acc435491b1779566e038757a205c1ffdcb47e4f81ea52ad8c3859a"}, ] +[[package]] +name = "google-auth" +version = "2.36.0" +requires_python = ">=3.7" +summary = "Google Authentication Library" +groups = ["operatorcert-dev"] +dependencies = [ + "cachetools<6.0,>=2.0.0", + "pyasn1-modules>=0.2.1", + "rsa<5,>=3.1.4", +] +files = [ + {file = "google_auth-2.36.0-py2.py3-none-any.whl", hash = "sha256:51a15d47028b66fd36e5c64a82d2d57480075bccc7da37cde257fc94177a61fb"}, + {file = "google_auth-2.36.0.tar.gz", hash = "sha256:545e9618f2df0bcbb7dcbc45a546485b1212624716975a1ea5ae8149ce769ab1"}, +] + [[package]] name = "gssapi" version = "1.8.3" @@ -630,6 +747,21 @@ files = [ {file = "idna-3.8.tar.gz", hash = "sha256:d838c2c0ed6fced7693d5e8ab8e734d5f8fda53a039c0164afb0b82e771e3603"}, ] +[[package]] +name = "importlib-metadata" +version = "8.5.0" +requires_python = ">=3.8" +summary = "Read metadata from Python packages" +groups = ["operatorcert-dev"] +dependencies = [ + "typing-extensions>=3.6.4; python_version < \"3.8\"", + "zipp>=3.20", +] +files = [ + {file = "importlib_metadata-8.5.0-py3-none-any.whl", hash = "sha256:45e54197d28b7a7f1559e60b95e7c567032b602131fbd588f1497f47880aa68b"}, + {file = "importlib_metadata-8.5.0.tar.gz", hash = "sha256:71522656f0abace1d072b9e5481a48f07c138e00f079c38c8f883823f9c26bd7"}, +] + [[package]] name = "iniconfig" version = "2.0.0" @@ -666,6 +798,50 @@ files = [ {file = "jinja2-3.1.4.tar.gz", hash = "sha256:4a3aee7acbbe7303aede8e9648d13b8bf88a429282aa6122a993f0ac800cb369"}, ] +[[package]] +name = "jmespath" +version = "1.0.1" +requires_python = ">=3.7" +summary = "JSON Matching Expressions" +groups = ["operatorcert-dev"] +files = [ + {file = "jmespath-1.0.1-py3-none-any.whl", hash = "sha256:02e2e4cc71b5bcab88332eebf907519190dd9e6e82107fa7f83b1003a6252980"}, + {file = "jmespath-1.0.1.tar.gz", hash = "sha256:90261b206d6defd58fdd5e85f478bf633a2901798906be2ad389150c5c60edbe"}, +] + +[[package]] +name = "jsonschema" +version = "4.23.0" +requires_python = ">=3.8" +summary = "An implementation of JSON Schema validation for Python" +groups = ["operatorcert-dev"] +dependencies = [ + "attrs>=22.2.0", + "importlib-resources>=1.4.0; python_version < \"3.9\"", + "jsonschema-specifications>=2023.03.6", + "pkgutil-resolve-name>=1.3.10; python_version < \"3.9\"", + "referencing>=0.28.4", + "rpds-py>=0.7.1", +] +files = [ + {file = "jsonschema-4.23.0-py3-none-any.whl", hash = "sha256:fbadb6f8b144a8f8cf9f0b89ba94501d143e50411a1278633f56a7acf7fd5566"}, + {file = "jsonschema-4.23.0.tar.gz", hash = "sha256:d71497fef26351a33265337fa77ffeb82423f3ea21283cd9467bb03999266bc4"}, +] + +[[package]] +name = "jsonschema-specifications" +version = "2024.10.1" +requires_python = ">=3.9" +summary = "The JSON Schema meta-schemas and vocabularies, exposed as a Registry" +groups = ["operatorcert-dev"] +dependencies = [ + "referencing>=0.31.0", +] +files = [ + {file = "jsonschema_specifications-2024.10.1-py3-none-any.whl", hash = "sha256:a09a0680616357d9a0ecf05c12ad234479f549239d0f5b55f3deea67475da9bf"}, + {file = "jsonschema_specifications-2024.10.1.tar.gz", hash = "sha256:0f38b83639958ce1152d02a7f062902c41c8fd20d558b0c34344292d417ae272"}, +] + [[package]] name = "krb5" version = "0.6.0" @@ -683,6 +859,30 @@ files = [ {file = "krb5-0.6.0.tar.gz", hash = "sha256:712ba092fbe3a28ec18820bb1b1ed2cc1037b75c5c7033f970c6a8c97bbd1209"}, ] +[[package]] +name = "kubernetes" +version = "31.0.0" +requires_python = ">=3.6" +summary = "Kubernetes python client" +groups = ["operatorcert-dev"] +dependencies = [ + "certifi>=14.05.14", + "durationpy>=0.7", + "google-auth>=1.0.1", + "oauthlib>=3.2.2", + "python-dateutil>=2.5.3", + "pyyaml>=5.4.1", + "requests", + "requests-oauthlib", + "six>=1.9.0", + "urllib3>=1.24.2", + "websocket-client!=0.40.0,!=0.41.*,!=0.42.*,>=0.32.0", +] +files = [ + {file = "kubernetes-31.0.0-py2.py3-none-any.whl", hash = "sha256:bf141e2d380c8520eada8b351f4e319ffee9636328c137aa432bc486ca1200e1"}, + {file = "kubernetes-31.0.0.tar.gz", hash = "sha256:28945de906c8c259c1ebe62703b56a03b714049372196f854105afe4e6d014c0"}, +] + [[package]] name = "markdown-it-py" version = "3.0.0" @@ -815,6 +1015,32 @@ files = [ {file = "mypy_extensions-1.0.0.tar.gz", hash = "sha256:75dbf8955dc00442a438fc4d0666508a9a97b6bd41aa2f0ffe9d2f2725af0782"}, ] +[[package]] +name = "oauthlib" +version = "3.2.2" +requires_python = ">=3.6" +summary = "A generic, spec-compliant, thorough implementation of the OAuth request-signing logic" +groups = ["operatorcert-dev"] +files = [ + {file = "oauthlib-3.2.2-py3-none-any.whl", hash = "sha256:8139f29aac13e25d502680e9e19963e83f16838d48a0d71c287fe40e7067fbca"}, + {file = "oauthlib-3.2.2.tar.gz", hash = "sha256:9859c40929662bec5d64f34d01c99e093149682a3f38915dc0655d5a633dd918"}, +] + +[[package]] +name = "openshift-client" +version = "2.0.4" +requires_python = ">=2.7" +summary = "OpenShift python client" +groups = ["default"] +dependencies = [ + "pyyaml", + "six", +] +files = [ + {file = "openshift-client-2.0.4.tar.gz", hash = "sha256:3fac20a093699f7a60fe79a1ba98dfb4f6e7fff09ffcb299b68439428e1e69c0"}, + {file = "openshift_client-2.0.4-py3-none-any.whl", hash = "sha256:c69d30e40752b468d4440d058d43dfba7a06f6c7c8ca630debab46879ed9d065"}, +] + [[package]] name = "operator-repo" version = "0.4.3" @@ -884,6 +1110,31 @@ files = [ {file = "pluggy-1.5.0.tar.gz", hash = "sha256:2cffa88e94fdc978c4c574f15f9e59b7f4201d439195c3715ca9e2486f1d0cf1"}, ] +[[package]] +name = "pyasn1" +version = "0.6.1" +requires_python = ">=3.8" +summary = "Pure-Python implementation of ASN.1 types and DER/BER/CER codecs (X.208)" +groups = ["operatorcert-dev"] +files = [ + {file = "pyasn1-0.6.1-py3-none-any.whl", hash = "sha256:0d632f46f2ba09143da3a8afe9e33fb6f92fa2320ab7e886e2d0f7672af84629"}, + {file = "pyasn1-0.6.1.tar.gz", hash = "sha256:6f580d2bdd84365380830acf45550f2511469f673cb4a5ae3857a3170128b034"}, +] + +[[package]] +name = "pyasn1-modules" +version = "0.4.1" +requires_python = ">=3.8" +summary = "A collection of ASN.1-based protocols modules" +groups = ["operatorcert-dev"] +dependencies = [ + "pyasn1<0.7.0,>=0.4.6", +] +files = [ + {file = "pyasn1_modules-0.4.1-py3-none-any.whl", hash = "sha256:49bfa96b45a292b711e986f222502c1c9a5e1f4e568fc30e2574a6c7d07838fd"}, + {file = "pyasn1_modules-0.4.1.tar.gz", hash = "sha256:c28e2dbf9c06ad61c71a075c7e0f9fd0f1b0bb2d2ad4377f240d33ac2ab60a7c"}, +] + [[package]] name = "pycparser" version = "2.22" @@ -897,88 +1148,95 @@ files = [ [[package]] name = "pydantic" -version = "2.9.1" +version = "2.10.0" requires_python = ">=3.8" summary = "Data validation using Python type hints" -groups = ["operatorcert-dev"] +groups = ["default", "operatorcert-dev"] dependencies = [ "annotated-types>=0.6.0", - "pydantic-core==2.23.3", - "typing-extensions>=4.12.2; python_version >= \"3.13\"", - "typing-extensions>=4.6.1; python_version < \"3.13\"", + "pydantic-core==2.27.0", + "typing-extensions>=4.12.2", ] files = [ - {file = "pydantic-2.9.1-py3-none-any.whl", hash = "sha256:7aff4db5fdf3cf573d4b3c30926a510a10e19a0774d38fc4967f78beb6deb612"}, - {file = "pydantic-2.9.1.tar.gz", hash = "sha256:1363c7d975c7036df0db2b4a61f2e062fbc0aa5ab5f2772e0ffc7191a4f4bce2"}, + {file = "pydantic-2.10.0-py3-none-any.whl", hash = "sha256:5e7807ba9201bdf61b1b58aa6eb690916c40a47acfb114b1b4fef3e7fd5b30fc"}, + {file = "pydantic-2.10.0.tar.gz", hash = "sha256:0aca0f045ff6e2f097f1fe89521115335f15049eeb8a7bef3dafe4b19a74e289"}, ] [[package]] name = "pydantic-core" -version = "2.23.3" +version = "2.27.0" requires_python = ">=3.8" summary = "Core functionality for Pydantic validation and serialization" -groups = ["operatorcert-dev"] +groups = ["default", "operatorcert-dev"] dependencies = [ "typing-extensions!=4.7.0,>=4.6.0", ] files = [ - {file = "pydantic_core-2.23.3-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:7f10a5d1b9281392f1bf507d16ac720e78285dfd635b05737c3911637601bae6"}, - {file = "pydantic_core-2.23.3-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:3c09a7885dd33ee8c65266e5aa7fb7e2f23d49d8043f089989726391dd7350c5"}, - {file = "pydantic_core-2.23.3-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6470b5a1ec4d1c2e9afe928c6cb37eb33381cab99292a708b8cb9aa89e62429b"}, - {file = "pydantic_core-2.23.3-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:9172d2088e27d9a185ea0a6c8cebe227a9139fd90295221d7d495944d2367700"}, - {file = "pydantic_core-2.23.3-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:86fc6c762ca7ac8fbbdff80d61b2c59fb6b7d144aa46e2d54d9e1b7b0e780e01"}, - {file = "pydantic_core-2.23.3-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f0cb80fd5c2df4898693aa841425ea1727b1b6d2167448253077d2a49003e0ed"}, - {file = "pydantic_core-2.23.3-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:03667cec5daf43ac4995cefa8aaf58f99de036204a37b889c24a80927b629cec"}, - {file = "pydantic_core-2.23.3-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:047531242f8e9c2db733599f1c612925de095e93c9cc0e599e96cf536aaf56ba"}, - {file = "pydantic_core-2.23.3-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:5499798317fff7f25dbef9347f4451b91ac2a4330c6669821c8202fd354c7bee"}, - {file = "pydantic_core-2.23.3-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:bbb5e45eab7624440516ee3722a3044b83fff4c0372efe183fd6ba678ff681fe"}, - {file = "pydantic_core-2.23.3-cp310-none-win32.whl", hash = "sha256:8b5b3ed73abb147704a6e9f556d8c5cb078f8c095be4588e669d315e0d11893b"}, - {file = "pydantic_core-2.23.3-cp310-none-win_amd64.whl", hash = "sha256:2b603cde285322758a0279995b5796d64b63060bfbe214b50a3ca23b5cee3e83"}, - {file = "pydantic_core-2.23.3-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:c889fd87e1f1bbeb877c2ee56b63bb297de4636661cc9bbfcf4b34e5e925bc27"}, - {file = "pydantic_core-2.23.3-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:ea85bda3189fb27503af4c45273735bcde3dd31c1ab17d11f37b04877859ef45"}, - {file = "pydantic_core-2.23.3-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a7f7f72f721223f33d3dc98a791666ebc6a91fa023ce63733709f4894a7dc611"}, - {file = "pydantic_core-2.23.3-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:2b2b55b0448e9da68f56b696f313949cda1039e8ec7b5d294285335b53104b61"}, - {file = "pydantic_core-2.23.3-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:c24574c7e92e2c56379706b9a3f07c1e0c7f2f87a41b6ee86653100c4ce343e5"}, - {file = "pydantic_core-2.23.3-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f2b05e6ccbee333a8f4b8f4d7c244fdb7a979e90977ad9c51ea31261e2085ce0"}, - {file = "pydantic_core-2.23.3-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e2c409ce1c219c091e47cb03feb3c4ed8c2b8e004efc940da0166aaee8f9d6c8"}, - {file = "pydantic_core-2.23.3-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:d965e8b325f443ed3196db890d85dfebbb09f7384486a77461347f4adb1fa7f8"}, - {file = "pydantic_core-2.23.3-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:f56af3a420fb1ffaf43ece3ea09c2d27c444e7c40dcb7c6e7cf57aae764f2b48"}, - {file = "pydantic_core-2.23.3-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:5b01a078dd4f9a52494370af21aa52964e0a96d4862ac64ff7cea06e0f12d2c5"}, - {file = "pydantic_core-2.23.3-cp311-none-win32.whl", hash = "sha256:560e32f0df04ac69b3dd818f71339983f6d1f70eb99d4d1f8e9705fb6c34a5c1"}, - {file = "pydantic_core-2.23.3-cp311-none-win_amd64.whl", hash = "sha256:c744fa100fdea0d000d8bcddee95213d2de2e95b9c12be083370b2072333a0fa"}, - {file = "pydantic_core-2.23.3-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:e0ec50663feedf64d21bad0809f5857bac1ce91deded203efc4a84b31b2e4305"}, - {file = "pydantic_core-2.23.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:db6e6afcb95edbe6b357786684b71008499836e91f2a4a1e55b840955b341dbb"}, - {file = "pydantic_core-2.23.3-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:98ccd69edcf49f0875d86942f4418a4e83eb3047f20eb897bffa62a5d419c8fa"}, - {file = "pydantic_core-2.23.3-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:a678c1ac5c5ec5685af0133262103defb427114e62eafeda12f1357a12140162"}, - {file = "pydantic_core-2.23.3-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:01491d8b4d8db9f3391d93b0df60701e644ff0894352947f31fff3e52bd5c801"}, - {file = "pydantic_core-2.23.3-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:fcf31facf2796a2d3b7fe338fe8640aa0166e4e55b4cb108dbfd1058049bf4cb"}, - {file = "pydantic_core-2.23.3-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7200fd561fb3be06827340da066df4311d0b6b8eb0c2116a110be5245dceb326"}, - {file = "pydantic_core-2.23.3-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:dc1636770a809dee2bd44dd74b89cc80eb41172bcad8af75dd0bc182c2666d4c"}, - {file = "pydantic_core-2.23.3-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:67a5def279309f2e23014b608c4150b0c2d323bd7bccd27ff07b001c12c2415c"}, - {file = "pydantic_core-2.23.3-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:748bdf985014c6dd3e1e4cc3db90f1c3ecc7246ff5a3cd4ddab20c768b2f1dab"}, - {file = "pydantic_core-2.23.3-cp312-none-win32.whl", hash = "sha256:255ec6dcb899c115f1e2a64bc9ebc24cc0e3ab097775755244f77360d1f3c06c"}, - {file = "pydantic_core-2.23.3-cp312-none-win_amd64.whl", hash = "sha256:40b8441be16c1e940abebed83cd006ddb9e3737a279e339dbd6d31578b802f7b"}, - {file = "pydantic_core-2.23.3-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:6daaf5b1ba1369a22c8b050b643250e3e5efc6a78366d323294aee54953a4d5f"}, - {file = "pydantic_core-2.23.3-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:d015e63b985a78a3d4ccffd3bdf22b7c20b3bbd4b8227809b3e8e75bc37f9cb2"}, - {file = "pydantic_core-2.23.3-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a3fc572d9b5b5cfe13f8e8a6e26271d5d13f80173724b738557a8c7f3a8a3791"}, - {file = "pydantic_core-2.23.3-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:f6bd91345b5163ee7448bee201ed7dd601ca24f43f439109b0212e296eb5b423"}, - {file = "pydantic_core-2.23.3-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:fc379c73fd66606628b866f661e8785088afe2adaba78e6bbe80796baf708a63"}, - {file = "pydantic_core-2.23.3-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:fbdce4b47592f9e296e19ac31667daed8753c8367ebb34b9a9bd89dacaa299c9"}, - {file = "pydantic_core-2.23.3-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fc3cf31edf405a161a0adad83246568647c54404739b614b1ff43dad2b02e6d5"}, - {file = "pydantic_core-2.23.3-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:8e22b477bf90db71c156f89a55bfe4d25177b81fce4aa09294d9e805eec13855"}, - {file = "pydantic_core-2.23.3-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:0a0137ddf462575d9bce863c4c95bac3493ba8e22f8c28ca94634b4a1d3e2bb4"}, - {file = "pydantic_core-2.23.3-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:203171e48946c3164fe7691fc349c79241ff8f28306abd4cad5f4f75ed80bc8d"}, - {file = "pydantic_core-2.23.3-cp313-none-win32.whl", hash = "sha256:76bdab0de4acb3f119c2a4bff740e0c7dc2e6de7692774620f7452ce11ca76c8"}, - {file = "pydantic_core-2.23.3-cp313-none-win_amd64.whl", hash = "sha256:37ba321ac2a46100c578a92e9a6aa33afe9ec99ffa084424291d84e456f490c1"}, - {file = "pydantic_core-2.23.3-pp310-pypy310_pp73-macosx_10_12_x86_64.whl", hash = "sha256:f399e8657c67313476a121a6944311fab377085ca7f490648c9af97fc732732d"}, - {file = "pydantic_core-2.23.3-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:6b5547d098c76e1694ba85f05b595720d7c60d342f24d5aad32c3049131fa5c4"}, - {file = "pydantic_core-2.23.3-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0dda0290a6f608504882d9f7650975b4651ff91c85673341789a476b1159f211"}, - {file = "pydantic_core-2.23.3-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:65b6e5da855e9c55a0c67f4db8a492bf13d8d3316a59999cfbaf98cc6e401961"}, - {file = "pydantic_core-2.23.3-pp310-pypy310_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:09e926397f392059ce0afdcac920df29d9c833256354d0c55f1584b0b70cf07e"}, - {file = "pydantic_core-2.23.3-pp310-pypy310_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:87cfa0ed6b8c5bd6ae8b66de941cece179281239d482f363814d2b986b79cedc"}, - {file = "pydantic_core-2.23.3-pp310-pypy310_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:e61328920154b6a44d98cabcb709f10e8b74276bc709c9a513a8c37a18786cc4"}, - {file = "pydantic_core-2.23.3-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:ce3317d155628301d649fe5e16a99528d5680af4ec7aa70b90b8dacd2d725c9b"}, - {file = "pydantic_core-2.23.3.tar.gz", hash = "sha256:3cb0f65d8b4121c1b015c60104a685feb929a29d7cf204387c7f2688c7974690"}, + {file = "pydantic_core-2.27.0-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:cd2ac6b919f7fed71b17fe0b4603c092a4c9b5bae414817c9c81d3c22d1e1bcc"}, + {file = "pydantic_core-2.27.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:e015833384ca3e1a0565a79f5d953b0629d9138021c27ad37c92a9fa1af7623c"}, + {file = "pydantic_core-2.27.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:db72e40628967f6dc572020d04b5f800d71264e0531c6da35097e73bdf38b003"}, + {file = "pydantic_core-2.27.0-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:df45c4073bed486ea2f18757057953afed8dd77add7276ff01bccb79982cf46c"}, + {file = "pydantic_core-2.27.0-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:836a4bfe0cc6d36dc9a9cc1a7b391265bf6ce9d1eb1eac62ac5139f5d8d9a6fa"}, + {file = "pydantic_core-2.27.0-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:4bf1340ae507f6da6360b24179c2083857c8ca7644aab65807023cf35404ea8d"}, + {file = "pydantic_core-2.27.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5ab325fc86fbc077284c8d7f996d904d30e97904a87d6fb303dce6b3de7ebba9"}, + {file = "pydantic_core-2.27.0-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:1da0c98a85a6c6ed702d5556db3b09c91f9b0b78de37b7593e2de8d03238807a"}, + {file = "pydantic_core-2.27.0-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:7b0202ebf2268954090209a84f9897345719e46a57c5f2c9b7b250ca0a9d3e63"}, + {file = "pydantic_core-2.27.0-cp310-cp310-musllinux_1_1_armv7l.whl", hash = "sha256:35380671c3c921fe8adf31ad349dc6f7588b7e928dbe44e1093789734f607399"}, + {file = "pydantic_core-2.27.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:6b4c19525c3538fbc0bbda6229f9682fb8199ce9ac37395880e6952798e00373"}, + {file = "pydantic_core-2.27.0-cp310-none-win32.whl", hash = "sha256:333c840a1303d1474f491e7be0b718226c730a39ead0f7dab2c7e6a2f3855555"}, + {file = "pydantic_core-2.27.0-cp310-none-win_amd64.whl", hash = "sha256:99b2863c1365f43f74199c980a3d40f18a218fbe683dd64e470199db426c4d6a"}, + {file = "pydantic_core-2.27.0-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:4523c4009c3f39d948e01962223c9f5538602e7087a628479b723c939fab262d"}, + {file = "pydantic_core-2.27.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:84af1cf7bfdcbc6fcf5a5f70cc9896205e0350306e4dd73d54b6a18894f79386"}, + {file = "pydantic_core-2.27.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e65466b31be1070b4a5b7dbfbd14b247884cb8e8b79c64fb0f36b472912dbaea"}, + {file = "pydantic_core-2.27.0-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:a5c022bb0d453192426221605efc865373dde43b17822a264671c53b068ac20c"}, + {file = "pydantic_core-2.27.0-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:6bb69bf3b6500f195c3deb69c1205ba8fc3cb21d1915f1f158a10d6b1ef29b6a"}, + {file = "pydantic_core-2.27.0-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:0aa4d1b2eba9a325897308b3124014a142cdccb9f3e016f31d3ebee6b5ea5e75"}, + {file = "pydantic_core-2.27.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8e96ca781e0c01e32115912ebdf7b3fb0780ce748b80d7d28a0802fa9fbaf44e"}, + {file = "pydantic_core-2.27.0-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:b872c86d8d71827235c7077461c502feb2db3f87d9d6d5a9daa64287d75e4fa0"}, + {file = "pydantic_core-2.27.0-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:82e1ad4ca170e8af4c928b67cff731b6296e6a0a0981b97b2eb7c275cc4e15bd"}, + {file = "pydantic_core-2.27.0-cp311-cp311-musllinux_1_1_armv7l.whl", hash = "sha256:eb40f828bc2f73f777d1eb8fee2e86cd9692a4518b63b6b5aa8af915dfd3207b"}, + {file = "pydantic_core-2.27.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:9a8fbf506fde1529a1e3698198fe64bfbe2e0c09557bc6a7dcf872e7c01fec40"}, + {file = "pydantic_core-2.27.0-cp311-none-win32.whl", hash = "sha256:24f984fc7762ed5f806d9e8c4c77ea69fdb2afd987b4fd319ef06c87595a8c55"}, + {file = "pydantic_core-2.27.0-cp311-none-win_amd64.whl", hash = "sha256:68950bc08f9735306322bfc16a18391fcaac99ded2509e1cc41d03ccb6013cfe"}, + {file = "pydantic_core-2.27.0-cp311-none-win_arm64.whl", hash = "sha256:3eb8849445c26b41c5a474061032c53e14fe92a11a5db969f722a2716cd12206"}, + {file = "pydantic_core-2.27.0-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:8117839a9bdbba86e7f9df57018fe3b96cec934c3940b591b0fd3fbfb485864a"}, + {file = "pydantic_core-2.27.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:a291d0b4243a259c8ea7e2b84eb9ccb76370e569298875a7c5e3e71baf49057a"}, + {file = "pydantic_core-2.27.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:84e35afd9e10b2698e6f2f32256678cb23ca6c1568d02628033a837638b3ed12"}, + {file = "pydantic_core-2.27.0-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:58ab0d979c969983cdb97374698d847a4acffb217d543e172838864636ef10d9"}, + {file = "pydantic_core-2.27.0-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:0d06b667e53320332be2bf6f9461f4a9b78092a079b8ce8634c9afaa7e10cd9f"}, + {file = "pydantic_core-2.27.0-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:78f841523729e43e3928a364ec46e2e3f80e6625a4f62aca5c345f3f626c6e8a"}, + {file = "pydantic_core-2.27.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:400bf470e4327e920883b51e255617dfe4496d4e80c3fea0b5a5d0bf2c404dd4"}, + {file = "pydantic_core-2.27.0-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:951e71da6c89d354572098bada5ba5b5dc3a9390c933af8a614e37755d3d1840"}, + {file = "pydantic_core-2.27.0-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:2a51ce96224eadd1845150b204389623c8e129fde5a67a84b972bd83a85c6c40"}, + {file = "pydantic_core-2.27.0-cp312-cp312-musllinux_1_1_armv7l.whl", hash = "sha256:483c2213a609e7db2c592bbc015da58b6c75af7360ca3c981f178110d9787bcf"}, + {file = "pydantic_core-2.27.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:359e7951f04ad35111b5ddce184db3391442345d0ab073aa63a95eb8af25a5ef"}, + {file = "pydantic_core-2.27.0-cp312-none-win32.whl", hash = "sha256:ee7d9d5537daf6d5c74a83b38a638cc001b648096c1cae8ef695b0c919d9d379"}, + {file = "pydantic_core-2.27.0-cp312-none-win_amd64.whl", hash = "sha256:2be0ad541bb9f059954ccf8877a49ed73877f862529575ff3d54bf4223e4dd61"}, + {file = "pydantic_core-2.27.0-cp312-none-win_arm64.whl", hash = "sha256:6e19401742ed7b69e51d8e4df3c03ad5ec65a83b36244479fd70edde2828a5d9"}, + {file = "pydantic_core-2.27.0-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:5f2b19b8d6fca432cb3acf48cf5243a7bf512988029b6e6fd27e9e8c0a204d85"}, + {file = "pydantic_core-2.27.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:c86679f443e7085ea55a7376462553996c688395d18ef3f0d3dbad7838f857a2"}, + {file = "pydantic_core-2.27.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:510b11e9c3b1a852876d1ccd8d5903684336d635214148637ceb27366c75a467"}, + {file = "pydantic_core-2.27.0-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:eb704155e73b833801c247f39d562229c0303f54770ca14fb1c053acb376cf10"}, + {file = "pydantic_core-2.27.0-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:9ce048deb1e033e7a865ca384770bccc11d44179cf09e5193a535c4c2f497bdc"}, + {file = "pydantic_core-2.27.0-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:58560828ee0951bb125c6f2862fbc37f039996d19ceb6d8ff1905abf7da0bf3d"}, + {file = "pydantic_core-2.27.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:abb4785894936d7682635726613c44578c420a096729f1978cd061a7e72d5275"}, + {file = "pydantic_core-2.27.0-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:2883b260f7a93235488699d39cbbd94fa7b175d3a8063fbfddd3e81ad9988cb2"}, + {file = "pydantic_core-2.27.0-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:c6fcb3fa3855d583aa57b94cf146f7781d5d5bc06cb95cb3afece33d31aac39b"}, + {file = "pydantic_core-2.27.0-cp313-cp313-musllinux_1_1_armv7l.whl", hash = "sha256:e851a051f7260e6d688267eb039c81f05f23a19431bd7dfa4bf5e3cb34c108cd"}, + {file = "pydantic_core-2.27.0-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:edb1bfd45227dec8d50bc7c7d86463cd8728bcc574f9b07de7369880de4626a3"}, + {file = "pydantic_core-2.27.0-cp313-none-win32.whl", hash = "sha256:678f66462058dd978702db17eb6a3633d634f7aa0deaea61e0a674152766d3fc"}, + {file = "pydantic_core-2.27.0-cp313-none-win_amd64.whl", hash = "sha256:d28ca7066d6cdd347a50d8b725dc10d9a1d6a1cce09836cf071ea6a2d4908be0"}, + {file = "pydantic_core-2.27.0-cp313-none-win_arm64.whl", hash = "sha256:6f4a53af9e81d757756508b57cae1cf28293f0f31b9fa2bfcb416cc7fb230f9d"}, + {file = "pydantic_core-2.27.0-pp310-pypy310_pp73-macosx_10_12_x86_64.whl", hash = "sha256:4fb49cfdb53af5041aba909be00cccfb2c0d0a2e09281bf542371c5fd36ad04c"}, + {file = "pydantic_core-2.27.0-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:49633583eb7dc5cba61aaf7cdb2e9e662323ad394e543ee77af265736bcd3eaa"}, + {file = "pydantic_core-2.27.0-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:153017e3d6cd3ce979de06d84343ca424bb6092727375eba1968c8b4693c6ecb"}, + {file = "pydantic_core-2.27.0-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ff63a92f6e249514ef35bc795de10745be0226eaea06eb48b4bbeaa0c8850a4a"}, + {file = "pydantic_core-2.27.0-pp310-pypy310_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:5982048129f40b082c2654de10c0f37c67a14f5ff9d37cf35be028ae982f26df"}, + {file = "pydantic_core-2.27.0-pp310-pypy310_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:91bc66f878557313c2a6bcf396e7befcffe5ab4354cfe4427318968af31143c3"}, + {file = "pydantic_core-2.27.0-pp310-pypy310_pp73-musllinux_1_1_armv7l.whl", hash = "sha256:68ef5377eb582fa4343c9d0b57a5b094046d447b4c73dd9fbd9ffb216f829e7d"}, + {file = "pydantic_core-2.27.0-pp310-pypy310_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:c5726eec789ee38f2c53b10b1821457b82274f81f4f746bb1e666d8741fcfadb"}, + {file = "pydantic_core-2.27.0-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:c0c431e4be5c1a0c6654e0c31c661cd89e0ca956ef65305c3c3fd96f4e72ca39"}, + {file = "pydantic_core-2.27.0.tar.gz", hash = "sha256:f57783fbaf648205ac50ae7d646f27582fc706be3977e87c3c124e7a92407b10"}, ] [[package]] @@ -1234,6 +1492,21 @@ files = [ {file = "pyyaml-6.0.2.tar.gz", hash = "sha256:d584d9ec91ad65861cc08d42e834324ef890a082e591037abe114850ff7bbc3e"}, ] +[[package]] +name = "referencing" +version = "0.35.1" +requires_python = ">=3.8" +summary = "JSON Referencing + Python" +groups = ["operatorcert-dev"] +dependencies = [ + "attrs>=22.2.0", + "rpds-py>=0.7.0", +] +files = [ + {file = "referencing-0.35.1-py3-none-any.whl", hash = "sha256:eda6d3234d62814d1c64e305c1331c9a3a6132da475ab6382eaa997b21ee75de"}, + {file = "referencing-0.35.1.tar.gz", hash = "sha256:25b42124a6c8b632a425174f24087783efb348a6f1e0008e63cd4466fedf703c"}, +] + [[package]] name = "requests" version = "2.32.3" @@ -1281,6 +1554,31 @@ files = [ {file = "requests_mock-1.12.1-py2.py3-none-any.whl", hash = "sha256:b1e37054004cdd5e56c84454cc7df12b25f90f382159087f4b6915aaeef39563"}, ] +[[package]] +name = "requests-oauthlib" +version = "2.0.0" +requires_python = ">=3.4" +summary = "OAuthlib authentication support for Requests." +groups = ["operatorcert-dev"] +dependencies = [ + "oauthlib>=3.0.0", + "requests>=2.0.0", +] +files = [ + {file = "requests-oauthlib-2.0.0.tar.gz", hash = "sha256:b3dffaebd884d8cd778494369603a9e7b58d29111bf6b41bdc2dcd87203af4e9"}, + {file = "requests_oauthlib-2.0.0-py2.py3-none-any.whl", hash = "sha256:7dd8a5c40426b779b0868c404bdef9768deccf22749cde15852df527e6269b36"}, +] + +[[package]] +name = "resolvelib" +version = "1.0.1" +summary = "Resolve abstract dependencies into concrete ones" +groups = ["operatorcert-dev"] +files = [ + {file = "resolvelib-1.0.1-py2.py3-none-any.whl", hash = "sha256:d2da45d1a8dfee81bdd591647783e340ef3bcb104b54c383f70d422ef5cc7dbf"}, + {file = "resolvelib-1.0.1.tar.gz", hash = "sha256:04ce76cbd63fded2078ce224785da6ecd42b9564b1390793f64ddecbe997b309"}, +] + [[package]] name = "rich" version = "13.8.1" @@ -1297,6 +1595,94 @@ files = [ {file = "rich-13.8.1.tar.gz", hash = "sha256:8260cda28e3db6bf04d2d1ef4dbc03ba80a824c88b0e7668a0f23126a424844a"}, ] +[[package]] +name = "rpds-py" +version = "0.21.0" +requires_python = ">=3.9" +summary = "Python bindings to Rust's persistent data structures (rpds)" +groups = ["operatorcert-dev"] +files = [ + {file = "rpds_py-0.21.0-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:a017f813f24b9df929674d0332a374d40d7f0162b326562daae8066b502d0590"}, + {file = "rpds_py-0.21.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:20cc1ed0bcc86d8e1a7e968cce15be45178fd16e2ff656a243145e0b439bd250"}, + {file = "rpds_py-0.21.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ad116dda078d0bc4886cb7840e19811562acdc7a8e296ea6ec37e70326c1b41c"}, + {file = "rpds_py-0.21.0-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:808f1ac7cf3b44f81c9475475ceb221f982ef548e44e024ad5f9e7060649540e"}, + {file = "rpds_py-0.21.0-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:de552f4a1916e520f2703ec474d2b4d3f86d41f353e7680b597512ffe7eac5d0"}, + {file = "rpds_py-0.21.0-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:efec946f331349dfc4ae9d0e034c263ddde19414fe5128580f512619abed05f1"}, + {file = "rpds_py-0.21.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b80b4690bbff51a034bfde9c9f6bf9357f0a8c61f548942b80f7b66356508bf5"}, + {file = "rpds_py-0.21.0-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:085ed25baac88953d4283e5b5bd094b155075bb40d07c29c4f073e10623f9f2e"}, + {file = "rpds_py-0.21.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:daa8efac2a1273eed2354397a51216ae1e198ecbce9036fba4e7610b308b6153"}, + {file = "rpds_py-0.21.0-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:95a5bad1ac8a5c77b4e658671642e4af3707f095d2b78a1fdd08af0dfb647624"}, + {file = "rpds_py-0.21.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:3e53861b29a13d5b70116ea4230b5f0f3547b2c222c5daa090eb7c9c82d7f664"}, + {file = "rpds_py-0.21.0-cp310-none-win32.whl", hash = "sha256:ea3a6ac4d74820c98fcc9da4a57847ad2cc36475a8bd9683f32ab6d47a2bd682"}, + {file = "rpds_py-0.21.0-cp310-none-win_amd64.whl", hash = "sha256:b8f107395f2f1d151181880b69a2869c69e87ec079c49c0016ab96860b6acbe5"}, + {file = "rpds_py-0.21.0-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:5555db3e618a77034954b9dc547eae94166391a98eb867905ec8fcbce1308d95"}, + {file = "rpds_py-0.21.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:97ef67d9bbc3e15584c2f3c74bcf064af36336c10d2e21a2131e123ce0f924c9"}, + {file = "rpds_py-0.21.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4ab2c2a26d2f69cdf833174f4d9d86118edc781ad9a8fa13970b527bf8236027"}, + {file = "rpds_py-0.21.0-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:4e8921a259f54bfbc755c5bbd60c82bb2339ae0324163f32868f63f0ebb873d9"}, + {file = "rpds_py-0.21.0-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:8a7ff941004d74d55a47f916afc38494bd1cfd4b53c482b77c03147c91ac0ac3"}, + {file = "rpds_py-0.21.0-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:5145282a7cd2ac16ea0dc46b82167754d5e103a05614b724457cffe614f25bd8"}, + {file = "rpds_py-0.21.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:de609a6f1b682f70bb7163da745ee815d8f230d97276db049ab447767466a09d"}, + {file = "rpds_py-0.21.0-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:40c91c6e34cf016fa8e6b59d75e3dbe354830777fcfd74c58b279dceb7975b75"}, + {file = "rpds_py-0.21.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:d2132377f9deef0c4db89e65e8bb28644ff75a18df5293e132a8d67748397b9f"}, + {file = "rpds_py-0.21.0-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:0a9e0759e7be10109645a9fddaaad0619d58c9bf30a3f248a2ea57a7c417173a"}, + {file = "rpds_py-0.21.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:9e20da3957bdf7824afdd4b6eeb29510e83e026473e04952dca565170cd1ecc8"}, + {file = "rpds_py-0.21.0-cp311-none-win32.whl", hash = "sha256:f71009b0d5e94c0e86533c0b27ed7cacc1239cb51c178fd239c3cfefefb0400a"}, + {file = "rpds_py-0.21.0-cp311-none-win_amd64.whl", hash = "sha256:e168afe6bf6ab7ab46c8c375606298784ecbe3ba31c0980b7dcbb9631dcba97e"}, + {file = "rpds_py-0.21.0-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:30b912c965b2aa76ba5168fd610087bad7fcde47f0a8367ee8f1876086ee6d1d"}, + {file = "rpds_py-0.21.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:ca9989d5d9b1b300bc18e1801c67b9f6d2c66b8fd9621b36072ed1df2c977f72"}, + {file = "rpds_py-0.21.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6f54e7106f0001244a5f4cf810ba8d3f9c542e2730821b16e969d6887b664266"}, + {file = "rpds_py-0.21.0-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:fed5dfefdf384d6fe975cc026886aece4f292feaf69d0eeb716cfd3c5a4dd8be"}, + {file = "rpds_py-0.21.0-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:590ef88db231c9c1eece44dcfefd7515d8bf0d986d64d0caf06a81998a9e8cab"}, + {file = "rpds_py-0.21.0-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f983e4c2f603c95dde63df633eec42955508eefd8d0f0e6d236d31a044c882d7"}, + {file = "rpds_py-0.21.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b229ce052ddf1a01c67d68166c19cb004fb3612424921b81c46e7ea7ccf7c3bf"}, + {file = "rpds_py-0.21.0-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:ebf64e281a06c904a7636781d2e973d1f0926a5b8b480ac658dc0f556e7779f4"}, + {file = "rpds_py-0.21.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:998a8080c4495e4f72132f3d66ff91f5997d799e86cec6ee05342f8f3cda7dca"}, + {file = "rpds_py-0.21.0-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:98486337f7b4f3c324ab402e83453e25bb844f44418c066623db88e4c56b7c7b"}, + {file = "rpds_py-0.21.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:a78d8b634c9df7f8d175451cfeac3810a702ccb85f98ec95797fa98b942cea11"}, + {file = "rpds_py-0.21.0-cp312-none-win32.whl", hash = "sha256:a58ce66847711c4aa2ecfcfaff04cb0327f907fead8945ffc47d9407f41ff952"}, + {file = "rpds_py-0.21.0-cp312-none-win_amd64.whl", hash = "sha256:e860f065cc4ea6f256d6f411aba4b1251255366e48e972f8a347cf88077b24fd"}, + {file = "rpds_py-0.21.0-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:ee4eafd77cc98d355a0d02f263efc0d3ae3ce4a7c24740010a8b4012bbb24937"}, + {file = "rpds_py-0.21.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:688c93b77e468d72579351a84b95f976bd7b3e84aa6686be6497045ba84be560"}, + {file = "rpds_py-0.21.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c38dbf31c57032667dd5a2f0568ccde66e868e8f78d5a0d27dcc56d70f3fcd3b"}, + {file = "rpds_py-0.21.0-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:2d6129137f43f7fa02d41542ffff4871d4aefa724a5fe38e2c31a4e0fd343fb0"}, + {file = "rpds_py-0.21.0-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:520ed8b99b0bf86a176271f6fe23024323862ac674b1ce5b02a72bfeff3fff44"}, + {file = "rpds_py-0.21.0-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:aaeb25ccfb9b9014a10eaf70904ebf3f79faaa8e60e99e19eef9f478651b9b74"}, + {file = "rpds_py-0.21.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:af04ac89c738e0f0f1b913918024c3eab6e3ace989518ea838807177d38a2e94"}, + {file = "rpds_py-0.21.0-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:b9b76e2afd585803c53c5b29e992ecd183f68285b62fe2668383a18e74abe7a3"}, + {file = "rpds_py-0.21.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:5afb5efde74c54724e1a01118c6e5c15e54e642c42a1ba588ab1f03544ac8c7a"}, + {file = "rpds_py-0.21.0-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:52c041802a6efa625ea18027a0723676a778869481d16803481ef6cc02ea8cb3"}, + {file = "rpds_py-0.21.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:ee1e4fc267b437bb89990b2f2abf6c25765b89b72dd4a11e21934df449e0c976"}, + {file = "rpds_py-0.21.0-cp313-none-win32.whl", hash = "sha256:0c025820b78817db6a76413fff6866790786c38f95ea3f3d3c93dbb73b632202"}, + {file = "rpds_py-0.21.0-cp313-none-win_amd64.whl", hash = "sha256:320c808df533695326610a1b6a0a6e98f033e49de55d7dc36a13c8a30cfa756e"}, + {file = "rpds_py-0.21.0-pp310-pypy310_pp73-macosx_10_12_x86_64.whl", hash = "sha256:6b4ef7725386dc0762857097f6b7266a6cdd62bfd209664da6712cb26acef035"}, + {file = "rpds_py-0.21.0-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:6bc0e697d4d79ab1aacbf20ee5f0df80359ecf55db33ff41481cf3e24f206919"}, + {file = "rpds_py-0.21.0-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:da52d62a96e61c1c444f3998c434e8b263c384f6d68aca8274d2e08d1906325c"}, + {file = "rpds_py-0.21.0-pp310-pypy310_pp73-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:98e4fe5db40db87ce1c65031463a760ec7906ab230ad2249b4572c2fc3ef1f9f"}, + {file = "rpds_py-0.21.0-pp310-pypy310_pp73-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:30bdc973f10d28e0337f71d202ff29345320f8bc49a31c90e6c257e1ccef4333"}, + {file = "rpds_py-0.21.0-pp310-pypy310_pp73-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:faa5e8496c530f9c71f2b4e1c49758b06e5f4055e17144906245c99fa6d45356"}, + {file = "rpds_py-0.21.0-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:32eb88c30b6a4f0605508023b7141d043a79b14acb3b969aa0b4f99b25bc7d4a"}, + {file = "rpds_py-0.21.0-pp310-pypy310_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:a89a8ce9e4e75aeb7fa5d8ad0f3fecdee813802592f4f46a15754dcb2fd6b061"}, + {file = "rpds_py-0.21.0-pp310-pypy310_pp73-musllinux_1_2_aarch64.whl", hash = "sha256:241e6c125568493f553c3d0fdbb38c74babf54b45cef86439d4cd97ff8feb34d"}, + {file = "rpds_py-0.21.0-pp310-pypy310_pp73-musllinux_1_2_i686.whl", hash = "sha256:3b766a9f57663396e4f34f5140b3595b233a7b146e94777b97a8413a1da1be18"}, + {file = "rpds_py-0.21.0-pp310-pypy310_pp73-musllinux_1_2_x86_64.whl", hash = "sha256:af4a644bf890f56e41e74be7d34e9511e4954894d544ec6b8efe1e21a1a8da6c"}, + {file = "rpds_py-0.21.0-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:3e30a69a706e8ea20444b98a49f386c17b26f860aa9245329bab0851ed100677"}, + {file = "rpds_py-0.21.0.tar.gz", hash = "sha256:ed6378c9d66d0de903763e7706383d60c33829581f0adff47b6535f1802fa6db"}, +] + +[[package]] +name = "rsa" +version = "4.9" +requires_python = ">=3.6,<4" +summary = "Pure-Python RSA implementation" +groups = ["operatorcert-dev"] +dependencies = [ + "pyasn1>=0.1.3", +] +files = [ + {file = "rsa-4.9-py3-none-any.whl", hash = "sha256:90260d9058e514786967344d0ef75fa8727eed8a7d2e43ce9f4bcf1b536174f7"}, + {file = "rsa-4.9.tar.gz", hash = "sha256:e38464a49c6c85d7f1351b0126661487a7e0a14a50f1675ec50eb34d4f20ef21"}, +] + [[package]] name = "ruamel-yaml" version = "0.18.6" @@ -1505,6 +1891,17 @@ files = [ {file = "stomp_py-8.1.2.tar.gz", hash = "sha256:b56e62da090863cc65e5fbf832230318cd53e99dc777de19ecb04e83914f1371"}, ] +[[package]] +name = "subprocess-tee" +version = "0.4.2" +requires_python = ">=3.8" +summary = "subprocess-tee" +groups = ["operatorcert-dev"] +files = [ + {file = "subprocess_tee-0.4.2-py3-none-any.whl", hash = "sha256:21942e976715af4a19a526918adb03a8a27a8edab959f2d075b777e3d78f532d"}, + {file = "subprocess_tee-0.4.2.tar.gz", hash = "sha256:91b2b4da3aae9a7088d84acaf2ea0abee3f4fd9c0d2eae69a9b9122a71476590"}, +] + [[package]] name = "tomli" version = "2.0.1" @@ -1625,7 +2022,7 @@ name = "typing-extensions" version = "4.12.2" requires_python = ">=3.8" summary = "Backported and Experimental Type Hints for Python 3.8+" -groups = ["operatorcert-dev", "tox"] +groups = ["default", "operatorcert-dev", "tox"] files = [ {file = "typing_extensions-4.12.2-py3-none-any.whl", hash = "sha256:04e5ca0351e0f3f85c6853954072df659d0d13fac324d0072316b67d7794700d"}, {file = "typing_extensions-4.12.2.tar.gz", hash = "sha256:1a7ead55c7e559dd4dee8856e3a88b41225abfe1ce8df57b7c13915fe121ffb8"}, @@ -1659,17 +2056,42 @@ files = [ {file = "virtualenv-20.27.1.tar.gz", hash = "sha256:142c6be10212543b32c6c45d3d3893dff89112cc588b7d0879ae5a1ec03a47ba"}, ] +[[package]] +name = "wcmatch" +version = "10.0" +requires_python = ">=3.8" +summary = "Wildcard/glob file name matcher." +groups = ["operatorcert-dev"] +dependencies = [ + "bracex>=2.1.1", +] +files = [ + {file = "wcmatch-10.0-py3-none-any.whl", hash = "sha256:0dd927072d03c0a6527a20d2e6ad5ba8d0380e60870c383bc533b71744df7b7a"}, + {file = "wcmatch-10.0.tar.gz", hash = "sha256:e72f0de09bba6a04e0de70937b0cf06e55f36f37b3deb422dfaf854b867b840a"}, +] + [[package]] name = "websocket-client" version = "1.8.0" requires_python = ">=3.8" summary = "WebSocket client for Python with low level API options" -groups = ["default"] +groups = ["default", "operatorcert-dev"] files = [ {file = "websocket_client-1.8.0-py3-none-any.whl", hash = "sha256:17b44cc997f5c498e809b22cdf2d9c7a9e71c02c8cc2b6c56e7c2d1239bfa526"}, {file = "websocket_client-1.8.0.tar.gz", hash = "sha256:3239df9f44da632f96012472805d40a23281a991027ce11d2f45a6f24ac4c3da"}, ] +[[package]] +name = "will-not-work-on-windows-try-from-wsl-instead" +version = "0.1.0" +requires_python = ">=3.11,<4.0" +summary = "" +groups = ["operatorcert-dev"] +marker = "platform_system == \"Windows\"" +files = [ + {file = "will_not_work_on_windows_try_from_wsl_instead-0.1.0.tar.gz", hash = "sha256:a4641e5420c23718d92986f33185b854c2cb345e136fc5b8bafe5b1c013e7039"}, +] + [[package]] name = "wrapt" version = "1.16.0" @@ -1753,3 +2175,14 @@ files = [ {file = "yq-3.4.3-py3-none-any.whl", hash = "sha256:547e34bc3caacce83665fd3429bf7c85f8e8b6b9aaee3f953db1ad716ff3434d"}, {file = "yq-3.4.3.tar.gz", hash = "sha256:ba586a1a6f30cf705b2f92206712df2281cd320280210e7b7b80adcb8f256e3b"}, ] + +[[package]] +name = "zipp" +version = "3.21.0" +requires_python = ">=3.9" +summary = "Backport of pathlib-compatible object wrapper for zip files" +groups = ["operatorcert-dev"] +files = [ + {file = "zipp-3.21.0-py3-none-any.whl", hash = "sha256:ac1bbe05fd2991f160ebce24ffbac5f6d11d83dc90891255885223d42b3cd931"}, + {file = "zipp-3.21.0.tar.gz", hash = "sha256:2c9958f6430a2040341a52eb608ed6dd93ef4392e02ffe219417c1b28b5dd1f4"}, +] diff --git a/pyproject.toml b/pyproject.toml index 86d7cf8f6..946f37a62 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -5,25 +5,8 @@ target_version = ['py312'] package-dir = "./operator-pipeline-images" [tool.pdm.dev-dependencies] -operatorcert-dev = [ - "black>=23.3.0", - "pytest>=7.3.2", - "pytest-cov>=4.1.0", - "yamllint>=1.32.0", - "requests-mock>=1.11.0", - "python-dateutil>=2.8.2", - "bandit>=1.7.5", - "safety>=2.3.4", - "mypy>=1.5.1", - "types-PyYAML>=6.0.12.11", - "types-python-dateutil>=2.8.19.14", - "types-requests>=2.31.0.2", - "pylint>=2.17.5", - # "pymarkdownlnt>=0.9.13.4", -] tox = ["tox>=4.16.0", "tox-pdm>=0.7.2"] - [project] name = "operatorcert" version = "1.0.0" @@ -50,6 +33,8 @@ dependencies = [ "semver>=3.0.1", "operator-repo @ git+https://github.com/mporrato/operator-repo.git@v0.4.3", "urllib3>=2.2.2", + "openshift-client>=2.0.4", + "pydantic>=2.10.0", ] [project.scripts] @@ -90,8 +75,36 @@ validate-catalog-format = "operatorcert.entrypoints.validate_catalog_format:main verify-changed-dirs = "operatorcert.entrypoints.verify_changed_dirs:main" invalidate-preflight-versions = "operatorcert.entrypoints.invalidate_preflight_versions:main" bulk-retrigger = "operatorcert.entrypoints.bulk_retrigger:main" +integration-tests = "operatorcert.entrypoints.integration_tests:main" [build-system] requires = ["pdm-pep517>=1.0"] build-backend = "pdm.pep517.api" + +[dependency-groups] +operatorcert-dev = [ + "black>=23.3.0", + "pytest>=7.3.2", + "pytest-cov>=4.1.0", + "yamllint>=1.32.0", + "requests-mock>=1.11.0", + "python-dateutil>=2.8.2", + "bandit>=1.7.5", + "safety>=2.3.4", + "mypy>=1.5.1", + "types-PyYAML>=6.0.12.11", + "types-python-dateutil>=2.8.19.14", + "types-requests>=2.31.0.2", + "pylint>=2.17.5", + "ansible-core>=2.17.6", + "jmespath>=1.0.1", + "kubernetes>=31.0.0", + "ansible-lint>=24.10.0", +] +tox = ["tox>=4.16.0", "tox-pdm>=0.7.2"] + +# The following two lines are required to install ansible-lint under pdm +# Ref: https://github.com/pdm-project/pdm/issues/1575 +[tool.pdm.resolution.overrides] +will-not-work-on-windows-try-from-wsl-instead = "0.1.0" diff --git a/tox.ini b/tox.ini index f387eab3e..6b64e53a7 100644 --- a/tox.ini +++ b/tox.ini @@ -69,7 +69,10 @@ commands = pdm export \ -G ":all" \ -f requirements \ -o /tmp/requirements.txt - safety check -i 70612 -r /tmp/requirements.txt + safety check \ + -i 70612 \ # CVE-2019-8341 - not applicable: users do not control templates + -i 73302 \ # CVE-2024-8775 - no fix available yet; mitigation applied by adding no_log where appropriate + -r /tmp/requirements.txt [testenv:pdm-lock-check] allowlist_externals = pdm