home / skills / basher83 / lunar-claude / ansible-testing

This skill helps you enforce ansible-lint rules, validate playbooks, and run integration tests to improve Ansible automation quality.

npx playbooks add skill basher83/lunar-claude --skill ansible-testing

Review the files below or copy the command above to add this skill to your agents.

Files (2)
SKILL.md
7.1 KB
---
name: ansible-testing
description: >
  This skill should be used when running ansible-lint, configuring linting rules,
  testing Ansible playbooks, validating playbook syntax, or setting up integration
  tests. Covers ansible-lint configuration and testing strategies.
---

# Ansible Testing

Testing strategies and ansible-lint configuration for Ansible automation.

## ansible-lint

### Running Lint

```bash
# Via mise (recommended)
mise run ansible-lint

# Directly with uv
uv run ansible-lint ansible/playbooks/

# Specific file
uv run ansible-lint ansible/playbooks/my-playbook.yml

# With verbose output
uv run ansible-lint -v ansible/
```

### Configuration File

Located at `ansible/.ansible-lint`:

```yaml
---
# Profile: null, min, basic, moderate, safety, shared, production
profile: moderate

# Offline mode - don't download Galaxy requirements
offline: true

# Exclude paths
exclude_paths:
  - .cache/
  - .venv/
  - .git/
  - "*/templates/"
  - "*.j2"
  - .deprecated/

# Rules to skip completely
skip_list:
  - var-naming[no-role-prefix]  # We use descriptive names
  - run-once[task]              # Safe with our strategy
  - command-instead-of-module   # CLI tools require command
  - yaml[line-length]           # Long lines in infra configs

# Rules to warn but not fail
warn_list:
  - fqcn[action-core]
  - fqcn[action]
  - no-handler
  - name[play]
```

### Common Rule Categories

| Category | Description |
|----------|-------------|
| `fqcn` | Fully qualified collection names |
| `yaml` | YAML formatting (indentation, line length) |
| `name` | Task/play naming conventions |
| `command-instead-of-module` | Using command when module exists |
| `no-changed-when` | Missing changed_when on command |
| `risky-file-permissions` | Missing explicit file permissions |

### Fixing Common Issues

**Missing name on task:**

```yaml
# BAD
- ansible.builtin.apt:
    name: nginx

# GOOD
- name: Install nginx
  ansible.builtin.apt:
    name: nginx
```

**Short module name:**

```yaml
# BAD (triggers fqcn warning)
- name: Install package
  apt:
    name: nginx

# GOOD
- name: Install package
  ansible.builtin.apt:
    name: nginx
```

**Using shell instead of command:**

```yaml
# BAD (when no shell features needed)
- name: List files
  ansible.builtin.shell: ls -la /tmp

# GOOD
- name: List files
  ansible.builtin.command: ls -la /tmp
  changed_when: false
```

**Missing changed_when:**

```yaml
# BAD (always shows changed)
- name: Check status
  ansible.builtin.command: systemctl status app

# GOOD
- name: Check status
  ansible.builtin.command: systemctl status app
  register: status_check
  changed_when: false
  failed_when: false
```

## Syntax Checking

Validate playbook syntax before running:

```bash
# Check syntax only
uv run ansible-playbook --syntax-check playbooks/my-playbook.yml

# Check mode (dry run)
uv run ansible-playbook playbooks/my-playbook.yml --check

# Diff mode (show changes)
uv run ansible-playbook playbooks/my-playbook.yml --check --diff
```

## Idempotency Testing

Verify playbooks are idempotent by running twice:

```bash
# First run - may show changes
uv run ansible-playbook playbooks/setup.yml

# Second run - should show 0 changes
uv run ansible-playbook playbooks/setup.yml

# If second run shows changes, playbook is NOT idempotent
```

### Automated Idempotency Check

```bash
#!/bin/bash
set -euo pipefail

PLAYBOOK="$1"

echo "First run..."
uv run ansible-playbook "$PLAYBOOK"

echo "Second run (checking idempotency)..."
OUTPUT=$(uv run ansible-playbook "$PLAYBOOK" 2>&1)

if echo "$OUTPUT" | grep -q "changed=0"; then
    echo "✓ Playbook is idempotent"
    exit 0
else
    echo "✗ Playbook is NOT idempotent"
    echo "$OUTPUT" | grep -E "(changed|failed)="
    exit 1
fi
```

## Integration Testing

### Test Against Real Infrastructure

```bash
# Limit to test hosts
uv run ansible-playbook playbooks/deploy.yml --limit test_hosts

# With verbose output
uv run ansible-playbook playbooks/deploy.yml --limit test_hosts -vv
```

### Pre-flight Validation

Add validation tasks at playbook start:

```yaml
---
- name: Deploy with validation
  hosts: all
  become: true

  pre_tasks:
    - name: Validate target environment
      ansible.builtin.assert:
        that:
          - ansible_distribution == "Debian"
          - ansible_distribution_major_version | int >= 11
        fail_msg: "Requires Debian 11+"

    - name: Check connectivity
      ansible.builtin.ping:

    - name: Verify disk space
      ansible.builtin.assert:
        that:
          - ansible_mounts | selectattr('mount', 'equalto', '/') | map(attribute='size_available') | first > 1073741824
        fail_msg: "Insufficient disk space"
```

