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:

  1. It uses a role-owned drop-in where practical.
  2. The resulting text file contains the source breadcrumb.
  3. Existing files are modified with context-preserving comments where practical.
  4. Non-commentable changes aren't forced into awkward formats.
  5. Testinfra verifies breadcrumb presence for role-owned text files.
  6. make check passes.