Przejdź do treści

Testowanie ról Ansible z użyciem Molecule i Proxmox

Molecule to framework umożliwiający automatyczne testowanie ról Ansible w odizolowanych środowiskach. W integracji z Proxmox pozwala na dynamiczne tworzenie maszyn wirtualnych na podstawie istniejących szablonów (VM Templates), uruchamianie roli Ansible, a następnie walidację jej działania.

Proces testowania obejmuje:
1. Klonowanie nowej VM na serwerze Proxmox z predefiniowanego szablonu.
2. Provisioning – uruchomienie roli Ansible i konfiguracja systemu.
3. Weryfikację poprawności konfiguracji za pomocą testów Ansible lub Testinfra.
4. Usunięcie instancji po zakończeniu testów, aby nie pozostawiać zbędnych zasobów.

Dzięki temu testowanie ról Ansible w Proxmox jest szybkie, powtarzalne i zautomatyzowane, co pozwala na wykrywanie błędów jeszcze przed wdrożeniem na produkcję. 🚀

Architektura rozwiązania

architektura

Krok 1. Utworzenie template z proxmox

Dokładnie opisałem to w tym artykule Tworzymy template vm na proxmox - alpine

Krok 2. Napisanie roli

Przygotowałem sobie rolę (konfiguracja serwera SSH) configure-ssh

Krok 3. Przygotowanie molecule

Przygotowanie venv

python3 -m venv ~/.venvs/molecule
source ~/.venvs/molecule/bin/activate
pip install --upgrade pip

pip3 install ansible-core molecule molecule-proxmox pytest-testinfra ansible-lint molecule-plugins requests testinfra

Utworzenie projektu molecule

molecule init role nazwa_roli
Napisanie małego testu w pythonie molecule/default/tests/test_sshd_config.py
import pytest

SSHD_SERVICE_NAME = "sshd"

def test_sshd_running_and_enabled(host):
    """Test sprawdzający, czy usługa SSHD działa i jest włączona"""
    ssh_service = host.service(SSHD_SERVICE_NAME)

    assert ssh_service.is_running, "❌ SSHD nie jest uruchomione!"
    assert ssh_service.is_enabled, "❌ SSHD nie jest ustawione do uruchamiania przy starcie systemu!"

Krok 4. Konfiguracja molecule

---
driver:
  name: molecule-proxmox
  options:
    debug: true
    # api_host:                       # from env TEST_PROXMOX_HOST
    # api_user:                       # from env TEST_PROXMOX_USER
    # api_password:                   # from env TEST_PROXMOX_PASSWORD 
    # node:                           # from env TEST_PROXMOX_NODE
    ssh_user: ansible
    ssh_port: 22
    ssh_identity_file: /tmp/molecule
    timeout: 3600

platforms:
  - name: molecule-ubuntu
    template_name: ubuntu-24.10
    # vmid: 103
    full: false
    pool: molecule
    ciuser: ansible
    cipassword: "ansible"
    net:
      net0: 'virtio,bridge=vmbr0,tag=10'
    ipconfig:
      ipconfig0: "ip=dhcp"
    core: 1
    cpu: 1
    memory: 2048

  - name: molecule-alpine
    template_name: alpine-3.20.6
    # vmid: 102
    full: false
    pool: molecule
    ciuser: ansible
    cipassword: "ansible"
    net:
      net0: 'virtio,bridge=vmbr0,tag=10'
    ipconfig:
      ipconfig0: "ip=dhcp"
    core: 1
    cpu: 1
    memory: 2048

  - name: molecule-alma
    template_name: AlmaLinux-9.5
    # vmid: 104
    full: false
    pool: molecule
    ciuser: ansible
    cipassword: "ansible"
    net:
      net0: 'virtio,bridge=vmbr0,tag=10'
    ipconfig:
      ipconfig0: "ip=dhcp"
    core: 1
    cpu: 1
    memory: 2048

provisioner:
  name: ansible
  env:
    ANSIBLE_ROLES_PATH: ../../../../roles/
  config_options:
    ssh-connection:
      host_key_checking: false

