Pourquoi les prévisualisations de branche sont essentielles dans le développement logiciel
Implémentation des prévisualisations de branche via Github Actions avec Google Cloud Platform
Nettoyage
Chaque développeur sait que l'écriture de code n'est qu'une partie du processus de développement logiciel. Une étape tout aussi importante est de tester les changements pour s'assurer qu'ils fonctionnent comme prévu et ne introduisent pas de bugs dans le système existant. C'est là que les prévisualisations de branche entrent en jeu. Elles fournissent un terrain de staging où les développeurs peuvent déployer, tester et prévisualiser des changements de manière isolée par rapport à la base de code principale, garantissant ainsi un environnement de production fluide et exempt de bugs.
Dans les environnements de développement où plusieurs contributeurs poussent fréquemment du nouveau code, suivre et tester ces mises à jour peut devenir assez complexe. Les prévisualisations de branche simplifient considérablement ce processus. Elles permettent de créer des environnements temporaires où le code de différentes branches peut être déployé, testé et prévisualisé avant d'être fusionné dans la branche principale.
1. Environnement de test isolé :
Les prévisualisations de branche permettent aux développeurs de tester leur code dans un environnement identique à la production. Cela isole le nouveau code de l'environnement de production stable. Cela signifie qu'il est possible d'identifier et de corriger les problèmes avant qu'ils n'affectent le système existant.
2. Meilleure collaboration :
Les membres de l'équipe peuvent partager l'URL unique de la prévisualisation de la branche avec leurs collègues et les parties prenantes. Cela permet des retours interactifs, favorisant une collaboration fluide que ce soit pour ajuster les composants de l'interface utilisateur, corriger des bugs ou améliorer les flux de travail.
3. Détection précoce des problèmes d'intégration :
Les prévisualisations de branche permettent aux développeurs de tester comment le nouveau code interagit avec la base de code existante, offrant ainsi la possibilité de rectifier tout problème d'intégration. En capturant ces problèmes potentiels tôt (avant la fusion), les équipes peuvent assurer un processus d'intégration plus fluide lorsque le nouveau code est finalement poussé dans la branche principale.
4. Assurance qualité :
En testant les nouvelles branches dans un environnement sécurisé, les équipes peuvent garantir la qualité de leur logiciel. Cette mesure supplémentaire aide les équipes à maintenir une base de code robuste et exempte d'erreurs.
5. Déploiement et intégration continus :
Avec le concept de DevOps et de CI/CD devenant des pratiques standard dans le développement logiciel, les prévisualisations de branche constituent une partie vitale de ce cycle de vie. La possibilité de prévisualiser les changements dans un environnement séparé mais identique facilite le processus CI/CD en garantissant que les changements ne vont pas casser l'environnement de production.
Prévisualisations de branche - Un avantage pour le développement logiciel
En résumé, les prévisualisations de branche ne sont pas seulement utiles mais cruciales dans le cycle de vie moderne du développement logiciel. En fournissant aux développeurs une plateforme où les changements proposés peuvent être testés de manière isolée, nous progressons vers un développement logiciel sans risque et de haute qualité.
Les prévisualisations de branche favorisent la collaboration, améliorent la qualité du code et optimisent le flux de travail efficace du CI/CD, en en faisant une fonctionnalité indispensable pour les équipes de développement. En tant que développeur, si vous n'utilisez pas encore les prévisualisations de branche, vous passez à côté d'un élément qui pourrait considérablement rationaliser votre processus de développement.
Google propose un excellent tutoriel pour implémenter des prévisualisations de branche avec son service Cloud Build. Vous pouvez suivre ce tutoriel et vérifier s'il répond à vos besoins. Cependant, après avoir parcouru ce tutoriel moi-même, j'ai réalisé que j'avais besoin de plus de personnalisation et qu'il serait préférable d'intégrer la fonctionnalité de déploiement de branche dans le flux de travail actuel de Github de mon organisation plutôt que d'utiliser un fichier cloudbuild.
Exigences :
Un compte de service Google Cloud avec ces autorisations (Plus d'informations sur les comptes de service ici) here
Cloud Run Admin
Artifact Registry Administrator
Un secret Github contenant les informations d'accès au compte de service au format JSON (Plus d'informations sur la création de ces informations d'accès ici here)
Un référentiel Artifact Registry (Plus d'informations sur la création de référentiels ici here)
Connaissance de la containerize de votre application avec Docker
Services que nous utiliserons pour le déploiement de branche :
Cloud Run
Artifact Registry
Cloud SQL (Bonus)
Variables :
LOCATION : Région où le service est situé, liste des régions disponibles ici here
IMAGE_NAME : Nom de l'image que vous avez construite avec Docker Compose tel que spécifié dans votre fichier docker-compose
PROJECT_ID : ID de votre projet GCP trouvé dans Cloud console
REPOSITORY : Nom de votre référentiel Artifact Registry
APP_NAME : Nom de votre application dans le référentiel
NAMESPACE : Soit votre nom d'utilisateur Github, soit votre organisation
USERNAME : Votre nom d'utilisateur Github
Ceci est le flux de développement final, chaque carré avec un losange à l'intérieur représente un déploiement.
Dans ce tutoriel, nous nous concentrerons sur la partie staging du flux de développement.
Configuration du Workflow
env:
TAG: pr-$ # This is to identify revisions
DOCKER_BUILDKIT: 1
COMPOSE_DOCKER_CLI_BUILD: 1
on:
pull_request:
branches:
- 'main'
J'ai divisé le workflow de prévisualisation de branche en deux tâches principales.
1. Configurer et pousser votre container vers l'Artifact Registry
La première tâche consiste à construire l'image Docker de votre application et à la pousser vers l'Artifact Registry.
Ici, le secret GCP_CREDENTIALS correspond à vos informations d'identification du compte de service.
configure_image:
environment: staging
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v3
- id: auth
uses: google-github-actions/auth@v1
with:
credentials_json: $ # Service account with enough permissions
- name: Set up Cloud SDK # Necessary because we're using the gcp cli
uses: google-github-actions/setup-gcloud@v1
- name: Build Docker image
run: docker compose -f docker-compose.yaml build
- name: Tag and Push Container
run: |
gcloud auth configure-docker LOCATION-docker.pkg.dev
docker tag IMAGE_NAME LOCATION-docker.pkg.dev/PROJECT-ID/REPOSITORY/APP_NAME:$
docker push LOCATION-docker.pkg.dev/PROJECT_ID/REPOSITORY/APP_NAME:$
2. Déployer sur Cloud Run
deploy:
environment: staging
runs-on: ubuntu-latest
needs: configure_image
steps:
- id: auth
uses: google-github-actions/auth@v1
with:
credentials_json: $ # Service account with enough permissions
- name: Deploy to Cloud Run
id: deploy
uses: google-github-actions/deploy-cloudrun@v1
with:
service: SERVICE_NAME
image: LOCATION-docker.pkg.dev/PROJECT_ID/REPOSITORY/APP_NAME:$
region: LOCATION
tag: $
no_traffic: true # We dont want traffic on the created revision
Voici le résultat dans l'onglet "Revisions" du service Cloud Run. Chaque révision est une PR (Pull Request). Vous pouvez prévisualiser vos branches en cliquant sur le tag de la révision. Par exemple, il s'agit d'un service Cloud Run de staging avec 4 PR ouvertes et en cours de révision et/ou de test.
Notez que chaque nouveau commit sur la branche en prévisualisation créera une nouvelle révision avec le commit le plus à jour et désactivera la révision du dernier commit, ce qui est très pratique.
Parfois, il est utile pour les réviseurs de voir les changements visuellement. Pour faciliter cela, vous pouvez ajouter un lien pour ouvrir l'URL de votre révision directement depuis Github.
1. Tout d'abord, vous devez ajouter le custom cloud builder de prévisualisation de déploiement de GCP à votre package Github de l'association.
Cela nécessite un token avec la permission write:package
et une certaine configuration de Docker. Voici les commandes de base nécessaires pour pousser les images de prévisualisation de déploiement de Google vers votre package Github :
git clone <https://github.com/GoogleCloudPlatform/python-docs-samples>
cd python-docs-samples/run/deployment-previews
docker build .
docker push ghcr.io/ORGANIZATION/IMAGE_NAME: latest
git clone https://github.com/GoogleCloudPlatfor/python-docs-samples
cd python-docs-samples/run/deployment-previews
docker build .
docker push ghcr.io/ORGANIZATION/IMAGE_NAME: latest
Plus d'informations sur ce processus peuvent être trouvées dans Github’s official docs et dans ce tutoriel tutorial
Une fois l'image téléchargée avec succès sur Github, vous devriez maintenant avoir un nouveau package dans l'onglet "package".
2. Vous pouvez maintenant utiliser le package dans vos workflows Github
preview:
needs: deploy
environment: staging
runs-on: ubuntu-latest
container: # Using the ghcr.io image that we pushed
image: ghcr.io/NAMESPACE/deployment-previews
credentials:
username: USERNAME # Username associated with github token
password: $ # Repo github token
steps:
- id: auth
uses: google-github-actions/auth@v1
with:
credentials_json: $
create_credentials_file: true # Required for the check_status.py file
- name: Setup Cloud SDK
uses: google-github-actions/setup-gcloud@v1.1.1
with:
project_id: PROJECT_ID # Project id from GCP
- name: Link revision on pull request
run: |
python3 /app/check_status.py set --project-id PROJECT_ID \
--region LOCATION \
--service APP_NAME \
--pull-request $ \
--repo-name $ \
--commit-sha $
Une fois que toutes vos vérifications sont passées, vous devriez voir cette nouvelle bannière en cliquant sur le bouton "Show all checks" vers la fin de la page PR.
En cliquant sur le lien "Details", vous devriez être redirigé vers l'URL de prévisualisation de votre branche. Gardez à l'esprit que toutes les révisions utilisent la même base de données.
Parfois, vous ne voulez pas que votre branche de fonctionnalité impacte directement la database de staging. Par exemple, si vous modifiez ou ajoutez une nouvelle entité et que vous ne voulez pas ajouter de mauvaises données par erreur dans la database de staging. Pour éviter cela, j'ai créé quelques étapes pour cloner la database de staging lorsque les développeurs le demandent en ajoutant le mot-clé "needs_db" dans le titre de la PR.
1. Ajoutez cette étape à la tâche configure_image
- name: Clone Cloud SQL instance
id: clone_sql_instance
if: contains(github.event.pull_request.title, 'needs_db')
run: |
INSTANCE_NAME="instance_name"
CLONE_NAME="instance_name-pr-$"
gcloud sql instances clone $INSTANCE_NAME $CLONE_NAME
continue-on-error: true
Cette étape clone l'instance Cloud SQL du projet nommée "instance_name" si le titre de la PR contient le mot "needs_db". Pour moi, c'est la solution la plus simple et si les développeurs oublient d'écrire le mot-clé dans leur PR initiale, ils peuvent toujours revenir et modifier le nom pour que l'instance Cloud SQL soit clonée. L'instance clonée est alors nommée avec le numéro de PR pour trouver facilement quelle database est liée à quelle PR.
2. Si nécessaire, mettez à jour vos fichiers d'environnement pour qu'ils pointent vers l'adresse IP de votre nouvelle database
Vous pouvez obtenir l'adresse IP de votre nouvelle instance avec cette étape. La condition if se réfère à l'étape Clone Cloud SQL instance, c'est pourquoi nous devons lui donner un identifiant. Cette étape va également dans la tâche configure_image.
- name: Update database url
if: contains(github.event.pull_request.title, 'needs_db') && steps.clone_sql_instance.conclusion == 'success'
run: |
DB_PRIVATE_IP=$(gcloud sql instances describe instance_name-pr-$ --format='get(ipAddresses[2].ipAddress)')
# Update .env files here
Now you should have a new database instance linked to your revision, this new database will be in your SQL service in GCP.
Maintenant que vous avez beaucoup de révisions et d'instances de database, vous devez les nettoyer après que votre PR ait été approuvée et fusionnée dans la branche principale.
Je fais généralement mon nettoyage dans un fichier de workflow différent qui est exécuté lors du push sur la branche principale.
env:
DOCKER_BUILDKIT: 1
COMPOSE_DOCKER_CLI_BUILD: 1
# This is to speed up docker compose
on:
push:
branches:
- 'main'
1. Nettoyez les tags en utilisant le fichier check_status fourni par l'image de prévisualisation de déploiement de Google.
Si vous n'avez pas encore ajouté l'image à votre registre de packages de l'association, vous pouvez suivre le début de l'étape bonus "Prévisualiser votre branche directement dans Github" que nous avons couverte plus tôt dans ce blog.
clean_up_tags:
needs: deploy
environment: staging
runs-on: ubuntu-latest
container: # Using the
image: ghcr.io/ASSOCIATION_NAME/deployment-previews
credentials:
username: USERNAME #Username associated with github token
password: $ #Repo github token
steps:
- id: auth
uses: google-github-actions/auth@v1
with:
credentials_json: $
create_credentials_file: true #required for the check_status.py file
- name: Setup Cloud SDK
uses: google-github-actions/setup-gcloud@v1.1.1
with:
project_id: PROJECT_ID # Project id from GCP
- name: Clean up old tag
run: |
python3 /app/check_status.py cleanup --project-id PROJECT_ID \
--region LOCATION \
--service $
Nettoyer les tags est important car cela désactivera les prévisualisations de branche qui ont été fusionnées, rendant ainsi le script de nettoyage de l'étape suivante plus simple.
2. Supprimez les anciennes images et révisions fusionnées avec un script personnalisé
Au cours de mes recherches, je n'ai pas trouvé de moyen facile de supprimer les révisions inutilisées et les images associées dans l'Artifact Registry, alors j'ai créé mon propre script de nettoyage en bash.
clean_up_revisions_and_artifacts:
needs: clean_up_tags
environment: staging
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v3
- name: Login
uses: google-github-actions/auth@v1
with:
credentials_json: $
- name: Fix permissions
run: chmod 700 ./gcloud_scripts/clean_up.sh
- name: Run clean up script
run: ./gcloud_scripts/clean_up.sh -r LOCATION -s APP_NAME -p PROJECT_ID
shell: bash
Le script :
#!/bin/bash
while getopts r:s:p: flag # Flag handler
do
case "${flag}" in
r) region=${OPTARG};;
s) service=${OPTARG};;
p) project_id=${OPTARG};;
*)
esac
done
# Clean revisions
for rev in $(gcloud run revisions list --region="$region" --service="$service" --filter="status.conditions.type:Active AND status.conditions.status:'False'" --format='value(metadata.name)') # Fetch all the disabled revisions
do
echo "Deleting revision : $rev"
gcloud run revisions delete "$rev" --region="$region" --quiet # Quiet flag is necessary
done
# Clean artifact registry
active_images_digest=()
# Fetch all the revisions that are not retired
for active_revisions in $(gcloud run revisions list --region="$region" --service="$service" --filter="-status.conditions.reason:Retired" --format='value(metadata.name)')
# Make and Array of the image digest of active revisions
do
active_image=$(gcloud run revisions describe "$active_revisions" --region="$region" --format='value(image)')
active_image=${active_image:89:160}
echo "Active image : $active_image"
active_images_digest+=("$active_image")
done
# List images in Artifact Registry
for digest in $(gcloud artifacts docker images list "$region"-docker.pkg.dev/"$project_id"/shopify-backend/"$service" --format='value(version)')
do
# Delete images that are not in the Array of active images digest
if [[ ! " ${active_images_digest[*]} " =~ ${digest} ]]; then
echo "Digest to delete : $digest"
gcloud artifacts docker images delete "$region"-docker.pkg.dev/"$project_id"/shopify-backend/"$service"@"$digest" --delete-tags
fi
done
Bonus : Nettoyage de la database
Si vous avez décidé de mettre en œuvre la duplication de l'instance de database dans votre workflow, voici l'étape pour la supprimer une fois que la PR est fusionnée dans la branche principale. Elle inclut quelques astuces pour trouver le titre de la PR et le numéro du commit actuel sur lequel le workflow est exécuté. Nous avons besoin du titre de la PR pour vérifier si le commit fait partie d'une PR qui avait le mot-clé 'needs_db'.
teardown_pr_db:
environment: staging
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v3
- name: Login
uses: google-github-actions/auth@v1
with:
credentials_json: $
- name: Find PR title
run: |
PR_TITLE=$(gh pr list --search $ --state merged --json title --jq '.[0].title')
echo "PR_TITLE<<EOF" >> $GITHUB_ENV
echo $PR_TITLE >> $GITHUB_ENV
echo "EOF" >> $GITHUB_ENV
- name: Find PR number
if: contains(env.PR_TITLE, 'needs_db')
run: |
PR_NUMBER=$(gh pr list --search $ --state merged --json number --jq '.[0].number')
echo "PR_NUMBER<<EOF" >> $GITHUB_ENV
echo $PR_NUMBER >> $GITHUB_ENV
echo "EOF" >> $GITHUB_ENV
- name: Remove deletion protection
if: contains(env.PR_TITLE, 'needs_db')
run: gcloud sql instances patch instance_name-pr-$ --no-deletion-protection
- name: Delete database
if: contains(env.PR_TITLE, 'needs_db')
run: gcloud sql instances delete instance_name-api-pr-$
Voilà, vous avez maintenant mis en place un très bon flux de développement dans vos projets personnels ou organisationnels. Je sais que cela fait beaucoup à assimiler, alors n'hésitez pas à laisser des commentaires sur ce blog pour aider à l'améliorer ou si vous avez des questions !