Automate Code Quality: ansible-lint, yaml-lint, and CI/CD Integration
Code quality is crucial in network automation and DevOps practices. When working with Ansible playbooks and YAML files, automated linting helps catch errors early, enforce best practices, and maintain consistent code standards. In this comprehensive guide, we'll explore how to use ansible-lint
and yaml-lint
, and integrate them into your CI/CD pipelines for automated code quality checks.
What is Linting?
Linting is a static code analysis tool that checks your code for potential errors, style violations, and suspicious constructs. For Ansible and YAML files, linting helps:
- Catch syntax errors before deployment
- Enforce coding standards and best practices
- Improve code readability and maintainability
- Prevent common mistakes that could cause runtime issues
- Ensure consistency across team members
ansible-lint: Ansible Code Quality Tool
ansible-lint is the official linting tool for Ansible playbooks, roles, and collections. It checks your Ansible code against a set of rules and best practices.
Installation
# Install via pip
pip install ansible-lint
# Install via package manager (Ubuntu/Debian)
sudo apt install ansible-lint
# Install via package manager (macOS)
brew install ansible-lint
Basic Usage
# Lint a single playbook
ansible-lint playbook.yml
# Lint an entire directory
ansible-lint .
# Lint with specific rules
ansible-lint --rules=no-tabs,no-jinja-when playbook.yml
# Generate a report
ansible-lint --format=json playbook.yml > lint-report.json
Configuration File (.ansible-lint)
Create a .ansible-lint
file in your project root to customize linting behavior:
---
# Enable/disable specific rules
enable_list:
- no-tabs
- no-jinja-when
- no-handler
- no-changed-when
- no-jinja-nesting
# Disable specific rules
disable_list:
- no-log-password # If you need to log passwords for debugging
# Customize rule severity
warn_list:
- no-tabs
- no-jinja-when
# Set minimum Ansible version
min_ansible_version: "2.10"
# Customize output format
format: rich # Options: rich, json, codeclimate, quiet, parseable
# Exclude files/directories
exclude_paths:
- "tests/"
- "molecule/"
- "*.j2"
# Set custom rules directory
rulesdir: "custom_rules/"
Common ansible-lint Rules
Here are some essential rules to enable:
enable_list:
# Code style
- no-tabs # No tabs in YAML files
- no-jinja-when # Avoid Jinja2 in when conditions
- no-handler # Avoid handlers when possible
- no-changed-when # Always specify changed_when
- no-jinja-nesting # Avoid nested Jinja2 expressions
# Security
- no-log-password # Don't log passwords
- no-command # Avoid raw commands
- no-shell # Avoid shell module
# Best practices
- no-relative-paths # Use absolute paths
- no-risky-file-permissions # Avoid risky file permissions
- no-risky-shell-pipe # Avoid shell pipes
- no-unsafe-reads # Avoid unsafe file reads
yaml-lint: YAML Syntax Validation
yaml-lint is a Python-based linter for YAML files that checks syntax, formatting, and style.
Installation
# Install via pip
pip install yamllint
# Install via package manager (Ubuntu/Debian)
sudo apt install yamllint
# Install via package manager (macOS)
brew install yamllint
Basic Usage
# Lint a single file
yamllint playbook.yml
# Lint entire directory
yamllint .
# Lint with specific configuration
yamllint -c .yamllint playbook.yml
# Generate detailed output
yamllint --format=parsable playbook.yml
Configuration File (.yamllint)
Create a .yamllint
file to customize YAML linting rules:
---
extends: default
rules:
# Line length
line-length:
max: 120
level: warning
# Indentation
indentation:
spaces: 2
indent-sequences: true
# Trailing spaces
trailing-spaces: enable
# Empty lines
empty-lines:
max: 1
max-end: 1
# Comments
comments:
min-spaces-from-content: 1
# Document start
document-start: disable
# Truthy values
truthy:
check-keys: false
# Hyphens
hyphens:
max-spaces-before: 1
max-spaces-after: 1
# Commas
commas:
max-spaces-before: 0
min-spaces-after: 1
# Colons
colons:
max-spaces-before: 0
max-spaces-after: 1
# Braces
braces:
min-spaces-inside: 0
max-spaces-inside: 1
# Brackets
brackets:
min-spaces-inside: 0
max-spaces-inside: 1
GitHub Actions Integration
GitHub Actions provides excellent support for automated linting. Here's a comprehensive workflow:
Complete GitHub Actions Workflow
name: Code Quality Checks
on:
push:
branches: [ main, develop ]
pull_request:
branches: [ main ]
jobs:
lint:
runs-on: ubuntu-latest
strategy:
matrix:
python-version: [3.9, 3.10, 3.11]
steps:
- name: Checkout code
uses: actions/checkout@v4
- name: Set up Python ${{ matrix.python-version }}
uses: actions/setup-python@v5
with:
python-version: ${{ matrix.python-version }}
- name: Cache pip dependencies
uses: actions/cache@v4
with:
path: ~/.cache/pip
key: ${{ runner.os }}-pip-${{ hashFiles('**/requirements.txt') }}
restore-keys: |
${{ runner.os }}-pip-
- name: Install dependencies
run: |
python -m pip install --upgrade pip
pip install ansible ansible-lint yamllint
- name: Run yamllint
run: |
yamllint -c .yamllint .
- name: Run ansible-lint
run: |
ansible-lint --format=rich .
- name: Upload lint results
uses: actions/upload-artifact@v4
if: always()
with:
name: lint-results-${{ matrix.python-version }}
path: |
lint-report.json
yamllint-report.txt
retention-days: 7
Advanced GitHub Actions with Multiple Tools
name: Advanced Code Quality
on:
push:
branches: [ main, develop ]
pull_request:
branches: [ main ]
jobs:
yaml-lint:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-python@v5
with:
python-version: '3.11'
- run: pip install yamllint
- run: yamllint -c .yamllint .
ansible-lint:
runs-on: ubuntu-latest
needs: yaml-lint
steps:
- uses: actions/checkout@v4
- uses: actions/setup-python@v5
with:
python-version: '3.11'
- run: pip install ansible ansible-lint
- run: ansible-lint --format=rich .
security-scan:
runs-on: ubuntu-latest
needs: [yaml-lint, ansible-lint]
steps:
- uses: actions/checkout@v4
- name: Run Bandit security scan
uses: python-security/bandit@main
with:
args: -r . -f json -o bandit-report.json
- name: Upload security report
uses: actions/upload-artifact@v4
with:
name: security-report
path: bandit-report.json
GitLab CI/CD Integration
GitLab CI/CD provides robust pipeline capabilities for linting:
Basic GitLab CI Pipeline
stages:
- lint
- test
- deploy
variables:
PIP_CACHE_DIR: "$CI_PROJECT_DIR/.pip-cache"
cache:
paths:
- .pip-cache/
yamllint:
stage: lint
image: python:3.11-slim
before_script:
- pip install yamllint
script:
- yamllint -c .yamllint .
rules:
- if: $CI_PIPELINE_SOURCE == "merge_request_event"
- if: $CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH
ansible-lint:
stage: lint
image: python:3.11-slim
before_script:
- pip install ansible ansible-lint
script:
- ansible-lint --format=rich .
rules:
- if: $CI_PIPELINE_SOURCE == "merge_request_event"
- if: $CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH
artifacts:
reports:
junit: ansible-lint-report.xml
expire_in: 1 week
Advanced GitLab CI with Parallel Jobs
stages:
- lint
- test
- security
- deploy
variables:
PIP_CACHE_DIR: "$CI_PROJECT_DIR/.pip-cache"
cache:
paths:
- .pip-cache/
.yamllint_template: &yamllint_template
stage: lint
image: python:3.11-slim
before_script:
- pip install yamllint
rules:
- if: $CI_PIPELINE_SOURCE == "merge_request_event"
- if: $CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH
yamllint-playbooks:
<<: *yamllint_template
script:
- yamllint -c .yamllint playbooks/
artifacts:
reports:
junit: yamllint-playbooks.xml
yamllint-roles:
<<: *yamllint_template
script:
- yamllint -c .yamllint roles/
artifacts:
reports:
junit: yamllint-roles.xml
.ansible_lint_template: &ansible_lint_template
stage: lint
image: python:3.11-slim
before_script:
- pip install ansible ansible-lint
rules:
- if: $CI_PIPELINE_SOURCE == "merge_request_event"
- if: $CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH
ansible-lint-playbooks:
<<: *ansible_lint_template
script:
- ansible-lint playbooks/
artifacts:
reports:
junit: ansible-lint-playbooks.xml
ansible-lint-roles:
<<: *ansible_lint_template
script:
- ansible-lint roles/
artifacts:
reports:
junit: ansible-lint-roles.xml
security-scan:
stage: security
image: python:3.11-slim
before_script:
- pip install bandit safety
script:
- bandit -r . -f json -o bandit-report.json
- safety check --json --output safety-report.json
artifacts:
reports:
junit: security-report.xml
paths:
- bandit-report.json
- safety-report.json
expire_in: 1 week
Pre-commit Hooks
Install pre-commit hooks to catch issues before committing:
.pre-commit-config.yaml
repos:
- repo: https://github.com/ansible/ansible-lint
rev: v6.22.1
hooks:
- id: ansible-lint
args: [--format=rich]
- repo: https://github.com/adrienverge/yamllint
rev: v1.35.1
hooks:
- id: yamllint
args: [-c, .yamllint]
- repo: https://github.com/pre-commit/pre-commit-hooks
rev: v4.5.0
hooks:
- id: trailing-whitespace
- id: end-of-file-fixer
- id: check-yaml
- id: check-added-large-files
- id: check-merge-conflict
- id: check-case-conflict
- id: check-docstring-first
- id: check-json
- id: check-merge-conflict
- id: debug-statements
- id: name-tests-test
- id: requirements-txt-fixer
- id: fix-byte-order-marker
Installation and Usage
# Install pre-commit
pip install pre-commit
# Install the git hook scripts
pre-commit install
# Run against all files
pre-commit run --all-files
# Run specific hook
pre-commit run ansible-lint --all-files
Best Practices and Tips
1. Progressive Rule Adoption
Start with essential rules and gradually add more:
# Start with these basic rules
enable_list:
- no-tabs
- no-jinja-when
- no-log-password
# Gradually add more rules
enable_list:
- no-tabs
- no-jinja-when
- no-log-password
- no-command
- no-shell
- no-relative-paths
2. Custom Rules for Your Organization
Create custom rules in a custom_rules/
directory:
# custom_rules/custom_rule.py
from ansiblelint.rules import AnsibleLintRule
class CustomRule(AnsibleLintRule):
id = 'custom-rule'
shortdesc = 'Custom rule description'
description = 'Detailed description of the custom rule'
tags = ['custom']
def match(self, file, line):
# Your custom logic here
return False
3. Integration with IDE
Configure your IDE for real-time linting:
VS Code Settings (.vscode/settings.json):
{
"ansible.ansibleLint.enabled": true,
"ansible.ansibleLint.path": "ansible-lint",
"ansible.ansibleLint.configFile": ".ansible-lint",
"yaml.validate": true,
"yaml.schemas": {
"https://json.schemastore.org/ansible-stable-2.9.json": "**/tasks/*.yml",
"https://json.schemastore.org/ansible-stable-2.9.json": "**/handlers/*.yml"
}
}
4. Performance Optimization
For large projects, optimize linting performance:
# Use parallel processing
ansible-lint --parallel .
# Exclude unnecessary directories
ansible-lint --exclude=test/ --exclude=docs/ .
# Use specific file patterns
ansible-lint "**/*.yml" "**/*.yaml"
5. Reporting and Metrics
Generate detailed reports for analysis:
# Generate JSON report
ansible-lint --format=json . > lint-report.json
# Generate CodeClimate format
ansible-lint --format=codeclimate . > codeclimate.json
# Generate JUnit XML for CI
ansible-lint --format=junit . > ansible-lint.xml
Troubleshooting Common Issues
1. False Positives
Handle false positives by disabling specific rules:
# In .ansible-lint
disable_list:
- no-log-password # If logging is required for debugging
- no-command # If raw commands are necessary
2. Performance Issues
Optimize for large codebases:
# Use caching
ansible-lint --cache .ansible-lint-cache .
# Limit file types
ansible-lint --exclude="*.j2" --exclude="*.md" .
# Use specific directories
ansible-lint playbooks/ roles/
3. Integration Issues
Common CI/CD integration problems:
# GitHub Actions - Handle failures gracefully
- name: Run ansible-lint
run: |
ansible-lint --format=rich . || {
echo "Linting found issues. Check the output above."
exit 1
}
continue-on-error: false
Conclusion
Automated linting with ansible-lint
and yaml-lint
is essential for maintaining code quality in Ansible projects. By integrating these tools into your CI/CD pipelines, you can:
- Catch errors early in the development process
- Enforce consistent coding standards across your team
- Improve code maintainability and readability
- Reduce deployment failures caused by syntax errors
- Build confidence in your automation code
Start with basic linting rules and gradually expand your quality checks. Remember that the goal is to improve code quality, not to create unnecessary friction in your development workflow.
Resources
- ansible-lint Documentation
- yamllint Documentation
- GitHub Actions Documentation
- GitLab CI/CD Documentation
- Pre-commit Framework
For more automation and DevOps content, check out our Ansible tutorials and network automation guides.