## Test Playbook Pattern

Create test playbooks for validation:

```yaml
# playbooks/test-role.yml
---
- name: Test role functionality
  hosts: test_hosts
  become: true

  vars:
    test_mode: true

  roles:
    - role: my_role

  tasks:
    - name: Verify service is running
      ansible.builtin.systemd:
        name: myservice
      register: service_status
      failed_when: service_status.status.ActiveState != "active"

    - name: Verify config file exists
      ansible.builtin.stat:
        path: /etc/myservice/config.yml
      register: config_stat
      failed_when: not config_stat.stat.exists

    - name: Verify port is listening
      ansible.builtin.wait_for:
        port: 8080
        timeout: 10
```

## CI/CD Integration

### GitHub Actions

```yaml
name: Ansible Lint

on: [push, pull_request]

jobs:
  lint:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4

      - name: Set up Python
        uses: actions/setup-python@v5
        with:
          python-version: '3.13'

      - name: Install uv
        run: pip install uv

      - name: Install dependencies
        run: uv sync

      - name: Run ansible-lint
        run: uv run ansible-lint ansible/
```

## Debugging Playbooks

### Verbose Output

```bash
# Increase verbosity
uv run ansible-playbook playbook.yml -v    # Basic
uv run ansible-playbook playbook.yml -vv   # More detail
uv run ansible-playbook playbook.yml -vvv  # Connection debugging
uv run ansible-playbook playbook.yml -vvvv # Maximum detail
```

### Debug Tasks

```yaml
- name: Debug variable value
  ansible.builtin.debug:
    var: my_variable

- name: Debug with message
  ansible.builtin.debug:
    msg: "The value is {{ my_variable }}"

- name: Debug registered result
  ansible.builtin.debug:
    var: command_result
  when: ansible_verbosity > 0
```

### Step Mode

```bash
# Pause after each task
uv run ansible-playbook playbook.yml --step
```

## Lint Profiles

Choose appropriate profile based on needs:

| Profile | Strictness | Use Case |
|---------|------------|----------|
| `min` | Lowest | Legacy code, quick fixes |
| `basic` | Low | Development |
| `moderate` | Medium | General infrastructure |
| `safety` | High | Security-sensitive |
| `production` | Highest | Production deployments |

## Additional Resources

For detailed testing patterns and techniques, consult:

- **`references/testing-comprehensive.md`** - ansible-lint configuration, integration testing strategies, CI/CD patterns

## Related Skills

- **ansible-fundamentals** - Core Ansible patterns
- **ansible-idempotency** - Ensuring tasks are idempotent

Overview

This skill helps run ansible-lint, validate playbook syntax, and design automated tests for Ansible playbooks and roles. It bundles recommended ansible-lint configuration, idempotency checks, pre-flight validation patterns, and CI integration examples. Use it to enforce standards, catch common mistakes, and automate testing in CI pipelines.

How this skill works

It inspects playbooks and roles with ansible-lint using a configurable profile and skip/warn lists to match project conventions. It provides syntax-check and dry-run commands, scripts to verify idempotency by comparing two runs, and test-playbook patterns for integration checks against test hosts. It includes CI examples to run lint and tests automatically on push or pull requests.

When to use it

  • Before merging changes to playbooks or roles to enforce lint rules
  • When adding or changing infrastructure code to validate syntax and idempotency
  • During CI to block regressions with automated lint and test runs
  • Before running against production—use pre-flight validations on target hosts
  • When configuring team-wide ansible-lint policies and skip/warn lists

Best practices

  • Use a shared ansible-lint config (profile, exclude_paths, skip_list, warn_list) to standardize rules
  • Run syntax-check and --check/--diff before applying changes to hosts
  • Automate idempotency checks by running playbooks twice and failing if second run shows changes
  • Add pre_tasks for environment validation (OS version, connectivity, disk space) to fail fast
  • Limit integration tests to dedicated test hosts and include assert checks for service state and files

Example use cases

  • Run ansible-lint in CI: install deps, sync with uv, and run lint on the ansible/ directory
  • Validate a new role by executing a test playbook against test_hosts and verify service, config, and listening ports
  • Verify idempotency with a script: run playbook twice and fail if changed!=0 on second run
  • Add pre-flight assert tasks to deployment playbooks to ensure target OS and resources meet requirements
  • Debug playbooks locally using increasing verbosity (-v to -vvvv) and step mode to pause after each task

FAQ

What does the ansible-lint profile setting do?

The profile selects a rule set (min, basic, moderate, safety, production). Choose moderate for general infra, safety or production for stricter security and production checks.

How do I check idempotency automatically in CI?

Run the playbook twice in the job: the first run applies changes, the second should report changed=0. Fail the job if the second run reports changes or failures.