diff --git a/.config/pydoclint-baseline.txt b/.config/pydoclint-baseline.txt index 72c803518..7567e4dcf 100644 --- a/.config/pydoclint-baseline.txt +++ b/.config/pydoclint-baseline.txt @@ -107,29 +107,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/status.py DOC601: Class `Status`: 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 `Status`: 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: [converged: str, created: str, driver_name: str, instance_name: str, provisioner_name: str, scenario_name: str]. (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(":")])