verifier:
  name: testinfra
  options:
    v: true
Pierwsze uruchomienie się nie udało, powodem było to, że defaultowe playbooki nie spełniają moich oczekiwań

Krok 5. Dostowanie defaultowych playbooków

Oryginalny playbook znajduje się ~/.venvs/molecule/lib/python3.12/site-packages/molecule_proxmox/playbooks/create.yml

cp ~/.venvs/molecule/lib/python3.12/site-packages/molecule_proxmox/playbooks/create.yml molecule/default/create.yml
---
- name: Create
  hosts: localhost
  connection: local
  gather_facts: false
  vars:
      options: "{{ molecule_yml.driver.options }}"
  tasks:
    - name: "Load proxmox secrets."
      ansible.builtin.include_vars: "{{ options.proxmox_secrets }}"
      when: options.proxmox_secrets is defined
      no_log: true
    - name: "Create molecule instance(s)."
      community.general.proxmox_kvm:
        state: present

        api_host: "{{ api_host | d(options.api_host) | d(omit) }}"
        api_user: "{{ api_user | d(options.api_user) | d(omit) }}"
        api_password: "{{ api_password | d(options.api_password) | d(omit) }}"
        api_host: "{{ api_host | d(options.api_host) | d(lookup('env', 'TEST_PROXMOX_HOST')) | d(omit) }}"
        api_port: "{{ api_port | d(options.api_port) | d(lookup('env', 'TEST_PROXMOX_PORT') | int) | d(omit) }}"
        api_user: "{{ api_user | d(options.api_user) | d(lookup('env', 'TEST_PROXMOX_USER')) | d(omit) }}"
        api_password: "{{ api_password | d(options.api_password) | d(lookup('env', 'TEST_PROXMOX_PASSWORD')) | d(omit) }}"
        api_token_id: "{{ api_token_id | d(options.api_token_id) | d(omit) }}"
        api_token_secret: "{{ api_token_secret | d(options.api_token_secret) | d(omit) }}"
        vmid: "{{ p.proxmox_template_vmid | d(p.template_vmid, true) | d(omit, true) }}"
        clone: "{{ p.proxmox_template_name | d(p.template_name, true) | d(options.template_name, true) | d(p.box, true) | d('molecule', true) }}"
        name: "{{ p.name }}"
        node: "{{ options.node }}"
        node: "{{ options.node | d(lookup('env', 'TEST_PROXMOX_NODE'))  }}"
        timeout: "{{ options.timeout | d(omit) }}"
        pool: "{{ options.pool | d(omit) }}"
        newid: "{{ p.newid | d(p.newid, true) | d(omit, true) }}"
      loop: "{{ molecule_yml.platforms }}"
      loop_control:
        loop_var: p
        label: "{{ p.name }}"
      register: proxmox_clone

    - name: "Update molecule instance config(s)"
      community.general.proxmox_kvm:
        state: present
        update: true
        api_host: "{{ api_host | d(options.api_host) | d(omit) }}"
        api_user: "{{ api_user | d(options.api_user) | d(omit) }}"
        api_password: "{{ api_password | d(options.api_password) | d(omit) }}"
        api_host: "{{ api_host | d(options.api_host) | d(lookup('env', 'TEST_PROXMOX_HOST')) | d(omit) }}"
        api_port: "{{ api_port | d(options.api_port) | d(lookup('env', 'TEST_PROXMOX_PORT') | int) | d(omit) }}"
        api_user: "{{ api_user | d(options.api_user) | d(lookup('env', 'TEST_PROXMOX_USER')) | d(omit) }}"
        api_password: "{{ api_password | d(options.api_password) | d(lookup('env', 'TEST_PROXMOX_PASSWORD')) | d(omit) }}"
        api_token_id: "{{ api_token_id | d(options.api_token_id) | d(omit) }}"
        api_token_secret: "{{ api_token_secret | d(options.api_token_secret) | d(omit) }}"
        vmid: "{{ rc.vmid }}"
        node: "{{ options.node }}"
        node: "{{ options.node | d(lookup('env', 'TEST_PROXMOX_NODE'))  }}"
        timeout: "{{ options.timeout | d(omit) }}"
        ciuser: "{{ rc.p.ciuser | d(omit, true) }}"
         # W celu przyszpieszenia stawiania vm, nie chce klonować dysku
        full: "{{ p.full | d(options.full) | d(omit) }}"
        cipassword: "{{ rc.p.cipassword | d(omit, true) }}"
        citype: "{{ rc.p.citype | d(omit, true) }}"
        ipconfig: "{{ rc.p.ipconfig | d(omit, true) }}"
        nameservers: "{{ rc.p.nameservers | d(omit, true) }}"
        searchdomains: "{{ rc.p.searchdomains | d(omit, true) }}"
        sshkeys: "{{ rc.p.sshkeys | d(omit, true) }}"
      when: >
        rc.p.ciuser is defined or
        rc.p.cipassword is defined or
        rc.p.citype is defined or
        rc.p.ipconfig is defined or
        rc.p.nameservers is defined or
        rc.p.searchdomains is defined or
        rc.p.sshkeys is defined
      loop: "{{ proxmox_clone.results }}"
      loop_control:
        loop_var: rc
        label: "{{ rc.p.name, rc.vmid }}"

    - name: "Start molecule instance(s)."
      proxmox_qemu_agent:
        api_host: "{{ api_host | d(options.api_host) | d(omit) }}"
        api_user: "{{ api_user | d(options.api_user) | d(omit) }}"
        api_password: "{{ api_password | d(options.api_password) | d(omit) }}"
        api_host: "{{ api_host | d(options.api_host) | d(lookup('env', 'TEST_PROXMOX_HOST')) | d(omit) }}"
        api_port: "{{ api_port | d(options.api_port) | d(lookup('env', 'TEST_PROXMOX_PORT') | int) | d(omit) }}"
        api_user: "{{ api_user | d(options.api_user) | d(lookup('env', 'TEST_PROXMOX_USER')) | d(omit) }}"
        api_password: "{{ api_password | d(options.api_password) | d(lookup('env', 'TEST_PROXMOX_PASSWORD')) | d(omit) }}"
        api_token_id: "{{ api_token_id | d(options.api_token_id) | d(omit) }}"
        api_token_secret: "{{ api_token_secret | d(options.api_token_secret) | d(omit) }}"
        vmid: "{{ rc.vmid }}"
        timeout: "{{ options.timeout | d(omit) }}"
      loop: "{{ proxmox_clone.results }}"
      loop_control:
        loop_var: rc
        label: "{{ rc.p.name, rc.vmid }}"
      register: proxmox_qemu_agent

    - name: "Populate instance configs."
      ansible.builtin.set_fact:
        instance_config:
          instance: "{{ ra.rc.p.name }}"
          address: "{{ ra.addresses[0] }}"
          user: "{{ options.ssh_user | d('molecule') }}"
          port: 22
          identity_file: "{{ options.ssh_identity_file }}"
          vmid: "{{ ra.vmid }}"
      loop: "{{ proxmox_qemu_agent.results }}"
      loop_control:
        loop_var: ra
        label: "{{ ra.rc.p.name, ra.vmid, ra.addresses[0] }}"
      register: instance_configs

    - name: "Set instance_config fact."
      ansible.builtin.set_fact:
        instance_configs: "{{ instance_configs.results | map(attribute='ansible_facts.instance_config') | list }}"

    - name: "Write instance configs."
      ansible.builtin.copy:
        content: "{{ instance_configs | to_nice_yaml }}"
        dest: "{{ molecule_instance_config }}"
        mode: '0644'
