Breadcrumb-Friendly Ansible Role Guidelines
These guidelines define how to write Ansible role tasks so the resulting target system is friendly to humans, LLMs, and AI agents investigating why a file exists or why a setting was changed.
Source repository:
https://github.com/adaptivegears/ansible-role-ubuntu2604
Principle: Breadcrumbs
Every role-created or role-modified text file should contain a clear source breadcrumb linking back to the role source code.
Standard breadcrumb:
Managed by ansible-role-ubuntu2604
Source: https://github.com/adaptivegears/ansible-role-ubuntu2604
Use the comment syntax native to the target file format.
Examples:
# Managed by ansible-role-ubuntu2604
# Source: https://github.com/adaptivegears/ansible-role-ubuntu2604
# Managed by ansible-role-ubuntu2604
# Source: https://github.com/adaptivegears/ansible-role-ubuntu2604
// Managed by ansible-role-ubuntu2604
// Source: https://github.com/adaptivegears/ansible-role-ubuntu2604
Prefer role-owned drop-ins
Prefer creating explicit drop-in files over editing distribution-owned files directly.
Good:
/etc/sysctl.d/99-ubuntu2604.conf
/etc/security/limits.d/60-ubuntu2604-container.conf
/etc/systemd/system.conf.d/60-ubuntu2604-density.conf
/etc/ssh/sshd_config.d/00-ubuntu2604-hardening.conf
Avoid when possible:
/etc/sysctl.conf
/etc/security/limits.conf
/etc/systemd/system.conf
/etc/ssh/sshd_config
Drop-ins are easier for agents to identify, safer for package upgrades, and easier to test.
Manage full files when breadcrumbs matter
If a module creates or edits a file without allowing a header, prefer managing the file explicitly with ansible.builtin.copy or ansible.builtin.template.
Example: prefer this for sysctl persistence:
- name: Kernel | Configure sysctls
become: true
ansible.builtin.copy:
dest: /etc/sysctl.d/99-ubuntu2604.conf
content: |
# Managed by ansible-role-ubuntu2604
# Source: https://github.com/adaptivegears/ansible-role-ubuntu2604
net.ipv4.ip_forward = 1
vm.swappiness = 10
owner: root
group: root
mode: "0644"
Then apply separately:
- name: Kernel | Apply sysctls
become: true
ansible.builtin.command: sysctl --load /etc/sysctl.d/99-ubuntu2604.conf
changed_when: false
Modified existing files
When an existing file must be edited, preserve context and leave a breadcrumb.
For line removals, prefer commenting with source rather than silent deletion when the format allows it.
Example:
- name: Swap | Disable swap entries in fstab
become: true
ansible.builtin.replace:
path: /etc/fstab
regexp: '^(?!#)(.*\sswap\s.*)$'
replace: '# Disabled by ansible-role-ubuntu2604 (https://github.com/adaptivegears/ansible-role-ubuntu2604): \1'
backup: true
Result:
# Disabled by ansible-role-ubuntu2604 (https://github.com/adaptivegears/ansible-role-ubuntu2604): /swap.img none swap sw 0 0
File-format-specific breadcrumbs
systemd units and drop-ins
Use comments plus native Documentation= when appropriate.
# Managed by ansible-role-ubuntu2604
# Source: https://github.com/adaptivegears/ansible-role-ubuntu2604
[Unit]
Description=Set Transparent Hugepages to madvise
Documentation=https://github.com/adaptivegears/ansible-role-ubuntu2604
APT conf files
Use // comments:
// Managed by ansible-role-ubuntu2604
// Source: https://github.com/adaptivegears/ansible-role-ubuntu2604
APT::Periodic::Unattended-Upgrade "1";
Deb822 APT sources
Use # comments:
# Managed by ansible-role-ubuntu2604
# Source: https://github.com/adaptivegears/ansible-role-ubuntu2604
Types: deb
URIs: mirror+file:/etc/apt/ubuntu-mirrors.txt
shell-style env files
# Managed by ansible-role-ubuntu2604
# Source: https://github.com/adaptivegears/ansible-role-ubuntu2604
LANG=en_US.UTF-8
Non-commentable changes
Some changes can't safely contain breadcrumbs, for example:
- Symlinks
- Service enable/disable state
- Masked units
- File permissions
- Empty files such as
/etc/motd
Don't force breadcrumbs into formats where they don't belong.
Instead:
- Use clear task names
- Prefer role-specific file names where possible
- Cover behavior with tests
- Avoid central manifests unless explicitly requested
Testing requirement
Every role-owned text file should have a test asserting that it contains the source URL.
Example:
ROLE_SOURCE_URL = "https://github.com/adaptivegears/ansible-role-ubuntu2604"
EXPECTED_BREADCRUMBED_FILES = (
"/etc/sysctl.d/99-ubuntu2604.conf",
"/etc/security/limits.d/60-ubuntu2604-container.conf",
"/etc/systemd/system.conf.d/60-ubuntu2604-density.conf",
)
@pytest.mark.parametrize("path", EXPECTED_BREADCRUMBED_FILES)
def test_role_owned_text_file_has_source_breadcrumb(host, path):
config = host.file(path)
assert config.exists
assert config.contains(grep_literal(ROLE_SOURCE_URL))
Definition of done
A task that creates or modifies target configuration is complete only when:
- It uses a role-owned drop-in where practical.
- The resulting text file contains the source breadcrumb.
- Existing files are modified with context-preserving comments where practical.
- Non-commentable changes aren't forced into awkward formats.
- Testinfra verifies breadcrumb presence for role-owned text files.
make checkpasses.