CI/CD pour Rust avec GitHub Actions
Pourquoi CI/CD pour Rust avec GitHub Actions ?
Dans un environnement de développement professionnel, la continuité d'intégration (CI) et la livraison continue (CD) sont des pratiques essentielles qui contribuent à une meilleure qualité du code, à une réduction des erreurs et à une acceleration du cycle de développement. Pour les développeurs Rust, GitHub Actions est un outil puissant et facile à utiliser pour mettre en place ces pratiques.
Un cas concret d'utilisation serait le déploiement automatique d'une API web Rust sur un serveur de production chaque fois qu'un commit est poussé dans la branche principale. Cela permet une mise à jour rapide et fiable de l'application, avec des tests automatisés pour s'assurer que les nouvelles fonctionnalités ne causent pas de regressions.
Prerequis
Pour suivre ce tutoriel, vous aurez besoin de :
- Un compte GitHub
- Un environnement Rust installé (version recommandée : 1.56 ou plus tard)
- L'outil
cargoqui est partant avec Rust - Node.js et npm pour le projet fil rouge (si nécessaire)
Concepts fondamentaux
Workflow
Un workflow est une séquence d'étapes automatisées exécutées par GitHub Actions. Il est défini dans un fichier YAML placé dans le répertoire .github/workflows/ de votre dépôt.
name: CI/CD Rust
on:
push:
branches:
- main
Job
Un job est une série d'étapes qui s'exécutent en parallèle ou séquentiellement. Chaque job a un conteneur Docker spécifié.
jobs:
build:
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v2
Step
Un step est une action spécifique qui peut être exécutée dans un job. Elle peut exécuter des commandes shell, installer des dépendances, ou appeler d'autres actions.
- name: Install dependencies
run: |
cargo build --release
Action
Une action est une unité réutilisable qui effectue une tâche spécifique. Par exemple, l'action actions/checkout@v2 permet de cloner le code du dépôt dans l'environnement d'exécution.
- name: Run tests
run: cargo test
Mise en pratique : projet fil rouge
Nous allons créer un simple gestionnaire de tâches Rust qui peut ajouter, supprimer et afficher des tâches. Ce projet sera utilisé pour mettre en œuvre le workflow GitHub Actions.
Étape 1 : Création du projet
cargo new task_manager
cd task_manager
Étape 2 : Structure du projet
Le projet aura une structure de base comme suit :
task_manager/
├── Cargo.toml
├── src/
│ ├── main.rs
│ └── task_manager.rs
└── .github/workflows/
└── ci_cd.yml
Étape 3 : Ajout des fonctionnalités
Créez un fichier src/task_manager.rs avec le code suivant :
// src/task_manager.rs
use std::collections::HashMap;
pub struct TaskManager {
tasks: HashMap<String, bool>,
}
impl TaskManager {
pub fn new() -> Self {
TaskManager { tasks: HashMap::new() }
}
pub fn add_task(&mut self, task_name: String) {
self.tasks.insert(task_name, false);
}
pub fn complete_task(&mut self, task_name: &str) {
if let Some(task) = self.tasks.get_mut(task_name) {
*task = true;
}
}
pub fn list_tasks(&self) -> Vec<(String, bool)> {
self.tasks.iter().map(|(k, v)| (k.clone(), *v)).collect()
}
}
Étape 4 : Utilisation du gestionnaire de tâches
Modifiez le fichier src/main.rs pour utiliser le gestionnaire de tâches :
// src/main.rs
mod task_manager;
use task_manager::TaskManager;
fn main() {
let mut tm = TaskManager::new();
tm.add_task("Write code".to_string());
tm.complete_task("Write code");
for (task, completed) in tm.list_tasks() {
println!("{} - {}", task, completed);
}
}
Étape 5 : Ajout des tests
Créez un fichier src/task_manager.rs avec le code suivant :
// src/task_manager.rs
use std::collections::HashMap;
pub struct TaskManager {
tasks: HashMap<String, bool>,
}
impl TaskManager {
pub fn new() -> Self {
TaskManager { tasks: HashMap::new() }
}
pub fn add_task(&mut self, task_name: String) {
self.tasks.insert(task_name, false);
}
pub fn complete_task(&mut self, task_name: &str) {
if let Some(task) = self.tasks.get_mut(task_name) {
*task = true;
}
}
pub fn list_tasks(&self) -> Vec<(String, bool)> {
self.tasks.iter().map(|(k, v)| (k.clone(), *v)).collect()
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_add_task() {
let mut tm = TaskManager::new();
tm.add_task("Write code".to_string());
assert_eq!(tm.list_tasks(), vec![("Write code".to_string(), false)]);
}
#[test]
fn test_complete_task() {
let mut tm = TaskManager::new();
tm.add_task("Write code".to_string());
tm.complete_task("Write code");
assert_eq!(tm.list_tasks(), vec![("Write code".to_string(), true)]);
}
}
Étape 6 : Ajout du workflow GitHub Actions
Créez un fichier .github/workflows/ci_cd.yml avec le code suivant :
name: CI/CD Rust
on:
push:
branches:
- main
jobs:
build:
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v2
- name: Set up Rust
uses: actions-rs/toolchain@v1
with:
toolchain: stable
profile: minimal
components: rustfmt, clippy
- name: Install dependencies
run: cargo build --release
- name: Run tests
run: cargo test
- name: Build and publish (optional)
if: github.ref == 'refs/heads/main'
uses: softprops/action-gh-release@v1
with:
files: target/release/task_manager
Étape 7 : Exécution du workflow
Push le code sur GitHub :
git add .
git commit -m "Add task manager and CI/CD"
git push origin main
GitHub Actions exécutera automatiquement les étapes définies dans le fichier ci_cd.yml.
Erreurs frequentes et debugging
1. Erreur : Le code ne compile pas
// src/main.rs
fn main() {
let x = 5;
println!("The value of x is: {}", y);
}
Erreur :
error[E0425]: cannot find value `y` in this scope
Correction :
// src/main.rs
fn main() {
let x = 5;
println!("The value of x is: {}", x);
}
2. Erreur : Les tests ne passent pas
// src/task_manager.rs
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_add_task() {
let mut tm = TaskManager::new();
tm.add_task("Write code".to_string());
assert_eq!(tm.list_tasks(), vec![("Write code".to_string(), false)]);
}
#[test]
fn test_complete_task() {
let mut tm = TaskManager::new();
tm.add_task("Write code".to_string());
tm.complete_task("Write code");
assert_eq!(tm.list_tasks(), vec![("Code write".to_string(), true)]);
}
}
Erreur :
error: test failed, to rerun pass '--test task_manager'
Correction :
// src/task_manager.rs
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_add_task() {
let mut tm = TaskManager::new();
tm.add_task("Write code".to_string());
assert_eq!(tm.list_tasks(), vec![("Write code".to_string(), false)]);
}
#[test]
fn test_complete_task() {
let mut tm = TaskManager::new();
tm.add_task("Write code".to_string());
tm.complete_task("Write code");
assert_eq!(tm.list_tasks(), vec![("Write code".to_string(), true)]);
}
}
3. Erreur : Le workflow ne fonctionne pas
name: CI/CD Rust
on:
push:
branches:
- main
jobs:
build:
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v2
- name: Set up Rust
uses: actions-rs/toolchain@v1
with:
toolchain: stable
profile: minimal
components: rustfmt, clippy
- name: Install dependencies
run: cargo build --release
- name: Run tests
run: cargo test
- name: Build and publish (optional)
if: github.ref == 'refs/heads/main'
uses: softprops/action-gh-release@v1
with:
files: target/release/task_manager
Erreur :
Error: no files were uploaded, expected one or more of the following files to be present in the repository root directory: target/release/task_manager
Correction :
name: CI/CD Rust
on:
push:
branches:
- main
jobs:
build:
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v2
- name: Set up Rust
uses: actions-rs/toolchain@v1
with:
toolchain: stable
profile: minimal
components: rustfmt, clippy
- name: Install dependencies
run: cargo build --release
- name: Run tests
run: cargo test
- name: Build and publish (optional)
if: github.ref == 'refs/heads/main'
uses: softprops/action-gh-release@v1
with:
files: target/release/task_manager
Pour aller plus loin
1. Intégration avec Docker
Utilisez un conteneur Docker pour exécuter le workflow GitHub Actions.
name: CI/CD Rust
on:
push:
branches:
- main
jobs:
build:
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v2
- name: Set up Docker Buildx
id: buildx
uses: docker/setup-buildx-action@v1
- name: Build and publish image
uses: docker/build-push-action@v2
with:
context: .
push: $github.ref == 'refs/heads/main'
2. Utilisation de secrets GitHub
Stockez des informations sensibles comme les clés d'API dans les variables d'environnement GitHub.
name: CI/CD Rust
on:
push:
branches:
- main
jobs:
build:
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v2
- name: Set up Rust
uses: actions-rs/toolchain@v1
with:
toolchain: stable
profile: minimal
components: rustfmt, clippy
- name: Install dependencies
run: cargo build --release
- name: Run tests
run: cargo test
3. Déploiement sur AWS Elastic Beanstalk
Déployez votre application Rust sur AWS Elastic Beanstalk.
name: CI/CD Rust
on:
push:
branches:
- main
jobs:
build:
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v2
- name: Set up Rust
uses: actions-rs/toolchain@v1
with:
toolchain: stable
profile: minimal
components: rustfmt, clippy
- name: Install dependencies
run: cargo build --release
- name: Run tests
run: cargo test
- name: Deploy to AWS Elastic Beanstalk
uses: einaregilsson/beanstalk-deploy@v18
with:
aws_access_key: $secrets.AWS_ACCESS_KEY
aws_secret_key: $secrets.AWS_SECRET_KEY
application_name: task-manager-app
environment_name: task-manager-env
region: us-west-2
Défi pratique
Développez une API de blog en Rust avec les fonctionnalités suivantes :
- Création d'un article
- Lecture d'un article
- Mise à jour d'un article
- Suppression d'un article
Utilisez GitHub Actions pour automatiser le déploiement sur un serveur AWS Elastic Beanstalk.