From 03e10370a3062949ce2ecd9ee29100f40c7ebedb Mon Sep 17 00:00:00 2001 From: Kate Case Date: Mon, 4 Nov 2024 08:11:03 -0500 Subject: [PATCH] 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 bcf911b5d4..2014529c20 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 edac53d3fb..26aeb1d12a 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: