From d19f1c44c19bf884c495773ab3d5fd1332775899 Mon Sep 17 00:00:00 2001 From: Kate Case Date: Thu, 31 Oct 2024 12:49:26 -0400 Subject: [PATCH 01/12] Add docstrings and type hints to ansible_playbook provisioner. --- .config/pydoclint-baseline.txt | 13 -- src/molecule/provisioner/ansible.py | 2 +- src/molecule/provisioner/ansible_playbook.py | 113 ++++++++++-------- .../unit/provisioner/test_ansible_playbook.py | 6 +- 4 files changed, 71 insertions(+), 63 deletions(-) diff --git a/.config/pydoclint-baseline.txt b/.config/pydoclint-baseline.txt index aef87e8c3..b58677d6c 100644 --- a/.config/pydoclint-baseline.txt +++ b/.config/pydoclint-baseline.txt @@ -118,19 +118,6 @@ src/molecule/provisioner/ansible.py DOC201: Method `Ansible._get_modules_directories` does not have a return section in docstring DOC201: Method `Ansible._get_filter_plugins_directories` does not have a return section in docstring -------------------- -src/molecule/provisioner/ansible_playbook.py - DOC106: Method `AnsiblePlaybook.__init__`: The option `--arg-type-hints-in-signature` is `True` but there are no argument type hints in the signature - DOC107: Method `AnsiblePlaybook.__init__`: The option `--arg-type-hints-in-signature` is `True` but not all args in the signature have type hints - DOC201: Method `AnsiblePlaybook.bake` does not have a return section in docstring - DOC101: Method `AnsiblePlaybook.execute`: Docstring contains fewer arguments than in function signature. - DOC106: Method `AnsiblePlaybook.execute`: The option `--arg-type-hints-in-signature` is `True` but there are no argument type hints in the signature - DOC107: Method `AnsiblePlaybook.execute`: The option `--arg-type-hints-in-signature` is `True` but not all args in the signature have type hints - DOC103: Method `AnsiblePlaybook.execute`: Docstring arguments are different from function arguments. (Or could be other formatting issues: https://jsh9.github.io/pydoclint/violation_codes.html#notes-on-doc103 ). Arguments in the function signature but not in the docstring: [action_args: ]. - DOC106: Method `AnsiblePlaybook.add_cli_arg`: The option `--arg-type-hints-in-signature` is `True` but there are no argument type hints in the signature - DOC107: Method `AnsiblePlaybook.add_cli_arg`: The option `--arg-type-hints-in-signature` is `True` but not all args in the signature have type hints - DOC106: Method `AnsiblePlaybook.add_env_arg`: The option `--arg-type-hints-in-signature` is `True` but there are no argument type hints in the signature - DOC107: Method `AnsiblePlaybook.add_env_arg`: The option `--arg-type-hints-in-signature` is `True` but not all args in the signature have type hints --------------------- src/molecule/provisioner/ansible_playbooks.py DOC106: Method `AnsiblePlaybooks.__init__`: The option `--arg-type-hints-in-signature` is `True` but there are no argument type hints in the signature DOC107: Method `AnsiblePlaybooks.__init__`: The option `--arg-type-hints-in-signature` is `True` but not all args in the signature have type hints diff --git a/src/molecule/provisioner/ansible.py b/src/molecule/provisioner/ansible.py index 31656dfd4..bcf911b5d 100644 --- a/src/molecule/provisioner/ansible.py +++ b/src/molecule/provisioner/ansible.py @@ -862,7 +862,7 @@ def _get_ansible_playbook(self, playbook, verify=False, **kwargs): # type: igno return ansible_playbook.AnsiblePlaybook( playbook, self._config, - verify, + verify=verify, **kwargs, ) diff --git a/src/molecule/provisioner/ansible_playbook.py b/src/molecule/provisioner/ansible_playbook.py index 4d6dce369..86f5f9a2f 100644 --- a/src/molecule/provisioner/ansible_playbook.py +++ b/src/molecule/provisioner/ansible_playbook.py @@ -24,18 +24,30 @@ import shlex import warnings +from typing import TYPE_CHECKING + from molecule import util from molecule.api import MoleculeRuntimeWarning +if TYPE_CHECKING: + from molecule.config import Config + + LOG = logging.getLogger(__name__) class AnsiblePlaybook: """Provisioner Playbook.""" - def __init__(self, playbook, config, verify=False) -> None: # type: ignore[no-untyped-def] # noqa: ANN001, FBT002 - """Set up the requirements to execute ``ansible-playbook`` and returns None. + def __init__( + self, + playbook: str, + config: Config, + *, + verify: bool = False, + ) -> None: + """Set up the requirements to execute ``ansible-playbook``. Args: playbook: A string containing the path to the playbook. @@ -43,74 +55,79 @@ def __init__(self, playbook, config, verify=False) -> None: # type: ignore[no-u verify: An optional bool to toggle the Playbook mode between provision and verify. False provision; True: verify. Default is False. """ - self._ansible_command = None + self._ansible_command: list[str] = [] self._playbook = playbook self._config = config - self._cli = {} # type: ignore[var-annotated] + self._cli: dict[str, str] = {} + self._env: dict[str, str] = {} if verify: self._env = util.merge_dicts( self._config.verifier.env, self._config.config["verifier"]["env"], ) - else: + elif self._config.provisioner: self._env = self._config.provisioner.env - def bake(self): # type: ignore[no-untyped-def] # noqa: ANN201 - """Bake an ``ansible-playbook`` command so it's ready to execute and returns ``None``.""" + def bake(self) -> None: + """Bake an ``ansible-playbook`` command so it's ready to execute.""" if not self._playbook: return - # Pass a directory as inventory to let Ansible merge the multiple - # inventory sources located under - self.add_cli_arg("inventory", self._config.provisioner.inventory_directory) # type: ignore[no-untyped-call] - options = util.merge_dicts(self._config.provisioner.options, self._cli) - verbose_flag = util.verbose_flag(options) - if self._playbook != self._config.provisioner.playbooks.converge: # noqa: SIM102 - if options.get("become"): - del options["become"] - - # We do not pass user-specified Ansible arguments to the create and - # destroy invocations because playbooks involved in those two - # operations are not always provided by end users. And in those cases, - # custom Ansible arguments can break the creation and destruction - # processes. - # - # If users need to modify the creation of deletion, they can supply - # custom playbooks and specify them in the scenario configuration. - if self._config.action not in ["create", "destroy"]: - ansible_args = list(self._config.provisioner.ansible_args) + list( - self._config.ansible_args, - ) - else: - ansible_args = [] - - self._ansible_command = [ # type: ignore[assignment] - "ansible-playbook", - *util.dict2args(options), - *util.bool2args(verbose_flag), - *ansible_args, - self._playbook, # must always go last - ] + if self._config.provisioner: + # Pass a directory as inventory to let Ansible merge the multiple + # inventory sources located under + self.add_cli_arg("inventory", self._config.provisioner.inventory_directory) + options = util.merge_dicts(self._config.provisioner.options, self._cli) + verbose_flag = util.verbose_flag(options) + if self._playbook != self._config.provisioner.playbooks.converge: # noqa: SIM102 + if options.get("become"): + del options["become"] + + # We do not pass user-specified Ansible arguments to the create and + # destroy invocations because playbooks involved in those two + # operations are not always provided by end users. And in those cases, + # custom Ansible arguments can break the creation and destruction + # processes. + # + # If users need to modify the creation of deletion, they can supply + # custom playbooks and specify them in the scenario configuration. + if self._config.action not in ["create", "destroy"]: + ansible_args = list(self._config.provisioner.ansible_args) + list( + self._config.ansible_args, + ) + else: + ansible_args = [] + + self._ansible_command = [ + "ansible-playbook", + *util.dict2args(options), + *util.bool2args(verbose_flag), + *ansible_args, + self._playbook, # must always go last + ] + + def execute(self, action_args: list[str] | None = None) -> str: # noqa: ARG002 + """Execute ``ansible-playbook``. - def execute(self, action_args=None): # type: ignore[no-untyped-def] # noqa: ANN001, ANN201, ARG002 - """Execute ``ansible-playbook`` and returns a string. + Args: + action_args: Arguments to forward to the action. Unused. Returns: - str + Output from ansible-playbook. """ if self._ansible_command is None: - self.bake() # type: ignore[no-untyped-call] + self.bake() if not self._playbook: LOG.warning("Skipping, %s action has no playbook.", self._config.action) - return None + return "" with warnings.catch_warnings(record=True) as warns: warnings.filterwarnings("default", category=MoleculeRuntimeWarning) self._config.driver.sanity_checks() cwd = self._config.scenario_path result = util.run_command( - cmd=self._ansible_command, # type: ignore[arg-type] + cmd=self._ansible_command, env=self._env, debug=self._config.debug, cwd=cwd, @@ -127,8 +144,8 @@ def execute(self, action_args=None): # type: ignore[no-untyped-def] # noqa: AN return result.stdout - def add_cli_arg(self, name, value): # type: ignore[no-untyped-def] # noqa: ANN001, ANN201 - """Add argument to CLI passed to ansible-playbook and returns None. + def add_cli_arg(self, name: str, value: str) -> None: + """Add argument to CLI passed to ansible-playbook. Args: name: A string containing the name of argument to be added. @@ -137,8 +154,8 @@ def add_cli_arg(self, name, value): # type: ignore[no-untyped-def] # noqa: ANN if value: self._cli[name] = value - def add_env_arg(self, name, value): # type: ignore[no-untyped-def] # noqa: ANN001, ANN201 - """Add argument to environment passed to ansible-playbook and returns None. + def add_env_arg(self, name: str, value: str) -> None: + """Add argument to environment passed to ansible-playbook. Args: name: A string containing the name of argument to be added. diff --git a/tests/unit/provisioner/test_ansible_playbook.py b/tests/unit/provisioner/test_ansible_playbook.py index 0b773dd5c..b6ba74cf4 100644 --- a/tests/unit/provisioner/test_ansible_playbook.py +++ b/tests/unit/provisioner/test_ansible_playbook.py @@ -54,7 +54,11 @@ def _provisioner_verifier_section_data(): # type: ignore[no-untyped-def] # noq @pytest.fixture def _instance_for_verifier_env(config_instance: config.Config): # type: ignore[no-untyped-def] # noqa: ANN202 - _instance = ansible_playbook.AnsiblePlaybook("playbook", config_instance, True) # noqa: FBT003 + _instance = ansible_playbook.AnsiblePlaybook( + "playbook", + config_instance, + verify=True, + ) return _instance # noqa: RET504 From f267312cd934635f2a04cad60adff1ee97b74ead Mon Sep 17 00:00:00 2001 From: Kate Case Date: Fri, 1 Nov 2024 12:06:34 -0400 Subject: [PATCH 02/12] Add docstrings and type hints to ansible_playbooks --- .config/pydoclint-baseline.txt | 14 -- src/molecule/provisioner/ansible_playbooks.py | 166 +++++++++++++----- 2 files changed, 118 insertions(+), 62 deletions(-) diff --git a/.config/pydoclint-baseline.txt b/.config/pydoclint-baseline.txt index b58677d6c..9e8d1eb9b 100644 --- a/.config/pydoclint-baseline.txt +++ b/.config/pydoclint-baseline.txt @@ -118,20 +118,6 @@ src/molecule/provisioner/ansible.py DOC201: Method `Ansible._get_modules_directories` does not have a return section in docstring DOC201: Method `Ansible._get_filter_plugins_directories` does not have a return section in docstring -------------------- -src/molecule/provisioner/ansible_playbooks.py - DOC106: Method `AnsiblePlaybooks.__init__`: The option `--arg-type-hints-in-signature` is `True` but there are no argument type hints in the signature - DOC107: Method `AnsiblePlaybooks.__init__`: The option `--arg-type-hints-in-signature` is `True` but not all args in the signature have type hints - DOC101: Method `AnsiblePlaybooks._get_playbook`: Docstring contains fewer arguments than in function signature. - DOC106: Method `AnsiblePlaybooks._get_playbook`: The option `--arg-type-hints-in-signature` is `True` but there are no argument type hints in the signature - DOC107: Method `AnsiblePlaybooks._get_playbook`: The option `--arg-type-hints-in-signature` is `True` but not all args in the signature have type hints - DOC103: Method `AnsiblePlaybooks._get_playbook`: Docstring arguments are different from function arguments. (Or could be other formatting issues: https://jsh9.github.io/pydoclint/violation_codes.html#notes-on-doc103 ). Arguments in the function signature but not in the docstring: [section: ]. - DOC201: Method `AnsiblePlaybooks._get_playbook` does not have a return section in docstring - DOC101: Method `AnsiblePlaybooks._normalize_playbook`: Docstring contains fewer arguments than in function signature. - DOC106: Method `AnsiblePlaybooks._normalize_playbook`: The option `--arg-type-hints-in-signature` is `True` but there are no argument type hints in the signature - DOC107: Method `AnsiblePlaybooks._normalize_playbook`: The option `--arg-type-hints-in-signature` is `True` but not all args in the signature have type hints - DOC103: Method `AnsiblePlaybooks._normalize_playbook`: Docstring arguments are different from function arguments. (Or could be other formatting issues: https://jsh9.github.io/pydoclint/violation_codes.html#notes-on-doc103 ). Arguments in the function signature but not in the docstring: [playbook: ]. - DOC201: Method `AnsiblePlaybooks._normalize_playbook` does not have a return section in docstring --------------------- src/molecule/provisioner/base.py DOC601: Class `Base`: Class docstring contains fewer class attributes than actual class attributes. (Please read https://jsh9.github.io/pydoclint/checking_class_attributes.html on how to correctly document class attributes.) DOC603: Class `Base`: Class docstring attributes are different from actual class attributes. (Or could be other formatting issues: https://jsh9.github.io/pydoclint/violation_codes.html#notes-on-doc103 ). Attributes in the class definition but not in the docstring: [__metaclass__: ]. (Please read https://jsh9.github.io/pydoclint/checking_class_attributes.html on how to correctly document class attributes.) diff --git a/src/molecule/provisioner/ansible_playbooks.py b/src/molecule/provisioner/ansible_playbooks.py index a5c2a8c5f..dd794bcd1 100644 --- a/src/molecule/provisioner/ansible_playbooks.py +++ b/src/molecule/provisioner/ansible_playbooks.py @@ -23,17 +23,36 @@ import logging import os +from pathlib import Path +from typing import TYPE_CHECKING + from molecule import util +if TYPE_CHECKING: + from typing import Literal + + from molecule.config import Config + + Section = Literal[ + "cleanup", + "create", + "converge", + "destroy", + "prepare", + "side_effect", + "verify", + ] + + LOG = logging.getLogger(__name__) class AnsiblePlaybooks: """A class to act as a module to namespace playbook properties.""" - def __init__(self, config) -> None: # type: ignore[no-untyped-def] # noqa: ANN001 - """Initialize a new namespace class and returns None. + def __init__(self, config: Config) -> None: + """Initialize a new namespace class. Args: config: An instance of a Molecule config. @@ -41,61 +60,108 @@ def __init__(self, config) -> None: # type: ignore[no-untyped-def] # noqa: ANN self._config = config @property - def cleanup(self): # type: ignore[no-untyped-def] # noqa: ANN201, D102 - return self._get_playbook("cleanup") # type: ignore[no-untyped-call] + def cleanup(self) -> str | None: + """Get the cleanup playbook path. + + Returns: + Path to cleanup.yml. + """ + return self._get_playbook("cleanup") @property - def create(self): # type: ignore[no-untyped-def] # noqa: ANN201, D102 - return self._get_playbook("create") # type: ignore[no-untyped-call] + def create(self) -> str | None: + """Get the create playbook path. + + Returns: + Path to create.yml. + """ + return self._get_playbook("create") @property - def converge(self): # type: ignore[no-untyped-def] # noqa: ANN201, D102 - return self._get_playbook("converge") # type: ignore[no-untyped-call] + def converge(self) -> str | None: + """Get the converge playbook path. + + Returns: + Path to converge.yml. + """ + return self._get_playbook("converge") @property - def destroy(self): # type: ignore[no-untyped-def] # noqa: ANN201, D102 - return self._get_playbook("destroy") # type: ignore[no-untyped-call] + def destroy(self) -> str | None: + """Get the destroy playbook path. + + Returns: + Path to destroy.yml. + """ + return self._get_playbook("destroy") @property - def prepare(self): # type: ignore[no-untyped-def] # noqa: ANN201, D102 - return self._get_playbook("prepare") # type: ignore[no-untyped-call] + def prepare(self) -> str | None: + """Get the prepare playbook path. + + Returns: + Path to prepare.yml. + """ + return self._get_playbook("prepare") @property - def side_effect(self): # type: ignore[no-untyped-def] # noqa: ANN201, D102 - return self._get_playbook("side_effect") # type: ignore[no-untyped-call] + def side_effect(self) -> str | None: + """Get the side_effect playbook path. + + Returns: + Path to side_effect.yml. + """ + return self._get_playbook("side_effect") @property - def verify(self): # type: ignore[no-untyped-def] # noqa: ANN201, D102 - return self._get_playbook("verify") # type: ignore[no-untyped-call] + def verify(self) -> str | None: + """Get the verify playbook path. - def _get_playbook_directory(self): # type: ignore[no-untyped-def] # noqa: ANN202 - return util.abs_path( - os.path.join(self._config.provisioner.directory, "playbooks"), # noqa: PTH118 - ) + Returns: + Path to verify.yml. + """ + return self._get_playbook("verify") + + def _get_playbook_directory(self) -> Path: + if self._config.provisioner: + return util.abs_path( + Path(self._config.provisioner.directory, "playbooks"), + ) + return Path() - def _get_playbook(self, section): # type: ignore[no-untyped-def] # noqa: ANN001, ANN202 + def _get_playbook(self, section: Section) -> str | None: """Return path to playbook or None if playbook is not needed. Return None when there is no playbook configured and when action is considered skippable. + + Args: + section: Named section to retrieve playbook for. + + Returns: + The playbook path, or none if one is not needed. """ c = self._config.config - driver_dict = c["provisioner"]["playbooks"].get(self._config.driver.name) + driver_dict: dict[Section, str | None] | None = c["provisioner"]["playbooks"].get( # type: ignore[assignment] + self._config.driver.name, + ) - playbook = c["provisioner"]["playbooks"][section] + playbook: str | None = c["provisioner"]["playbooks"][section] if driver_dict: try: playbook = driver_dict[section] except Exception as exc: LOG.exception(exc) # noqa: TRY401 - if playbook is not None: + if self._config.provisioner and playbook is not None: playbook = self._config.provisioner.abs_path(playbook) - playbook = self._normalize_playbook(playbook) # type: ignore[no-untyped-call] + if playbook: + playbook = self._normalize_playbook(playbook) - if os.path.exists(playbook): # noqa: PTH110 - return playbook - if os.path.exists(self._get_bundled_driver_playbook(section)): # type: ignore[no-untyped-call] # noqa: PTH110 - return self._get_bundled_driver_playbook(section) # type: ignore[no-untyped-call] + if os.path.exists(playbook): # noqa: PTH110 + return playbook + + if os.path.exists(self._get_bundled_driver_playbook(section)): # noqa: PTH110 + return self._get_bundled_driver_playbook(section) if section not in [ # these playbooks can be considered optional "prepare", @@ -108,45 +174,49 @@ def _get_playbook(self, section): # type: ignore[no-untyped-def] # noqa: ANN00 return playbook return None - def _get_bundled_driver_playbook(self, section): # type: ignore[no-untyped-def] # noqa: ANN001, ANN202 - path = self._config.driver.get_playbook(section) + def _get_bundled_driver_playbook(self, section: Section) -> str: + path = self._config.driver.get_playbook(section) # type: ignore[no-untyped-call] if path: - return path + return path # type: ignore[no-any-return] - path = os.path.join( # noqa: PTH118 - self._get_playbook_directory(), # type: ignore[no-untyped-call] + path = Path( + self._get_playbook_directory(), self._config.driver.name, self._config.config["provisioner"]["playbooks"][section], ) - if os.path.exists(path): # noqa: PTH110 - return path - path = os.path.join( # noqa: PTH118 + if path.exists(): + return str(path) + path = Path( self._config.driver._path, # noqa: SLF001 "playbooks", self._config.config["provisioner"]["playbooks"][section], ) - return path # noqa: RET504 + return str(path) - def _normalize_playbook(self, playbook): # type: ignore[no-untyped-def] # noqa: ANN001, ANN202 + def _normalize_playbook(self, playbook: str) -> str: """Return current filename to use for a playbook by allowing fallbacks. Currently used to deprecate use of playbook.yml in favour of converge.yml + + Args: + playbook: Playbook path to alter. + + Returns: + Normalized playbook path. """ - if not playbook or os.path.isfile(playbook): # noqa: PTH113 + play_path = Path(playbook) + if not playbook or play_path.is_file(): return playbook pb_rename_map = {"converge.yml": "playbook.yml"} - basename = os.path.basename(playbook) # noqa: PTH119 + basename = play_path.name if basename in pb_rename_map: - fb_playbook = os.path.join( # noqa: PTH118 - os.path.dirname(playbook), # noqa: PTH120 - pb_rename_map[basename], - ) - if os.path.isfile(fb_playbook): # noqa: PTH113 + fb_playbook = play_path.parent / pb_rename_map[basename] + if fb_playbook.is_file(): LOG.warning( "%s was deprecated, rename it to %s", - pb_rename_map[basename], + fb_playbook.name, basename, ) - playbook = fb_playbook + playbook = str(fb_playbook) return playbook From 646f1b4a9b07decf4c09f14b6f729a63995d670e Mon Sep 17 00:00:00 2001 From: Kate Case Date: Fri, 1 Nov 2024 12:31:09 -0400 Subject: [PATCH 03/12] Add docstings and type hints to base --- .config/pydoclint-baseline.txt | 7 ------- src/molecule/provisioner/base.py | 34 ++++++++++++++++++-------------- 2 files changed, 19 insertions(+), 22 deletions(-) diff --git a/.config/pydoclint-baseline.txt b/.config/pydoclint-baseline.txt index 9e8d1eb9b..406302e3e 100644 --- a/.config/pydoclint-baseline.txt +++ b/.config/pydoclint-baseline.txt @@ -118,13 +118,6 @@ src/molecule/provisioner/ansible.py DOC201: Method `Ansible._get_modules_directories` does not have a return section in docstring DOC201: Method `Ansible._get_filter_plugins_directories` does not have a return section in docstring -------------------- -src/molecule/provisioner/base.py - DOC601: Class `Base`: Class docstring contains fewer class attributes than actual class attributes. (Please read https://jsh9.github.io/pydoclint/checking_class_attributes.html on how to correctly document class attributes.) - DOC603: Class `Base`: Class docstring attributes are different from actual class attributes. (Or could be other formatting issues: https://jsh9.github.io/pydoclint/violation_codes.html#notes-on-doc103 ). Attributes in the class definition but not in the docstring: [__metaclass__: ]. (Please read https://jsh9.github.io/pydoclint/checking_class_attributes.html on how to correctly document class attributes.) - DOC106: Method `Base.__init__`: The option `--arg-type-hints-in-signature` is `True` but there are no argument type hints in the signature - DOC107: Method `Base.__init__`: The option `--arg-type-hints-in-signature` is `True` but not all args in the signature have type hints - DOC202: Method `Base.name` has a return section in docstring, but there are no return statements or annotations --------------------- src/molecule/verifier/base.py DOC601: Class `Verifier`: Class docstring contains fewer class attributes than actual class attributes. (Please read https://jsh9.github.io/pydoclint/checking_class_attributes.html on how to correctly document class attributes.) DOC603: Class `Verifier`: Class docstring attributes are different from actual class attributes. (Or could be other formatting issues: https://jsh9.github.io/pydoclint/violation_codes.html#notes-on-doc103 ). Attributes in the class definition but not in the docstring: [__metaclass__: ]. (Please read https://jsh9.github.io/pydoclint/checking_class_attributes.html on how to correctly document class attributes.) diff --git a/src/molecule/provisioner/base.py b/src/molecule/provisioner/base.py index 4f35de101..edac53d3f 100644 --- a/src/molecule/provisioner/base.py +++ b/src/molecule/provisioner/base.py @@ -22,13 +22,17 @@ import abc +from typing import TYPE_CHECKING + + +if TYPE_CHECKING: + from molecule.config import Config -class Base: - """Provisioner Base Class.""" - __metaclass__ = abc.ABCMeta +class Base(abc.ABC): + """Provisioner Base Class.""" - def __init__(self, config) -> None: # type: ignore[no-untyped-def] # noqa: ANN001 + def __init__(self, config: Config) -> None: """Initialize code for all :ref:`Provisioner` classes. Args: @@ -38,27 +42,27 @@ def __init__(self, config) -> None: # type: ignore[no-untyped-def] # noqa: ANN @property @abc.abstractmethod - def default_options(self): # type: ignore[no-untyped-def] # pragma: no cover # noqa: ANN201 - """Get default CLI arguments provided to ``cmd`` as a dict. + def default_options(self) -> dict[str, str]: # pragma: no cover + """Get default CLI arguments provided to ``cmd``. - Return: - dict + Returns: + The default CLI arguments. """ @property @abc.abstractmethod - def default_env(self): # type: ignore[no-untyped-def] # pragma: no cover # noqa: ANN201 - """Get default env variables provided to ``cmd`` as a dict. + def default_env(self) -> dict[str, str]: # pragma: no cover + """Get default env variables provided to ``cmd``. - Return: - dict + Returns: + The default env variables. """ @property @abc.abstractmethod - def name(self): # type: ignore[no-untyped-def] # pragma: no cover # noqa: ANN201 - """Name of the provisioner and returns a string. + def name(self) -> str: # pragma: no cover + """Name of the provisioner. Returns: - str + The provisioner's name. """ From 32dc0d73cfc7991789693d95bbac9dc76701b9dc Mon Sep 17 00:00:00 2001 From: Kate Case Date: Mon, 4 Nov 2024 08:11:03 -0500 Subject: [PATCH 04/12] In-progress work on ansible --- src/molecule/provisioner/ansible.py | 145 ++++++++++++++-------------- src/molecule/provisioner/base.py | 2 +- 2 files changed, 72 insertions(+), 75 deletions(-) diff --git a/src/molecule/provisioner/ansible.py b/src/molecule/provisioner/ansible.py index bcf911b5d..2014529c2 100644 --- a/src/molecule/provisioner/ansible.py +++ b/src/molecule/provisioner/ansible.py @@ -414,14 +414,6 @@ class Ansible(base.Base): ``` """ - def __init__(self, config) -> None: # type: ignore[no-untyped-def] # pylint: disable=useless-parent-delegation # noqa: ANN001 - """Initialize a new ansible class and returns None. - - Args: - config: An instance of a Molecule config. - """ - super().__init__(config) - @property def default_config_options(self) -> dict[str, Any]: """Provide Default options to construct ansible.cfg and returns a dict.""" @@ -442,20 +434,20 @@ def default_config_options(self) -> dict[str, Any]: } @property - def default_options(self): # type: ignore[no-untyped-def] # noqa: ANN201, D102 - d = {"skip-tags": "molecule-notest,notest"} + def default_options(self) -> dict[str, str | bool]: # noqa: D102 + d: dict[str, str | bool] = {"skip-tags": "molecule-notest,notest"} if self._config.action == "idempotence": d["skip-tags"] += ",molecule-idempotence-notest" if self._config.debug: - d["vvv"] = True # type: ignore[assignment] - d["diff"] = True # type: ignore[assignment] + d["vvv"] = True + d["diff"] = True return d @property - def default_env(self): # type: ignore[no-untyped-def] # noqa: ANN201, D102 + def default_env(self) -> dict[str, str]: # noqa: D102 # Finds if the current project is part of an ansible_collections hierarchy collection_indicator = "ansible_collections" # isolating test environment by injects ephemeral scenario directory on @@ -534,22 +526,22 @@ def default_env(self): # type: ignore[no-untyped-def] # noqa: ANN201, D102 return env # noqa: RET504 @property - def name(self): # type: ignore[no-untyped-def] # noqa: ANN201, D102 + def name(self) -> str: # noqa: D102 return self._config.config["provisioner"]["name"] @property - def ansible_args(self): # type: ignore[no-untyped-def] # noqa: ANN201, D102 + def ansible_args(self) -> list[str]: # noqa: D102 return self._config.config["provisioner"]["ansible_args"] @property - def config_options(self): # type: ignore[no-untyped-def] # noqa: ANN201, D102 + def config_options(self): # noqa: ANN201, D102 return util.merge_dicts( self.default_config_options, self._config.config["provisioner"]["config_options"], ) @property - def options(self): # type: ignore[no-untyped-def] # noqa: ANN201, D102 + def options(self): # noqa: ANN201, D102 if self._config.action in ["create", "destroy"]: return self.default_options @@ -562,7 +554,7 @@ def options(self): # type: ignore[no-untyped-def] # noqa: ANN201, D102 return util.merge_dicts(self.default_options, o) @property - def env(self): # type: ignore[no-untyped-def] # noqa: ANN201, D102 + def env(self): # noqa: ANN201, D102 default_env = self.default_env env = self._config.config["provisioner"]["env"].copy() # ensure that all keys and values are strings @@ -572,13 +564,13 @@ def env(self): # type: ignore[no-untyped-def] # noqa: ANN201, D102 filter_plugins_path = default_env["ANSIBLE_FILTER_PLUGINS"] try: - path = self._absolute_path_for(env, "ANSIBLE_LIBRARY") # type: ignore[no-untyped-call] + path = self._absolute_path_for(env, "ANSIBLE_LIBRARY") library_path = f"{library_path}:{path}" except KeyError: pass try: - path = self._absolute_path_for(env, "ANSIBLE_FILTER_PLUGINS") # type: ignore[no-untyped-call] + path = self._absolute_path_for(env, "ANSIBLE_FILTER_PLUGINS") filter_plugins_path = f"{filter_plugins_path}:{path}" except KeyError: pass @@ -589,23 +581,23 @@ def env(self): # type: ignore[no-untyped-def] # noqa: ANN201, D102 return util.merge_dicts(default_env, env) @property - def hosts(self): # type: ignore[no-untyped-def] # noqa: ANN201, D102 + def hosts(self): # noqa: ANN201, D102 return self._config.config["provisioner"]["inventory"]["hosts"] @property - def host_vars(self): # type: ignore[no-untyped-def] # noqa: ANN201, D102 + def host_vars(self): # noqa: ANN201, D102 return self._config.config["provisioner"]["inventory"]["host_vars"] @property - def group_vars(self): # type: ignore[no-untyped-def] # noqa: ANN201, D102 + def group_vars(self): # noqa: ANN201, D102 return self._config.config["provisioner"]["inventory"]["group_vars"] @property - def links(self): # type: ignore[no-untyped-def] # noqa: ANN201, D102 + def links(self): # noqa: ANN201, D102 return self._config.config["provisioner"]["inventory"]["links"] @property - def inventory(self): # type: ignore[no-untyped-def] # noqa: ANN201 + def inventory(self): # noqa: ANN201 """Create an inventory structure and returns a dict. ``` yaml @@ -628,11 +620,11 @@ def inventory(self): # type: ignore[no-untyped-def] # noqa: ANN201 ansible_connection: docker ``` """ - dd = self._vivify() # type: ignore[no-untyped-call] + dd = self._vivify() for platform in self._config.platforms.instances: for group in platform.get("groups", ["ungrouped"]): instance_name = platform["name"] - connection_options = self.connection_options(instance_name) # type: ignore[no-untyped-call] + connection_options = self.connection_options(instance_name) molecule_vars = { "molecule_file": "{{ lookup('env', 'MOLECULE_FILE') }}", "molecule_ephemeral_directory": "{{ lookup('env', 'MOLECULE_EPHEMERAL_DIRECTORY') }}", # noqa: E501 @@ -655,29 +647,29 @@ def inventory(self): # type: ignore[no-untyped-def] # noqa: ANN201 for child_group in platform.get("children", []): dd[group]["children"][child_group]["hosts"][instance_name] = connection_options - return self._default_to_regular(dd) # type: ignore[no-untyped-call] + return self._default_to_regular(dd) @property - def inventory_directory(self): # type: ignore[no-untyped-def] # noqa: ANN201, D102 + def inventory_directory(self): # noqa: ANN201, D102 return self._config.scenario.inventory_directory @property - def inventory_file(self): # type: ignore[no-untyped-def] # noqa: ANN201, D102 + def inventory_file(self): # noqa: ANN201, D102 return os.path.join(self.inventory_directory, "ansible_inventory.yml") # noqa: PTH118 @property - def config_file(self): # type: ignore[no-untyped-def] # noqa: ANN201, D102 + def config_file(self): # noqa: ANN201, D102 return os.path.join( # noqa: PTH118 self._config.scenario.ephemeral_directory, "ansible.cfg", ) @cached_property - def playbooks(self): # type: ignore[no-untyped-def] # noqa: ANN201, D102 + def playbooks(self): # noqa: ANN201, D102 return ansible_playbooks.AnsiblePlaybooks(self._config) @property - def directory(self): # type: ignore[no-untyped-def] # noqa: ANN201, D102 + def directory(self): # noqa: ANN201, D102 return os.path.join( # noqa: PTH118 os.path.dirname(__file__), # noqa: PTH120 os.path.pardir, @@ -687,12 +679,12 @@ def directory(self): # type: ignore[no-untyped-def] # noqa: ANN201, D102 "ansible", ) - def cleanup(self): # type: ignore[no-untyped-def] # noqa: ANN201 + def cleanup(self): # noqa: ANN201 """Execute `ansible-playbook` against the cleanup playbook and returns None.""" - pb = self._get_ansible_playbook(self.playbooks.cleanup) # type: ignore[no-untyped-call] + pb = self._get_ansible_playbook(self.playbooks.cleanup) pb.execute() - def connection_options(self, instance_name): # type: ignore[no-untyped-def] # noqa: ANN001, ANN201, D102 + def connection_options(self, instance_name): # noqa: ANN001, ANN201, D102 d = self._config.driver.ansible_connection_options(instance_name) return util.merge_dicts( @@ -700,13 +692,13 @@ def connection_options(self, instance_name): # type: ignore[no-untyped-def] # self._config.config["provisioner"]["connection_options"], ) - def check(self): # type: ignore[no-untyped-def] # noqa: ANN201 + def check(self): # noqa: ANN201 """Execute ``ansible-playbook`` against the converge playbook with the ``--check`` flag.""" - pb = self._get_ansible_playbook(self.playbooks.converge) # type: ignore[no-untyped-call] + pb = self._get_ansible_playbook(self.playbooks.converge) pb.add_cli_arg("check", True) # noqa: FBT003 pb.execute() - def converge(self, playbook=None, **kwargs): # type: ignore[no-untyped-def] # noqa: ANN001, ANN003, ANN201 + def converge(self, playbook=None, **kwargs): # noqa: ANN001, ANN003, ANN201 """Execute ``ansible-playbook`` against the converge playbook. unless specified otherwise. Args: @@ -716,44 +708,44 @@ def converge(self, playbook=None, **kwargs): # type: ignore[no-untyped-def] # Returns: str: The output from the ``ansible-playbook`` command. """ - pb = self._get_ansible_playbook(playbook or self.playbooks.converge, **kwargs) # type: ignore[no-untyped-call] + pb = self._get_ansible_playbook(playbook or self.playbooks.converge, **kwargs) return pb.execute() - def destroy(self): # type: ignore[no-untyped-def] # noqa: ANN201 + def destroy(self): # noqa: ANN201 """Execute ``ansible-playbook`` against the destroy playbook and returns None.""" - pb = self._get_ansible_playbook(self.playbooks.destroy) # type: ignore[no-untyped-call] + pb = self._get_ansible_playbook(self.playbooks.destroy) pb.execute() - def side_effect(self, action_args=None): # type: ignore[no-untyped-def] # noqa: ANN001, ANN201 + def side_effect(self, action_args=None): # noqa: ANN001, ANN201 """Execute ``ansible-playbook`` against the side_effect playbook and returns None.""" if action_args: playbooks = [ - self._get_ansible_playbook(self._config.provisioner.abs_path(playbook)) # type: ignore[no-untyped-call] + self._get_ansible_playbook(self._config.provisioner.abs_path(playbook)) for playbook in action_args ] else: - playbooks = [self._get_ansible_playbook(self.playbooks.side_effect)] # type: ignore[no-untyped-call] + playbooks = [self._get_ansible_playbook(self.playbooks.side_effect)] for pb in playbooks: pb.execute() - def create(self): # type: ignore[no-untyped-def] # noqa: ANN201 + def create(self): # noqa: ANN201 """Execute ``ansible-playbook`` against the create playbook and returns None.""" - pb = self._get_ansible_playbook(self.playbooks.create) # type: ignore[no-untyped-call] + pb = self._get_ansible_playbook(self.playbooks.create) pb.execute() - def prepare(self): # type: ignore[no-untyped-def] # noqa: ANN201 + def prepare(self): # noqa: ANN201 """Execute ``ansible-playbook`` against the prepare playbook and returns None.""" - pb = self._get_ansible_playbook(self.playbooks.prepare) # type: ignore[no-untyped-call] + pb = self._get_ansible_playbook(self.playbooks.prepare) pb.execute() - def syntax(self): # type: ignore[no-untyped-def] # noqa: ANN201 + def syntax(self): # noqa: ANN201 """Execute `ansible-playbook` against the converge playbook with the -syntax-check flag.""" - pb = self._get_ansible_playbook(self.playbooks.converge) # type: ignore[no-untyped-call] + pb = self._get_ansible_playbook(self.playbooks.converge) pb.add_cli_arg("syntax-check", True) # noqa: FBT003 pb.execute() - def verify(self, action_args=None): # type: ignore[no-untyped-def] # noqa: ANN001, ANN201 + def verify(self, action_args=None): # noqa: ANN001, ANN201 """Execute ``ansible-playbook`` against the verify playbook and returns None.""" if action_args: playbooks = [self._config.provisioner.abs_path(playbook) for playbook in action_args] @@ -764,30 +756,30 @@ def verify(self, action_args=None): # type: ignore[no-untyped-def] # noqa: ANN return for playbook in playbooks: # Get ansible playbooks for `verify` instead of `provision` - pb = self._get_ansible_playbook(playbook, verify=True) # type: ignore[no-untyped-call] + pb = self._get_ansible_playbook(playbook, verify=True) pb.execute() - def write_config(self): # type: ignore[no-untyped-def] # noqa: ANN201 + def write_config(self): # noqa: ANN201 """Write the provisioner's config file to disk and returns None.""" template = util.render_template( - self._get_config_template(), # type: ignore[no-untyped-call] + self._get_config_template(), config_options=self.config_options, ) util.write_file(self.config_file, template) - def manage_inventory(self): # type: ignore[no-untyped-def] # noqa: ANN201 + def manage_inventory(self): # noqa: ANN201 """Manage inventory for Ansible and returns None.""" - self._write_inventory() # type: ignore[no-untyped-call] - self._remove_vars() # type: ignore[no-untyped-call] + self._write_inventory() + self._remove_vars() if not self.links: - self._add_or_update_vars() # type: ignore[no-untyped-call] + self._add_or_update_vars() else: - self._link_or_update_vars() # type: ignore[no-untyped-call] + self._link_or_update_vars() def abs_path(self, path: str) -> str | None: # noqa: D102 return util.abs_path(os.path.join(self._config.scenario.directory, path)) # noqa: PTH118 - def _add_or_update_vars(self): # type: ignore[no-untyped-def] # noqa: ANN202 + def _add_or_update_vars(self): # noqa: ANN202 """Create host and/or group vars and returns None.""" # Create the hosts extra inventory source (only if not empty) hosts_file = os.path.join(self.inventory_directory, "hosts") # noqa: PTH118 @@ -814,13 +806,13 @@ def _add_or_update_vars(self): # type: ignore[no-untyped-def] # noqa: ANN202 path = target_vars_directory / target util.write_file(path, util.safe_dump(target_var_content)) - def _write_inventory(self): # type: ignore[no-untyped-def] # noqa: ANN202 + def _write_inventory(self): # noqa: ANN202 """Write the provisioner's inventory file to disk and returns None.""" - self._verify_inventory() # type: ignore[no-untyped-call] + self._verify_inventory() util.write_file(self.inventory_file, util.safe_dump(self.inventory)) - def _remove_vars(self): # type: ignore[no-untyped-def] # noqa: ANN202 + def _remove_vars(self): # noqa: ANN202 """Remove hosts/host_vars/group_vars and returns None.""" for name in ("hosts", "group_vars", "host_vars"): d = os.path.join(self.inventory_directory, name) # noqa: PTH118 @@ -829,7 +821,7 @@ def _remove_vars(self): # type: ignore[no-untyped-def] # noqa: ANN202 elif os.path.isdir(d): # noqa: PTH112 shutil.rmtree(d) - def _link_or_update_vars(self): # type: ignore[no-untyped-def] # noqa: ANN202 + def _link_or_update_vars(self): # noqa: ANN202 """Create or updates the symlink to group_vars and returns None.""" for d, source in self.links.items(): target = os.path.join(self.inventory_directory, d) # noqa: PTH118 @@ -850,7 +842,12 @@ def _link_or_update_vars(self): # type: ignore[no-untyped-def] # noqa: ANN202 LOG.debug(msg) os.symlink(source, target) - def _get_ansible_playbook(self, playbook, verify=False, **kwargs): # type: ignore[no-untyped-def] # noqa: ANN001, ANN003, ANN202, FBT002 + def _get_ansible_playbook( + self, + playbook, + verify=False, + **kwargs, + ): """Get an instance of AnsiblePlaybook and returns it. Args: @@ -866,13 +863,13 @@ def _get_ansible_playbook(self, playbook, verify=False, **kwargs): # type: igno **kwargs, ) - def _verify_inventory(self): # type: ignore[no-untyped-def] # noqa: ANN202 + def _verify_inventory(self): # noqa: ANN202 """Verify the inventory is valid and returns None.""" if not self.inventory: msg = "Instances missing from the 'platform' section of molecule.yml." util.sysexit_with_message(msg) - def _get_config_template(self): # type: ignore[no-untyped-def] # noqa: ANN202 + def _get_config_template(self): # noqa: ANN202 """Return a config template string. Returns: @@ -887,7 +884,7 @@ def _get_config_template(self): # type: ignore[no-untyped-def] # noqa: ANN202 {% endfor -%} """.strip() - def _vivify(self): # type: ignore[no-untyped-def] # noqa: ANN202 + def _vivify(self): # noqa: ANN202 """Return an autovivification default dict. Return: @@ -895,9 +892,9 @@ def _vivify(self): # type: ignore[no-untyped-def] # noqa: ANN202 """ return collections.defaultdict(self._vivify) - def _default_to_regular(self, d): # type: ignore[no-untyped-def] # noqa: ANN001, ANN202 + def _default_to_regular(self, d): # noqa: ANN001, ANN202 if isinstance(d, collections.defaultdict): - d = {k: self._default_to_regular(v) for k, v in d.items()} # type: ignore[no-untyped-call] + d = {k: self._default_to_regular(v) for k, v in d.items()} return d @@ -946,7 +943,7 @@ def _get_modules_directories(self) -> list[str]: return [path for path in paths if path is not None] - def _get_filter_plugin_directory(self): # type: ignore[no-untyped-def] # noqa: ANN202 + def _get_filter_plugin_directory(self): # noqa: ANN202 return util.abs_path(os.path.join(self._get_plugin_directory(), "filter")) # noqa: PTH118 def _get_filter_plugins_directories(self) -> list[str]: @@ -959,7 +956,7 @@ def _get_filter_plugins_directories(self) -> list[str]: paths.extend( [ - self._get_filter_plugin_directory(), # type: ignore[no-untyped-call] + self._get_filter_plugin_directory(), util.abs_path( os.path.join( # noqa: PTH118 self._config.scenario.ephemeral_directory, @@ -988,5 +985,5 @@ def _get_filter_plugins_directories(self) -> list[str]: return [path for path in paths if path is not None] - def _absolute_path_for(self, env, key): # type: ignore[no-untyped-def] # noqa: ANN001, ANN202 + def _absolute_path_for(self, env, key): # noqa: ANN001, ANN202 return ":".join([self.abs_path(p) for p in env[key].split(":")]) # type: ignore[misc] diff --git a/src/molecule/provisioner/base.py b/src/molecule/provisioner/base.py index edac53d3f..26aeb1d12 100644 --- a/src/molecule/provisioner/base.py +++ b/src/molecule/provisioner/base.py @@ -42,7 +42,7 @@ def __init__(self, config: Config) -> None: @property @abc.abstractmethod - def default_options(self) -> dict[str, str]: # pragma: no cover + def default_options(self) -> dict[str, str | bool]: # pragma: no cover """Get default CLI arguments provided to ``cmd``. Returns: From b3039c65859c279c47e758c028702e95f22defba Mon Sep 17 00:00:00 2001 From: Kate Case Date: Mon, 4 Nov 2024 14:26:24 -0500 Subject: [PATCH 05/12] Additional work on ansible --- .config/pydoclint-baseline.txt | 23 ---- src/molecule/provisioner/ansible.py | 172 +++++++++++++++++----------- 2 files changed, 108 insertions(+), 87 deletions(-) diff --git a/.config/pydoclint-baseline.txt b/.config/pydoclint-baseline.txt index 406302e3e..72c4d5fdc 100644 --- a/.config/pydoclint-baseline.txt +++ b/.config/pydoclint-baseline.txt @@ -95,29 +95,6 @@ src/molecule/model/schema_v3.py DOC103: Function `validate`: Docstring arguments are different from function arguments. (Or could be other formatting issues: https://jsh9.github.io/pydoclint/violation_codes.html#notes-on-doc103 ). Arguments in the function signature but not in the docstring: [c: ]. DOC201: Function `validate` does not have a return section in docstring -------------------- -src/molecule/provisioner/ansible.py - DOC106: Method `Ansible.__init__`: The option `--arg-type-hints-in-signature` is `True` but there are no argument type hints in the signature - DOC107: Method `Ansible.__init__`: The option `--arg-type-hints-in-signature` is `True` but not all args in the signature have type hints - DOC106: Method `Ansible.converge`: The option `--arg-type-hints-in-signature` is `True` but there are no argument type hints in the signature - DOC107: Method `Ansible.converge`: The option `--arg-type-hints-in-signature` is `True` but not all args in the signature have type hints - DOC103: Method `Ansible.converge`: Docstring arguments are different from function arguments. (Or could be other formatting issues: https://jsh9.github.io/pydoclint/violation_codes.html#notes-on-doc103 ). Arguments in the function signature but not in the docstring: [**kwargs: ]. Arguments in the docstring but not in the function signature: [kwargs: ]. - DOC101: Method `Ansible.side_effect`: Docstring contains fewer arguments than in function signature. - DOC106: Method `Ansible.side_effect`: The option `--arg-type-hints-in-signature` is `True` but there are no argument type hints in the signature - DOC107: Method `Ansible.side_effect`: The option `--arg-type-hints-in-signature` is `True` but not all args in the signature have type hints - DOC103: Method `Ansible.side_effect`: Docstring arguments are different from function arguments. (Or could be other formatting issues: https://jsh9.github.io/pydoclint/violation_codes.html#notes-on-doc103 ). Arguments in the function signature but not in the docstring: [action_args: ]. - DOC101: Method `Ansible.verify`: Docstring contains fewer arguments than in function signature. - DOC106: Method `Ansible.verify`: The option `--arg-type-hints-in-signature` is `True` but there are no argument type hints in the signature - DOC107: Method `Ansible.verify`: The option `--arg-type-hints-in-signature` is `True` but not all args in the signature have type hints - DOC103: Method `Ansible.verify`: Docstring arguments are different from function arguments. (Or could be other formatting issues: https://jsh9.github.io/pydoclint/violation_codes.html#notes-on-doc103 ). Arguments in the function signature but not in the docstring: [action_args: ]. - DOC201: Method `Ansible.verify` does not have a return section in docstring - DOC106: Method `Ansible._get_ansible_playbook`: The option `--arg-type-hints-in-signature` is `True` but there are no argument type hints in the signature - DOC107: Method `Ansible._get_ansible_playbook`: The option `--arg-type-hints-in-signature` is `True` but not all args in the signature have type hints - DOC103: Method `Ansible._get_ansible_playbook`: Docstring arguments are different from function arguments. (Or could be other formatting issues: https://jsh9.github.io/pydoclint/violation_codes.html#notes-on-doc103 ). Arguments in the function signature but not in the docstring: [**kwargs: ]. Arguments in the docstring but not in the function signature: [kwargs: ]. - DOC201: Method `Ansible._get_ansible_playbook` does not have a return section in docstring - DOC201: Method `Ansible._vivify` does not have a return section in docstring - DOC201: Method `Ansible._get_modules_directories` does not have a return section in docstring - DOC201: Method `Ansible._get_filter_plugins_directories` does not have a return section in docstring --------------------- src/molecule/verifier/base.py DOC601: Class `Verifier`: Class docstring contains fewer class attributes than actual class attributes. (Please read https://jsh9.github.io/pydoclint/checking_class_attributes.html on how to correctly document class attributes.) DOC603: Class `Verifier`: Class docstring attributes are different from actual class attributes. (Or could be other formatting issues: https://jsh9.github.io/pydoclint/violation_codes.html#notes-on-doc103 ). Attributes in the class definition but not in the docstring: [__metaclass__: ]. (Please read https://jsh9.github.io/pydoclint/checking_class_attributes.html on how to correctly document class attributes.) diff --git a/src/molecule/provisioner/ansible.py b/src/molecule/provisioner/ansible.py index 2014529c2..a1bd561e2 100644 --- a/src/molecule/provisioner/ansible.py +++ b/src/molecule/provisioner/ansible.py @@ -28,7 +28,7 @@ import shutil from pathlib import Path -from typing import Any +from typing import TYPE_CHECKING from ansible_compat.ports import cached_property @@ -37,6 +37,10 @@ from molecule.provisioner import ansible_playbook, ansible_playbooks, base +if TYPE_CHECKING: + from typing import Any + + LOG = logging.getLogger(__name__) @@ -416,7 +420,11 @@ class Ansible(base.Base): @property def default_config_options(self) -> dict[str, Any]: - """Provide Default options to construct ansible.cfg and returns a dict.""" + """Provide default options to construct ansible.cfg. + + Returns: + Default config options. + """ return { "defaults": { "ansible_managed": "Ansible managed: Do NOT edit this file manually!", @@ -434,11 +442,16 @@ def default_config_options(self) -> dict[str, Any]: } @property - def default_options(self) -> dict[str, str | bool]: # noqa: D102 + def default_options(self) -> dict[str, str | bool]: + """Provide default options. + + Returns: + Default options. + """ d: dict[str, str | bool] = {"skip-tags": "molecule-notest,notest"} if self._config.action == "idempotence": - d["skip-tags"] += ",molecule-idempotence-notest" + d["skip-tags"] += ",molecule-idempotence-notest" # type: ignore[assignment, operator] if self._config.debug: d["vvv"] = True @@ -447,7 +460,12 @@ def default_options(self) -> dict[str, str | bool]: # noqa: D102 return d @property - def default_env(self) -> dict[str, str]: # noqa: D102 + def default_env(self) -> dict[str, str]: + """Provide default environment variables. + + Returns: + Default set of environment variables. + """ # Finds if the current project is part of an ansible_collections hierarchy collection_indicator = "ansible_collections" # isolating test environment by injects ephemeral scenario directory on @@ -526,22 +544,37 @@ def default_env(self) -> dict[str, str]: # noqa: D102 return env # noqa: RET504 @property - def name(self) -> str: # noqa: D102 + def name(self) -> str: + """Provisioner name. + + Returns: + The provisioner name. + """ return self._config.config["provisioner"]["name"] @property - def ansible_args(self) -> list[str]: # noqa: D102 + def ansible_args(self) -> list[str]: + """Provisioner ansible args. + + Returns: + The provisioner ansible_args. + """ return self._config.config["provisioner"]["ansible_args"] @property - def config_options(self): # noqa: ANN201, D102 + def config_options(self) -> dict[str, Any]: + """Provisioner config options. + + Returns: + The provisioner config options. + """ return util.merge_dicts( self.default_config_options, self._config.config["provisioner"]["config_options"], ) @property - def options(self): # noqa: ANN201, D102 + def options(self) -> dict[str, Any]: # noqa: D102 if self._config.action in ["create", "destroy"]: return self.default_options @@ -554,7 +587,7 @@ def options(self): # noqa: ANN201, D102 return util.merge_dicts(self.default_options, o) @property - def env(self): # noqa: ANN201, D102 + def env(self) -> dict[str, str]: # noqa: D102 default_env = self.default_env env = self._config.config["provisioner"]["env"].copy() # ensure that all keys and values are strings @@ -581,23 +614,23 @@ def env(self): # noqa: ANN201, D102 return util.merge_dicts(default_env, env) @property - def hosts(self): # noqa: ANN201, D102 + def hosts(self) -> dict[str, str]: # noqa: D102 return self._config.config["provisioner"]["inventory"]["hosts"] @property - def host_vars(self): # noqa: ANN201, D102 + def host_vars(self) -> dict[str, str]: # noqa: D102 return self._config.config["provisioner"]["inventory"]["host_vars"] @property - def group_vars(self): # noqa: ANN201, D102 + def group_vars(self) -> dict[str, str]: # noqa: D102 return self._config.config["provisioner"]["inventory"]["group_vars"] @property - def links(self): # noqa: ANN201, D102 + def links(self) -> dict[str, str]: # noqa: D102 return self._config.config["provisioner"]["inventory"]["links"] @property - def inventory(self): # noqa: ANN201 + def inventory(self) -> dict: """Create an inventory structure and returns a dict. ``` yaml @@ -650,55 +683,54 @@ def inventory(self): # noqa: ANN201 return self._default_to_regular(dd) @property - def inventory_directory(self): # noqa: ANN201, D102 + def inventory_directory(self) -> str: # noqa: D102 return self._config.scenario.inventory_directory @property - def inventory_file(self): # noqa: ANN201, D102 - return os.path.join(self.inventory_directory, "ansible_inventory.yml") # noqa: PTH118 + def inventory_file(self) -> str: # noqa: D102 + return str(Path(self.inventory_directory, "ansible_inventory.yml")) @property - def config_file(self): # noqa: ANN201, D102 - return os.path.join( # noqa: PTH118 - self._config.scenario.ephemeral_directory, - "ansible.cfg", + def config_file(self) -> str: # noqa: D102 + return str( + Path( + self._config.scenario.ephemeral_directory, + "ansible.cfg", + ), ) @cached_property - def playbooks(self): # noqa: ANN201, D102 + def playbooks(self) -> ansible_playbooks.AnsiblePlaybooks: # noqa: D102 return ansible_playbooks.AnsiblePlaybooks(self._config) @property - def directory(self): # noqa: ANN201, D102 - return os.path.join( # noqa: PTH118 - os.path.dirname(__file__), # noqa: PTH120 - os.path.pardir, - os.path.pardir, - "molecule", - "provisioner", - "ansible", - ) + def directory(self) -> str: # noqa: D102 + return str(Path(__file__).parent.parent.parent / "molecule" / "provisioner" / "ansible") - def cleanup(self): # noqa: ANN201 + def cleanup(self) -> None: """Execute `ansible-playbook` against the cleanup playbook and returns None.""" - pb = self._get_ansible_playbook(self.playbooks.cleanup) - pb.execute() + if self.playbooks.cleanup: + pb = self._get_ansible_playbook(self.playbooks.cleanup) + pb.execute() - def connection_options(self, instance_name): # noqa: ANN001, ANN201, D102 - d = self._config.driver.ansible_connection_options(instance_name) + def connection_options( + self, + instance_name: str, + ) -> dict[str, Any]: + d = self._config.driver.ansible_connection_options(instance_name) # type: ignore[no-untyped-call] return util.merge_dicts( d, self._config.config["provisioner"]["connection_options"], ) - def check(self): # noqa: ANN201 + def check(self) -> None: """Execute ``ansible-playbook`` against the converge playbook with the ``--check`` flag.""" pb = self._get_ansible_playbook(self.playbooks.converge) - pb.add_cli_arg("check", True) # noqa: FBT003 + pb.add_cli_arg("check", value=True) pb.execute() - def converge(self, playbook=None, **kwargs): # noqa: ANN001, ANN003, ANN201 + def converge(self, playbook: str = "", **kwargs: object) -> str: """Execute ``ansible-playbook`` against the converge playbook. unless specified otherwise. Args: @@ -712,12 +744,12 @@ def converge(self, playbook=None, **kwargs): # noqa: ANN001, ANN003, ANN201 return pb.execute() - def destroy(self): # noqa: ANN201 + def destroy(self) -> None: """Execute ``ansible-playbook`` against the destroy playbook and returns None.""" pb = self._get_ansible_playbook(self.playbooks.destroy) pb.execute() - def side_effect(self, action_args=None): # noqa: ANN001, ANN201 + def side_effect(self, action_args: list[str] | None = None) -> None: """Execute ``ansible-playbook`` against the side_effect playbook and returns None.""" if action_args: playbooks = [ @@ -729,23 +761,23 @@ def side_effect(self, action_args=None): # noqa: ANN001, ANN201 for pb in playbooks: pb.execute() - def create(self): # noqa: ANN201 + def create(self) -> None: """Execute ``ansible-playbook`` against the create playbook and returns None.""" pb = self._get_ansible_playbook(self.playbooks.create) pb.execute() - def prepare(self): # noqa: ANN201 + def prepare(self) -> None: """Execute ``ansible-playbook`` against the prepare playbook and returns None.""" pb = self._get_ansible_playbook(self.playbooks.prepare) pb.execute() - def syntax(self): # noqa: ANN201 + def syntax(self) -> None: """Execute `ansible-playbook` against the converge playbook with the -syntax-check flag.""" pb = self._get_ansible_playbook(self.playbooks.converge) pb.add_cli_arg("syntax-check", True) # noqa: FBT003 pb.execute() - def verify(self, action_args=None): # noqa: ANN001, ANN201 + def verify(self, action_args: list[str] | None = None) -> None: """Execute ``ansible-playbook`` against the verify playbook and returns None.""" if action_args: playbooks = [self._config.provisioner.abs_path(playbook) for playbook in action_args] @@ -759,7 +791,7 @@ def verify(self, action_args=None): # noqa: ANN001, ANN201 pb = self._get_ansible_playbook(playbook, verify=True) pb.execute() - def write_config(self): # noqa: ANN201 + def write_config(self) -> None: """Write the provisioner's config file to disk and returns None.""" template = util.render_template( self._get_config_template(), @@ -767,7 +799,7 @@ def write_config(self): # noqa: ANN201 ) util.write_file(self.config_file, template) - def manage_inventory(self): # noqa: ANN201 + def manage_inventory(self) -> None: """Manage inventory for Ansible and returns None.""" self._write_inventory() self._remove_vars() @@ -776,10 +808,19 @@ def manage_inventory(self): # noqa: ANN201 else: self._link_or_update_vars() - def abs_path(self, path: str) -> str | None: # noqa: D102 - return util.abs_path(os.path.join(self._config.scenario.directory, path)) # noqa: PTH118 + def abs_path(self, path: str | Path) -> str: + """Return absolute scenario-adjacent path. + + Args: + path: Scenario-adjacent relative path. - def _add_or_update_vars(self): # noqa: ANN202 + Returns: + Absolute path. + """ + path = Path(self._config.scenario.directory) / path + return str(util.abs_path(path)) + + def _add_or_update_vars(self) -> None: """Create host and/or group vars and returns None.""" # Create the hosts extra inventory source (only if not empty) hosts_file = os.path.join(self.inventory_directory, "hosts") # noqa: PTH118 @@ -806,13 +847,13 @@ def _add_or_update_vars(self): # noqa: ANN202 path = target_vars_directory / target util.write_file(path, util.safe_dump(target_var_content)) - def _write_inventory(self): # noqa: ANN202 + def _write_inventory(self) -> None: """Write the provisioner's inventory file to disk and returns None.""" self._verify_inventory() util.write_file(self.inventory_file, util.safe_dump(self.inventory)) - def _remove_vars(self): # noqa: ANN202 + def _remove_vars(self) -> None: """Remove hosts/host_vars/group_vars and returns None.""" for name in ("hosts", "group_vars", "host_vars"): d = os.path.join(self.inventory_directory, name) # noqa: PTH118 @@ -821,7 +862,7 @@ def _remove_vars(self): # noqa: ANN202 elif os.path.isdir(d): # noqa: PTH112 shutil.rmtree(d) - def _link_or_update_vars(self): # noqa: ANN202 + def _link_or_update_vars(self) -> None: """Create or updates the symlink to group_vars and returns None.""" for d, source in self.links.items(): target = os.path.join(self.inventory_directory, d) # noqa: PTH118 @@ -844,10 +885,10 @@ def _link_or_update_vars(self): # noqa: ANN202 def _get_ansible_playbook( self, - playbook, - verify=False, - **kwargs, - ): + playbook: str, + verify: bool = False, # noqa: FBT001, FBT002 + **kwargs: object, + ) -> ansible_playbook.AnsiblePlaybook: """Get an instance of AnsiblePlaybook and returns it. Args: @@ -863,13 +904,13 @@ def _get_ansible_playbook( **kwargs, ) - def _verify_inventory(self): # noqa: ANN202 + def _verify_inventory(self) -> None: """Verify the inventory is valid and returns None.""" if not self.inventory: msg = "Instances missing from the 'platform' section of molecule.yml." util.sysexit_with_message(msg) - def _get_config_template(self): # noqa: ANN202 + def _get_config_template(self) -> str: """Return a config template string. Returns: @@ -892,7 +933,10 @@ def _vivify(self): # noqa: ANN202 """ return collections.defaultdict(self._vivify) - def _default_to_regular(self, d): # noqa: ANN001, ANN202 + def _default_to_regular( + self, + d: dict[str, Any] | collections.defaultdict[str, Any], + ) -> dict[str, Any]: if isinstance(d, collections.defaultdict): d = {k: self._default_to_regular(v) for k, v in d.items()} @@ -943,7 +987,7 @@ def _get_modules_directories(self) -> list[str]: return [path for path in paths if path is not None] - def _get_filter_plugin_directory(self): # noqa: ANN202 + def _get_filter_plugin_directory(self) -> str: return util.abs_path(os.path.join(self._get_plugin_directory(), "filter")) # noqa: PTH118 def _get_filter_plugins_directories(self) -> list[str]: @@ -985,5 +1029,5 @@ def _get_filter_plugins_directories(self) -> list[str]: return [path for path in paths if path is not None] - def _absolute_path_for(self, env, key): # noqa: ANN001, ANN202 - return ":".join([self.abs_path(p) for p in env[key].split(":")]) # type: ignore[misc] + def _absolute_path_for(self, env: dict[str, str], key: str) -> str: + return ":".join([self.abs_path(p) for p in env[key].split(":")]) From 272b6652a6b053f0a829f722ba868c5cd7e1c992 Mon Sep 17 00:00:00 2001 From: Kate Case Date: Thu, 7 Nov 2024 10:40:16 -0500 Subject: [PATCH 06/12] Finish pydoclint on ansible. --- src/molecule/provisioner/ansible.py | 48 +++++++++++++++++++++++------ 1 file changed, 38 insertions(+), 10 deletions(-) diff --git a/src/molecule/provisioner/ansible.py b/src/molecule/provisioner/ansible.py index a1bd561e2..96725a73f 100644 --- a/src/molecule/provisioner/ansible.py +++ b/src/molecule/provisioner/ansible.py @@ -40,6 +40,8 @@ if TYPE_CHECKING: from typing import Any + Vivify = collections.defaultdict[str, Any | "Vivify"] + LOG = logging.getLogger(__name__) @@ -630,7 +632,7 @@ def links(self) -> dict[str, str]: # noqa: D102 return self._config.config["provisioner"]["inventory"]["links"] @property - def inventory(self) -> dict: + def inventory(self) -> dict[str, str]: """Create an inventory structure and returns a dict. ``` yaml @@ -717,6 +719,14 @@ def connection_options( self, instance_name: str, ) -> dict[str, Any]: + """Computed connection options combining instance and provisioner options. + + Args: + instance_name: The instance to base the connection options on. + + Returns: + The combined connection options. + """ d = self._config.driver.ansible_connection_options(instance_name) # type: ignore[no-untyped-call] return util.merge_dicts( @@ -735,12 +745,12 @@ def converge(self, playbook: str = "", **kwargs: object) -> str: Args: playbook: An optional string containing an absolute path to a playbook. - kwargs: An optional keyword arguments. + **kwargs: An optional keyword arguments. Returns: str: The output from the ``ansible-playbook`` command. """ - pb = self._get_ansible_playbook(playbook or self.playbooks.converge, **kwargs) + pb = self._get_ansible_playbook(playbook or self.playbooks.converge, **kwargs) # type: ignore[arg-type] return pb.execute() @@ -750,7 +760,11 @@ def destroy(self) -> None: pb.execute() def side_effect(self, action_args: list[str] | None = None) -> None: - """Execute ``ansible-playbook`` against the side_effect playbook and returns None.""" + """Execute ``ansible-playbook`` against the side_effect playbook. + + Args: + action_args: Arguments to pass to the side_effect playbook. + """ if action_args: playbooks = [ self._get_ansible_playbook(self._config.provisioner.abs_path(playbook)) @@ -778,7 +792,11 @@ def syntax(self) -> None: pb.execute() def verify(self, action_args: list[str] | None = None) -> None: - """Execute ``ansible-playbook`` against the verify playbook and returns None.""" + """Execute ``ansible-playbook`` against the verify playbook. + + Args: + action_args: Arguments to pass on to the verify playbook. + """ if action_args: playbooks = [self._config.provisioner.abs_path(playbook) for playbook in action_args] else: @@ -895,7 +913,10 @@ def _get_ansible_playbook( playbook: A string containing an absolute path to a provisioner's playbook. verify: An optional bool to toggle the Playbook mode between provision and verify. False: provision; True: verify. Default is False. - kwargs: An optional keyword arguments. + **kwargs: An optional keyword arguments. + + Returns: + An AnsiblePlaybook object. """ return ansible_playbook.AnsiblePlaybook( playbook, @@ -925,11 +946,11 @@ def _get_config_template(self) -> str: {% endfor -%} """.strip() - def _vivify(self): # noqa: ANN202 + def _vivify(self) -> Vivify: """Return an autovivification default dict. - Return: - dict + Returns: + A defaultdict whose default value is other defaultdict objects (and so on). """ return collections.defaultdict(self._vivify) @@ -949,6 +970,9 @@ def _get_modules_directories(self) -> list[str]: """Return list of ansible module includes directories. Adds modules directory from molecule and its plugins. + + Returns: + List of module includes directories. """ paths: list[str | None] = [] if os.environ.get("ANSIBLE_LIBRARY"): @@ -991,7 +1015,11 @@ def _get_filter_plugin_directory(self) -> str: return util.abs_path(os.path.join(self._get_plugin_directory(), "filter")) # noqa: PTH118 def _get_filter_plugins_directories(self) -> list[str]: - """Return list of ansible filter plugins includes directories.""" + """Return list of ansible filter plugins includes directories. + + Returns: + List of filter includes directories. + """ paths: list[str | None] = [] if os.environ.get("ANSIBLE_FILTER_PLUGINS"): paths = list( From 1d7f2b7160505cb2036b7857a20ff88490cf0776 Mon Sep 17 00:00:00 2001 From: Kate Case Date: Thu, 7 Nov 2024 10:43:23 -0500 Subject: [PATCH 07/12] Remove dangling no-untyped-calls --- src/molecule/command/base.py | 4 ++-- src/molecule/command/check.py | 2 +- src/molecule/command/cleanup.py | 2 +- src/molecule/command/converge.py | 2 +- src/molecule/command/create.py | 2 +- src/molecule/command/destroy.py | 2 +- src/molecule/command/idempotence.py | 2 +- src/molecule/command/prepare.py | 2 +- src/molecule/command/side_effect.py | 2 +- src/molecule/command/syntax.py | 2 +- 10 files changed, 11 insertions(+), 11 deletions(-) diff --git a/src/molecule/command/base.py b/src/molecule/command/base.py index 8d78eef81..10124c479 100644 --- a/src/molecule/command/base.py +++ b/src/molecule/command/base.py @@ -93,8 +93,8 @@ def _setup(self) -> None: """Prepare Molecule's provisioner and returns None.""" self._config.write() if self._config.provisioner is not None: - self._config.provisioner.write_config() # type: ignore[no-untyped-call] - self._config.provisioner.manage_inventory() # type: ignore[no-untyped-call] + self._config.provisioner.write_config() + self._config.provisioner.manage_inventory() def execute_cmdline_scenarios( diff --git a/src/molecule/command/check.py b/src/molecule/command/check.py index 52603c173..c879ca081 100644 --- a/src/molecule/command/check.py +++ b/src/molecule/command/check.py @@ -49,7 +49,7 @@ def execute(self, action_args: list[str] | None = None) -> None: # noqa: ARG002 action_args: Arguments for this command. Unused. """ if self._config.provisioner is not None: - self._config.provisioner.check() # type: ignore[no-untyped-call] + self._config.provisioner.check() @base.click_command_ex() diff --git a/src/molecule/command/cleanup.py b/src/molecule/command/cleanup.py index f4d11bef0..4c1241bed 100644 --- a/src/molecule/command/cleanup.py +++ b/src/molecule/command/cleanup.py @@ -51,7 +51,7 @@ def execute(self, action_args: list[str] | None = None) -> None: # noqa: ARG002 LOG.warning(msg) return - self._config.provisioner.cleanup() # type: ignore[no-untyped-call] + self._config.provisioner.cleanup() @base.click_command_ex() diff --git a/src/molecule/command/converge.py b/src/molecule/command/converge.py index 566c42410..7526b5d14 100644 --- a/src/molecule/command/converge.py +++ b/src/molecule/command/converge.py @@ -46,7 +46,7 @@ def execute(self, action_args: list[str] | None = None) -> None: # noqa: ARG002 action_args: Arguments for this command. Unused. """ if self._config.provisioner: - self._config.provisioner.converge() # type: ignore[no-untyped-call] + self._config.provisioner.converge() self._config.state.change_state("converged", value=True) diff --git a/src/molecule/command/create.py b/src/molecule/command/create.py index 6edd87edd..5f15cbf34 100644 --- a/src/molecule/command/create.py +++ b/src/molecule/command/create.py @@ -55,7 +55,7 @@ def execute(self, action_args: list[str] | None = None) -> None: # noqa: ARG002 return if self._config.provisioner: - self._config.provisioner.create() # type: ignore[no-untyped-call] + self._config.provisioner.create() self._config.state.change_state("created", value=True) diff --git a/src/molecule/command/destroy.py b/src/molecule/command/destroy.py index 84adc6569..438a06eb4 100644 --- a/src/molecule/command/destroy.py +++ b/src/molecule/command/destroy.py @@ -56,7 +56,7 @@ def execute(self, action_args: list[str] | None = None) -> None: # noqa: ARG002 return if self._config.provisioner: - self._config.provisioner.destroy() # type: ignore[no-untyped-call] + self._config.provisioner.destroy() self._config.state.reset() diff --git a/src/molecule/command/idempotence.py b/src/molecule/command/idempotence.py index a97ec4b91..d8f0e9a23 100644 --- a/src/molecule/command/idempotence.py +++ b/src/molecule/command/idempotence.py @@ -57,7 +57,7 @@ def execute(self, action_args: list[str] | None = None) -> None: # noqa: ARG002 util.sysexit_with_message(msg) if self._config.provisioner: - output = self._config.provisioner.converge() # type: ignore[no-untyped-call] + output = self._config.provisioner.converge() idempotent = self._is_idempotent(output) if idempotent: diff --git a/src/molecule/command/prepare.py b/src/molecule/command/prepare.py index 3342f1c05..2047bc578 100644 --- a/src/molecule/command/prepare.py +++ b/src/molecule/command/prepare.py @@ -108,7 +108,7 @@ def execute(self, action_args: list[str] | None = None) -> None: # noqa: ARG002 LOG.warning(msg) return - self._config.provisioner.prepare() # type: ignore[no-untyped-call] + self._config.provisioner.prepare() self._config.state.change_state("prepared", value=True) diff --git a/src/molecule/command/side_effect.py b/src/molecule/command/side_effect.py index cb7edbd08..6cf50ffb5 100644 --- a/src/molecule/command/side_effect.py +++ b/src/molecule/command/side_effect.py @@ -54,7 +54,7 @@ def execute(self, action_args: list[str] | None = None) -> None: LOG.warning(msg) return - self._config.provisioner.side_effect(action_args) # type: ignore[no-untyped-call] + self._config.provisioner.side_effect(action_args) @base.click_command_ex() diff --git a/src/molecule/command/syntax.py b/src/molecule/command/syntax.py index cad3df406..8f33572f5 100644 --- a/src/molecule/command/syntax.py +++ b/src/molecule/command/syntax.py @@ -46,7 +46,7 @@ def execute(self, action_args: list[str] | None = None) -> None: # noqa: ARG002 action_args: Arguments for this command. Unused. """ if self._config.provisioner: - self._config.provisioner.syntax() # type: ignore[no-untyped-call] + self._config.provisioner.syntax() @base.click_command_ex() From 68b2f8b100bb947aded793f43271bc951b9aaa22 Mon Sep 17 00:00:00 2001 From: Kate Case Date: Thu, 7 Nov 2024 12:40:33 -0500 Subject: [PATCH 08/12] Final pass provisioner --- src/molecule/config.py | 2 +- src/molecule/provisioner/__init__.py | 2 +- src/molecule/provisioner/ansible.py | 49 +++++++++++--------- src/molecule/provisioner/ansible_playbook.py | 4 +- src/molecule/types.py | 6 ++- src/molecule/util.py | 2 +- 6 files changed, 38 insertions(+), 27 deletions(-) diff --git a/src/molecule/config.py b/src/molecule/config.py index 3006fd151..5ce932c81 100644 --- a/src/molecule/config.py +++ b/src/molecule/config.py @@ -135,7 +135,7 @@ def write(self) -> None: # noqa: D102 @property def ansible_collections_path( self, - ) -> Literal["ANSIBLE_COLLECTIONS_PATH", "ANSIBLE_COLLECTIONS_PATHS"]: + ) -> str: """Return collection path variable for current version of Ansible.""" # https://github.com/ansible/ansible/pull/70007 if self.runtime.version >= Version("2.10.0.dev0"): diff --git a/src/molecule/provisioner/__init__.py b/src/molecule/provisioner/__init__.py index d2583e366..6e031999e 100644 --- a/src/molecule/provisioner/__init__.py +++ b/src/molecule/provisioner/__init__.py @@ -1 +1 @@ -# D104 # noqa: D104, ERA001 +# noqa: D104 diff --git a/src/molecule/provisioner/ansible.py b/src/molecule/provisioner/ansible.py index 96725a73f..868e5533f 100644 --- a/src/molecule/provisioner/ansible.py +++ b/src/molecule/provisioner/ansible.py @@ -1,3 +1,4 @@ +# pylint: disable=too-many-lines # Copyright (c) 2015-2018 Cisco Systems, Inc. # Permission is hereby granted, free of charge, to any person obtaining a copy @@ -529,10 +530,10 @@ def default_env(self) -> dict[str, str]: list(map(util.abs_path, os.environ["ANSIBLE_ROLES_PATH"].split(":"))), ) - env = util.merge_dicts( - os.environ, + env = util.merge_dicts( # type: ignore[type-var] + dict(os.environ), { - "ANSIBLE_CONFIG": self._config.provisioner.config_file, + "ANSIBLE_CONFIG": self.config_file, "ANSIBLE_ROLES_PATH": ":".join(roles_path_list), self._config.ansible_collections_path: ":".join(collections_path_list), "ANSIBLE_LIBRARY": ":".join(self._get_modules_directories()), @@ -541,7 +542,7 @@ def default_env(self) -> dict[str, str]: ), }, ) - env = util.merge_dicts(env, self._config.env) + env = util.merge_dicts(env, self._config.env) # type: ignore[type-var] return env # noqa: RET504 @@ -736,9 +737,10 @@ def connection_options( def check(self) -> None: """Execute ``ansible-playbook`` against the converge playbook with the ``--check`` flag.""" - pb = self._get_ansible_playbook(self.playbooks.converge) - pb.add_cli_arg("check", value=True) - pb.execute() + if self.playbooks.converge: + pb = self._get_ansible_playbook(self.playbooks.converge) + pb.add_cli_arg("check", value=True) + pb.execute() def converge(self, playbook: str = "", **kwargs: object) -> str: """Execute ``ansible-playbook`` against the converge playbook. unless specified otherwise. @@ -756,8 +758,9 @@ def converge(self, playbook: str = "", **kwargs: object) -> str: def destroy(self) -> None: """Execute ``ansible-playbook`` against the destroy playbook and returns None.""" - pb = self._get_ansible_playbook(self.playbooks.destroy) - pb.execute() + if self.playbooks.destroy: + pb = self._get_ansible_playbook(self.playbooks.destroy) + pb.execute() def side_effect(self, action_args: list[str] | None = None) -> None: """Execute ``ansible-playbook`` against the side_effect playbook. @@ -765,31 +768,34 @@ def side_effect(self, action_args: list[str] | None = None) -> None: Args: action_args: Arguments to pass to the side_effect playbook. """ + playbooks = [] if action_args: playbooks = [ - self._get_ansible_playbook(self._config.provisioner.abs_path(playbook)) - for playbook in action_args + self._get_ansible_playbook(self.abs_path(playbook)) for playbook in action_args ] - else: + elif self.playbooks.side_effect: playbooks = [self._get_ansible_playbook(self.playbooks.side_effect)] for pb in playbooks: pb.execute() def create(self) -> None: """Execute ``ansible-playbook`` against the create playbook and returns None.""" - pb = self._get_ansible_playbook(self.playbooks.create) - pb.execute() + if self.playbooks.create: + pb = self._get_ansible_playbook(self.playbooks.create) + pb.execute() def prepare(self) -> None: """Execute ``ansible-playbook`` against the prepare playbook and returns None.""" - pb = self._get_ansible_playbook(self.playbooks.prepare) - pb.execute() + if self.playbooks.prepare: + pb = self._get_ansible_playbook(self.playbooks.prepare) + pb.execute() def syntax(self) -> None: """Execute `ansible-playbook` against the converge playbook with the -syntax-check flag.""" - pb = self._get_ansible_playbook(self.playbooks.converge) - pb.add_cli_arg("syntax-check", True) # noqa: FBT003 - pb.execute() + if self.playbooks.converge: + pb = self._get_ansible_playbook(self.playbooks.converge) + pb.add_cli_arg("syntax-check", value=True) + pb.execute() def verify(self, action_args: list[str] | None = None) -> None: """Execute ``ansible-playbook`` against the verify playbook. @@ -797,9 +803,10 @@ def verify(self, action_args: list[str] | None = None) -> None: Args: action_args: Arguments to pass on to the verify playbook. """ + playbooks = [] if action_args: - playbooks = [self._config.provisioner.abs_path(playbook) for playbook in action_args] - else: + playbooks = [self.abs_path(playbook) for playbook in action_args] + elif self.playbooks.verify: playbooks = [self.playbooks.verify] if not playbooks: LOG.warning("Skipping, verify playbook not configured.") diff --git a/src/molecule/provisioner/ansible_playbook.py b/src/molecule/provisioner/ansible_playbook.py index 86f5f9a2f..aed44cb60 100644 --- a/src/molecule/provisioner/ansible_playbook.py +++ b/src/molecule/provisioner/ansible_playbook.py @@ -58,7 +58,7 @@ def __init__( self._ansible_command: list[str] = [] self._playbook = playbook self._config = config - self._cli: dict[str, str] = {} + self._cli: dict[str, str | bool] = {} self._env: dict[str, str] = {} if verify: self._env = util.merge_dicts( @@ -144,7 +144,7 @@ def execute(self, action_args: list[str] | None = None) -> str: # noqa: ARG002 return result.stdout - def add_cli_arg(self, name: str, value: str) -> None: + def add_cli_arg(self, name: str, value: str | bool) -> None: """Add argument to CLI passed to ansible-playbook. Args: diff --git a/src/molecule/types.py b/src/molecule/types.py index b66034c2a..052b9d1b2 100644 --- a/src/molecule/types.py +++ b/src/molecule/types.py @@ -61,14 +61,18 @@ class InventoryData(TypedDict): links: dict[str, str] -class PlatformData(TypedDict): +class PlatformData(TypedDict, total=False): """Platform data for a Molecule run. Attributes: name: Name of the platform. + groups: Optional list of groups. + children: Optional list of child groups. """ name: str + groups: list[str] + children: list[str] class PlaybookData(TypedDict, total=False): diff --git a/src/molecule/util.py b/src/molecule/util.py index 7ce9bc8a4..da6b756d8 100644 --- a/src/molecule/util.py +++ b/src/molecule/util.py @@ -234,7 +234,7 @@ def os_walk( yield str(filename) -def render_template(template: str, **kwargs: str) -> str: +def render_template(template: str, **kwargs: dict[str, str]) -> str: """Render a jinaj2 template. Args: From 7e14cf1bfc3aef8f565e211485b43daf3be870b2 Mon Sep 17 00:00:00 2001 From: Kate Case Date: Thu, 7 Nov 2024 13:44:03 -0500 Subject: [PATCH 09/12] This still gets used both ways. --- src/molecule/util.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/molecule/util.py b/src/molecule/util.py index da6b756d8..7f6340f12 100644 --- a/src/molecule/util.py +++ b/src/molecule/util.py @@ -234,7 +234,7 @@ def os_walk( yield str(filename) -def render_template(template: str, **kwargs: dict[str, str]) -> str: +def render_template(template: str, **kwargs: str | dict[str, str]) -> str: """Render a jinaj2 template. Args: From e470eb326cb2f26c1b911489962d4a895a2502cb Mon Sep 17 00:00:00 2001 From: Kate Case Date: Mon, 18 Nov 2024 09:28:45 -0500 Subject: [PATCH 10/12] Fix generation of ansible commands --- src/molecule/provisioner/ansible_playbook.py | 2 +- src/molecule/util.py | 4 +--- 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/src/molecule/provisioner/ansible_playbook.py b/src/molecule/provisioner/ansible_playbook.py index aed44cb60..99f0ef52f 100644 --- a/src/molecule/provisioner/ansible_playbook.py +++ b/src/molecule/provisioner/ansible_playbook.py @@ -115,7 +115,7 @@ def execute(self, action_args: list[str] | None = None) -> str: # noqa: ARG002 Returns: Output from ansible-playbook. """ - if self._ansible_command is None: + if not self._ansible_command: self.bake() if not self._playbook: diff --git a/src/molecule/util.py b/src/molecule/util.py index 7f6340f12..68b599ab1 100644 --- a/src/molecule/util.py +++ b/src/molecule/util.py @@ -182,13 +182,11 @@ def run_command( # noqa: PLR0913 Raises: CalledProcessError: If return code is nonzero and check is True. """ - args = cmd - if debug: print_environment_vars(env) result = app.runtime.run( - args=args, + args=cmd, env=env, cwd=cwd, tee=True, From 630abe659455564e9baf81fda785b2c064d0cf37 Mon Sep 17 00:00:00 2001 From: Kate Case Date: Mon, 18 Nov 2024 11:27:19 -0500 Subject: [PATCH 11/12] Test types --- tests/unit/provisioner/test_ansible.py | 65 +++++++++++++++----------- 1 file changed, 39 insertions(+), 26 deletions(-) diff --git a/tests/unit/provisioner/test_ansible.py b/tests/unit/provisioner/test_ansible.py index 37aa14c64..6668eb7bd 100644 --- a/tests/unit/provisioner/test_ansible.py +++ b/tests/unit/provisioner/test_ansible.py @@ -33,12 +33,13 @@ if TYPE_CHECKING: from pathlib import Path + from unittest.mock import MagicMock, Mock from pytest_mock import MockerFixture @pytest.fixture -def _patched_ansible_playbook(mocker): # type: ignore[no-untyped-def] # noqa: ANN001, ANN202 +def _patched_ansible_playbook(mocker: MockerFixture) -> MagicMock: m = mocker.patch("molecule.provisioner.ansible_playbook.AnsiblePlaybook") m.return_value.execute.return_value = b"patched-ansible-playbook-stdout" @@ -46,17 +47,17 @@ def _patched_ansible_playbook(mocker): # type: ignore[no-untyped-def] # noqa: @pytest.fixture -def _patched_write_inventory(mocker): # type: ignore[no-untyped-def] # noqa: ANN001, ANN202 +def _patched_write_inventory(mocker: MockerFixture) -> MagicMock: return mocker.patch("molecule.provisioner.ansible.Ansible._write_inventory") @pytest.fixture -def _patched_remove_vars(mocker): # type: ignore[no-untyped-def] # noqa: ANN001, ANN202 +def _patched_remove_vars(mocker: MockerFixture) -> MagicMock: return mocker.patch("molecule.provisioner.ansible.Ansible._remove_vars") @pytest.fixture -def _patched_link_or_update_vars(mocker): # type: ignore[no-untyped-def] # noqa: ANN001, ANN202 +def _patched_link_or_update_vars(mocker: MockerFixture) -> MagicMock: return mocker.patch("molecule.provisioner.ansible.Ansible._link_or_update_vars") @@ -352,13 +353,13 @@ def test_playbooks_side_effect_property(instance): # type: ignore[no-untyped-de assert instance.playbooks.side_effect is None -def test_check(instance, mocker: MockerFixture, _patched_ansible_playbook): # type: ignore[no-untyped-def] # noqa: ANN001, ANN201, PT019, ARG001, D103 +def test_check(instance: ansible.Ansible, mocker: MockerFixture, _patched_ansible_playbook: Mock): # type: ignore[no-untyped-def] # noqa: ANN201, PT019, ARG001, D103 instance.check() _patched_ansible_playbook.assert_called_once_with( instance._config.provisioner.playbooks.converge, instance._config, - False, # noqa: FBT003 + verify=False, ) _patched_ansible_playbook.return_value.add_cli_arg.assert_called_once_with( "check", @@ -367,13 +368,17 @@ def test_check(instance, mocker: MockerFixture, _patched_ansible_playbook): # t _patched_ansible_playbook.return_value.execute.assert_called_once_with() -def test_converge(instance, mocker: MockerFixture, _patched_ansible_playbook): # type: ignore[no-untyped-def] # noqa: ANN001, ANN201, PT019, ARG001, D103 +def test_converge( # noqa: D103 + instance: ansible.Ansible, + mocker: MockerFixture, # noqa: ARG001 + _patched_ansible_playbook: Mock, # noqa: PT019 +) -> None: result = instance.converge() _patched_ansible_playbook.assert_called_once_with( instance._config.provisioner.playbooks.converge, instance._config, - False, # noqa: FBT003 + verify=False, ) # NOTE(retr0h): This is not the true return type. This is a mock return # which didn't go through str.decode(). @@ -392,7 +397,7 @@ def test_converge_with_playbook( # type: ignore[no-untyped-def] # noqa: ANN201 _patched_ansible_playbook.assert_called_once_with( "playbook", instance._config, - False, # noqa: FBT003 + verify=False, ) # NOTE(retr0h): This is not the true return type. This is a mock return # which didn't go through str.decode(). @@ -401,68 +406,72 @@ def test_converge_with_playbook( # type: ignore[no-untyped-def] # noqa: ANN201 _patched_ansible_playbook.return_value.execute.assert_called_once_with() -def test_cleanup(instance, mocker: MockerFixture, _patched_ansible_playbook): # type: ignore[no-untyped-def] # noqa: ANN001, ANN201, PT019, ARG001, D103 +def test_cleanup(instance: ansible.Ansible, mocker: MockerFixture, _patched_ansible_playbook: Mock): # type: ignore[no-untyped-def] # noqa: ANN201, PT019, ARG001, D103 instance.cleanup() _patched_ansible_playbook.assert_called_once_with( instance._config.provisioner.playbooks.cleanup, instance._config, - False, # noqa: FBT003 + verify=False, ) _patched_ansible_playbook.return_value.execute.assert_called_once_with() -def test_destroy(instance, mocker: MockerFixture, _patched_ansible_playbook): # type: ignore[no-untyped-def] # noqa: ANN001, ANN201, PT019, ARG001, D103 +def test_destroy(instance: ansible.Ansible, mocker: MockerFixture, _patched_ansible_playbook: Mock): # type: ignore[no-untyped-def] # noqa: ANN201, PT019, ARG001, D103 instance.destroy() _patched_ansible_playbook.assert_called_once_with( instance._config.provisioner.playbooks.destroy, instance._config, - False, # noqa: FBT003 + verify=False, ) _patched_ansible_playbook.return_value.execute.assert_called_once_with() -def test_side_effect(instance, mocker: MockerFixture, _patched_ansible_playbook): # type: ignore[no-untyped-def] # noqa: ANN001, ANN201, PT019, ARG001, D103 +def test_side_effect( # noqa: D103 + instance: ansible.Ansible, + mocker: MockerFixture, # noqa: ARG001 + _patched_ansible_playbook: Mock, # noqa: PT019 +) -> None: instance.side_effect() _patched_ansible_playbook.assert_called_once_with( instance._config.provisioner.playbooks.side_effect, instance._config, - False, # noqa: FBT003 + verify=False, ) _patched_ansible_playbook.return_value.execute.assert_called_once_with() -def test_create(instance, mocker: MockerFixture, _patched_ansible_playbook): # type: ignore[no-untyped-def] # noqa: ANN001, ANN201, PT019, ARG001, D103 +def test_create(instance: ansible.Ansible, mocker: MockerFixture, _patched_ansible_playbook: Mock): # type: ignore[no-untyped-def] # noqa: ANN201, PT019, ARG001, D103 instance.create() _patched_ansible_playbook.assert_called_once_with( instance._config.provisioner.playbooks.create, instance._config, - False, # noqa: FBT003 + verify=False, ) _patched_ansible_playbook.return_value.execute.assert_called_once_with() -def test_prepare(instance, mocker: MockerFixture, _patched_ansible_playbook): # type: ignore[no-untyped-def] # noqa: ANN001, ANN201, PT019, ARG001, D103 +def test_prepare(instance: ansible.Ansible, mocker: MockerFixture, _patched_ansible_playbook: Mock): # type: ignore[no-untyped-def] # noqa: ANN201, PT019, ARG001, D103 instance.prepare() _patched_ansible_playbook.assert_called_once_with( instance._config.provisioner.playbooks.prepare, instance._config, - False, # noqa: FBT003 + verify=False, ) _patched_ansible_playbook.return_value.execute.assert_called_once_with() -def test_syntax(instance, mocker: MockerFixture, _patched_ansible_playbook): # type: ignore[no-untyped-def] # noqa: ANN001, ANN201, PT019, ARG001, D103 +def test_syntax(instance: ansible.Ansible, mocker: MockerFixture, _patched_ansible_playbook: Mock): # type: ignore[no-untyped-def] # noqa: ANN201, PT019, ARG001, D103 instance.syntax() _patched_ansible_playbook.assert_called_once_with( instance._config.provisioner.playbooks.converge, instance._config, - False, # noqa: FBT003 + verify=False, ) _patched_ansible_playbook.return_value.add_cli_arg.assert_called_once_with( "syntax-check", @@ -471,7 +480,7 @@ def test_syntax(instance, mocker: MockerFixture, _patched_ansible_playbook): # _patched_ansible_playbook.return_value.execute.assert_called_once_with() -def test_verify(instance, mocker: MockerFixture, _patched_ansible_playbook): # type: ignore[no-untyped-def] # noqa: ANN001, ANN201, PT019, ARG001, D103 +def test_verify(instance: ansible.Ansible, mocker: MockerFixture, _patched_ansible_playbook: Mock): # type: ignore[no-untyped-def] # noqa: ANN201, PT019, ARG001, D103 instance.verify() if instance._config.provisioner.playbooks.verify: @@ -679,7 +688,7 @@ def test_link_vars(instance): # type: ignore[no-untyped-def] # noqa: ANN001, A assert os.path.lexists(target_host_vars) -def test_link_vars_raises_when_source_not_found(instance, caplog): # type: ignore[no-untyped-def] # noqa: ANN001, ANN201, D103 +def test_link_vars_raises_when_source_not_found(instance: ansible.Ansible, caplog): # type: ignore[no-untyped-def] # noqa: ANN001, ANN201, D103 c = instance._config.config c["provisioner"]["inventory"]["links"] = {"foo": "../bar"} @@ -761,7 +770,7 @@ def test_get_modules_directories_default( assert paths[4] == "/usr/share/ansible/plugins/modules" -def test_get_modules_directories_single_ansible_library(instance, monkeypatch): # type: ignore[no-untyped-def] # noqa: ANN001, ANN201, D103 +def test_get_modules_directories_single_ansible_library(instance: ansible.Ansible, monkeypatch): # type: ignore[no-untyped-def] # noqa: ANN001, ANN201, D103 monkeypatch.setenv("ANSIBLE_LIBRARY", "/abs/path/lib") paths = instance._get_modules_directories() @@ -770,7 +779,7 @@ def test_get_modules_directories_single_ansible_library(instance, monkeypatch): assert paths[0] == "/abs/path/lib" -def test_get_modules_directories_multi_ansible_library(instance, monkeypatch): # type: ignore[no-untyped-def] # noqa: ANN001, ANN201, D103 +def test_get_modules_directories_multi_ansible_library(instance: ansible.Ansible, monkeypatch): # type: ignore[no-untyped-def] # noqa: ANN001, ANN201, D103 monkeypatch.setenv("ANSIBLE_LIBRARY", "relpath/lib:/abs/path/lib") paths = instance._get_modules_directories() @@ -788,7 +797,11 @@ def test_get_filter_plugin_directory(instance): # type: ignore[no-untyped-def] assert x == parts[-5:] -def test_get_filter_plugins_directories_default(instance, monkeypatch, test_cache_path: Path): # type: ignore[no-untyped-def] # noqa: ANN001, ANN201, D103 +def test_get_filter_plugins_directories_default( # noqa: D103 + instance: ansible.Ansible, + monkeypatch: pytest.MonkeyPatch, + test_cache_path: Path, +) -> None: monkeypatch.delenv("ANSIBLE_FILTER_PLUGINS", raising=False) paths = instance._get_filter_plugins_directories() From 3f853f28784fb94b69e352955c235779210c6562 Mon Sep 17 00:00:00 2001 From: Kate Case Date: Tue, 19 Nov 2024 11:15:59 -0500 Subject: [PATCH 12/12] Fix unintentional changes to ansible provisioner --- src/molecule/provisioner/ansible.py | 39 ++++------ src/molecule/provisioner/ansible_playbook.py | 2 +- tests/unit/provisioner/test_ansible.py | 76 +++++++++++++++---- .../unit/provisioner/test_ansible_playbook.py | 8 +- 4 files changed, 83 insertions(+), 42 deletions(-) diff --git a/src/molecule/provisioner/ansible.py b/src/molecule/provisioner/ansible.py index 868e5533f..f59c66415 100644 --- a/src/molecule/provisioner/ansible.py +++ b/src/molecule/provisioner/ansible.py @@ -712,9 +712,8 @@ def directory(self) -> str: # noqa: D102 def cleanup(self) -> None: """Execute `ansible-playbook` against the cleanup playbook and returns None.""" - if self.playbooks.cleanup: - pb = self._get_ansible_playbook(self.playbooks.cleanup) - pb.execute() + pb = self._get_ansible_playbook(self.playbooks.cleanup) + pb.execute() def connection_options( self, @@ -737,10 +736,9 @@ def connection_options( def check(self) -> None: """Execute ``ansible-playbook`` against the converge playbook with the ``--check`` flag.""" - if self.playbooks.converge: - pb = self._get_ansible_playbook(self.playbooks.converge) - pb.add_cli_arg("check", value=True) - pb.execute() + pb = self._get_ansible_playbook(self.playbooks.converge) + pb.add_cli_arg("check", value=True) + pb.execute() def converge(self, playbook: str = "", **kwargs: object) -> str: """Execute ``ansible-playbook`` against the converge playbook. unless specified otherwise. @@ -758,9 +756,8 @@ def converge(self, playbook: str = "", **kwargs: object) -> str: def destroy(self) -> None: """Execute ``ansible-playbook`` against the destroy playbook and returns None.""" - if self.playbooks.destroy: - pb = self._get_ansible_playbook(self.playbooks.destroy) - pb.execute() + pb = self._get_ansible_playbook(self.playbooks.destroy) + pb.execute() def side_effect(self, action_args: list[str] | None = None) -> None: """Execute ``ansible-playbook`` against the side_effect playbook. @@ -773,29 +770,25 @@ def side_effect(self, action_args: list[str] | None = None) -> None: playbooks = [ self._get_ansible_playbook(self.abs_path(playbook)) for playbook in action_args ] - elif self.playbooks.side_effect: - playbooks = [self._get_ansible_playbook(self.playbooks.side_effect)] + playbooks = [self._get_ansible_playbook(self.playbooks.side_effect)] for pb in playbooks: pb.execute() def create(self) -> None: """Execute ``ansible-playbook`` against the create playbook and returns None.""" - if self.playbooks.create: - pb = self._get_ansible_playbook(self.playbooks.create) - pb.execute() + pb = self._get_ansible_playbook(self.playbooks.create) + pb.execute() def prepare(self) -> None: """Execute ``ansible-playbook`` against the prepare playbook and returns None.""" - if self.playbooks.prepare: - pb = self._get_ansible_playbook(self.playbooks.prepare) - pb.execute() + pb = self._get_ansible_playbook(self.playbooks.prepare) + pb.execute() def syntax(self) -> None: """Execute `ansible-playbook` against the converge playbook with the -syntax-check flag.""" - if self.playbooks.converge: - pb = self._get_ansible_playbook(self.playbooks.converge) - pb.add_cli_arg("syntax-check", value=True) - pb.execute() + pb = self._get_ansible_playbook(self.playbooks.converge) + pb.add_cli_arg("syntax-check", value=True) + pb.execute() def verify(self, action_args: list[str] | None = None) -> None: """Execute ``ansible-playbook`` against the verify playbook. @@ -910,7 +903,7 @@ def _link_or_update_vars(self) -> None: def _get_ansible_playbook( self, - playbook: str, + playbook: str | None, verify: bool = False, # noqa: FBT001, FBT002 **kwargs: object, ) -> ansible_playbook.AnsiblePlaybook: diff --git a/src/molecule/provisioner/ansible_playbook.py b/src/molecule/provisioner/ansible_playbook.py index 99f0ef52f..bac897604 100644 --- a/src/molecule/provisioner/ansible_playbook.py +++ b/src/molecule/provisioner/ansible_playbook.py @@ -42,7 +42,7 @@ class AnsiblePlaybook: def __init__( self, - playbook: str, + playbook: str | None, config: Config, *, verify: bool = False, diff --git a/tests/unit/provisioner/test_ansible.py b/tests/unit/provisioner/test_ansible.py index 6668eb7bd..3e87db4f2 100644 --- a/tests/unit/provisioner/test_ansible.py +++ b/tests/unit/provisioner/test_ansible.py @@ -353,9 +353,15 @@ def test_playbooks_side_effect_property(instance): # type: ignore[no-untyped-de assert instance.playbooks.side_effect is None -def test_check(instance: ansible.Ansible, mocker: MockerFixture, _patched_ansible_playbook: Mock): # type: ignore[no-untyped-def] # noqa: ANN201, PT019, ARG001, D103 +def test_check( # noqa: D103 + instance: ansible.Ansible, + mocker: MockerFixture, # noqa: ARG001 + _patched_ansible_playbook: Mock, # noqa: PT019 +) -> None: instance.check() + assert instance._config.provisioner + _patched_ansible_playbook.assert_called_once_with( instance._config.provisioner.playbooks.converge, instance._config, @@ -363,7 +369,7 @@ def test_check(instance: ansible.Ansible, mocker: MockerFixture, _patched_ansibl ) _patched_ansible_playbook.return_value.add_cli_arg.assert_called_once_with( "check", - True, # noqa: FBT003 + value=True, ) _patched_ansible_playbook.return_value.execute.assert_called_once_with() @@ -375,6 +381,8 @@ def test_converge( # noqa: D103 ) -> None: result = instance.converge() + assert instance._config.provisioner + _patched_ansible_playbook.assert_called_once_with( instance._config.provisioner.playbooks.converge, instance._config, @@ -382,16 +390,16 @@ def test_converge( # noqa: D103 ) # NOTE(retr0h): This is not the true return type. This is a mock return # which didn't go through str.decode(). - assert result == b"patched-ansible-playbook-stdout" + assert result == b"patched-ansible-playbook-stdout" # type: ignore[comparison-overlap] _patched_ansible_playbook.return_value.execute.assert_called_once_with() -def test_converge_with_playbook( # type: ignore[no-untyped-def] # noqa: ANN201, D103 - instance, # noqa: ANN001 +def test_converge_with_playbook( # noqa: D103 + instance: ansible.Ansible, mocker: MockerFixture, # noqa: ARG001 - _patched_ansible_playbook, # noqa: ANN001, PT019 -): + _patched_ansible_playbook: Mock, # noqa: PT019 +) -> None: result = instance.converge("playbook") _patched_ansible_playbook.assert_called_once_with( @@ -401,14 +409,20 @@ def test_converge_with_playbook( # type: ignore[no-untyped-def] # noqa: ANN201 ) # NOTE(retr0h): This is not the true return type. This is a mock return # which didn't go through str.decode(). - assert result == b"patched-ansible-playbook-stdout" + assert result == b"patched-ansible-playbook-stdout" # type: ignore[comparison-overlap] _patched_ansible_playbook.return_value.execute.assert_called_once_with() -def test_cleanup(instance: ansible.Ansible, mocker: MockerFixture, _patched_ansible_playbook: Mock): # type: ignore[no-untyped-def] # noqa: ANN201, PT019, ARG001, D103 +def test_cleanup( # noqa: D103 + instance: ansible.Ansible, + mocker: MockerFixture, # noqa: ARG001 + _patched_ansible_playbook: Mock, # noqa: PT019 +) -> None: instance.cleanup() + assert instance._config.provisioner + _patched_ansible_playbook.assert_called_once_with( instance._config.provisioner.playbooks.cleanup, instance._config, @@ -417,9 +431,15 @@ def test_cleanup(instance: ansible.Ansible, mocker: MockerFixture, _patched_ansi _patched_ansible_playbook.return_value.execute.assert_called_once_with() -def test_destroy(instance: ansible.Ansible, mocker: MockerFixture, _patched_ansible_playbook: Mock): # type: ignore[no-untyped-def] # noqa: ANN201, PT019, ARG001, D103 +def test_destroy( # noqa: D103 + instance: ansible.Ansible, + mocker: MockerFixture, # noqa: ARG001 + _patched_ansible_playbook: Mock, # noqa: PT019 +) -> None: instance.destroy() + assert instance._config.provisioner + _patched_ansible_playbook.assert_called_once_with( instance._config.provisioner.playbooks.destroy, instance._config, @@ -435,6 +455,8 @@ def test_side_effect( # noqa: D103 ) -> None: instance.side_effect() + assert instance._config.provisioner + _patched_ansible_playbook.assert_called_once_with( instance._config.provisioner.playbooks.side_effect, instance._config, @@ -443,9 +465,15 @@ def test_side_effect( # noqa: D103 _patched_ansible_playbook.return_value.execute.assert_called_once_with() -def test_create(instance: ansible.Ansible, mocker: MockerFixture, _patched_ansible_playbook: Mock): # type: ignore[no-untyped-def] # noqa: ANN201, PT019, ARG001, D103 +def test_create( # noqa: D103 + instance: ansible.Ansible, + mocker: MockerFixture, # noqa: ARG001 + _patched_ansible_playbook: Mock, # noqa: PT019 +) -> None: instance.create() + assert instance._config.provisioner + _patched_ansible_playbook.assert_called_once_with( instance._config.provisioner.playbooks.create, instance._config, @@ -454,9 +482,15 @@ def test_create(instance: ansible.Ansible, mocker: MockerFixture, _patched_ansib _patched_ansible_playbook.return_value.execute.assert_called_once_with() -def test_prepare(instance: ansible.Ansible, mocker: MockerFixture, _patched_ansible_playbook: Mock): # type: ignore[no-untyped-def] # noqa: ANN201, PT019, ARG001, D103 +def test_prepare( # noqa: D103 + instance: ansible.Ansible, + mocker: MockerFixture, # noqa: ARG001 + _patched_ansible_playbook: Mock, # noqa: PT019 +) -> None: instance.prepare() + assert instance._config.provisioner + _patched_ansible_playbook.assert_called_once_with( instance._config.provisioner.playbooks.prepare, instance._config, @@ -465,9 +499,15 @@ def test_prepare(instance: ansible.Ansible, mocker: MockerFixture, _patched_ansi _patched_ansible_playbook.return_value.execute.assert_called_once_with() -def test_syntax(instance: ansible.Ansible, mocker: MockerFixture, _patched_ansible_playbook: Mock): # type: ignore[no-untyped-def] # noqa: ANN201, PT019, ARG001, D103 +def test_syntax( # noqa: D103 + instance: ansible.Ansible, + mocker: MockerFixture, # noqa: ARG001 + _patched_ansible_playbook: Mock, # noqa: PT019 +) -> None: instance.syntax() + assert instance._config.provisioner + _patched_ansible_playbook.assert_called_once_with( instance._config.provisioner.playbooks.converge, instance._config, @@ -475,14 +515,20 @@ def test_syntax(instance: ansible.Ansible, mocker: MockerFixture, _patched_ansib ) _patched_ansible_playbook.return_value.add_cli_arg.assert_called_once_with( "syntax-check", - True, # noqa: FBT003 + value=True, ) _patched_ansible_playbook.return_value.execute.assert_called_once_with() -def test_verify(instance: ansible.Ansible, mocker: MockerFixture, _patched_ansible_playbook: Mock): # type: ignore[no-untyped-def] # noqa: ANN201, PT019, ARG001, D103 +def test_verify( # noqa: D103 + instance: ansible.Ansible, + mocker: MockerFixture, # noqa: ARG001 + _patched_ansible_playbook: Mock, # noqa: PT019 +) -> None: instance.verify() + assert instance._config.provisioner + if instance._config.provisioner.playbooks.verify: _patched_ansible_playbook.assert_called_once_with( instance._config.provisioner.playbooks.verify, diff --git a/tests/unit/provisioner/test_ansible_playbook.py b/tests/unit/provisioner/test_ansible_playbook.py index b6ba74cf4..4a981d7a9 100644 --- a/tests/unit/provisioner/test_ansible_playbook.py +++ b/tests/unit/provisioner/test_ansible_playbook.py @@ -28,7 +28,7 @@ @pytest.fixture -def _instance(config_instance: config.Config): # type: ignore[no-untyped-def] # noqa: ANN202 +def _instance(config_instance: config.Config) -> ansible_playbook.AnsiblePlaybook: _instance = ansible_playbook.AnsiblePlaybook("playbook", config_instance) return _instance # noqa: RET504 @@ -94,8 +94,10 @@ def _inventory_directory(_instance): # type: ignore[no-untyped-def] # noqa: AN return _instance._config.provisioner.inventory_directory -def test_ansible_command_private_member(_instance): # type: ignore[no-untyped-def] # noqa: ANN001, ANN201, PT019, D103 - assert _instance._ansible_command is None +def test_ansible_command_private_member( # noqa: D103 + _instance: ansible_playbook.AnsiblePlaybook, # noqa: PT019 +) -> None: + assert _instance._ansible_command == [] def test_ansible_playbook_private_member(_instance): # type: ignore[no-untyped-def] # noqa: ANN001, ANN201, PT019, D103