Aller au contenu
Infrastructure as Code medium

Créer une collection Ansible custom : ansible-galaxy collection init, module Python, build et publish

11 min de lecture

Logo Ansible

Créer sa propre collection est l'aboutissement du parcours collections : packager plusieurs ressources (modules Python custom + rôles + plugins + playbooks) sous un même namespace versionné, distribuable sur Galaxy ou Automation Hub privé. Cette page construit pas à pas une collection minimaliste avec un module Python qui retourne Hello, valide le tout avec ansible-test sanity --docker default, et produit un tarball publiable.

À la fin, vous saurez initialiser une collection conforme, écrire un module Python avec ses 3 sections obligatoires (DOCUMENTATION, EXAMPLES, RETURN), builder un tarball semver, et exécuter la batterie de validations pré-publication.

  • ansible-galaxy collection init : générer la structure standard.
  • Remplir galaxy.yml : namespace, name, version, dependencies, tags: obligatoires.
  • Remplir meta/runtime.yml : requires_ansible: + plugin_routing:.
  • Écrire un module Python avec DOCUMENTATION + EXAMPLES + RETURN + AnsibleModule.
  • Ajouter un rôle dans roles/<name>/.
  • ansible-galaxy collection build : produire le tarball.
  • ansible-test sanity --docker : validation pré-publication.
Fenêtre de terminal
mkdir -p ansible_collections
ansible-galaxy collection init student.webapp \
--init-path ansible_collections/

Sortie :

- Collection student.webapp was created successfully

Structure générée :

  • Répertoireansible_collections/student/webapp/
    • galaxy.yml
    • README.md
    • LICENSE
    • Répertoiremeta/
      • runtime.yml
    • Répertoireplugins/
      • Répertoiremodules/
      • Répertoirefilter/
      • Répertoirelookup/
      • Répertoireinventory/
      • Répertoiremodule_utils/
    • Répertoireroles/
    • Répertoireplaybooks/
    • Répertoiretests/
    • Répertoirechangelogs/
    • Répertoiredocs/

🔍 Observation : init génère toute la structure conforme d'une collection. Pas besoin de tout remplir : ne garder que ce dont on a besoin (les dossiers vides ne posent pas de problème).

namespace: student
name: webapp
version: 1.0.0
readme: README.md
authors:
- "Apprenant RHCE 2026 <student@example.com>"
description: "Collection lab — déploie webapp avec nginx"
license:
- GPL-3.0-or-later
tags: # ← OBLIGATOIRE pour Galaxy
- linux
- web
- nginx
dependencies:
ansible.posix: ">=2.0.0"
repository: https://github.com/student/webapp
documentation: https://github.com/student/webapp
issues: https://github.com/student/webapp/issues
build_ignore:
- .github
- .venv
- tests/output

🔍 Observation cruciale : tags: est obligatoire pour publier sur Galaxy. Sans, l'import échoue silencieusement avec un message peu explicite. dependencies: déclare les autres collections requises avec contraintes semver.

---
requires_ansible: ">=2.18.0"
# Optionnel : redirects pour les modules renommés (cf migration role)
# plugin_routing:
# modules:
# old_module:
# redirect: student.webapp.new_module

🔍 Observation : requires_ansible: est obligatoire dès qu'on a un module Python. Sans, ansible-test sanity échoue avec ERROR! No requires_ansible declared.

plugins/modules/webapp_healthcheck.py :

#!/usr/bin/python
# -*- coding: utf-8 -*-
from __future__ import absolute_import, division, print_function
__metaclass__ = type
DOCUMENTATION = r'''
---
module: webapp_healthcheck
short_description: Health check minimaliste d'une URL HTTP
version_added: "1.0.0"
description:
- Vérifie qu'une URL HTTP répond avec un code 200.
options:
url:
description: URL à tester.
required: true
type: str
author:
- "Apprenant RHCE 2026"
'''
EXAMPLES = r'''
- name: Vérifier que nginx répond
student.webapp.webapp_healthcheck:
url: "http://localhost:80"
'''
RETURN = r'''
status_code:
description: Code HTTP retourné.
type: int
returned: always
'''
import urllib.request
from ansible.module_utils.basic import AnsibleModule
def run_module():
module_args = dict(url=dict(type='str', required=True))
module = AnsibleModule(argument_spec=module_args, supports_check_mode=True)
url = module.params['url']
try:
with urllib.request.urlopen(url, timeout=5) as response:
code = response.getcode()
module.exit_json(changed=False, status_code=code)
except Exception as exc:
module.fail_json(msg=f"Health check KO: {exc}")
def main():
run_module()
if __name__ == '__main__':
main()

