Rules
Antes de empezar a hablar de rules, debes saber que es una evolución de algo que todavía se utiliza pero está en desuso llamado only.
Antes se utilizaban dos cosas juntas (only y except) que hoy resolvemos solo con rules. Usa rules siempre que sea posible. only está en desuso.
Solo para entender el contexto de lo que teníamos antes...
job:
script: echo "Ejecutando job"
only:
- main # Ejecuta solo en la rama main
- tags
only:ejecuta el job solamente cuando el commit proviene de ciertas condiciones (rama, etiqueta, etc).
O except...
job:
script: echo "Ejecutando job"
except:
- develop
except:no ejecuta el job si cumple alguna de estas condiciones.
Limitaciones:
- No tenemos if o lógica condicional usando only y except.
- No podemos hacer lógicas combinadas utilizando las variables.
Así que olvídate de eso y vayamos directo a lo que nos da más poder, rules.
job:
script:
...
rules: [] # Es una lista
- Las reglas se evalúan en orden, la que haga match primero define el comportamiento del job.
- Súper personalizable con expresiones, variables, etiquetas, rama, MR, etc.
Una tabla rápida para entender la diferencia.
| Aspecto | only / except | rules |
|---|---|---|
| Soporte oficial | Obsoleto | Actual y recomendado |
| Flexibilidad | Baja | Alta |
| Condicional (if) | No | Sí |
| Orden de evaluación | Limitada | Secuencial y lógica |
| Detección MR | Limitada | Avanzada |
| Expresividad | Baja | Alta |
Only y except solo están presentes por una cuestión de retrocompatibilidad y nada más.
Documentación Oficial Only y Except
La estructura de un bloque rules es la siguiente.
job:
script:
...
rules:
- if: <expresión>
when: [on_success | on_failure | manual | always | never | delayed]
start_in: <tiempo>
allow_failure: true/false
exists:
- <ruta/del/archivo>
changes:
- <archivo o carpeta>
variables:
- <variable>
| Campo | ¿Obligatorio? | Descripción |
|---|---|---|
| if | ❌ | Expresión booleana usando variables de GitLab ($CI_*, etc). |
| when | ❌ | Define cuándo se ejecutará el job (on_success, manual, etc). |
| start_in | ❌ | Usado con when: delayed (ej: "5 minutes"). |
| allow_failure | ❌ | Si es true, el fallo en el job no rompe el pipeline. |
| exists | ❌ | Ejecuta el job si el/los archivo(s) existe(n). |
| changes | ❌ | Ejecuta el job si los archivos listados fueron modificados. |
| variables | ❌ | Lista de variables obligatorias para que esta regla sea aplicada. |
when
Tenemos un elemento importante ahí que además de estar dentro de rules también puede ser declarado fuera (directamente en el job) que es el when. Primero vamos a entender qué hace y después entender la diferencia entre usar en rules y fuera de él.
| Valor de when | Comportamiento |
|---|---|
| on_success | Ejecuta el job solamente si ningún job anterior falla. (Por defecto) |
| on_failure | Ejecuta el job solamente si algún job anterior falla. |
| never | Nunca ejecuta el job, independientemente de cualquier condición. Solo puede ser usado en rules, al fin y al cabo no tendría sentido |
| always | Ejecuta el job siempre, incluso si jobs anteriores fallan. |
| manual | El job queda esperando ejecución manual (aparece botón en la interfaz de GitLab). |
| delayed | El job se ejecuta con retraso, conforme al tiempo definido en start_in:. |
Ejemplo de when: manual sería que alguien necesita aprobar el despliegue para ir a producción.
job_manual:
script: echo "Ejecutando el despliegue..."
when: manual
Ejemplo delayed + start_in sería dar un tiempo entre el despliegue en staging y el despliegue en producción para que dé tiempo a probar y si alguien percibe un error da tiempo a cancelar el job antes de que se ejecute. Si usas delayed, el start_in es obligatorio.
job_delayed:
script: echo "Ejecutando el despliegue..."
when: delayed
start_in: 10 minutes
Un ejemplo de always podría ser enviar una notificación al final del pipeline incluso si falla, por ejemplo enviar un email y una notificación en Slack. Si quieres que solo envíe si falla entonces podría ser on_failure.
Cuando declaramos el when fuera de las rules lo que estamos haciendo en realidad es colocando el mismo when para todas las rules. Esto sirve para dejar el rules más limpio.
job:
script: echo "Ejecutando"
when: manual
rules:
- if: Condición 1
- if: Condición 2
Es decir, es lo mismo que hacer esto.
job:
script: echo "Ejecutando"
rules:
- if: Condición 1
when: manual
- if: Condición 2
when: manual
Pero podemos sobrescribir para alguna regla específica. En este caso el job siempre es manual, pero si es en la rama develop entonces se ejecuta directamente sin intervención.
job:
script: echo "Desplegando"
when: manual
rules:
- if: '$CI_COMMIT_BRANCH == "main"'
- if: '$CI_COMMIT_BRANCH == "staging"'
- if: '$CI_COMMIT_BRANCH == "develop"'
when: on_success
allow_failure
Lo mismo vale para allow_failure, que puede ser definido directamente en el nivel del job o individualmente dentro de cada regla en rules. Cuando se define en el nivel del job, el valor será aplicado como valor predeterminado para todas las rules.
Por defecto, allow_failure es false, es decir: si el job falla, el pipeline también será considerado con fallo. Cuando se define como true, el job puede fallar sin romper el pipeline, permitiendo que los próximos jobs continúen normalmente.
Esto es útil, por ejemplo, para pruebas experimentales, análisis estáticos o cualquier etapa no crítica que no quieras que bloquee el resto de la ejecución.
exists, changes y variables
Muchas veces el código sufre cambios que no cambian nada en el entorno, un excelente ejemplo de esto es hacer una alteración en el README.md. ¿Para qué vamos a hacer un despliegue?
En el bloque de rules, además de if, puedes usar exists y changes, que son formas de activar un job con base en la presencia o modificación de archivos.
job:
script: "Creando una imagen"
when: manual
rules:
- if: '$CI_COMMIT_BRANCH == "main"'
exists:
- Dockerfile
- .dockerignore
job:
script: "Creando una imagen"
when: manual
rules: # Observa que puede existir sin el IF. tanto changes como exists.
- changes: # Si alguno de estos archivos sufre cambios
- src/**
- config/*.yaml
Ya el bloque variables, como vimos antes, sirve para definir variables personalizadas.
deploy:
stage: deploy
image: alpine
script:
- echo "Despliegue iniciando con TOKEN=$TOKEN"
environment:
name: $DEPLOY_ENV
rules:
- if: '$CI_COMMIT_BRANCH == "develop"'
variables:
DEPLOY_ENV: develop
- if: '$CI_COMMIT_BRANCH == "main"'