100% Free Forever
AI-Powered Learning
Industry Expert Content
Certificates & Badges
Learn At Your Own Pace
Infrastructure as Code — Terraform & Ansible
30 minintermediate

Molecule — Scenario Design and Testinfra Assertions

Molecule is the testing framework for Ansible roles — it provides a complete lifecycle for creating test instances, running the role against them, verifying the resulting state, and destroying the instances. Molecule for Ansible roles is analogous to Terratest for Terraform modules: it validates actual behaviour on real (or containerised) systems rather than just checking that the YAML syntax is valid. A role that passes 'ansible-playbook --syntax-check' may still fail to install packages, configure services incorrectly, or produce broken configuration files — only Molecule's verify step, which runs assertions against the configured system, catches these failures before the role reaches production.

Molecule's driver system supports multiple targets: Docker (fastest, cheapest, most common for local development — spins up containers in seconds), Vagrant (full VMs for closer production parity), EC2 (actual cloud instances for the most realistic testing), and Podman. The Docker driver is appropriate for most role testing because it validates the role's logic without full OS boot — services like systemd may require '--privileged' mode or workarounds for container environments. The testinfra library provides the assertion framework: Python-based tests that query the running system state and make assertions using pytest. Testinfra checks cover packages (is nginx installed and the correct version?), services (is nginx running and enabled?), files (does /etc/nginx/nginx.conf exist, is it owned by root, does it contain the expected content?), ports (is port 80 listening?), users, groups, commands and more.

Analogy🏏Cricket
🏏 Think of it like cricket: Terraform Cloud is the ICC's centralised match management platform — instead of each national board (team) maintaining its own scoring system, umpire assignment software and results database (self-managed CI/CD + S3 backend), the ICC platform handles all of this centrally. When a board member proposes a rule change (pull request), the platform automatically simulates the match under the new rules (speculative plan on PR), shows the referees the impact (plan output in PR comment), and requires the match committee to approve (policy gates) before the rule takes effect. The audit log records every change, every approval, and who made each decision — providing the governance and traceability that serious tournament operations require.
yaml
# molecule/default/molecule.yml — Molecule scenario configuration
---
dependency:
  name: galaxy
  options:
    requirements-file: requirements.yml

driver:
  name: docker

platforms:
  - name: cricket-test-ubuntu
    image: geerlingguy/docker-ubuntu2204-ansible  # Ubuntu with systemd support
    pre_build_image: true
    privileged: true           # Required for systemd in Docker
    volumes:
      - /sys/fs/cgroup:/sys/fs/cgroup:ro
    command: /lib/systemd/systemd  # Run systemd as init
    groups:
      - cricket_api              # Group membership for inventory

  - name: cricket-test-al2023
    image: geerlingguy/docker-amazonlinux2-ansible
    pre_build_image: true
    privileged: true
    command: /sbin/init
    groups:
      - cricket_api

provisioner:
  name: ansible
  config_options:
    defaults:
      stdout_callback: yaml
  playbooks:
    converge: converge.yml    # Playbook that applies the role
    verify: verify.yml         # Playbook that runs testinfra
    prepare: prepare.yml       # Optional pre-role setup

verifier:
  name: ansible               # Can also use: testinfra, goss

lint: |                       # Run linting before test
  set -e
  ansible-lint
yaml
# molecule/default/converge.yml — Apply the role under test
---
- name: Converge
  hosts: all
  become: true
  roles:
    - role: cricket_nginx
      vars:
        # Test-specific overrides
        nginx_upstream_port: 8080
        nginx_ssl_enabled: false  # Skip SSL for container test
        nginx_server_name: localhost

---
# molecule/default/verify.yml — Testinfra-style assertions via Ansible
- name: Verify
  hosts: all
  become: true
  tasks:
    - name: Gather service facts
      ansible.builtin.service_facts:

    - name: Assert Nginx is installed
      ansible.builtin.assert:
        that:
          - ansible_facts.packages['nginx'] is defined
        fail_msg: 'Nginx package must be installed'
        success_msg: 'Nginx is installed'

    - name: Assert Nginx service is running and enabled
      ansible.builtin.assert:
        that:
          - ansible_facts.services['nginx.service'].state == 'running'
          - ansible_facts.services['nginx.service'].status == 'enabled'
        fail_msg: 'Nginx must be running and enabled'

    - name: Assert Nginx configuration file exists
      ansible.builtin.stat:
        path: /etc/nginx/nginx.conf
      register: nginx_conf

    - name: Verify nginx.conf attributes
      ansible.builtin.assert:
        that:
          - nginx_conf.stat.exists
          - nginx_conf.stat.mode == '0644'
          - nginx_conf.stat.pw_name == 'root'
        fail_msg: 'nginx.conf must exist with mode 0644 owned by root'

    - name: Verify Nginx configuration is valid
      ansible.builtin.command: nginx -t
      changed_when: false
      register: nginx_test

    - name: Assert Nginx config is syntactically valid
      ansible.builtin.assert:
        that: nginx_test.rc == 0
        fail_msg: 'nginx -t failed: {{ nginx_test.stderr }}'

    - name: Verify Nginx is listening on port 80
      ansible.builtin.wait_for:
        port: 80
        timeout: 10

    - name: Verify health endpoint response
      ansible.builtin.uri:
        url: http://localhost/health
        status_code: [200, 404]  # 404 if upstream not running
        timeout: 10
      register: health_response

    - name: Show health response
      ansible.builtin.debug:
        msg: 'Health endpoint returned: {{ health_response.status }}'
bash
#!/bin/bash
# Molecule lifecycle commands

# Install Molecule and drivers
pip install molecule molecule-plugins[docker] ansible-lint

# ── Full test lifecycle ───────────────────────────────────────────────────────
cd roles/cricket_nginx

# Run complete test sequence: create → prepare → converge → verify → destroy
molecule test

# Individual lifecycle commands for development
molecule create    # Spin up test instances
molecule prepare   # Run prepare.yml (optional pre-role setup)
molecule converge  # Apply the role (converge.yml)
molecule verify    # Run assertions (verify.yml)
molecule destroy   # Tear down test instances

# Login to test container for debugging
molecule login --host cricket-test-ubuntu

# Run idempotency test (converge twice, check no changes on second run)
molecule test --scenario-name default 2>&1 | grep 'Idempotence\|changed'

# Test against specific scenario
molecule test --scenario-name ubuntu    # Run ubuntu scenario only
molecule test --scenario-name amazonlinux

# List test instances
molecule list

# Check molecule configuration
molecule check
Lesson 24 of 33
0% complete