From 0fa336b7936e25a3b212bd0f7df94ae6e9a51b3b Mon Sep 17 00:00:00 2001 From: 0x4D616E75 <0x4d616e75@elektronische-nachricht.de> Date: Mon, 23 Jun 2025 11:02:33 +0200 Subject: [PATCH 1/5] fix(vagrant): use FQDN for vagrant module in playbooks --- src/molecule_plugins/vagrant/playbooks/create.yml | 2 +- src/molecule_plugins/vagrant/playbooks/destroy.yml | 2 +- .../vagrant-plugin/scenarios/molecule/default-compat/create.yml | 2 +- .../scenarios/molecule/default-compat/destroy.yml | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/molecule_plugins/vagrant/playbooks/create.yml b/src/molecule_plugins/vagrant/playbooks/create.yml index 6c236079..8f0a5d59 100644 --- a/src/molecule_plugins/vagrant/playbooks/create.yml +++ b/src/molecule_plugins/vagrant/playbooks/create.yml @@ -6,7 +6,7 @@ no_log: "{{ molecule_no_log }}" tasks: - name: Create molecule instance(s) # noqa fqcn[action] - vagrant: + community.vagrant.vagrant: instances: "{{ molecule_yml.platforms }}" default_box: "{{ molecule_yml.driver.default_box | default('generic/alpine316') }}" provider_name: "{{ molecule_yml.driver.provider.name | default(omit, true) }}" diff --git a/src/molecule_plugins/vagrant/playbooks/destroy.yml b/src/molecule_plugins/vagrant/playbooks/destroy.yml index f1d12625..afb88a60 100644 --- a/src/molecule_plugins/vagrant/playbooks/destroy.yml +++ b/src/molecule_plugins/vagrant/playbooks/destroy.yml @@ -6,7 +6,7 @@ no_log: "{{ molecule_no_log }}" tasks: - name: Destroy molecule instance(s) # noqa fqcn[action] - vagrant: + community.vagrant.vagrant: instances: "{{ molecule_yml.platforms }}" default_box: "{{ molecule_yml.driver.default_box | default('generic/alpine316') }}" provider_name: "{{ molecule_yml.driver.provider.name | default(omit, true) }}" diff --git a/test/vagrant-plugin/scenarios/molecule/default-compat/create.yml b/test/vagrant-plugin/scenarios/molecule/default-compat/create.yml index 2e4b3533..b712d41e 100644 --- a/test/vagrant-plugin/scenarios/molecule/default-compat/create.yml +++ b/test/vagrant-plugin/scenarios/molecule/default-compat/create.yml @@ -6,7 +6,7 @@ no_log: "{{ molecule_no_log }}" tasks: - name: Create molecule instance(s) # noqa fqcn[action] - vagrant: + community.vagrant.vagrant: instance_name: "{{ item.name }}" instance_interfaces: "{{ item.interfaces | default(omit) }}" instance_raw_config_args: "{{ item.instance_raw_config_args | default(omit) }}" diff --git a/test/vagrant-plugin/scenarios/molecule/default-compat/destroy.yml b/test/vagrant-plugin/scenarios/molecule/default-compat/destroy.yml index e0aaa35e..932ab5fa 100644 --- a/test/vagrant-plugin/scenarios/molecule/default-compat/destroy.yml +++ b/test/vagrant-plugin/scenarios/molecule/default-compat/destroy.yml @@ -6,7 +6,7 @@ no_log: "{{ molecule_no_log }}" tasks: - name: Destroy molecule instance(s) # noqa fqcn[action] - vagrant: + community.vagrant.vagrant: instance_name: "{{ item.name }}" platform_box: "{{ item.box | default(omit) }}" provider_name: "{{ molecule_yml.driver.provider.name | default(omit, true) }}" From 55fce74cb2bbf29237478b64b8fcba75828cf0a3 Mon Sep 17 00:00:00 2001 From: 0x4D616E75 <0x4d616e75@elektronische-nachricht.de> Date: Tue, 24 Jun 2025 22:14:56 +0200 Subject: [PATCH 2/5] test: fixing ephemeral_directory import --- conftest.py | 9 ++++----- test/vagrant-plugin/functional/test_func.py | 7 +++++-- 2 files changed, 9 insertions(+), 7 deletions(-) diff --git a/conftest.py b/conftest.py index 2a96b19d..7792ce76 100644 --- a/conftest.py +++ b/conftest.py @@ -7,9 +7,8 @@ import pytest -from molecule import config, logger +from molecule import config, logger, util from molecule.app import get_app -from molecule.scenario import ephemeral_directory LOG = logger.get_logger(__name__) @@ -84,9 +83,9 @@ def molecule_ephemeral_directory(_fixture_uuid): project_directory = f"test-project-{_fixture_uuid}" scenario_name = "test-instance" - return ephemeral_directory( - os.path.join("molecule_test", project_directory, scenario_name), - ) + return util.os.path.abspath(os.path.join( + 'molecule_test', project_directory, scenario_name + )) def metadata_lint_update(role_directory: str) -> None: diff --git a/test/vagrant-plugin/functional/test_func.py b/test/vagrant-plugin/functional/test_func.py index 3373ab15..00c7b2f8 100644 --- a/test/vagrant-plugin/functional/test_func.py +++ b/test/vagrant-plugin/functional/test_func.py @@ -30,10 +30,13 @@ from conftest import change_dir_to from molecule import logger, util from molecule.app import get_app -from molecule.scenario import ephemeral_directory LOG = logger.get_logger(__name__) +def ephemeral_directory(path=None): + """Return temporary directory for use by Molecule.""" + return util.os.path.abspath(path or 'molecule_test') + def is_vagrant_supported() -> bool: """Return True if vagrant is installed and current platform is supported.""" @@ -146,7 +149,7 @@ def test_multi_node(temp_dir): result = get_app(Path()).run_command(cmd) assert result.returncode == 0 - molecule_eph_directory = ephemeral_directory() + molecule_eph_directory = ephemeral_directory('molecule_test') vagrantfile = os.path.join( molecule_eph_directory, "scenarios", From 33e6120e97570565f9a02b114f335fc95bd27174 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Tue, 24 Jun 2025 20:16:15 +0000 Subject: [PATCH 3/5] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- conftest.py | 6 +++--- test/vagrant-plugin/functional/test_func.py | 5 +++-- 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/conftest.py b/conftest.py index 7792ce76..b29257d6 100644 --- a/conftest.py +++ b/conftest.py @@ -83,9 +83,9 @@ def molecule_ephemeral_directory(_fixture_uuid): project_directory = f"test-project-{_fixture_uuid}" scenario_name = "test-instance" - return util.os.path.abspath(os.path.join( - 'molecule_test', project_directory, scenario_name - )) + return util.os.path.abspath( + os.path.join("molecule_test", project_directory, scenario_name) + ) def metadata_lint_update(role_directory: str) -> None: diff --git a/test/vagrant-plugin/functional/test_func.py b/test/vagrant-plugin/functional/test_func.py index 00c7b2f8..f0cccb44 100644 --- a/test/vagrant-plugin/functional/test_func.py +++ b/test/vagrant-plugin/functional/test_func.py @@ -33,9 +33,10 @@ LOG = logger.get_logger(__name__) + def ephemeral_directory(path=None): """Return temporary directory for use by Molecule.""" - return util.os.path.abspath(path or 'molecule_test') + return util.os.path.abspath(path or "molecule_test") def is_vagrant_supported() -> bool: @@ -149,7 +150,7 @@ def test_multi_node(temp_dir): result = get_app(Path()).run_command(cmd) assert result.returncode == 0 - molecule_eph_directory = ephemeral_directory('molecule_test') + molecule_eph_directory = ephemeral_directory("molecule_test") vagrantfile = os.path.join( molecule_eph_directory, "scenarios", From cb125014813230120f56204ab8781727a15322f4 Mon Sep 17 00:00:00 2001 From: 0x4D616E75 <0x4d616e75@elektronische-nachricht.de> Date: Tue, 24 Jun 2025 23:21:05 +0200 Subject: [PATCH 4/5] test: workaround for vagrant collection --- requirements.yml | 4 +- .../vagrant/modules/__init__.py | 0 .../vagrant/modules/vagrant.py | 757 ------------------ 3 files changed, 3 insertions(+), 758 deletions(-) delete mode 100644 src/molecule_plugins/vagrant/modules/__init__.py delete mode 100755 src/molecule_plugins/vagrant/modules/vagrant.py diff --git a/requirements.yml b/requirements.yml index b8eaacfa..95f9df75 100644 --- a/requirements.yml +++ b/requirements.yml @@ -10,5 +10,7 @@ collections: version: ">=1,<2" - name: community.crypto version: ">=1.8.0" - - name: community.vagrant + # Workaround since https://github.com/ansible-community/molecule-plugins/issues/301#issuecomment-2675066085 is fixed + # - name: community.vagrant + - git+https://github.com/apatard/community.vagrant.git,initial-import - name: openstack.cloud diff --git a/src/molecule_plugins/vagrant/modules/__init__.py b/src/molecule_plugins/vagrant/modules/__init__.py deleted file mode 100644 index e69de29b..00000000 diff --git a/src/molecule_plugins/vagrant/modules/vagrant.py b/src/molecule_plugins/vagrant/modules/vagrant.py deleted file mode 100755 index 0e094280..00000000 --- a/src/molecule_plugins/vagrant/modules/vagrant.py +++ /dev/null @@ -1,757 +0,0 @@ -#!/usr/bin/python - -# Copyright (c) 2015-2018 Cisco Systems, Inc. -# -# Permission is hereby granted, free of charge, to any person obtaining a copy -# of this software and associated documentation files (the "Software"), to -# deal in the Software without restriction, including without limitation the -# rights to use, copy, modify, merge, publish, distribute, sublicense, and/or -# sell copies of the Software, and to permit persons to whom the Software is -# furnished to do so, subject to the following conditions: -# -# The above copyright notice and this permission notice shall be included in -# all copies or substantial portions of the Software. -# -# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING -# FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER -# DEALINGS IN THE SOFTWARE. - - -import contextlib -import copy -import datetime -import os -import subprocess -import sys -from collections.abc import MutableMapping - -import jinja2 -from ansible.module_utils.basic import AnsibleModule - -try: - import vagrant -except ImportError: - sys.exit("ERROR: Driver missing, install python-vagrant.") -ANSIBLE_METADATA = { - "metadata_version": "0.1", - "status": ["preview"], - "supported_by": "community", -} - -DOCUMENTATION = """ ---- -module: vagrant -short_description: Manage Vagrant instances -description: - - Manage the life cycle of Vagrant instances. -version_added: 2.0 -author: - - Cisco Systems, Inc. -options: - instances: - description: - - list of instances to handle - required: False - default: [] - instance_name: - description: - - Assign a name to a new instance or match an existing instance. - required: False - default: None - instance_interfaces: - description: - - Assign interfaces to a new instance. - required: False - default: [] - instance_raw_config_args: - description: - - Additional Vagrant options not explicitly exposed by this module. - required: False - default: None - config_options: - description: - - Additional config options not explicitly exposed by this module. - required: False - default: {} - platform_box: - description: - - Name of Vagrant box. - required: True - default: None - platform_box_version: - description: - - Explicit version of Vagrant box to use. - required: False - default: None - platform_box_url: - description: - - The URL to a Vagrant box. - required: False - default: None - platform_box_download_checksum: - description: - - The checksum of the box specified by platform_box_url. - required: False - default: None - platform_box_download_checksum_type: - description: - - The type of checksum specified by platform_box_download_checksum (if any). - required: False - default: None - provider_name: - description: - - Name of the Vagrant provider to use. - required: False - default: virtualbox - provider_memory: - description: - - Amount of memory to allocate to the instance. - required: False - default: 512 - provider_cpus: - description: - - Number of CPUs to allocate to the instance. - required: False - default: 2 - provider_options: - description: - - Additional provider options not explicitly exposed by this module. - required: False - default: {} - provider_override_args: - description: - - Additional override options not explicitly exposed by this module. - required: False - default: None - provider_raw_config_args: - description: - - Additional Vagrant options not explicitly exposed by this module. - required: False - default: None - force_stop: - description: - - Force halt the instance, then destroy the instance. - required: False - default: False - state: - description: - - The desired state of the instance. - required: True - choices: ['up', 'halt', 'destroy'] - default: None - workdir: - description: - - vagrant working directory - required: False - default: content of MOLECULE_EPHEMERAL_DIRECTORY environment variable - default_box: - description: - - Default vagrant box to use when not specified in instance configuration - required: False - default: None - cachier: - description: - - Cachier scope configuration. Can be "machine" or "box". Any other value - will disable cachier. - required: False - default: "machine" - parallel: - description: - - Enable or disable parallelism when bringing up the VMs by - setting VAGRANT_NO_PARALLEL environment variable. - required: False - default: True - -requirements: - - python >= 2.6 - - python-vagrant - - vagrant -""" - -EXAMPLES = """ -See doc/source/configuration.rst -""" - -VAGRANTFILE_TEMPLATE = """ -{% macro ruby_format(value) %} - {% if value is boolean %} - {{- value | string | lower -}} - {% elif value is string %} - {# "Bug" compat. To be removed later #} - {% if value[0] == value[-1] and value.startswith(("'", '"')) %} -{{ value }} - {% else %} -"{{ value }}" - {% endif %} - {% else %} -{{ value }} - {% endif %} -{% endmacro %} - -{% macro dict2args(dictionary) %} - {% set sep = joiner(", ") %} - {% for key, value in dictionary.items() -%} - {{ sep() }}{{ key }}: {{ ruby_format(value) | trim }} - {%- endfor %} -{% endmacro -%} - -# Ansible managed - -Vagrant.configure('2') do |config| - if Vagrant.has_plugin?('vagrant-cachier') - {% if cachier is not none and cachier in [ "machine", "box" ] %} - config.cache.scope = '{{ cachier }}' - {% else %} - config.cache.disable! - {% endif %} - end - -{% for instance in instances %} - config.vm.define "{{ instance.name }}" do |c| - # Box definition - c.vm.box = "{{ instance.box }}" - {% if instance.box_version %} - {{ 'c.vm.box_version = "{}"'.format(instance.box_version) | safe }} - {% endif %} - {% if instance.box_url %} - {{ 'c.vm.box_url = "{}"'.format(instance.box_url) | safe }} - {% endif %} - {% if instance.box_architecture %} - {{ 'c.vm.box_architecture = "{}"'.format(instance.box_architecture) | safe }} - {% endif %} - {% if instance.box_download_checksum %} - {{ 'c.vm.box_download_checksum = "{}"'.format(instance.box_download_checksum) | safe }} - {% endif %} - {% if instance.box_download_checksum_type %} - {{ 'c.vm.box_download_checksum_type = "{}"'.format(instance.box_download_checksum_type) | safe }} - {% endif %} - - # Config options - {% if instance.config_options['synced_folder'] is sameas false %} - c.vm.synced_folder ".", "/vagrant", disabled: true - {% endif %} - {% for k,v in instance.config_options.items() %} - {% if k not in ['synced_folder', 'cachier'] %} - c.{{ k }} = {{ ruby_format(v) }} - {% endif %} - {% endfor %} - {% if instance.hostname is boolean and instance.hostname is sameas false %} - # c.vm.hostname not set - {% elif instance.hostname is string %} - c.vm.hostname = "{{ instance.hostname }}" - {% else %} - c.vm.hostname = "{{ instance.name }}" - {% endif %} - {% if instance.networks | length > 0 %} - - # Network - {% for n in instance.networks %} - c.vm.network "{{ n.name }}", {{ dict2args(n.options) | trim }} - {% endfor %} - {% endif %} - {% if instance.instance_raw_config_args is not none %} - - # instance_raw_config_args - {% for arg in instance.instance_raw_config_args %} - c.{{ arg | safe }} - {% endfor %} - {% endif %} - - # Provider options - c.vm.provider "{{ instance.provider }}" do |{{ instance.provider | lower }}, override| - {% if instance.provider == "vsphere" %} - {{ instance.provider | lower }}.memory_mb = {{ instance.memory }} - {{ instance.provider | lower }}.cpu_count = {{ instance.cpus }} - {% elif instance.provider == 'vmware_esxi' %} - {{ instance.provider | lower }}.guest_memsize = {{ instance.memory }} - {{ instance.provider | lower }}.guest_numvcpus = {{ instance.cpus }} - {% elif instance.provider.startswith('vmware_') %} - {{ instance.provider | lower }}.vmx['memsize'] = {{ instance.memory }} - {{ instance.provider | lower }}.vmx['numvcpus'] = {{ instance.cpus }} - {% else %} - {{ instance.provider | lower }}.memory = {{ instance.memory }} - {{ instance.provider | lower }}.cpus = {{ instance.cpus }} - {% endif %} - {% for option, value in instance.provider_options.items() %} - {{ instance.provider | lower }}.{{ option }} = {{ ruby_format(value) | trim }} - {% endfor %} - {% if instance.provider == 'virtualbox' %} - {% if 'linked_clone' not in instance.provider_options %} - virtualbox.linked_clone = true - {% endif %} - {% endif %} - {% if instance.provider == 'libvirt' %} - {% if no_kvm is sameas true and 'driver' not in instance.provider_options %} - libvirt.driver='qemu' - {% endif %} - {% set libvirt_use_qemu = no_kvm %} - {% if 'driver' in instance.provider_options and 'qemu' in instance.provider_options['driver'] %} - {% set libvirt_use_qemu = true %} - {% endif %} - {% if libvirt_use_qemu is sameas true %} - {% if 'cpu_mode' not in instance.provider_options %} - # When using qemu instead of kvm, some libvirt systems - # will use EPYC as vCPU model inside the new VM. - # This will lead to process hang with ssh-keygen -A on alpine. - # Not sure were the bug is (libvirt or qemu or alpine or all of them) but using QEMU64 - # cpu model will work around this. Hopefully, by checking 'cpu_mode' option, it will - # allow people to override the model to use. - libvirt.cpu_mode = 'custom' - libvirt.cpu_model = 'qemu64' - {% endif %} - {% endif %} - {% endif %} - {% if instance.provider_raw_config_args is not none %} - - # provider_raw_config_args - {% for arg in instance.provider_raw_config_args %} - {{ instance.provider | lower }}.{{ arg | safe }} - {% endfor %} - {% endif %} - {% if instance.provider_override_args is not none %} - - # provider_override_args - {% for arg in instance.provider_override_args %} - override.{{ arg | safe }} - {% endfor %} - {% endif %} - end - end -{% endfor %} -end -""".strip() - -RETURN = r""" -rc: - description: The command return code (0 means success) - returned: always - type: int -cmd: - description: The command executed by the task - returned: always - type: str -stdout: - description: The command standard output - returned: changed - type: str -stderr: - description: Output on stderr - returned: changed - type: str -""" - - -# Taken from molecule.util. -def merge_dicts(a: MutableMapping, b: MutableMapping) -> MutableMapping: - """Merge the values of b into a and returns a new dict. - - This function uses the same algorithm as Ansible's `combine(recursive=True)` filter. - - :param a: the target dictionary - :param b: the dictionary to import - :return: dict - """ - result = copy.deepcopy(a) - - for k, v in b.items(): - if k in a and isinstance(a[k], dict) and isinstance(v, dict): - result[k] = merge_dicts(a[k], v) - else: - result[k] = v - - return result - - -class VagrantClient: - def __init__(self, module) -> None: - self._module = module - self.provision = self._module.params["provision"] - self.cachier = self._module.params["cachier"] - - # compat - if self._module.params["instance_name"] is not None: - self._module.warn( - "Please convert your playbook to use the instances parameter. Compat layer will be removed later.", - ) - self.instances = [ - { - "name": self._module.params["instance_name"], - "interfaces": self._module.params["instance_interfaces"], - "instance_raw_config_args": self._module.params[ - "instance_raw_config_args" - ], - "config_options": self._module.params["config_options"], - "box": self._module.params["platform_box"], - "box_version": self._module.params["platform_box_version"], - "box_url": self._module.params["platform_box_url"], - "box_download_checksum": self._module.params[ - "platform_box_download_checksum" - ], - "box_download_checksum_type": self._module.params[ - "platform_box_download_checksum_type" - ], - "memory": self._module.params["provider_memory"], - "cpus": self._module.params["provider_cpus"], - "provider_options": self._module.params["provider_options"], - "provider_override_args": self._module.params[ - "provider_override_args" - ], - "provider_raw_config_args": self._module.params[ - "provider_raw_config_args" - ], - }, - ] - else: - self.instances = self._module.params["instances"] - - self._config = self._get_config() - self._vagrantfile = self._config["vagrantfile"] - self._vagrant = self._get_vagrant() - self._write_configs() - self._has_error = None - self._datetime = datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S") - self.result = {} - - @contextlib.contextmanager - def stdout_cm(self): - """Redirect the stdout to a log file.""" - with open(self._get_stdout_log(), "a+") as fh: - msg = f"### {self._datetime} ###\n" - fh.write(msg) - fh.flush() - - yield fh - - @contextlib.contextmanager - def stderr_cm(self): - """Redirect the stderr to a log file.""" - with open(self._get_stderr_log(), "a+") as fh: - msg = f"### {self._datetime} ###\n" - fh.write(msg) - fh.flush() - - try: - yield fh - except subprocess.CalledProcessError as e: - self._has_error = True - self.result["cmd"] = e.cmd - self.result["rc"] = e.returncode - self.result["stderr"] = e.output or "" - - # raise - except Exception as e: - self._has_error = True - if hasattr(e, "message"): - fh.write(e.message) - else: - fh.write(e) - fh.flush() - raise - - def up(self): - changed = False - if self._running() != len(self.instances): - changed = True - provision = self.provision - with contextlib.suppress(Exception): - self._vagrant.up(provision=provision) - - # NOTE(retr0h): Ansible wants only one module return `fail_json` - # or `exit_json`. - if not self._has_error: - # compat - if self._module.params["instance_name"] is not None: - self._module.exit_json( - changed=changed, - log=self._get_stdout_log(), - **self._conf()[0], - ) - self._module.exit_json( - changed=changed, - log=self._get_stdout_log(), - results=self._conf(), - ) - - msg = f"Failed to start the VM(s): See log file '{self._get_stderr_log()}'" - with open(self._get_stderr_log(), encoding="utf-8") as f: - self.result["stderr"] = f.read() - self._module.fail_json(msg=msg, **self.result) - - def destroy(self): - changed = False - if self._created() > 0: - changed = True - if self._module.params["force_stop"]: - self._vagrant.halt(force=True) - self._vagrant.destroy() - - self._module.exit_json(changed=changed) - - def halt(self): - changed = False - if self._running() > 0: - changed = True - self._vagrant.halt(force=self._module.params["force_stop"]) - - self._module.exit_json(changed=changed) - - def _conf_instance(self, instance_name): - try: - return self._vagrant.conf(vm_name=instance_name) - except Exception: - msg = f"Failed to get vagrant config for {instance_name}: See log file '{self._get_stderr_log()}'" - with open(self._get_stderr_log(), encoding="utf-8") as f: - self.result["stderr"] = f.read() - self._module.fail_json(msg=msg, **self.result) - - def _status_instance(self, instance_name): - try: - s = self._vagrant.status(vm_name=instance_name)[0] - - return {"name": s.name, "state": s.state, "provider": s.provider} - except Exception: - msg = f"Failed to get status for {instance_name}: See log file '{self._get_stderr_log()}'" - with open(self._get_stderr_log(), encoding="utf-8") as f: - self.result["stderr"] = f.read() - self._module.fail_json(msg=msg, **self.result) - - def _conf(self): - conf = [] - - for i in self.instances: - instance_name = i["name"] - c = self._conf_instance(instance_name) - if c: - conf.append(c) - - return conf - - def _status(self): - vms_status = [] - - for i in self.instances: - instance_name = i["name"] - s = self._status_instance(instance_name) - if s: - vms_status.append(s) - - return vms_status - - def _created(self): - status = self._status() - if len(status) == 0: - return 0 - - count = sum(s["state"] == "not_created" for s in status) - return len(status) - count - - def _running(self): - status = self._status() - if len(status) == 0: - return 0 - - count = sum(s["state"] == "running" for s in status) - return count - - def _get_config(self): - conf = {} - conf["workdir"] = os.getenv("MOLECULE_EPHEMERAL_DIRECTORY") - if self._module.params["workdir"] is not None: - conf["workdir"] = self._module.params["workdir"] - if conf["workdir"] is None: - self._module.fail_json( - msg="Either workdir parameter or MOLECULE_EPHEMERAL_DIRECTORY env variable has to be set", - ) - conf["vagrantfile"] = os.path.join(conf["workdir"], "Vagrantfile") - return conf - - def _write_vagrantfile(self): - instances = self._get_vagrant_config_dict() - j_env = jinja2.Environment( - autoescape=True, - trim_blocks=True, - lstrip_blocks=True, - ) - t = j_env.from_string(VAGRANTFILE_TEMPLATE) - template = t.render( - instances=instances, - cachier=self.cachier, - no_kvm=not os.path.exists("/dev/kvm"), - ) - with open(self._vagrantfile, "w") as f: - f.write(template) - - def _write_configs(self): - self._write_vagrantfile() - try: - self._vagrant.validate(self._config["workdir"]) - except subprocess.CalledProcessError as e: - self._module.fail_json( - msg=f"Failed to validate generated Vagrantfile: {e.stderr}", - ) - - def _get_vagrant(self): - vagrant_env = os.environ.copy() - if self._module.params["parallel"] is False: - vagrant_env["VAGRANT_NO_PARALLEL"] = "1" - v = vagrant.Vagrant( - out_cm=self.stdout_cm, - err_cm=self.stderr_cm, - root=self._config["workdir"], - env=vagrant_env, - ) - - return v - - def _get_instance_vagrant_config_dict(self, instance): - checksum = instance.get("box_download_checksum") - checksum_type = instance.get("box_download_checksum_type") - if bool(checksum) ^ bool(checksum_type): - self._module.fail_json( - msg="box_download_checksum and box_download_checksum_type must be used together", - ) - - networks = [] - if "interfaces" in instance: - for iface in instance["interfaces"]: - net = {} - net["name"] = iface["network_name"] - iface.pop("network_name") - net["options"] = iface - networks.append(net) - - # compat - provision = instance.get("provision") - if provision is not None: - self.provision = self.provision or provision - self._module.warn( - "Please convert your molecule.yml to move provision parameter to driver:. Compat layer will be removed later.", - ) - d = { - "name": instance.get("name"), - "hostname": instance.get("hostname", instance.get("name")), - "memory": instance.get("memory", 512), - "cpus": instance.get("cpus", 2), - "networks": networks, - "instance_raw_config_args": instance.get("instance_raw_config_args", None), - "config_options": { - # NOTE(retr0h): `synced_folder` does not represent the - # actual key used by Vagrant. Is used as a flag to - # simply enable/disable shared folder. - "synced_folder": False, - "ssh.insert_key": True, - }, - "box": instance.get("box", self._module.params["default_box"]), - "box_version": instance.get("box_version"), - "box_url": instance.get("box_url"), - "box_architecture": instance.get("box_architecture"), - "box_download_checksum": checksum, - "box_download_checksum_type": checksum_type, - "provider": self._module.params["provider_name"], - "provider_options": {}, - "provider_raw_config_args": instance.get("provider_raw_config_args", None), - "provider_override_args": instance.get("provider_override_args", None), - } - - d["config_options"].update( - merge_dicts( - d["config_options"], - instance.get("config_options", {}), - ), - ) - if "cachier" in d["config_options"]: - self.cachier = d["config_options"]["cachier"] - self._module.warn( - "Please convert your molecule.yml to move cachier parameter to driver:. Compat layer will be removed later.", - ) - - d["provider_options"].update( - merge_dicts( - d["provider_options"], - instance.get("provider_options", {}), - ), - ) - - return d - - def _get_vagrant_config_dict(self): - config_list = [ - self._get_instance_vagrant_config_dict(instance) - for instance in self.instances - ] - return config_list - - def _get_stdout_log(self): - return self._get_vagrant_log("out") - - def _get_stderr_log(self): - return self._get_vagrant_log("err") - - def _get_vagrant_log(self, __type, /): - return os.path.join(self._config["workdir"], f"vagrant.{__type}") - - -def main(): - module = AnsibleModule( - argument_spec={ - "instances": {"type": "list", "required": False}, - "instance_name": {"type": "str", "required": False, "default": None}, - "instance_interfaces": {"type": "list", "default": []}, - "instance_raw_config_args": {"type": "list", "default": None}, - "config_options": {"type": "dict", "default": {}}, - "platform_box": {"type": "str", "required": False}, - "platform_box_version": {"type": "str"}, - "platform_box_url": {"type": "str"}, - "platform_box_architecture": {"type": "str"}, - "platform_box_download_checksum": {"type": "str"}, - "platform_box_download_checksum_type": {"type": "str"}, - "provider_memory": {"type": "int", "default": 512}, - "provider_cpus": {"type": "int", "default": 2}, - "provider_options": {"type": "dict", "default": {}}, - "provider_override_args": {"type": "list", "default": None}, - "provider_raw_config_args": {"type": "list", "default": None}, - "provider_name": {"type": "str", "default": "virtualbox"}, - "default_box": {"type": "str", "default": None}, - "provision": {"type": "bool", "default": False}, - "force_stop": {"type": "bool", "default": False}, - "cachier": {"type": "str", "default": "machine"}, - "state": { - "type": "str", - "default": "up", - "choices": ["up", "destroy", "halt"], - }, - "workdir": {"type": "str"}, - "parallel": {"type": "bool", "default": True}, - }, - required_together=[ - ("platform_box_download_checksum", "platform_box_download_checksum_type"), - ("instances", "default_box"), - ], - supports_check_mode=False, - ) - - if not (bool(module.params["instances"]) ^ bool(module.params["instance_name"])): - module.fail_json( - msg="Either instances or instance_name parameters should be used and not at the same time", - ) - - v = VagrantClient(module) - - if module.params["state"] == "up": - v.up() - - if module.params["state"] == "destroy": - v.destroy() - - if module.params["state"] == "halt": - v.halt() - - module.fail_json(msg="Unknown error", **v.result) - - -if __name__ == "__main__": - main() From 3b324d9c82c254e79001afa4ea42f016a42abd42 Mon Sep 17 00:00:00 2001 From: 0x4D616E75 <0x4d616e75@elektronische-nachricht.de> Date: Wed, 25 Jun 2025 00:12:02 +0200 Subject: [PATCH 5/5] lint: fix ansible-lint errors --- .ansible-lint | 3 --- .ansible-lint-ignore | 43 +++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 43 insertions(+), 3 deletions(-) diff --git a/.ansible-lint b/.ansible-lint index c70f8861..d40e8d4f 100644 --- a/.ansible-lint +++ b/.ansible-lint @@ -8,6 +8,3 @@ exclude_paths: skip_list: # Temporary skips made during migration - args[module] - -mock_modules: - - vagrant # only until we publish community.vagrant collection diff --git a/.ansible-lint-ignore b/.ansible-lint-ignore index 9e9fc2c1..1727146c 100644 --- a/.ansible-lint-ignore +++ b/.ansible-lint-ignore @@ -21,3 +21,46 @@ test/roles/ec2plugin/molecule/default/destroy.yml risky-file-permissions test/roles/openstackplugin/molecule/default/create.yml yaml[octal-values] test/roles/openstackplugin/molecule/default/destroy.yml yaml[octal-values] + + +# Can be removed when https://github.com/ansible/ansible/pull/85005 was merged +test/roles/azureplugin/defaults/main.yml yaml[comments] +test/roles/azureplugin/handlers/main.yml yaml[comments] +test/roles/azureplugin/meta/main.yml yaml[comments] +test/roles/azureplugin/tasks/main.yml yaml[comments] +test/roles/azureplugin/vars/main.yml yaml[comments] +test/roles/containersplugin/defaults/main.yml yaml[comments] +test/roles/containersplugin/handlers/main.yml yaml[comments] +test/roles/containersplugin/meta/main.yml yaml[comments] +test/roles/containersplugin/tasks/main.yml yaml[comments] +test/roles/containersplugin/vars/main.yml yaml[comments] +test/roles/dockerplugin/defaults/main.yml yaml[comments] +test/roles/dockerplugin/handlers/main.yml yaml[comments] +test/roles/dockerplugin/meta/main.yml yaml[comments] +test/roles/dockerplugin/tasks/main.yml yaml[comments] +test/roles/dockerplugin/vars/main.yml yaml[comments] +test/roles/ec2plugin/defaults/main.yml yaml[comments] +test/roles/ec2plugin/handlers/main.yml yaml[comments] +test/roles/ec2plugin/meta/main.yml yaml[comments] +test/roles/ec2plugin/tasks/main.yml yaml[comments] +test/roles/ec2plugin/vars/main.yml yaml[comments] +test/roles/gceplugin/defaults/main.yml yaml[comments] +test/roles/gceplugin/handlers/main.yml yaml[comments] +test/roles/gceplugin/meta/main.yml yaml[comments] +test/roles/gceplugin/tasks/main.yml yaml[comments] +test/roles/gceplugin/vars/main.yml yaml[comments] +test/roles/openstackplugin/defaults/main.yml yaml[comments] +test/roles/openstackplugin/handlers/main.yml yaml[comments] +test/roles/openstackplugin/meta/main.yml yaml[comments] +test/roles/openstackplugin/tasks/main.yml yaml[comments] +test/roles/openstackplugin/vars/main.yml yaml[comments] +test/roles/podmanplugin/defaults/main.yml yaml[comments] +test/roles/podmanplugin/handlers/main.yml yaml[comments] +test/roles/podmanplugin/meta/main.yml yaml[comments] +test/roles/podmanplugin/tasks/main.yml yaml[comments] +test/roles/podmanplugin/vars/main.yml yaml[comments] +test/roles/vagrantplugin/defaults/main.yml yaml[comments] +test/roles/vagrantplugin/handlers/main.yml yaml[comments] +test/roles/vagrantplugin/meta/main.yml yaml[comments] +test/roles/vagrantplugin/tasks/main.yml yaml[comments] +test/roles/vagrantplugin/vars/main.yml yaml[comments]