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:

Data hosted with ♥ by Pastebin.com - Download Raw - See Original
  1. ### REQUERIDO
  2. # 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
  3. # Solo puede contener letras en minusculas y guines bajos (_) El Namespace no puede comenzar con
  4. # guiones bajos o números y no puede tener guiones bajos consecutivos
  5. namespace: enmanuelmoreira
  6.  
  7. # El nombre de la colección. Se debe cumplir la misma restricción de caracteres como la del Namespace
  8. name: micoleccion
  9.  
  10. # La versión de de la colección. Debe ser compatibile con versionado semántico
  11. # https://es.wikipedia.org/wiki/Versionado_de_software
  12. version: 1.0.0
  13.  
  14. # Archivo README.md. El path debe ser relativo al directorio raíz de la colección
  15. readme: README.md
  16.  
  17. # Lista de Autores. Puede ser solo el nombre o con el formato 'Nombre Completo <email> (url)
  18. # @nicks:irc/im.site#channel'
  19. authors:
  20. - your name <example@domain.com>
  21.  
  22. ### OPCIONAL pero altamente recomendado
  23. # Una breve descripción de la colección
  24. description: descripción de la colección
  25.  
  26. # Lista de Licencias por el contenido de la colección. Ansible Galaxy actualmente acepta solo
  27. # Licencias L(SPDX,https://spdx.org/licenses/) .
  28. license:
  29. - GPL-2.0-or-later
  30.  
  31. # 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
  32. # mutuamente excluyente con la clave 'license'
  33. license_file: ''
  34.  
  35. # 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
  36. # 'namespace' y 'name'
  37. tags: []
  38.  
  39. # Las Collecciones como esta necesitan ser instaladas para que se puedan utilizar. La forma en que se representa es
  40. # 'namespace.name'.
  41. dependencies: {}
  42.  
  43. # La URL del repositorio original
  44. repository: http://example.com/repository
  45.  
  46. # La URL con la documentación de la colección
  47. documentation: http://docs.example.com
  48.  
  49. # La URL a la pagina principal de la colección o proyecto
  50. homepage: http://example.com
  51.  
  52. # La URL del issue tracker de la colección
  53. issues: http://example.com/issue/tracker
  54.  
  55. build_ignore: []
  56.  

Ajustamos el archivo galaxy.yml a nuestras necesidades:

Data hosted with ♥ by Pastebin.com - Download Raw - See Original
  1. ---
  2. namespace: enmanuelmoreira
  3. name: micoleccion
  4. version: "1.0.0"
  5. readme: README.md
  6.  
  7. authors:
  8.  - enmanuelmoreira
  9.  
  10. description: Ejemplo de despliegue de una colección en Ansible Galaxy con GitLab.
  11.  
  12. license:
  13.  - MIT
  14.  
  15. tags:
  16.  - software
  17.   - download
  18.  
  19. dependencies:
  20.   community.general: ">=3.0.0"
  21.  
  22. repository: https://gitlab.com/enmanuelmoreira/ansible-collection-micoleccion
  23. documentation: https://gitlab.com/enmanuelmoreira/ansible-collection-micoleccion
  24. homepage: https://www.enmanuelmoreira.com
  25. issues: https://gitlab.com/enmanuelmoreira/ansible-collection-micoleccion/-/issues
  26.  
  27. build_ignore: []
  28.  

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:

Data hosted with ♥ by Pastebin.com - Download Raw - See Original
  1. variables:
  2.   GALAXY_NAME: micoleccion
  3.  
  4. image: enmanuelmoreira/docker-ansible-alpine:latest
  5.  
  6. stages:
  7.  - build
  8.   - publish
  9.  
  10. build:
  11.   stage: build
  12.   rules:
  13.     - if: $CI_COMMIT_TAG
  14.   script:
  15.    - ansible-galaxy collection build --force
  16.   artifacts:
  17.     paths:
  18.      - $CI_PROJECT_NAMESPACE-$GALAXY_NAME-$CI_COMMIT_TAG.tar.gz
  19.     expire_in: 30 days
  20.  
  21. publish:
  22.   stage: publish
  23.   rules:
  24.     - if: $CI_COMMIT_TAG
  25.   dependencies:
  26.    - build
  27.   script:
  28.    - ansible-galaxy collection publish ./$CI_PROJECT_NAMESPACE-$GALAXY_NAME-$CI_COMMIT_TAG.tar.gz --token $GALAXY_TOKEN
  29.  
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