Publicando Colecciones de Ansible en Galaxy con GitLab

Tabla de contenido
Ansible es una de mis herramientas favoritas para configurar servidores en masa, y a diferencia de Chef o Puppet, Ansible no necesita un agente para poder ejecutar las tareas, solo se necesita tener ssh instalado en los servidores remotos para poder hacer su trabajo.
Una de las maneras más fáciles para poder compartir y descargar Roles y Collections es la plataforma Ansible Galaxy, sin embargo, por alguna razón solo nos permite autenticarnos a través de GitHub. En este articulo te voy a mostrar como publicar tus collections en GitLab de manera automática con GitLab CI.
PROMO DigitalOcean #
Antes de comenzar, quería contarles que hay una promoción en DigitalOcean donde te dan un crédito de USD 100.00 durante 60 días para que puedas probar los servicios que este Proveedor Cloud ofrece. Lo único que tienes que hacer es suscribirte a DigitalOcean con el siguiente botón:
O a través del siguiente enlace: https://bit.ly/digitalocean-itsm
Preparando el Terreno #
En este artículo estoy asumiendo que sabes Ansible y entiendes el concepto de Integración Continua. De todas maneras voy a ser lo más específico posible para que el usuario mas novel pueda hacerlo.
Asumo también que tienes cuentas creadas tanto en GitHub como en GitLab para poder avanzar.
Instalando Ansible en Nuestro Equipo #
Dependiendo de tu distribución, la instalación será un poco diferente. En este caso vamos a instalar Ansible directamente desde pip:
sudo pip3 install ansible
Creando la Cuenta en Ansible Galaxy #
Podemos crear nuestra cuenta en Ansible Galaxy yendo al sitio: https://galaxy.ansible.com, vamos a la esquina superior derecha en la opción Login:
Iniciamos sesión con nuestra cuenta GitHub:
Una vez iniciamos sesión, nos redirigirá de nuevo a la página de Ansible Galaxy, vamos de nuevo a la esquina superior derecha y hacemos click en nuestro nombre de usuario, seguido en Preferences:
Y vamos a ver nuestra API Key, vamos a copiarla y reservarla para configurarla en GitLab.
Creando la Colección #
- Creamos una colección de Ansible en nuestro equipo:
ansible-galaxy collection init usuario.micoleccion
Lo cual creará una colección con la siguiente estructura:
.
├── docs
├── galaxy.yml
├── plugins
│ └── README.md
├── README.md
└── roles
docs: en este directorio almacenamos la documentación de la colección.
plugins: en este directorio se almacenan plugins de Ansible.
roles: en este directorio almacenamos los roles que se ejecutarán en la colección.
galaxy.yml: es el manifiesto de la colección. El cual tiene la siguiente estructura:
### REQUERIDO
# Es el namespace de la colección. Puede ser el nombre de la empresa, persona u organización (normalmente colocamos el nombre de usuario del repositorio donde se almacenará la colección
# Solo puede contener letras en minusculas y guines bajos (_) El Namespace no puede comenzar con
# guiones bajos o números y no puede tener guiones bajos consecutivos
namespace: enmanuelmoreira
# El nombre de la colección. Se debe cumplir la misma restricción de caracteres como la del Namespace
name: micoleccion
# La versión de de la colección. Debe ser compatibile con versionado semántico
# https://es.wikipedia.org/wiki/Versionado_de_software
version: 1.0.0
# Archivo README.md. El path debe ser relativo al directorio raíz de la colección
readme: README.md
# Lista de Autores. Puede ser solo el nombre o con el formato 'Nombre Completo <email> (url)
# @nicks:irc/im.site#channel'
authors:
- your name <example@domain.com>
### OPCIONAL pero altamente recomendado
# Una breve descripción de la colección
description: descripción de la colección
# Lista de Licencias por el contenido de la colección. Ansible Galaxy actualmente acepta solo
# Licencias L(SPDX,https://spdx.org/licenses/) .
license:
- GPL-2.0-or-later
# La ruta de la Licencia de la colección. El path debe ser relativo al directorio raíz de la colección. Esta clave es
# mutuamente excluyente con la clave 'license'
license_file: ''
# Una lista de etiquetas para que asocies el contenido de la colección para facilitar la búsqueda / indexación. Una etiqueta debe tener la misma restricción de caracteres que
# 'namespace' y 'name'
tags: []
# Las Collecciones como esta necesitan ser instaladas para que se puedan utilizar. La forma en que se representa es
# 'namespace.name'.
dependencies: {}
# La URL del repositorio original
repository: http://example.com/repository
# La URL con la documentación de la colección
documentation: http://docs.example.com
# La URL a la pagina principal de la colección o proyecto
homepage: http://example.com
# La URL del issue tracker de la colección
issues: http://example.com/issue/tracker
build_ignore: []
Ajustamos el archivo galaxy.yml a nuestras necesidades:
---
namespace: enmanuelmoreira
name: micoleccion
version: "1.0.0"
readme: README.md
authors:
- enmanuelmoreira
description: Ejemplo de despliegue de una colección en Ansible Galaxy con GitLab.
license:
- MIT
tags:
- software
- download
dependencies:
community.general: ">=3.0.0"
repository: https://gitlab.com/enmanuelmoreira/ansible-collection-micoleccion
documentation: https://gitlab.com/enmanuelmoreira/ansible-collection-micoleccion
homepage: https://www.enmanuelmoreira.com
issues: https://gitlab.com/enmanuelmoreira/ansible-collection-micoleccion/-/issues
build_ignore: []
Creamos también una carpeta en la raiz de la colección llamada meta
con el archivo runtime.yml
:
mkdir meta
touch meta/runtime.yml
Y pegamos el siguiente contenido:
---
requires_ansible: '>=2.9.10'
Creando los Roles #
Entramos en la carpeta roles y vamos a crear un par de roles:
cd roles
ansible-galaxy role init software
ansible-galaxy role init download
Tendremos entonces siguiente estructura:
.
├── download
│ ├── defaults
│ │ └── main.yml
│ ├── files
│ ├── handlers
│ │ └── main.yml
│ ├── meta
│ │ └── main.yml
│ ├── README.md
│ ├── tasks
│ │ └── main.yml
│ ├── templates
│ ├── tests
│ │ ├── inventory
│ │ └── test.yml
│ └── vars
│ └── main.yml
└── software
├── defaults
│ └── main.yml
├── files
├── handlers
│ └── main.yml
├── meta
│ └── main.yml
├── README.md
├── tasks
│ └── main.yml
├── templates
├── tests
│ ├── inventory
│ └── test.yml
└── vars
└── main.yml
Para ahorrarnos tiempo, vamos a editar el archivo tasks/main.yml
en cada rol, con una tarea sencilla:
En el Rol download vamos realizar una tarea que descargue una imagen de Debian en el directorio /tmp:
- name: Descargando Imagen minima de debian
get_url:
url: https://cdimage.debian.org/debian-cd/current/amd64/iso-cd/debian-11.2.0-amd64-netinst.iso
dest: /tmp
mode: 0755
En el rol software vamos a instalar MariaDB y Apache:
---
- name: Instalando MariaDB y apache.
pkg:
name:
- httpd
- mariadb-server
state: latest
Subiendo Nuestra Colección a GitLab #
En esta etapa vamos a crear el proyecto en GitLab y luego subir los cambios realizados.
Creando Proyecto #
Vamos a la página de GitLab (donde ya devemos haber iniciando sesión) Esquina superior derecha, Nuevo Proyecto:
Creamos un proyecto en blanco:
Le damos un nombre, una descripción y destildamos las opciones Crear README y SAST.
Una vez creado, nos redireccionará a la página principal del proyecto, Vamos a la parte Izquierda en Configuración -> CI / CD
Bajamos un poco y nos encontraremos con la opción variables. Damos click a Expandir y click en la opción Añadir Variable, aquí vamos a añadir la API Key que sacamos de Ansible Galaxy para que GitLab construya la colección y se autentique con Galaxy para publicarla.
En Clave colocamos el nombre con que queremos guardar la variable, en este caso GALAXY_TOKEN.
En Valor colocamos la API Key de Ansible Galaxy.
Tildamos ambas opciones de: Proteger Variable y Enmascarar Variable. La primera nos va a permitir que esa variable solo pueda ser accedida por este repositorio y no por otros. y al segunda opción va a evitar que la API Key se muestre por el output del Pipeline, así nos cercioramos de no exponer la API Key a otros ojos.
Listo, quedaría asi:
Creando el Pipeline de Gitlab #
Ahora viene la parte interesante. Vamos a crear el pipeline con las instrucciones que debe seguir GitLab CI para que nos construya el paquete de la colección y lo publique en Ansible Galaxy todo de manera automática.
Creamos el archivo .gitlab-ci.yml
en la raíz de la colección. Veremos uno por uno las instrucciones al detalle:
variables:
GALAXY_NAME: micoleccion
image: enmanuelmoreira/docker-ansible-alpine:latest
stages:
- build
- publish
build:
stage: build
rules:
- if: $CI_COMMIT_TAG
script:
- ansible-galaxy collection build --force
artifacts:
paths:
- $CI_PROJECT_NAMESPACE-$GALAXY_NAME-$CI_COMMIT_TAG.tar.gz
expire_in: 30 days
publish:
stage: publish
rules:
- if: $CI_COMMIT_TAG
dependencies:
- build
script:
- ansible-galaxy collection publish ./$CI_PROJECT_NAMESPACE-$GALAXY_NAME-$CI_COMMIT_TAG.tar.gz --token $GALAXY_TOKEN
variables:
GALAXY_NAME: micoleccion
La clave variables
nos permite predefinir variables que se van a ejecutar a lo largo del pipeline. De verdad que nos ahorra un montón de trabajo si es que una variable es utilizada por varios stages.
En este caso particular, para efectos prácticos, estamos definiendo una variable GALAXY_NAME
con el nombre de nuestra colección que nos va a servir en la etapa de build para nombrar el paquete que contiene la colección. Podríamos usar una variable definida de GitLab llamada CI_PROJECT_NAME
y a menos que el repositorio se llame tal cual como Galaxy pide que sean los nombres de las colecciones, la puedes utilizar. En caso contrario, nos toca definir esta variable que cumple con lo que Galaxy pide en términos de nombrar los archivos.
ìmage: enmanuelmoreira/docker-ansible-alpine:latest
Es una imagen Docker que contiene Ansible instalado. Estoy utilizando una que ya construí y que puede ver el código fuente en https://gitlab.com/enmanuelmoreira/docker-ansible-alpine
stages:
- build
- publish
Los stages son las etapas en la que el pipeline ejecutará los pasos contenidos. En este caso tenemos dos etapas: build y publish.
En la etapa build vamos a empaquetar la colección utilizando una serie de instrucciones:
build:
stage: build
rules:
- if: $CI_COMMIT_TAG
script:
- ansible-galaxy collection build --force
artifacts:
paths:
- $CI_PROJECT_NAMESPACE-$GALAXY_NAME-$CI_COMMIT_TAG.tar.gz
expire_in: 30 days
rules: se ejecutará la etapa build solo si cumple con las reglas establecidas. En este caso vamos a restringirla al tag del commit (en un momento explico el porqué)
script: aquí vamos a ejecutar los comandos para construir el paquete que se enviará a Galaxy.
artifacts: es el archivo resultante de la construcción. Para que el paquete cumpla con los requerimientos de nombres de Galaxy, vamos a utilizar una variable que nos provee GitLab: $CI_PROJECT_NAMESPACE
que nos proveerá el nombre de usuario del repo (el dueño del repo), seguidor de un guión y luego la variable que definimos previamente en el encabezado del manifiesto $GALAXY_NAME
, seguido de otro guión con la variable del commit tag $CI_COMMIT_TAG
y al extensión del archivo .tag.gz
Este “artefacto” o archivo resultante se va a almacenar en GitLab por 30 días con la clave expire_in
En la etapa publish vamos a autenticarnos con Ansible Galaxy y publicar nuestra colección empaquetada:
publish:
stage: publish
rules:
- if: $CI_COMMIT_TAG
dependencies:
- build
script:
- ansible-galaxy collection publish ./$CI_PROJECT_NAMESPACE-$GALAXY_NAME-$CI_COMMIT_TAG.tar.gz --token $GALAXY_TOKEN
Bueno, aquí tenemos algo nuevo dependencies
. Esta clave le dice a GitLab que esta etapa solo se va a ejecutar si la etapa build tuvo éxito.
En script
podemos ver el comando con el cual vamos a publicar nuestra colección, con el nombre del artefacto tal cual como lo hicimos en la etapa de build y el argumento final sería proporcionar el token de Galaxy y que previamente lo guardamos como una variable dentro de GitLab.
Subiendo los Cambios #
Perfecto. Solo nos queda subir los cambios que hemos realizado a GitLab:
Inicializamos el repo con la rama main por defecto:
git init --initial-branch=main
Añadimos el repositorio de GitLab con nuestras credenciales (sustituir usuario por tu usuario)
git remote add origin git@gitlab.com:usuario/ansible-collection-micoleccion.git
Añadimos los archivos de la colección
git add .
Hacemos commit de los cambios:
git commit -m "Initial commit"
Etiquetamos la version 1.0.0 de nuestra colección:
git tag -a 1.0.0 -m "Version 1.0.0"
Subimos los cambios:
git push -u origin main
Por ultimo subimos los cambios del tag 1.0.0:
git push -u origin 1.0.0
¿Recuerdas que te mencioné que restringimos la ejecución del pipeline al tag o etiquetas? La razón es que Galaxy necesita saber que versión de la colección se está enviando, por lo que, además de colocarla en el manifiesto galaxy.yml
esta debe coincidir con el nombre del paquete y etiquetando o colocándole tag a la versión del paquete podemos tener ese dato e incluirlo. Por consiguiente, cada vez que realicemos un cambio a nuestra colección y esta pase a ser la versión 1.1.0 por ejemplo, debemos hacer git tag 1.1.0
y ejecutar git push -u origin 1.1.0
y asi GitLab nos creará el tag de version en nuestro repo y ejecutará el pipeline.
Monitoreando el Pipeline #
Vamos a GitLab en la parte izquierda en el Menu CI/CD -> Pipelines:
Una vez dentro, veremos todos los pipelines que se han ejecutado hasta el momento:
Vamos a entrar en el primero y aprovechar de hacer un poco de troubleshooting:
Entremos al stage build que si se ejecutó correctamente:
Si observamos bien, está todo correcto, y si te fijas en la parte derecha, hay tres botones: Mantener, Descargar y Explorar, esto nos permite ver nuestro artefacto recien creado (que no es mas que la coleccion empaquetada), descargarla, etc. Durante 30 dias va a permanecer almacenado en GitLab porque asi se lo hemos dicho en el archivo .gitlab-ci.yml
Vamos ahora a ver que pasó con el stage publish:
Pues por alguna razón el comando falló al no encontrar el token, pero si ya lo habiamos configurado en GitLab con su respectiva variable llamada $GALAXY_TOKEN
, ¿Qué ha podido suceder entonces?
¿Recuerdas cuando al momento de configurar el token como variable activamos la opción Proteger Variable? Nos limita a utilizar las ramas del repositorio que estén protegidas. Por defecto los tags no vienen protegidos y por ende, si configuramos un tag la 1.0.1 por ejemplo, al actualizar a la 1.0.2 no se va a ejecutar el pipeline porque el tag 1.0.2 no estaría protegido. Una solución a esto seria agregar de una vez todos los tags que se suban a continuación.
Para esto tenemos que ir a Configuración -> Repositorio, en la opción Protected Tags, donde dice etiqueta le colocamos un asterisco * escogemos Create wildcard, Autorizado a crear: Mantainers (puedes colocarte a ti mismo o un miembro del equipo) y click en Proteger:
Ahora nos vamos de nuevo a CI/CD -> Pipelines, vamos a el stage que falló (publish) y hacemos click en Reintentar:
Y voilá, se ha ejecutado el stage publish:
Revisando en Ansible Galaxy #
Para terminar, nos aseguramos que nuestra colección se encuentra publicada, nos vamos a la página de Ansible Galaxy, en el menú de la izquierda a My Contents:
Ahí lo tenemos:
Si queremos ver más detalles, hagamos click en el nombre de la colección micoleccion
Y con eso estaríamos.
Conclusión #
GitLab nos permite poder publicar nuestras colecciones a Ansible Galaxy de manera fácil y automatizada a través de GitLab CI. Si quisieramos extender más nuestro pipeline y colocarle pruebas con molecule, por ejemplo, lo podríamos hacer, pero eso sería tema para otro artículo 😄
El código del pipeline como de la coleccion de prueba, quedará en el repositorio:
https://gitlab.com/enmanuelmoreira/ansible-collection-micoleccion
Espero les haya gustado y nos vemos en la próxima!