Oryginalny playbook znajduje się ~/.venvs/molecule/lib/python3.12/site-packages/molecule_proxmox/playbooks/destroy.yml
cp ~/.venvs/molecule/lib/python3.12/site-packages/molecule_proxmox/playbooks/destroy.yml molecule/default/destroy.yml

---
- name: Destroy
  hosts: localhost
  connection: local
  gather_facts: false
  vars:
      options: "{{ molecule_yml.driver.options }}"
  tasks:
    - name: "Load proxmox secrets."
      ansible.builtin.include_vars: "{{ options.proxmox_secrets }}"
      when: options.proxmox_secrets is defined
      no_log: true

    # Remove instances by numeric vmid instead of by name, which seems
    # safer and more reliable. Since the Ansible lookup() plugin complains
    # even when error=ingore is set, just create an empty file to ignore
    # a missing instance_configs.
    - name: "Check for instance configs."
      ansible.builtin.stat:
        path: "{{ molecule_instance_config }}"
      register: instance_config_stat

    - name: "Write empty instance configs."
      ansible.builtin.copy:
        content: "[]"
        dest: "{{ molecule_instance_config }}"
        mode: '0644'
      when: not instance_config_stat.stat.exists

    - name: "Remove molecule instance(s)."
      community.general.proxmox_kvm:
        api_host: "{{ api_host | d(options.api_host) | d(omit) }}"
        api_user: "{{ api_user | d(options.api_user) | d(omit) }}"
        api_password: "{{ api_password | d(options.api_password) | d(omit) }}"
        api_host: "{{ api_host | d(options.api_host) | d(lookup('env', 'TEST_PROXMOX_HOST')) | d(omit) }}"
        api_port: "{{ api_port | d(options.api_port) | d(lookup('env', 'TEST_PROXMOX_PORT') | int) | d(omit) }}"
        api_user: "{{ api_user | d(options.api_user) | d(lookup('env', 'TEST_PROXMOX_USER')) | d(omit) }}"
        api_password: "{{ api_password | d(options.api_password) | d(lookup('env', 'TEST_PROXMOX_PASSWORD')) | d(omit) }}"
        api_token_id: "{{ api_token_id | d(options.api_token_id) | d(omit) }}"
        api_token_secret: "{{ api_token_secret | d(options.api_token_secret) | d(omit) }}"
        node: "{{ options.node }}"
        node: "{{ options.node | d(lookup('env', 'TEST_PROXMOX_NODE'))  }}"
        state: absent
        vmid: "{{ i.vmid }}"
        force: yes
        timeout: "{{ options.timeout | d(omit) }}"
      loop: "{{ lookup('file', molecule_instance_config) | from_yaml }}"
      loop_control:
        loop_var: i
        label: "{{ i.instance, i.vmid }}"