🔍 Observation : tous les modules Python suivent ce squelette. Les 3 sections DOCUMENTATION, EXAMPLES, RETURN sont obligatoires pour ansible-test sanity. AnsibleModule + argument_spec sécurise les paramètres + supporte check_mode.

roles/nginx/tasks/main.yml :

---
- name: Installer nginx
ansible.builtin.dnf:
name: nginx
state: present
- name: Activer et démarrer nginx
ansible.builtin.systemd_service:
name: nginx
state: started
enabled: true

🔍 Observation : un rôle dans une collection suit la même structure qu'un rôle standalone. La différence : on l'utilise avec son FQCN : student.webapp.nginx.

playbooks/deploy.yml :

---
- name: Déployer la webapp avec la collection student.webapp
hosts: webservers
become: true
tasks:
- name: Appliquer le rôle nginx
ansible.builtin.import_role:
name: student.webapp.nginx # FQCN du rôle
- name: Health check via le module custom
student.webapp.webapp_healthcheck: # FQCN du module
url: "http://localhost:80"
register: hc
- ansible.builtin.debug:
var: hc.status_code

🔍 Observation : depuis l'extérieur de la collection, on appelle modules et rôles avec leur FQCN complet. À l'intérieur de la collection, on peut utiliser des noms courts (mais déconseillé pour la lisibilité).

Fenêtre de terminal
cd ansible_collections/student/webapp/
ansible-galaxy collection build --output-path ../../../build/

Sortie :

Created collection for student.webapp at /home/.../build/student-webapp-1.0.0.tar.gz

🔍 Observation : le tarball est publiable avec ansible-galaxy collection publish <tarball> --token "$GALAXY_TOKEN". Versions automatiques via CI : tag Git v1.0.0 → workflow build → publish.

Fenêtre de terminal
cd ansible_collections/student/webapp/
ansible-test sanity --docker default -v

Cible attendue :

Running sanity test "ansible-doc"
Running sanity test "validate-modules"
Running sanity test "yamllint"
Running sanity test "pep8"
...
All sanity tests passed.

🔍 Observation : --docker default lance la batterie de validations dans un conteneur Docker : doc YAML cohérente, types Python, FQCN, PEP8, yamllint. Indispensable en CI avant publication. Sans, l'import Galaxy échoue silencieusement.

Fenêtre de terminal
# Token API depuis https://galaxy.ansible.com/me/preferences
ansible-galaxy collection publish \
../../../build/student-webapp-1.0.0.tar.gz \
--token "$GALAXY_TOKEN"

Workflow d'import sur Galaxy NG (backend Galaxy v3, 2026) :

  1. Upload : tarball reçu par Galaxy.
  2. Unpack : extraction et validation du MANIFEST.json.
  3. Sanity scan : vérification automatique du galaxy.yml (tags, version, namespace).
  4. Tests CI : ansible-test sanity exécuté sur les serveurs Galaxy (Galaxy NG le fait depuis 2024).
  5. Import dans staging : version visible mais pas listée publique.
  6. Approval (manuel ou auto selon namespace) → publication dans published.

Le lab collections/creer-custom (labs/collections/creer-custom/) reproduit ce parcours avec 9 tests pytest structurels (galaxy.yml conforme, module avec sections obligatoires, tarball buildé). Le lab jumeau dans student.rhce.webapp ajoute un rôle nginx et un module webapp_healthcheck complet.

  • ansible-galaxy collection init namespace.name génère la structure conforme.
  • galaxy.yml : namespace, name, version semver, tags: obligatoire, dependencies.
  • meta/runtime.yml : requires_ansible: obligatoire dès qu'on a un module Python.
  • Module Python : DOCUMENTATION + EXAMPLES + RETURN en YAML + AnsibleModule.
  • FQCN depuis l'extérieur : namespace.collection.plugin.
  • ansible-galaxy collection build produit un tarball publiable.
  • ansible-test sanity --docker default valide doc + types + FQCN + PEP8.

Ce site vous est utile ?

Sachez que moins de 1% des lecteurs soutiennent ce site.

Je maintiens +700 guides gratuits, sans pub ni tracking. Un soutien, même symbolique, m'aide à couvrir l'hébergement et à garder ces ressources gratuites. Merci pour votre appui.

Le formulaire ne s'affiche pas ? Ouvrir Ko-fi dans un onglet.

Abonnez-vous et suivez mon actualité DevSecOps sur LinkedIn