How to manage Vagrant VM lifecycle with ansible?

I’m trying to create a molecule scenario where through Vagrant a VM would be created, then the tests would be ran against that. I tried to ask LLMs but they get very confused with the pre-ansible-native vs ansible-native configuration and they are giving me non-sense, so I’m trying my luck here. I understand the ansible-native config of molecule that far that it’s my responsibility to create a create.yml playbook which would spin up a VM through Vagrant.
I can’t find any collection which would be like:

tasks:
  - name: Create virtual machine
    vagrant:
      box: ....

rather I ended up with the solution to execute vagrant like this:

tasks:
  - name: Create virtual machine
    command:
      cmd: vagrant up
      # Vagrantfile is in the scenario's directory
      chdir: "{{ lookup('env', 'MOLECULE_SCENARIO_DIRECTORY') }}"

I think the downside of this approach is that I can’t really register the state “natively” whether the virtual machine is running and ready. Is there a more “native” method to do “molecule test start → ensure virtual machine created via vagrant → ensure virtual machine is running through vagrant” → run tests?

I’ve solved it on my own. Below are the things I’ve done if anyone else needs it.
The best answer I could find was a custom inventory plugin.

The inventory plugin which uses vagrant global-state command to find hosts:

Place this inside plugins/inventory folder, and add an empty __init__.py file next to it.
I’ve used the following repo as a baseline for it: vagrant-ansible-dynamic-inventory/vagrant_inventory.py at master · chornberger-c2c/vagrant-ansible-dynamic-inventory · GitHub
I just upgraded it to be a module intentory plugin not an inventory script.

Add an inventory.yml file to your molecule scenario directory with the following contents:

---
plugin: vagrant
vagrantfile_path: ${MOLECULE_SCENARIO_DIRECTORY}/Vagrantfile
inventory_hostname: host
groups:
  my_machines:
    hosts: {}

Your molecule.yml file for your scenario should look like this:

---
ansible:
  executor:
    args:
      ansible_playbook:
        - --inventory=${MOLECULE_SCENARIO_DIRECTORY}/inventory.yml
  cfg:
    defaults:
      collections_path: collections
      library: "$MOLECULE_PROJECT_DIRECTORY/.."
      inventory_plugins: "$MOLECULE_SCENARIO_DIRECTORY/../../plugins/inventory"
    inventory:
      enable_plugins: host_list, script, auto, yaml, ini, toml, vagrant

Key highlights:

  • enable_plugins enables the custom vagrant inventory plugin
  • inventory_plugins tells ansible to look for plugins in the plugins/inventory directory

The create.yml playbook for the molecule scenario should look like the following:

---
- name: Create virtual machine
  hosts: localhost
  gather_facts: false
  tasks:
    - name: Start test VM through vagrant
      ansible.builtin.command:
        cmd: vagrant up
        chdir: "{{ lookup('env', 'MOLECULE_SCENARIO_DIRECTORY') }}"
      register: vagrant_up
      changed_when: "'already running' not in vagrant_up.stderr"
    - name: Add vm to inventory
      ansible.builtin.meta: refresh_inventory
    - name: Validate inventory
      assert:
        that:
          - groups['my_machine'] | length > 0
- name: Wait for vm to be ready
  hosts: "{{ groups['my_machines'] | map('extract', hostvars, 'inventory_hostname') | list }}"
  gather_facts: false
  vars:
    acceptable_systemd_states:
      - running
      - degraded
  tasks:
    - name: Wait for systemd to settle
      ansible.builtin.shell: /bin/sh -c 'systemctl is-system-running --wait || systemctl is-system-running'
      register: systemd_status
      until: systemd_status.rc == 0 or (systemd_status.stdout_lines | last | trim) in acceptable_systemd_states
      failed_when: false
      changed_when: false
      retries: 10
      delay: 3
    - name: Wait for container PID1 to be ready
      ansible.builtin.shell: /bin/bash -lc 'true'
      register: _ready
      retries: 10
      delay: 3
      until: _ready is succeeded

This way molecule would start with an empty inventory, during the create phase it would execute vagrant up and with an inventory refresh it will get the hosts from the custom vagrant inventory plugin.

1 Like