Digamos que tenemos un repositorio en Github app1 donde versionamos el código de dicha aplicación.
El equipo usa este repositorio de forma típica, creando ramas feature, hotfix, etc. y mergeandolas en main
una vez que el pipeline se completa sin error. Es decir, cada push en main ha sido testeado en Github tanto como
el equipo es capaz de testear.
Una vez que se ha mergeado en main, Github ejecuta a su vez los tests, genera una imagen docker usando el
commit de la rama como tag y lo publica en ECR, por ejemplo usando algo parecido a este (fragmento) action:
build:
name: Build & Push
runs-on: ubuntu-latest
needs: test
if: github.event_name == 'push' && github.ref == 'refs/heads/main'
steps:
- name: Checkout code
uses: actions/checkout@v4
- name: Generate GitHub App Token
id: generate_token
uses: actions/create-github-app-token@v1
with:
app-id: ${{ secrets.GH_TOKEN_APP_ID }}
private-key: ${{ secrets.GH_TOKEN_PRIVATE_KEY }}
- name: Configure AWS credentials
uses: aws-actions/configure-aws-credentials@v4
with:
aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }}
aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
aws-region: ${{ env.AWS_REGION }}
- name: Login to Amazon ECR
id: login-ecr
uses: aws-actions/amazon-ecr-login@v2
- name: Get short SHA
id: shortsha
run: echo "sha7=$(echo ${GITHUB_SHA} | cut -c1-7)" >> $GITHUB_OUTPUT
- name: Build, tag, and push image to Amazon ECR
id: build-image
env:
ECR_REGISTRY: ${{ steps.login-ecr.outputs.registry }}
IMAGE_TAG: ${{ steps.shortsha.outputs.sha7 }}
run: |
docker build --build-arg GITHUB_TOKEN=${{ steps.generate_token.outputs.token }} -t $ECR_REGISTRY/$ECR_REPOSITORY:$IMAGE_TAG .
docker push $ECR_REGISTRY/$ECR_REPOSITORY:$IMAGE_TAG
IMAGE_URI="$ECR_REGISTRY/$ECR_REPOSITORY:$IMAGE_TAG"
echo "image=$IMAGE_URI" >> $GITHUB_OUTPUT
echo "✅ Built and pushed image: $IMAGE_URI"
outputs:
image_tag: ${{ steps.shortsha.outputs.sha7 }}
En este punto la aplicación ha subido una nueva imagen a su ECR con un tag asociado a un commit específico
Ahora lo que querríamos es que esta versión se desplegara automáticamente en staging por lo que añadimos un nuevo step:
notify-infra:
needs: build
uses: OUR_ORGANIZATION/platform-ci/.github/workflows/deploy-version.yml@main
secrets:
GH_TOKEN_APP_ID: ${{ secrets.GH_TOKEN_APP_ID }}
GH_TOKEN_PRIVATE_KEY: ${{ secrets.GH_TOKEN_PRIVATE_KEY }}
with:
environment: staging
project: app1
variable: service_tag
value: ${{ needs.build.outputs.image_tag }}
Este steps va a invocar a un Github action que se encuentra en otro repositorio, específicamente en
OUR_ORGANIZATION/platform-ci donde OUR_ORGANIZATION sería nuestra organización y platform_ci, es el repositorio
donde tenemos definidos una serie de scripts genéricos y reutilizables (notificar de fallos en los pipelines,
de despliegues de versiones, etc.)
A este action le proporcionamos 4 argumentos:
-
El entorno donde queremos actuar. En nuestro caso siempre será staging
-
El proyecto. Tal vez se podría extraer del nombre del repositorio, en nuestro caso hemos optado por especificarlo
-
Variable, es el nombre de la variable que queremos actualizar. Corresponde con service_tag de cada auto-vars
-
Value, es el valor a actualizar en la variable anterior.
Básicamente, este step está invocando a un action de otro repo indicándole que actualize una variable del proyecto
app1 en el entorno staging con el commit del repositorio.
platform-ci/.github/workflows/deploy-version.yml
on:
workflow_call:
secrets:
GH_TOKEN_APP_ID:
required: true
GH_TOKEN_PRIVATE_KEY:
required: true
inputs:
environment:
required: true
type: string
project:
required: true
type: string
variable:
required: true
type: string
value:
required: true
type: string
jobs:
update-version:
runs-on: ubuntu-latest
steps:
- name: Generate GitHub App Token
id: generate_token
uses: actions/create-github-app-token@v1
with:
app-id: ${{ secrets.GH_TOKEN_APP_ID }}
private-key: ${{ secrets.GH_TOKEN_PRIVATE_KEY }}
owner: OUR_ORGANIZATION
-
name: Checkout Infra Repo
uses: actions/checkout@v4
with:
repository: OUR_ORGANIZATION/tr-infra
token: ${{ steps.generate_token.outputs.token }}
-
name: Update Version File
run: |
cd environments/${{ inputs.environment }}/${{ inputs.project }}
FILE="versions.auto.tfvars"
LINE='${{ inputs.variable }} = "${{ inputs.value }}"'
if [ -f "$FILE" ]; then
if grep -q "^${{ inputs.variable }} =" "$FILE"; then
sed -i "s/^${{ inputs.variable }} = .*/$LINE/" "$FILE"
else
echo "$LINE" >> "$FILE"
fi
else
echo "$LINE" > "$FILE"
fi
-
name: Create Pull Request
id: cpr
uses: peter-evans/create-pull-request@v5
with:
token: ${{ steps.generate_token.outputs.token }}
commit-message: "deploy: update ${{ inputs.variable }} (${{ inputs.environment }}) to ${{ inputs.value }}"
title: "🚀 Deploy ${{ inputs.project }}-${{ inputs.value }} to ${{ inputs.environment }}"
body: "Deploy new version of ${{ inputs.project }} \n ${{inputs.variable}}=${{inputs.value}}"
branch: "gitops/${{ inputs.project }}-${{ inputs.value }}"
base: main
-
name: Automerge Pull Request
if: steps.cpr.outputs.pull-request-operation == 'created'
run: |
gh pr merge "${{ steps.cpr.outputs.pull-request-number }}" --merge --auto || \
gh pr merge "${{ steps.cpr.outputs.pull-request-number }}" --merge
env:
GH_TOKEN: ${{ steps.generate_token.outputs.token }}
---
Este action automáticamente descarga la última versión del proyecto tr-infra y actualiza el fichero
versions.auto.tfvars cambiando el valor de la variable que se le indica, es decir, si por ejemplo el contenido
de este fichero fuera:
versions.auto.tfvars
log=INFO
service_tag=8901234
La ejecución de este action lo modificaría, por ejemplo, a:
versions.auto.tfvars
log=INFO
service_tag=1234567
Una vez actualizado el fichero crearía una PR sobre el proyecto tf-infra y automáticamente la mergearía
Visualmente, el desarrollador de app1 (o cualquier otro obviamente) podría observar cómo, tras hacer el merge a main,
aparece una PR en el repo de tr-infra con los detalles del cambio en app1 y cómo esta PR se mergea automáticamente
en el repo tr-infra