GitHub Actions est un outil d'intégration continue (CI) et de déploiement continu (CD) permettant d'automatiser des « workflows » (= une liste de processus prédéfinis) sans utiliser de service externe.
Dans cet article, nous allons nous intéresser à l'intégration continue (CI), en utilisant GitHub Actions avec un projet Django.
Les workflows sont déclenchés par des événements dans votre dépôt GitHub, comme un push ou une pull request.
GitHub Actions permet donc d'exécuter un workflow, déclenché par un événement, qui va exécuter un ou plusieurs travaux (jobs).
Ces jobs sont eux-mêmes composés d'étapes (steps).
Concrètement, ces workflows permettent d'automatiser le lancement de tests, le linting (analyse statique du code source pour détecter des problèmes) ou encore le déploiement de votre code.
Structure d'un fichier GitHub Actions
Les workflows sont définis dans des fichiers YAML (extension .yml ou .yaml). YAML est un format texte qui utilise l'indentation pour organiser les relations hiérarchiques entre les données.
name: Nom du workflow
on: # Événements qui déclenchent le workflow
push:
branches: [ "main" ]
pull_request:
branches: [ "main" ]
jobs:
nom_du_job:
runs-on: ubuntu-latest # Système d'exploitation du runner
steps:
- name: Nom de l'étape
uses: action/quelconque@version # Action à utiliser
- name: Autre étape
run: commande-à-exécuter # Commande shell à exécuter
Dans cet exemple, plusieurs composants :
-
Le nom (
name) du workflow qui apparaît dans l'interface GitHub -
La section
onqui définit les déclencheurs du workflow (push et pull request dans l'exemple) -
Les
jobsqui représentent les travaux à effectuer -
runs-onqui spécifie l'environnement d'exécution
Concernant le runs-on, GitHub propose plusieurs types d'environnements virtuels. Il est possible d'exécuter les actions sur Ubuntu (ubuntu-latest), Windows (windows-latest) ou macOS (macos-latest) par exemple. Vous retrouverez une liste des environnements disponibles sur cette page.
Chaque job contient des étapes (steps) qui peuvent utiliser des actions prédéfinies (uses) ou exécuter des commandes shell (run).
Configuration de base pour un projet Django
Création de l'Action
Pour cet article, j'ai créé un projet Django et un dépôt sur GitHub.
Vous pouvez ensuite depuis votre dépôt sur Github, cliquer sur l'onglet Actions et utiliser un template Django existant (s'il n'apparaît pas directement, vous pouvez utiliser le moteur de recherche).
Onglet Actions de github
Avant les prochaines étapes :
-
Pensez à générer un
requirements.txtpour votre projet -
Ajoutez un test dans votre projet (vous pouvez ajouter un test factice qui retourne
Trueau départ pour tester le bon fonctionnement du workflow)
En sélectionnant le template Django via le bouton Configure, un fichier est créé dans .github/workflows/django.yml.
J'y apporte quelques modifications :
-
Les versions de Python : je spécifie des versions plus récentes, tout en les nommant entre guillemets (cela évite des problèmes d'interprétation du fichier)
-
La commande de test, car j'utilise pytest
Vous pouvez ensuite cliquer sur Commit changes et le workflow va s'exécuter.
Fichier django.yml de github actions
En revenant sur l'onglet Actions, nous remarquons que le workflow s'est exécuté avec succès.
Workflow exécuté avec succès
Pensez maintenant à faire un git pull dans le terminal de votre projet pour intégrer le workflow en local.
Composition d'un fichier de workflow
Comme nous l'avons vu précédemment, un workflow est défini par un fichier YAML situé dans le répertoire .github/workflows de votre dépôt. Ce fichier décrit l'ensemble du processus d'automatisation.
name: Django CI
on:
push:
branches: [ "main" ]
pull_request:
branches: [ "main" ]
jobs:
build:
runs-on: ubuntu-latest
strategy:
max-parallel: 4
matrix:
python-version: ["3.10", "3.11"]
steps:
- uses: actions/checkout@v4
- name: Set up Python ${{ matrix.python-version }}
uses: actions/setup-python@v3
with:
python-version: ${{ matrix.python-version }}
- name: Install Dependencies
run: |
python -m pip install --upgrade pip
pip install -r requirements.txt
- name: Run Tests
run: |
pytest
Les déclencheurs
on:
push:
branches: [ "main" ]
pull_request:
branches: [ "main" ]
Cette section va déterminer quand le workflow va s'exécuter. Dans ce cas :
-
à chaque push sur la branche main,
-
à chaque pull request ciblant la branche main.
Les jobs
jobs:
build:
Un job est une tâche qui va s'exécuter dans votre workflow, qui peut éventuellement en contenir plusieurs. Dans notre cas, nous avons un unique job que nous appelons build (mais on pourrait l'appeler autrement).
L'environnement d'exécution
runs-on: ubuntu-latest
Cette ligne spécifie sur quel type de machine virtuelle sera exécuté le job. Dans ce cas, la dernière version d'ubuntu.
La stratégie
strategy:
max-parallel: 4
matrix:
python-version: ["3.10", "3.11"]
-
matrixpermet de définir des combinaisons de variables pour générer des exécutions multiples du job, -
max-parallellimite le nombre de jobs du matrix qui peuvent s'exécuter en même temps.
Les étapes d'exécution (steps)
- uses: actions/checkout@v4
Cette étape utilise l'action checkout qui permet de récupérer le code source du dépôt et de le clôner dans l'environnement d'exécution. Le paramètre @v4 indique que nous utilisons la version 4 de cette action.
- name: Set up Python ${{ matrix.python-version }}
uses: actions/setup-python@v3
with:
python-version: ${{ matrix.python-version }}
Cette étape permet de configurer l'environnement Python pour chaque version spécifiée dans le matrix.
python-version: ${{ matrix.python-version }} permet de préciser la version de python à installer de manière dynamique.
L'installation des dépendances
- name: Install Dependencies
run: |
python -m pip install --upgrade pip
pip install -r requirements.txt
Cette étape utilise run pour exécuter des commandes shell, pour installer les dépendances depuis le fichier requirements.txt.
L'exécution des tests
- name: Run Tests
run: |
pytest
Cette dernière étape exécute la commande pytest pour lancer les tests du projet Django.
Configurer les secrets
Il arrive bien souvent qu'on ait besoin de créer un fichier .env pour stocker des informations sensibles comme les clés secrètes, des identifiants de base de données ou des clés d'API.
Ces informations n'apparaissent (heureusement !) pas en clair dans votre code et sont bien souvent stockées dans un fichier à part.
Gestion des données avec django-environ
Dans le projet qui sert d'exemple, j'installe django-environ pour gérer les données sensibles. Ici, on prendra l'exemple de la SECRET_KEY de Django.
Attention
N'oubliez pas d'ajouter django-environ au fichier requirements.txt. Sinon, le package ne sera pas installé au moment de l'exécution du workflow.
Voici à quoi ressemblent les premières lignes du fichier settings.py :
from pathlib import Path import environ env = environ.Env() # Build paths inside the project like this: BASE_DIR / 'subdir'. BASE_DIR = Path(__file__).resolve().parent.parent environ.Env.read_env(BASE_DIR / '.env') # Quick-start development settings - unsuitable for production # See https://docs.djangoproject.com/en/5.2/howto/deployment/checklist/ # SECURITY WARNING: keep the secret key used in production secret! SECRET_KEY = env('SECRET_KEY')
Il faut importer environ, initialiser une variable env avec :
env = environ.Env()
Et récupérer le fichier .env avec :
environ.Env.read_env(BASE_DIR / '.env')
Il faut donc créer un fichier .env dans votre projet Django, y insérer la clé secrète :
SECRET_KEY=django-insecure-p67cf0xh#*tax*c-gkv2ij96659v#^x$3+542br2$4=n_kge_h
Et la récupérer dans votre fichier settings.py :
SECRET_KEY = env('SECRET_KEY')
Configurer les secrets sur GitHub
Il faut maintenant penser à configurer les secrets sur GitHub, pour récupérer cette clé secrète, car le fichier .env n'est bien sûr pas versionné pour ne pas exposer les clés.
Il faut donc un moyen de donner ces clés secrètes à notre workflow.
Le menu settings de GitHub
Pour ce faire, il suffit d'aller dans le menu Settings > Secrets and variables > Actions et cliquer sur New repository secret.
Ajout d'un secret dans GitHub
Utiliser les secrets dans le workflow
Une fois les secrets configurés (dans cet exemple je n'ai configuré que la clé secrète de Django), il faut la référencer dans le workflow :
name: Django CI
on:
push:
branches: [ "main" ]
pull_request:
branches: [ "main" ]
jobs:
build:
runs-on: ubuntu-latest
env:
SECRET_KEY: ${{ secrets.SECRET_KEY }}
strategy:
max-parallel: 4
matrix:
python-version: ["3.10", "3.11"]
steps:
- uses: actions/checkout@v4
- name: Set up Python ${{ matrix.python-version }}
uses: actions/setup-python@v3
with:
python-version: ${{ matrix.python-version }}
- name: Install Dependencies
run: |
python -m pip install --upgrade pip
pip install -r requirements.txt
- name: Run Tests
run: |
pytest
Dans le job build, j'ai ajouté :
env:
SECRET_KEY: ${{ secrets.SECRET_KEY }}
Vous pouvez évidemment ajouter plusieurs secrets dans votre dépôt GitHub, mais pensez bien à les renseigner à chaque fois dans votre fichier de workflow.
Désormais, quand je fais un commit et que je push mes modifications, le workflow est de nouveau exécuté. Si vous avez bien configuré votre ou vos secrets, vous ne devriez pas avoir d'erreurs :
Nouvelle exécution du workflow
Avec quelques lignes de YAML, des secrets bien configurés, vous avez mis en place un workflow directement sur GitHub en quelques minutes 💪
Vous disposez désormais d'un processus d'intégration continue (CI) qui permet de vérifier à chaque modification de votre projet que vos tests et autres processus s'exécutent correctement.
Cela vous permet d'éviter beaucoup de problèmes en amont.
Vous pourrez par la suite envisager un système de déploiement continu (CD) pour déployer votre code si l'intégration continue passe avec succès.
Cela permet d'être sûr que votre projet fonctionnera bien en production (du moins, les fonctionnalités qui auront été testées, si celles-ci sont correctement testées, mais c'est un autre sujet !).