Skip to content

Ansible native inventory

Note

This example demonstrates the use of an ansible-native configuration.

This example demonstrates how to use native Ansible inventory sources with Molecule instead of relying on platform-based inventory generation.

Overview

Traditional Molecule scenarios define infrastructure in the platforms section and let Molecule generate inventory. This example shows how to use existing Ansible inventory files and plugins directly, providing more flexibility and better integration with existing infrastructure management.

Configuration structure

molecule.yml
---
ansible:
  cfg:
    defaults:
      deprecation_warnings: false
  executor:
    backend: ansible-playbook
    args:
      ansible_playbook:
        - --inventory=${MOLECULE_SCENARIO_DIRECTORY}/inventory/

dependency:
  name: galaxy
  options:
    requirements-file: requirements.yml

scenario:
  test_sequence:
    - dependency
    - destroy
    - create
    - converge
    - verify
    - destroy

Key points: The ansible_args configuration points to the inventory directory using ${MOLECULE_SCENARIO_DIRECTORY} environment variable. The inventory here is scenario specific but could be adjacent to the scenario directories if shared between them. These args are provided to all actions (create, converge, verify, destroy) when using user-provided playbooks, unless MOLECULE_ANSIBLE_ARGS_STRICT_MODE is set to revert to legacy behavior (where ansible_args were excluded from create/destroy actions for safety). The default driver indicates ansible is being used and the platform placeholder is not used but satisfies the schema requirements.

requirements.yml
---
collections:
  - name: containers.podman

Key points: Standard collection requirements for Podman container management.

Inventory structure

inventory/01-inventory.yml
all:
  hosts:
    container-devtools:
      container_image: ghcr.io/ansible/community-ansible-dev-tools:latest

Key points: Static YAML inventory defining the base host container-devtools with its container image. The 01- prefix ensures this loads before the constructed plugin.

inventory/02-constructed.yml
plugin: ansible.builtin.constructed
strict: false

groups:
  # Create containers group for hosts starting with 'container'
  containers: inventory_hostname.startswith('container')

Key points: Constructed plugin that creates the containers group dynamically based on hostname pattern. The 02- prefix ensures this runs after the static inventory is loaded.

inventory/group_vars/containers.yml
ansible_connection: containers.podman.podman

Key points: Sets the Ansible connection type for all hosts in the containers group. This applies to hosts dynamically added by the constructed plugin.

inventory/host_vars/container-devtools.yml
container_env:
  MOLECULE_TEST_CONTAINER: "true"
  TEST_HOST_VAR: "from_host_vars"

Key points: Host-specific environment variables that will be passed to the container. These demonstrate how inventory variables flow into container configuration.

Playbook structure

Provision resources

create.yml
---
- name: Create containers from inventory
  hosts: localhost
  gather_facts: false
  tasks:
    - name: Validate containers group exists and has hosts
      ansible.builtin.assert:
        that:
          - groups['containers'] | length > 0
        fail_msg: "No hosts found in containers group - constructed plugin may not be working"

    - name: Create containers for container hosts
      containers.podman.podman_container:
        name: "{{ item }}"
        image: "{{ hostvars[item]['container_image'] }}"
        state: started
        command: sleep 1d
        log_driver: json-file
        env: "{{ hostvars[item]['container_env'] | default(omit) }}"
      register: result
      loop: "{{ groups['containers'] }}"
      loop_control:
        label: "{{ item }}"

    - name: Display container log and fail
      ansible.builtin.fail:
        msg: |
          Container failed to start. Logs:
          {{ container_logs | default('Unable to retrieve logs') }}
      vars:
        container_logs: "{{ lookup('pipe', 'podman logs ' + item.container.Name | quote, errors='ignore') }}"
        exit_code: "{{ item.container.State.ExitCode }}"
        running: "{{ item.container.State.Running }}"
      when: (exit_code | int) != 0 or not (running | bool)
      loop: "{{ result.results }}"
      loop_control:
        label: "{{ item.container.Name }}"

Key points: Validates that the containers group exists (proving the constructed plugin worked), then creates containers by looping over the dynamic group. Uses hostvars to access inventory variables for container configuration.

