AnsibleXXVII · Collections, Galaxy and Dependency TrustCollections and dependency trust
Collections, namespaces and the FQCN
What you'll learn
- Describe what a collection contains and where its parts live on disk
- Read a fully qualified collection name and name each of its three segments
- Predict which module runs when two collections ship the same short name
- Explain why a repository that uses FQCN everywhere is auditable and one that does not is not
Prerequisites
Verified against ansible-core 2.21.x · ansible (community package) 14.x · Python (controller) 3.12+ · ansible-lint 26.x · Molecule 26.x · Ubuntu 24.04 LTS · Debian 12 (Bookworm) · RHEL / Rocky / AlmaLinux 9.x · 2026-08-11
Part VIII established that a bare module name is a search and a fully
qualified one is an address. That lesson stopped at the boundary of
ansible.builtin, because everything it needed was already installed.
This part starts where the code stops being yours. A collection is somebody else’s Python, downloaded over the internet, executed by your controller, and — for most useful collections — executed as root on every host in the play. The naming rules below are not bureaucracy. They are how you keep track of whose code you are running.
What a collection actually is
A collection is a directory tree with a manifest. Nothing more exotic than that. Here is one, freshly scaffolded, before anybody has put anything in it:
$ ansible-galaxy collection init example_corp.platform --init-path ./coll && find ./coll -maxdepth 4 | sort- Collection example_corp.platform was created successfully
./coll
./coll/example_corp
./coll/example_corp/platform
./coll/example_corp/platform/docs
./coll/example_corp/platform/galaxy.yml
./coll/example_corp/platform/meta
./coll/example_corp/platform/meta/runtime.yml
./coll/example_corp/platform/plugins
./coll/example_corp/platform/plugins/README.md
./coll/example_corp/platform/README.md
./coll/example_corp/platform/rolesRead the path from the bottom up and the naming rule falls out of it:
example_corp/— the namespace. An organisation, a vendor, a community working group.ansible,community,redhat,amazon,cisco, and — once you build one — yours.platform/— the collection name within that namespace.plugins/— the content. Subdirectories per plugin type:modules/,filter/,lookup/,inventory/,callback/, and the rest.roles/sits alongside, because a collection can ship roles too.galaxy.yml— the manifest: version, licence, dependencies, author, repository URL. This is the file you will learn to read suspiciously in lesson 5.meta/runtime.yml— compatibility metadata, most usefullyrequires_ansible, and the redirect table that keeps old names working after a rename.
A fully qualified collection name — an FQCN — is those first two directory components plus the plugin’s own name:
example_corp . platform . to_cidr
└─ namespace ┘ └─ name ─┘ └ plugin ┘
ansible.builtin.copy follows the same rule. So does
community.docker.docker_container, amazon.aws.ec2_instance and
ansible.posix.mount. Three segments, always, and the first two are a
directory path on your controller.
The demonstration: two collections, one short name
The rule that matters is what happens when two collections both ship a
module called report. This is not hypothetical — short names like
instance, network, volume, secret and user collide across
cloud, container and platform collections constantly.
Two collections in a project-local collections/ directory, each with a
report module that reports only which collection it came from:
#!/usr/bin/python
from ansible.module_utils.basic import AnsibleModule
def main():
module = AnsibleModule(argument_spec={})
module.exit_json(changed=False, source="example_corp.tools")
if __name__ == '__main__':
main()other_corp/tools/plugins/modules/report.py is identical apart from the
string. Now a play that lists both namespaces and calls the short name:
- name: Which report wins
hosts: localhost
gather_facts: false
collections:
- example_corp.tools
- other_corp.tools
tasks:
- name: Short name
report:
register: r
- ansible.builtin.debug:
msg: "short name resolved to {{ r.source }}"
- name: Fully qualified
other_corp.tools.report:
register: f
- ansible.builtin.debug:
msg: "fqcn resolved to {{ f.source }}"$ ansible-playbook -i localhost, -c local collide.ymlTASK [Short name] **************************************************************
ok: [localhost]
TASK [ansible.builtin.debug] ***************************************************
ok: [localhost] => {
"msg": "short name resolved to example_corp.tools"
}
TASK [Fully qualified] *********************************************************
ok: [localhost]
TASK [ansible.builtin.debug] ***************************************************
ok: [localhost] => {
"msg": "fqcn resolved to other_corp.tools"
}Now swap the two lines under collections: — change nothing else, not
one character in any task — and run it again:
$ ansible-playbook -i localhost, -c local collide-swapped.ymlTASK [ansible.builtin.debug] ***************************************************
ok: [localhost] => {
"msg": "short name resolved to other_corp.tools"
}
TASK [ansible.builtin.debug] ***************************************************
ok: [localhost] => {
"msg": "fqcn resolved to other_corp.tools"
}The short-name task now runs somebody else’s module. The fully qualified task is untouched, because there was never a search to influence.
Where FQCN pays: reading a repository you did not write
You inherit a 4,000-line Ansible repository. First question, always: whose code does this run?
If the repository uses fully qualified names, that is a grep:
grep -rhoE '\b[a-z0-9_]+\.[a-z0-9_]+\.[a-z0-9_]+:' roles/ playbooks/ \
| sort -uEvery third-party collection the repository touches falls out of that,
and you can compare it against requirements.yml in about a minute. A
name in the code that is not in requirements.yml is an undeclared
dependency; a name in requirements.yml that is not in the code is
dead weight that still gets installed and still gets executed by
whatever else pulls it in.
If the repository uses short names, there is no equivalent search. You have to resolve every name against the controller’s installed set, and the answer is only true for that controller on that day.
Knowledge check
Knowledge check · 4 questions
Q1. A play lists two collections under collections:, both of which ship a module named volume. A colleague sorts the list alphabetically in a tidy-up commit. What is the risk?
Q2. What does the namespace segment of community.docker.docker_container tell you about the collection?
Q3. You inherit a repository and need to know which third-party collections it executes. Which of these help? Select all that apply.
Q4. A collection that renames a module can keep existing playbooks working by adding a redirect under plugin_routing in meta/runtime.yml.
Passing score: 75%. Answers are checked in this browser.