Publicando Colecciones de Ansible en Galaxy con GitLab


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:

DigitalOcean Referral Badge

O a través del siguiente enlace: https://bit.ly/digitalocean-itsm


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.

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.

Dependiendo de tu distribución, la instalación será un poco diferente. En este caso vamos a instalar Ansible directamente desde pip:

1
sudo pip3 install ansible

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:

/images/ansible-galaxy-gitlab/galaxy-0.png
Ansible Galaxy

Iniciamos sesión con nuestra cuenta GitHub:

/images/ansible-galaxy-gitlab/galaxy-1.png
Inicio de Sesión

/images/ansible-galaxy-gitlab/galaxy-2.png
Ingresamos credenciales

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:

/images/ansible-galaxy-gitlab/galaxy-3.png
Preferencias

Y vamos a ver nuestra API Key, vamos a copiarla y reservarla para configurarla en GitLab.

/images/ansible-galaxy-gitlab/galaxy-4.png
Copiamos API Key

Advertencia
Recuerda que la API Key es una clave privada por lo que debes tratarla como si fuera tu clave personal.
  • Creamos una colección de Ansible en nuestro equipo:
1
ansible-galaxy collection init usuario.micoleccion

Lo cual creará una colección con la siguiente estructura:

1
2
3
4
5
6
7
.
├── 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:

Ajustamos el archivo galaxy.yml a nuestras necesidades:

Creamos también una carpeta en la raiz de la colección llamada meta con el archivo runtime.yml:

1
2
mkdir meta
touch meta/runtime.yml

Y pegamos el siguiente contenido:

1
2
---
requires_ansible: '>=2.9.10'

Entramos en la carpeta roles y vamos a crear un par de roles:

1
cd roles
1
ansible-galaxy role init software
1
ansible-galaxy role init download

Tendremos entonces siguiente estructura:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
.
├── 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 ahorranos 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:

1
2
3
4
5
- 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:

1
2
3
4
5
6
7
---
- name: Instalando MariaDB y apache.
  pkg:
    name:
      - httpd
      - mariadb-server
    state: latest

En esta etapa vamos a crear el proyecto en GitLab y luego subir los cambios realizados.

Vamos a la página de GitLab (donde ya devemos haber iniciando sesión) Esquina superior derecha, Nuevo Proyecto:

/images/ansible-galaxy-gitlab/gitlab-0.png
Nuevo Proyecto

Creamos un proyecto en blanco:

/images/ansible-galaxy-gitlab/gitlab-1.png
Crear un proyecto en blanco

Le damos un nombre, una descripción y destildamos las opciones Crear README y SAST.

/images/ansible-galaxy-gitlab/gitlab-2.png
Crear Proyecto

Una vez creado, nos redireccionará a la página principal del proyecto, Vamos a la parte Izquierda en Configuración -> CI / CD

/images/ansible-galaxy-gitlab/gitlab-3.png
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.

/images/ansible-galaxy-gitlab/gitlab-4.png
Variables

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.

/images/ansible-galaxy-gitlab/gitlab-5.png
Añadir Variable

Listo, quedaría asi:

/images/ansible-galaxy-gitlab/gitlab-6.png

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:

1
2
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.

1
ì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

1
2
3
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:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
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:

1
2
3
4
5
6
7
8
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.

Perfecto. Solo nos queda subir los cambios que hemos realizado a GitLab:

Inicializamos el repo con la rama main por defecto:

1
git init --initial-branch=main

Añadimos el repositorio de GitLab con nuestras credenciales (sustituir usuario por tu usuario)

1
git remote add origin git@gitlab.com:usuario/ansible-collection-micoleccion.git

Añadimos los archivos de la colección

1
git add .

Hacemos commit de los cambios:

1
git commit -m "Initial commit"

Etiquetamos la version 1.0.0 de nuestra colección:

1
git tag -a 1.0.0 -m "Version 1.0.0"

Subimos los cambios:

1
git push -u origin main

Por ultimo subimos los cambios del tag 1.0.0:

1
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.

Vamos a GitLab en la parte izquierda en el Menu CI/CD -> Pipelines:

/images/ansible-galaxy-gitlab/gitlab-7.png

Una vez dentro, veremos todos los pipelines que se han ejecutado hasta el momento:

/images/ansible-galaxy-gitlab/gitlab-8.png
Pipelines Ejecutados

Vamos a entrar en el primero y aprovechar de hacer un poco de troubleshooting:

/images/ansible-galaxy-gitlab/gitlab-9.png
Pipelines Ejecutados

Entremos al stage build que si se ejecutó correctamente:

/images/ansible-galaxy-gitlab/gitlab-10.png
Stage build

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:

/images/ansible-galaxy-gitlab/gitlab-11.png
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:

/images/ansible-galaxy-gitlab/gitlab-12.png
Crear wilddard

/images/ansible-galaxy-gitlab/gitlab-13.png
Proteger tags

Ahora nos vamos de nuevo a CI/CD -> Pipelines, vamos a el stage que falló (publish) y hacemos click en Reintentar:

/images/ansible-galaxy-gitlab/gitlab-14.png
Reintentamos el stage

Y voilá, se ha ejecutado el stage publish:

/images/ansible-galaxy-gitlab/gitlab-15.png
Stage Terminado

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:

/images/ansible-galaxy-gitlab/galaxy-5.png

Ahí lo tenemos:

/images/ansible-galaxy-gitlab/galaxy-6.png

Si queremos ver más detalles, hagamos click en el nombre de la colección micoleccion

/images/ansible-galaxy-gitlab/galaxy-7.png

Y con eso estaríamos.

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!

Si te pareció útil este artículo y el proyecto en general, considera brindarme un café :)

Buy me a coffeeBuy me a coffee