Apply configuration changes

converge.yml
---
- name: Converge containers
  hosts: containers # Uses the constructed group
  gather_facts: false
  tasks:
    - name: Create test file
      ansible.builtin.copy:
        content: "Hello from {{ inventory_hostname }} in containers group"
        dest: /tmp/converge.txt
        mode: "0644"

Key points: Targets the containers group created by the constructed plugin. Demonstrates how playbooks can use dynamic groups from native inventory.

Verify configuration changes

verify.yml
---
- name: Verify native inventory sources
  hosts: containers
  gather_facts: true
  tasks:
    - name: Validate environment variable from host_vars
      ansible.builtin.assert:
        that:
          - ansible_env.TEST_HOST_VAR == "from_host_vars"
        fail_msg: "Environment variable from host_vars not set correctly"

    - name: Validate Molecule environment variable
      ansible.builtin.assert:
        that:
          - ansible_env.MOLECULE_TEST_CONTAINER == "true"
        fail_msg: "MOLECULE_TEST_CONTAINER environment variable not set"

    - name: Read test file
      ansible.builtin.slurp:
        src: /tmp/converge.txt
      register: file

    - name: Validate file content
      ansible.builtin.assert:
        that:
          - (file.content | b64decode | trim) is search("containers group")
        fail_msg: "File content validation failed"

Resource

Key points: Uses gather_facts: true to access ansible_env for validating environment variables set from host_vars. Tests the complete inventory → container → verification flow.

Deprovision resources

destroy.yml
---
- name: Destroy containers
  hosts: localhost
  gather_facts: false
  tasks:
    - name: Remove containers
      containers.podman.podman_container:
        name: "{{ item }}"
        state: absent
      loop: "{{ groups['containers'] | default([]) }}"
      loop_control:
        label: "{{ item }}"
      ignore_errors: true

Key points: Loop over group members to destroy instances.

Why Use Native Ansible Inventory?

1. Leverage Existing Infrastructure

  • Use existing inventory files, scripts, and plugins
  • No need to duplicate host definitions in molecule.yml
  • Seamless integration with production inventory

2. Dynamic Inventory Support

  • Use any Ansible inventory plugin (cloud providers, CMDB, etc.)
  • Combine static and dynamic sources
  • Real-time infrastructure discovery

3. Advanced Inventory Features

  • Constructed plugin for dynamic grouping
  • Complex variable hierarchies with group_vars/host_vars
  • Inventory composition and merging

4. Consistency

  • Same inventory format as production
  • Test with actual inventory structure
  • Reduce configuration drift

Inventory File Naming Convention

Ansible loads inventory sources alphabetically, so naming is important:

inventory/
├── 01-inventory.yml    # Loaded first (static hosts)
├── 02-constructed.yml  # Loaded second (dynamic grouping)
├── group_vars/
└── host_vars/

Why this order matters:

  1. 01-inventory.yml defines the base hosts and variables
  2. 02-constructed.yml processes those hosts to create dynamic groups
  3. The numeric prefixes ensure correct load order - constructed plugin needs existing hosts to process

Key Configuration

The critical configuration is the ansible_args that points Ansible to the inventory directory:

provisioner:
  name: ansible
  ansible_args:
    - --inventory=${MOLECULE_SCENARIO_DIRECTORY}/inventory/

This allows Ansible to automatically discover and load all inventory sources in the directory.

Benefits Demonstrated

  1. Multiple Inventory Sources: Static file + constructed plugin
  2. Environment Variables: From host_vars applied to containers
  3. Dynamic Grouping: Constructed plugin creates containers group
  4. Variable Hierarchy: group_vars and host_vars working together
  5. Real Ansible Patterns: Standard inventory structure and conventions

Best Practices

  1. Use absolute paths with ${MOLECULE_SCENARIO_DIRECTORY} for inventory
  2. Name inventory files carefully to control load order
  3. Leverage group_vars/host_vars for clean variable organization
  4. Test dynamic groups in create playbooks before using them
  5. Document inventory conventions for team consistency

This approach provides much more flexibility than platform-based inventory while maintaining compatibility with existing Ansible infrastructure and patterns.