Krok 5. Uruchamiamy testy

export TEST_PROXMOX_DEBUG="true"
export TEST_PROXMOX_HOST="<<pve>>"
export TEST_PROXMOX_PORT="8006"
export TEST_PROXMOX_USER="root@pam"
export TEST_PROXMOX_PASSWORD="<<password>>"
export TEST_PROXMOX_NODE="<<node>>"

molecule test

# molecule create
# molecule converge
# molecule verify
# molecule destroy
WARNING  Driver molecule-proxmox does not provide a schema.
INFO     default scenario test matrix: dependency, cleanup, destroy, syntax, create, prepare, converge, idempotence, side_effect, verify, cleanup, destroy
INFO     Performing prerun with role_name_check=0...
INFO     Running default > dependency
Molecule default > dependency
00:00
WARNING  Skipping, missing the requirements file.
WARNING  Skipping, missing the requirements file.
INFO     Running default > cleanup
Molecule default > cleanup
00:00
WARNING  Skipping, cleanup playbook not configured.
INFO     Running default > destroy
Molecule default > destroy
00:28
PLAY [Destroy] *****************************************************************
TASK [Load proxmox secrets.] ***************************************************
skipping: [localhost]
TASK [Check for instance configs.] *********************************************
ok: [localhost]
TASK [Write empty instance configs.] *******************************************
changed: [localhost]
TASK [Remove molecule instance(s).] ********************************************
skipping: [localhost]
PLAY RECAP *********************************************************************
localhost                  : ok=2    changed=1    unreachable=0    failed=0    skipped=2    rescued=0    ignored=0
INFO     Running default > syntax
Molecule default > syntax
00:01
playbook: /builds/pl.rachuna-net/infrastructure/ansible/roles/configure-ssh/molecule/default/converge.yml
INFO     Running default > create
Molecule default > create
00:11
PLAY [Create] ******************************************************************
TASK [Create molecule instance(s).] ********************************************
ok: [localhost] => (item=molecule-ubuntu)
ok: [localhost] => (item=molecule-alpine)
ok: [localhost] => (item=molecule-alma)
TASK [Update molecule instance config(s)] **************************************
changed: [localhost] => (item=('molecule-ubuntu', 105))
changed: [localhost] => (item=('molecule-alpine', 106))
changed: [localhost] => (item=('molecule-alma', 107))
TASK [Start molecule instance(s).] *********************************************
ok: [localhost] => (item=('molecule-ubuntu', 105))
ok: [localhost] => (item=('molecule-alpine', 106))
ok: [localhost] => (item=('molecule-alma', 107))
TASK [Populate instance configs.] **********************************************
ok: [localhost] => (item=('molecule-ubuntu', 105, '10.3.1.208'))
ok: [localhost] => (item=('molecule-alpine', 106, '10.3.1.209'))
ok: [localhost] => (item=('molecule-alma', 107, '10.3.1.210'))
TASK [Set instance_config fact.] ***********************************************
ok: [localhost]
TASK [Write instance configs.] *************************************************
changed: [localhost]
PLAY RECAP *********************************************************************
localhost                  : ok=6    changed=2    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0
INFO     Running default > prepare
Molecule default > prepare
00:05
PLAY [Prepare] *****************************************************************
TASK [Waiting for instance ssh connection.] ************************************
ok: [molecule-alpine]
ok: [molecule-alma]
ok: [molecule-ubuntu]
TASK [Set instance hostname.] **************************************************
ok: [molecule-alma]
ok: [molecule-alpine]
ok: [molecule-ubuntu]
TASK [Gather facts.] ***********************************************************
ok: [molecule-alpine]
ok: [molecule-alma]
ok: [molecule-ubuntu]
TASK [Remove workaround loopback from /etc/hosts file.] ************************
changed: [molecule-alpine]
changed: [molecule-ubuntu]
ok: [molecule-alma]
TASK [Add address and hostname to /etc/hosts file.] ****************************
changed: [molecule-ubuntu]
changed: [molecule-alpine]
changed: [molecule-alma]
PLAY RECAP *********************************************************************
molecule-alma              : ok=5    changed=1    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0
molecule-alpine            : ok=5    changed=2    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0
molecule-ubuntu            : ok=5    changed=2    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0
INFO     Running default > converge
Molecule default > converge
00:10
PLAY [Converge] ****************************************************************
TASK [Gathering Facts] *********************************************************
ok: [molecule-alpine]
ok: [molecule-alma]
ok: [molecule-ubuntu]
TASK [configure-ssh : Include install tasks] ***********************************
included: /builds/pl.rachuna-net/infrastructure/ansible/roles/configure-ssh/tasks/install.yml for molecule-alma, molecule-alpine, molecule-ubuntu
TASK [configure-ssh : debug] ***************************************************
ok: [molecule-alma] => {
    "msg": "RedHat"
}
ok: [molecule-alpine] => {
    "msg": "Alpine"
}
ok: [molecule-ubuntu] => {
    "msg": "Debian"
}
TASK [configure-ssh : [Debian] Install ssh] ************************************
skipping: [molecule-alma]
skipping: [molecule-alpine]
ok: [molecule-ubuntu]
TASK [configure-ssh : [Alpine] Install ssh] ************************************
skipping: [molecule-alma]
skipping: [molecule-ubuntu]
ok: [molecule-alpine]
TASK [configure-ssh : [RedHat] Install ssh] ************************************
skipping: [molecule-alpine]
skipping: [molecule-ubuntu]
ok: [molecule-alma]
TASK [configure-ssh : Include config tasks] ************************************
included: /builds/pl.rachuna-net/infrastructure/ansible/roles/configure-ssh/tasks/config.yml for molecule-alma, molecule-alpine, molecule-ubuntu
TASK [configure-ssh : Set SSH Port] ********************************************
changed: [molecule-ubuntu]
changed: [molecule-alpine]
changed: [molecule-alma]
TASK [configure-ssh : Block Password Authentication ssh] ***********************
changed: [molecule-ubuntu]
changed: [molecule-alpine]
changed: [molecule-alma]
TASK [configure-ssh : Block login as root] *************************************
changed: [molecule-ubuntu]
ok: [molecule-alpine]
changed: [molecule-alma]
TASK [configure-ssh : Change mode file] ****************************************
ok: [molecule-ubuntu]
ok: [molecule-alpine]
changed: [molecule-alma]
RUNNING HANDLER [configure-ssh : Restart SSHD] *********************************
changed: [molecule-alma]
changed: [molecule-alpine]
changed: [molecule-ubuntu]
PLAY RECAP *********************************************************************
molecule-alma              : ok=10   changed=5    unreachable=0    failed=0    skipped=2    rescued=0    ignored=0
molecule-alpine            : ok=10   changed=3    unreachable=0    failed=0    skipped=2    rescued=0    ignored=0
molecule-ubuntu            : ok=10   changed=4    unreachable=0    failed=0    skipped=2    rescued=0    ignored=0
INFO     Running default > idempotence
Molecule default > idempotence
00:08
PLAY [Converge] ****************************************************************
TASK [Gathering Facts] *********************************************************
ok: [molecule-alpine]
ok: [molecule-ubuntu]
ok: [molecule-alma]
TASK [configure-ssh : Include install tasks] ***********************************
included: /builds/pl.rachuna-net/infrastructure/ansible/roles/configure-ssh/tasks/install.yml for molecule-alma, molecule-alpine, molecule-ubuntu
TASK [configure-ssh : debug] ***************************************************
ok: [molecule-alma] => {
    "msg": "RedHat"
}
ok: [molecule-alpine] => {
    "msg": "Alpine"
}
ok: [molecule-ubuntu] => {
    "msg": "Debian"
}
TASK [configure-ssh : [Debian] Install ssh] ************************************
skipping: [molecule-alma]
skipping: [molecule-alpine]
ok: [molecule-ubuntu]
TASK [configure-ssh : [Alpine] Install ssh] ************************************
skipping: [molecule-alma]
skipping: [molecule-ubuntu]
ok: [molecule-alpine]
TASK [configure-ssh : [RedHat] Install ssh] ************************************
skipping: [molecule-alpine]
skipping: [molecule-ubuntu]
ok: [molecule-alma]
TASK [configure-ssh : Include config tasks] ************************************
included: /builds/pl.rachuna-net/infrastructure/ansible/roles/configure-ssh/tasks/config.yml for molecule-alma, molecule-alpine, molecule-ubuntu
TASK [configure-ssh : Set SSH Port] ********************************************
ok: [molecule-ubuntu]
ok: [molecule-alpine]
ok: [molecule-alma]
TASK [configure-ssh : Block Password Authentication ssh] ***********************
ok: [molecule-alpine]
ok: [molecule-ubuntu]
ok: [molecule-alma]
TASK [configure-ssh : Block login as root] *************************************
ok: [molecule-alpine]
ok: [molecule-ubuntu]
ok: [molecule-alma]
TASK [configure-ssh : Change mode file] ****************************************
ok: [molecule-ubuntu]
ok: [molecule-alpine]
ok: [molecule-alma]
PLAY RECAP *********************************************************************
molecule-alma              : ok=9    changed=0    unreachable=0    failed=0    skipped=2    rescued=0    ignored=0
molecule-alpine            : ok=9    changed=0    unreachable=0    failed=0    skipped=2    rescued=0    ignored=0
molecule-ubuntu            : ok=9    changed=0    unreachable=0    failed=0    skipped=2    rescued=0    ignored=0
INFO     Idempotence completed successfully.
INFO     Running default > side_effect
Molecule default > side_effect
00:00
WARNING  Skipping, side effect playbook not configured.
INFO     Running default > verify
Molecule default > verify
00:02
INFO     Executing Testinfra tests found in /builds/pl.rachuna-net/infrastructure/ansible/roles/configure-ssh/molecule/default/tests/...
============================= test session starts ==============================
platform linux -- Python 3.9.21, pytest-8.3.4, pluggy-1.5.0 -- /usr/local/bin/python3.9
rootdir: /
plugins: testinfra-10.1.1, testinfra-6.0.0
collecting ... collected 21 items
tests/test_sshd_config.py::test_sshd_running_and_enabled[ansible://molecule-alma] PASSED [  4%]
tests/test_sshd_config.py::test_sshd_config_exist[ansible://molecule-alma] PASSED [  9%]
tests/test_sshd_config.py::test_sshd_config_check_mode[ansible://molecule-alma] PASSED [ 14%]
tests/test_sshd_config.py::test_sshd_config_is_empty[ansible://molecule-alma] PASSED [ 19%]
tests/test_sshd_config.py::test_sshd_config_is_set_permit_root_login[ansible://molecule-alma] PASSED [ 23%]
tests/test_sshd_config.py::test_sshd_config_is_set_password_authentication[ansible://molecule-alma] PASSED [ 28%]
tests/test_sshd_config.py::test_sshd_config_is_set_port[ansible://molecule-alma] PASSED [ 33%]
tests/test_sshd_config.py::test_sshd_running_and_enabled[ansible://molecule-alpine] PASSED [ 38%]
tests/test_sshd_config.py::test_sshd_config_exist[ansible://molecule-alpine] PASSED [ 42%]
tests/test_sshd_config.py::test_sshd_config_check_mode[ansible://molecule-alpine] PASSED [ 47%]
tests/test_sshd_config.py::test_sshd_config_is_empty[ansible://molecule-alpine] PASSED [ 52%]
tests/test_sshd_config.py::test_sshd_config_is_set_permit_root_login[ansible://molecule-alpine] PASSED [ 57%]
tests/test_sshd_config.py::test_sshd_config_is_set_password_authentication[ansible://molecule-alpine] PASSED [ 61%]
tests/test_sshd_config.py::test_sshd_config_is_set_port[ansible://molecule-alpine] PASSED [ 66%]
tests/test_sshd_config.py::test_sshd_running_and_enabled[ansible://molecule-ubuntu] PASSED [ 71%]
tests/test_sshd_config.py::test_sshd_config_exist[ansible://molecule-ubuntu] PASSED [ 76%]
tests/test_sshd_config.py::test_sshd_config_check_mode[ansible://molecule-ubuntu] PASSED [ 80%]
tests/test_sshd_config.py::test_sshd_config_is_empty[ansible://molecule-ubuntu] PASSED [ 85%]
tests/test_sshd_config.py::test_sshd_config_is_set_permit_root_login[ansible://molecule-ubuntu] PASSED [ 90%]
tests/test_sshd_config.py::test_sshd_config_is_set_password_authentication[ansible://molecule-ubuntu] PASSED [ 95%]
tests/test_sshd_config.py::test_sshd_config_is_set_port[ansible://molecule-ubuntu] PASSED [100%]
============================== 21 passed in 1.37s ==============================
INFO     Verifier completed successfully.
INFO     Running default > cleanup
Molecule default > cleanup
00:00
WARNING  Skipping, cleanup playbook not configured.
INFO     Running default > destroy
Molecule default > destroy
00:28
PLAY [Destroy] *****************************************************************
TASK [Load proxmox secrets.] ***************************************************
skipping: [localhost]
TASK [Check for instance configs.] *********************************************
ok: [localhost]
TASK [Write empty instance configs.] *******************************************
skipping: [localhost]
TASK [Remove molecule instance(s).] ********************************************
changed: [localhost] => (item=('molecule-ubuntu', '105'))
changed: [localhost] => (item=('molecule-alpine', '106'))
changed: [localhost] => (item=('molecule-alma', '107'))
PLAY RECAP *********************************************************************
localhost                  : ok=2    changed=1    unreachable=0    failed=0    skipped=2    rescued=0    ignored=0