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¶
---
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.
Key points: Standard collection requirements for Podman container management.
Inventory structure¶
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.
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.
Key points: Sets the Ansible connection type for all hosts in the containers group. This applies to hosts dynamically added by the constructed plugin.
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¶
---
- 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¶
---
- 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¶
---
- 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¶
---
- 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:
01-inventory.ymldefines the base hosts and variables02-constructed.ymlprocesses those hosts to create dynamic groups- 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:
This allows Ansible to automatically discover and load all inventory sources in the directory.
Benefits Demonstrated¶
- Multiple Inventory Sources: Static file + constructed plugin
- Environment Variables: From host_vars applied to containers
- Dynamic Grouping: Constructed plugin creates
containersgroup - Variable Hierarchy: group_vars and host_vars working together
- Real Ansible Patterns: Standard inventory structure and conventions
Best Practices¶
- Use absolute paths with
${MOLECULE_SCENARIO_DIRECTORY}for inventory - Name inventory files carefully to control load order
- Leverage group_vars/host_vars for clean variable organization
- Test dynamic groups in create playbooks before using them
- 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.