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.

Étape 6 — Ajouter un playbook d’orchestration

Section intitulée « Étape 6 — Ajouter un playbook d’orchestration »

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 tracing. Aujourd'hui, ce site ne couvre même pas mes frais d'hébergement, d'électricité, de matériel, de logiciels, mais surtout de cafés.

Un soutien régulier, même symbolique, m'aide à garder ces ressources gratuites et à continuer de produire des guides de qualité. Merci pour votre appui.

Abonnez-vous et suivez mon actualité DevSecOps sur LinkedIn