translate comment
ci/woodpecker/push/tests Pipeline was successful

This commit is contained in:
2026-09-17 14:49:41 +00:00
parent 04cc172848
commit 78ad2bf701
2 changed files with 59 additions and 59 deletions
+56 -56
View File
@@ -1,20 +1,20 @@
//! Container lifecycle primitives for a parsed [`DevContainer`]. //! Primitives de cycle de vie de container pour un [`DevContainer`] analysé.
//! //!
//! This module shells out to a container runtime (`docker` or `podman`) to build //! Ce module invoque un runtime de containers (`docker` ou `podman`) pour construire
//! the devcontainer image, start a container with the workspace mounted, run the //! l'image devcontainer, démarrer un container avec le workspace monté, exécuter les
//! `postCreateCommand` / `postStartCommand` hooks and execute commands inside the //! hooks `postCreateCommand` / `postStartCommand` et lancer des commandes à l'intérieur
//! running container. //! du container en cours d'exécution.
//! //!
//! It is intentionally runtime-agnostic: any binary exposing the `docker` CLI //! Il est volontairement agnostique du runtime : tout binaire exposant l'interface
//! surface (including `podman`) can be used via [`ContainerRuntime::new`]. //! CLI `docker` (y compris `podman`) peut être utilisé via [`ContainerRuntime::new`].
//! //!
//! # Isolation //! # Isolation
//! //!
//! Each sandbox gets its own image tag, its own container and its own network. //! Chaque sandbox dispose de son propre tag d'image, de son propre container et de
//! The container starts attached to that network so the `postCreateCommand` / //! son propre réseau. Le container démarre attaché à ce réseau afin que les hooks
//! `postStartCommand` hooks can fetch dependencies (e.g. `npm install`); once the //! `postCreateCommand` / `postStartCommand` puissent récupérer des dépendances
//! hooks have run, the container is disconnected from the network for the rest of //! (par ex. `npm install`) ; une fois les hooks exécutés, le container est déconnecté
//! its lifetime. Every command is bounded by a timeout. //! du réseau pour le reste de sa durée de vie. Chaque commande est bornée par un timeout.
use std::{ use std::{
path::{Path, PathBuf}, path::{Path, PathBuf},
@@ -26,16 +26,16 @@ use tokio::process::Command;
use crate::DevContainer; use crate::DevContainer;
/// Timeout applied to build/run/stop/remove operations. /// Timeout appliqué aux opérations de build/run/stop/remove.
const DEFAULT_COMMAND_TIMEOUT: Duration = Duration::from_secs(600); const DEFAULT_COMMAND_TIMEOUT: Duration = Duration::from_secs(600);
/// Timeout applied to commands executed inside a running container. /// Timeout appliqué aux commandes exécutées dans un container en cours d'exécution.
const DEFAULT_EXEC_TIMEOUT: Duration = Duration::from_secs(60); const DEFAULT_EXEC_TIMEOUT: Duration = Duration::from_secs(60);
/// Result of a command executed inside a container. /// Résultat d'une commande exécutée dans un container.
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
pub struct ExecOutput { pub struct ExecOutput {
/// Exit code, or `-1` if the process was terminated by a signal. /// Code de sortie, ou `-1` si le processus a été terminé par un signal.
pub status: i32, pub status: i32,
pub stdout: String, pub stdout: String,
pub stderr: String, pub stderr: String,
@@ -46,7 +46,7 @@ impl ExecOutput {
self.status == 0 self.status == 0
} }
/// Turns a non-zero exit code into a [`ContainerError::Command`]. /// Transforme un code de sortie non nul en [`ContainerError::Command`].
pub fn ensure_success(self, program: &str, args: &[String]) -> Result<Self, ContainerError> { pub fn ensure_success(self, program: &str, args: &[String]) -> Result<Self, ContainerError> {
if self.success() { if self.success() {
return Ok(self); return Ok(self);
@@ -85,7 +85,7 @@ pub enum ContainerError {
}, },
} }
/// A container runtime binary exposing the `docker` CLI surface. /// Un binaire de runtime de containers exposant l'interface CLI `docker`.
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
pub struct ContainerRuntime { pub struct ContainerRuntime {
program: String, program: String,
@@ -108,7 +108,7 @@ impl ContainerRuntime {
Self::new("podman") Self::new("podman")
} }
/// Overrides the timeout applied to build/run/stop/remove operations. /// Remplace le timeout appliqué aux opérations de build/run/stop/remove.
pub fn with_timeout(mut self, timeout: Duration) -> Self { pub fn with_timeout(mut self, timeout: Duration) -> Self {
self.timeout = timeout; self.timeout = timeout;
self self
@@ -122,7 +122,7 @@ impl ContainerRuntime {
self.timeout self.timeout
} }
/// Checks that the runtime binary is present and responsive. /// Vérifie que le binaire du runtime est présent et répond.
pub async fn available(&self) -> bool { pub async fn available(&self) -> bool {
Command::new(&self.program) Command::new(&self.program)
.arg("version") .arg("version")
@@ -135,15 +135,15 @@ impl ContainerRuntime {
.unwrap_or(false) .unwrap_or(false)
} }
/// Runs the runtime with the given arguments, capturing stdout/stderr. /// Exécute le runtime avec les arguments donnés, en capturant stdout/stderr.
/// ///
/// Only spawn failures and timeouts are reported as errors; a non-zero exit /// Seuls les échecs de lancement (spawn) et les timeouts sont des erreurs ; un
/// code is returned in the [`ExecOutput`] so callers can decide how to react. /// code non nul est renvoyé dans [`ExecOutput`] afin que les appelants réagissent.
pub async fn run(&self, args: &[String]) -> Result<ExecOutput, ContainerError> { pub async fn run(&self, args: &[String]) -> Result<ExecOutput, ContainerError> {
self.run_with_timeout(args, self.timeout).await self.run_with_timeout(args, self.timeout).await
} }
/// Like [`run`](Self::run) with an explicit timeout. /// Comme [`run`](Self::run), mais avec un timeout explicite.
pub async fn run_with_timeout( pub async fn run_with_timeout(
&self, &self,
args: &[String], args: &[String],
@@ -174,7 +174,7 @@ impl ContainerRuntime {
} }
} }
/// A running devcontainer. /// Un devcontainer en cours d'exécution.
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
pub struct Container { pub struct Container {
runtime: ContainerRuntime, runtime: ContainerRuntime,
@@ -194,10 +194,10 @@ impl Container {
&self.workspace_folder &self.workspace_folder
} }
/// Executes a command inside the container, returning its output. /// Exécute une commande dans le container et renvoie sa sortie.
/// ///
/// The command is passed as an argv vector (no shell), so no quoting or /// La commande est transmise sous forme de vecteur d'arguments (argv, sans shell),
/// interpolation is performed. /// donc aucun échappement ni interpolation n'est effectué.
pub async fn exec(&self, cmd: &[&str]) -> Result<ExecOutput, ContainerError> { pub async fn exec(&self, cmd: &[&str]) -> Result<ExecOutput, ContainerError> {
self.exec_with_timeout(cmd, DEFAULT_EXEC_TIMEOUT).await self.exec_with_timeout(cmd, DEFAULT_EXEC_TIMEOUT).await
} }
@@ -220,12 +220,12 @@ impl Container {
self.runtime.run_with_timeout(&args, timeout).await self.runtime.run_with_timeout(&args, timeout).await
} }
/// Executes a shell script inside the container via `sh -c`. /// Exécute un script shell dans le container via `sh -c`.
pub async fn exec_shell(&self, script: &str) -> Result<ExecOutput, ContainerError> { pub async fn exec_shell(&self, script: &str) -> Result<ExecOutput, ContainerError> {
self.exec(&["sh", "-c", script]).await self.exec(&["sh", "-c", script]).await
} }
/// Stops the container. /// Arrête le container.
pub async fn stop(&self) -> Result<(), ContainerError> { pub async fn stop(&self) -> Result<(), ContainerError> {
let args = vec!["stop".to_string(), self.name.clone()]; let args = vec!["stop".to_string(), self.name.clone()];
self.runtime self.runtime
@@ -235,9 +235,9 @@ impl Container {
Ok(()) Ok(())
} }
/// Removes the container, its network and its image. /// Supprime le container, son réseau et son image.
/// ///
/// Network and image removal are best-effort: they may already be gone. /// La suppression du réseau et de l'image est best-effort : ils peuvent déjà être absents.
pub async fn remove(&self) -> Result<(), ContainerError> { pub async fn remove(&self) -> Result<(), ContainerError> {
let args = vec![ let args = vec![
"rm".to_string(), "rm".to_string(),
@@ -265,21 +265,21 @@ impl Container {
} }
impl DevContainer { impl DevContainer {
/// Base image name derived from the devcontainer name. /// Nom de l'image de base dérivé du nom du devcontainer.
pub fn image_name(&self) -> String { pub fn image_name(&self) -> String {
let base = self.name.as_deref().unwrap_or("devcontainer"); let base = self.name.as_deref().unwrap_or("devcontainer");
format!("devcontainer-rs/{}", sanitize(base)) format!("devcontainer-rs/{}", sanitize(base))
} }
/// Unique image tag for a single sandbox run. /// Tag d'image unique pour une exécution de sandbox donnée.
/// ///
/// Uniqueness matters: two concurrent sandboxes (possibly for different /// L'unicité est importante : deux sandboxes concurrentes (éventuellement pour des
/// repositories sharing a devcontainer name) must not race on a shared tag. /// dépôts différents partageant un nom de devcontainer) ne doivent pas se disputer le même tag.
pub fn image_tag(&self) -> String { pub fn image_tag(&self) -> String {
format!("{}:{}", self.image_name(), unique_suffix()) format!("{}:{}", self.image_name(), unique_suffix())
} }
/// Arguments passed to `docker build` (everything after the `build` verb). /// Arguments passés à `docker build` (tout ce qui suit le verbe `build`).
pub fn build_args(&self, image_tag: &str) -> Vec<String> { pub fn build_args(&self, image_tag: &str) -> Vec<String> {
let context = self let context = self
.container_file_path .container_file_path
@@ -302,7 +302,7 @@ impl DevContainer {
args args
} }
/// Arguments passed to `docker run` (everything after the `run` verb). /// Arguments passés à `docker run` (tout ce qui suit le verbe `run`).
pub fn run_args( pub fn run_args(
&self, &self,
workspace_dir: &Path, workspace_dir: &Path,
@@ -340,27 +340,27 @@ impl DevContainer {
args.extend(self.run_args.iter().cloned()); args.extend(self.run_args.iter().cloned());
args.push(image_tag.to_string()); args.push(image_tag.to_string());
// Keep the container alive so we can `exec` into it. // Maintient le container en vie pour pouvoir y exécuter `exec`.
args.push("sleep".to_string()); args.push("sleep".to_string());
args.push("infinity".to_string()); args.push("infinity".to_string());
args args
} }
/// Workspace folder inside the container, defaulting to `/workspaces/workspace`. /// Dossier de workspace dans le container, par défaut `/workspaces/workspace`.
pub fn workspace_folder(&self) -> String { pub fn workspace_folder(&self) -> String {
self.workspace_folder self.workspace_folder
.clone() .clone()
.unwrap_or_else(|| "/workspaces/workspace".to_string()) .unwrap_or_else(|| "/workspaces/workspace".to_string())
} }
/// Unique container name for this run. /// Nom de container unique pour cette exécution.
pub fn container_name(&self) -> String { pub fn container_name(&self) -> String {
let base = self.name.as_deref().unwrap_or("devcontainer"); let base = self.name.as_deref().unwrap_or("devcontainer");
format!("devcontainer-rs-{}-{}", sanitize(base), unique_suffix()) format!("devcontainer-rs-{}-{}", sanitize(base), unique_suffix())
} }
/// Builds the devcontainer image under `image_tag`. /// Construit l'image devcontainer sous `image_tag`.
pub async fn build( pub async fn build(
&self, &self,
runtime: &ContainerRuntime, runtime: &ContainerRuntime,
@@ -377,12 +377,12 @@ impl DevContainer {
Ok(()) Ok(())
} }
/// Builds the image, starts the container with the workspace mounted, runs /// Construit l'image, démarre le container avec le workspace monté, exécute les
/// the `postCreateCommand` / `postStartCommand` hooks with network access, /// hooks `postCreateCommand` / `postStartCommand` avec accès au réseau, puis
/// then disconnects the container from the network. /// déconnecte le container du réseau.
/// ///
/// On any failure the container, network and image are cleaned up before /// En cas d'échec, le container, le réseau et l'image sont nettoyés avant de
/// returning, so no resource is leaked. /// renvoyer l'erreur, afin qu'aucune ressource ne soit laissée en place.
pub async fn up( pub async fn up(
&self, &self,
runtime: &ContainerRuntime, runtime: &ContainerRuntime,
@@ -394,7 +394,7 @@ impl DevContainer {
let name = self.container_name(); let name = self.container_name();
let network = format!("{name}-net"); let network = format!("{name}-net");
// Dedicated network so connectivity can be cut after the hooks. // Réseau dédié afin de pouvoir couper la connectivité après les hooks.
let args = vec!["network".to_string(), "create".to_string(), network.clone()]; let args = vec!["network".to_string(), "create".to_string(), network.clone()];
if let Err(err) = runtime if let Err(err) = runtime
.run(&args) .run(&args)
@@ -429,13 +429,13 @@ impl DevContainer {
image: Some(image_tag), image: Some(image_tag),
}; };
// Hooks run with network access (dependency installation, etc.). // Les hooks s'exécutent avec accès au réseau (installation de dépendances, etc.).
if let Err(err) = self.run_hooks(&container).await { if let Err(err) = self.run_hooks(&container).await {
let _ = container.remove().await; let _ = container.remove().await;
return Err(err); return Err(err);
} }
// Cut network access for the rest of the sandbox lifetime. // Coupe l'accès réseau pour le reste de la durée de vie de la sandbox.
let args = vec![ let args = vec![
"network".to_string(), "network".to_string(),
"disconnect".to_string(), "disconnect".to_string(),
@@ -455,8 +455,8 @@ impl DevContainer {
} }
async fn run_hooks(&self, container: &Container) -> Result<(), ContainerError> { async fn run_hooks(&self, container: &Container) -> Result<(), ContainerError> {
// Hooks may install dependencies, so they get the long command timeout // Les hooks peuvent installer des dépendances, ils utilisent donc le timeout
// rather than the short one used for tool execution. // long des commandes plutôt que le court réservé à l'exécution des outils.
let timeout = container.runtime.timeout(); let timeout = container.runtime.timeout();
for command in [&self.post_create_command, &self.post_start_command] for command in [&self.post_create_command, &self.post_start_command]
@@ -476,7 +476,7 @@ impl DevContainer {
} }
} }
/// Sanitizes a string so it can be used as a docker image/container name. /// Nettoie une chaîne pour qu'elle puisse servir de nom d'image/container docker.
fn sanitize(input: &str) -> String { fn sanitize(input: &str) -> String {
let sanitized: String = input let sanitized: String = input
.chars() .chars()
@@ -497,7 +497,7 @@ fn sanitize(input: &str) -> String {
} }
} }
/// Suffix unique to a sandbox run, combining the process id and a timestamp. /// Suffixe unique à une exécution de sandbox, combinant l'identifiant de processus et un horodatage.
fn unique_suffix() -> String { fn unique_suffix() -> String {
let nanos = SystemTime::now() let nanos = SystemTime::now()
.duration_since(UNIX_EPOCH) .duration_since(UNIX_EPOCH)
@@ -507,8 +507,8 @@ fn unique_suffix() -> String {
format!("{}-{}", std::process::id(), nanos) format!("{}-{}", std::process::id(), nanos)
} }
/// Lexically normalizes a path, resolving `.` and `..` without touching the /// Normalise lexicalement un chemin, en résolvant `.` et `..` sans toucher au
/// filesystem. Returns `None` if the path escapes its root. /// système de fichiers. Renvoie `None` si le chemin sort de sa racine.
pub fn normalize(path: &Path) -> Option<PathBuf> { pub fn normalize(path: &Path) -> Option<PathBuf> {
use std::path::Component; use std::path::Component;
+3 -3
View File
@@ -119,9 +119,9 @@ impl TryFrom<(DevContainerSchema, PathBuf)> for DevContainer {
} }
} }
/// Resolves `${localEnv:VAR}` and `${localEnv:VAR:default}` references using the /// Résout les références `${localEnv:VAR}` et `${localEnv:VAR:default}` à l'aide de
/// current process environment, as described by the devcontainer specification. /// l'environnement du processus courant, comme décrit par la spécification devcontainer.
/// Unresolved variables without a default expand to an empty string. /// Les variables non résolues sans valeur par défaut sont remplacées par une chaîne vide.
fn substitute_local_env(input: &str) -> String { fn substitute_local_env(input: &str) -> String {
const PREFIX: &str = "${localEnv:"; const PREFIX: &str = "${localEnv:";