Skip to main content
RunBook Academy

← All checklists in Ansible

Before deploymentDeployment

Checklist: Role readiness before merge

18 items ·12 critical ·6 warn ·0 info

Run this on the pull request, before the role is merged into the branch that production runs from. It is a code review aid: most of it is a static read of roles/, and the reviewer runs it from a checkout of the branch under review.

The commands are written against a role directory named webapp and a repository whose roles live in roles/. Substitute your own names. Several checks loop over roles/*/ and will therefore report on every role in the repository, not only the one under review - filter the output to the role you are reviewing, or the pre-existing findings will drown the new ones.

What a failure means

The critical items are the ones that make a role behave differently for its next caller than for its author. A role with an unnamespaced handler, an undeclared variable or an assumed sibling works perfectly in the repository it was written in and breaks the first time somebody reuses it - which is the whole reason the role exists.

Access this needs

Everything except the two Molecule items is a static read and needs only a checkout. Read them before you run anything.

Where the evidence goes

The Molecule run output - converge, idempotence and verify - is the evidence that belongs on the pull request. So does the platform list, because “tested on every supported platform” is a claim about a set, and a reviewer cannot check a set they cannot see.

Sign-off

  • Author: _________________ Date: ___________
  • Reviewer: ________________ Date: ___________

Critical12 items

  1. for r in roles/*/; do test -f "$r/meta/argument_specs.yml" || echo "MISSING argument_specs: $r"; done
  2. python3 - <<'PY'
    import glob, os, re, yaml
    for role in sorted(glob.glob('roles/*/')):
        name = os.path.basename(role.rstrip('/'))
        known = set()
        for f in (role + 'defaults/main.yml', role + 'vars/main.yml'):
            if os.path.exists(f):
                known |= set((yaml.safe_load(open(f)) or {}).keys())
        used = set()
        for f in glob.glob(role + '**/*', recursive=True):
            if os.path.isdir(f): continue
            used |= set(re.findall(r'\{\{\s*([a-z][a-z0-9_]*)', open(f, errors='ignore').read()))
        for v in sorted(used - known):
            if v.startswith(('ansible_', 'item', 'inventory_', 'group_', 'hostvars', 'lookup', 'play_')):
                continue
            print(f"UNDOCUMENTED: {name}: uses {v!r} but neither defaults/ nor vars/ defines it")
    PY
  3. for h in roles/*/handlers/main.yml; do
      r=$(basename "$(dirname "$(dirname "$h")")")
      python3 -c "
    import sys, yaml
    role, path = sys.argv[1], sys.argv[2]
    for t in (yaml.safe_load(open(path)) or []):
        n = (t or {}).get('name', '')
        if not n.startswith(role):
            print(f'FINDING: {path}: handler {n!r} is not namespaced with {role!r}')
    " "$r" "$h"
    done
  4. cat roles/*/meta/main.yml 2>/dev/null; grep -rn 'include_role\|import_role' roles/
  5. ansible-lint --nocolor -q --profile production roles/webapp; echo "exit=$?"
  6. grep -rInE '(pass|password|passwd|secret|token|api_key|private_key)[a-z_]*:[[:space:]]*[^[:space:]{]' roles/*/defaults/ roles/*/vars/ 2>/dev/null
  7. ansible-lint --nocolor -q --profile production roles/webapp 2>/dev/null | grep -A1 'no-changed-when'
  8. for r in roles/*/; do test -f "$r/molecule/default/molecule.yml" || echo "MISSING molecule scenario: $r"; done
  9. molecule test -s default
  10. grep -A10 'platforms:' roles/*/meta/main.yml 2>/dev/null; grep -rn 'image:\|box:' roles/*/molecule/*/molecule.yml 2>/dev/null
  11. ls roles/*/molecule/*/verify.yml 2>/dev/null || echo "FINDING: no verify.yml - the scenario converges but checks nothing"
  12. grep -nE 'version:|src:' requirements.yml

Warning6 items

  1. for d in roles/*/defaults/main.yml; do
      r=$(basename "$(dirname "$(dirname "$d")")")
      python3 -c "
    import sys, yaml
    role, path = sys.argv[1], sys.argv[2]
    for k in (yaml.safe_load(open(path)) or {}):
        if not k.startswith(role.replace('-', '_') + '_'):
            print(f'FINDING: {path}: default {k!r} is not prefixed with {role!r}')
    " "$r" "$d"
    done
  2. ls -la roles/*/vars/main.yml 2>/dev/null && cat roles/*/vars/main.yml
  3. grep -rn 'ansible.builtin.assert' roles/*/tasks/
  4. grep -rn 'become' roles/*/tasks/ roles/*/defaults/ roles/*/meta/
  5. for r in roles/*/; do test -s "$r/README.md" || echo "MISSING or empty README: $r"; done