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
//! the devcontainer image, start a container with the workspace mounted, run the
//! `postCreateCommand` / `postStartCommand` hooks and execute commands inside the
//! running container.
//! Ce module invoque un runtime de containers (`docker` ou `podman`) pour construire
//! l'image devcontainer, démarrer un container avec le workspace monté, exécuter les
//! hooks `postCreateCommand` / `postStartCommand` et lancer des commandes à l'intérieur
//! du container en cours d'exécution.
//!
//! It is intentionally runtime-agnostic: any binary exposing the `docker` CLI
//! surface (including `podman`) can be used via [`ContainerRuntime::new`].
//! Il est volontairement agnostique du runtime : tout binaire exposant l'interface
//! CLI `docker` (y compris `podman`) peut être utilisé via [`ContainerRuntime::new`].
//!
//! # Isolation
//!
//! Each sandbox gets its own image tag, its own container and its own network.
//! The container starts attached to that network so the `postCreateCommand` /
//! `postStartCommand` hooks can fetch dependencies (e.g. `npm install`); once the
//! hooks have run, the container is disconnected from the network for the rest of
//! its lifetime. Every command is bounded by a timeout.
//! Chaque sandbox dispose de son propre tag d'image, de son propre container et de
//! son propre réseau. Le container démarre attaché à ce réseau afin que les hooks
//! `postCreateCommand` / `postStartCommand` puissent récupérer des dépendances
//! (par ex. `npm install`) ; une fois les hooks exécutés, le container est déconnecté
//! du réseau pour le reste de sa durée de vie. Chaque commande est bornée par un timeout.
use std::{
path::{Path, PathBuf},
@@ -26,16 +26,16 @@ use tokio::process::Command;
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);
/// 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);
/// Result of a command executed inside a container.
/// Résultat d'une commande exécutée dans un container.
#[derive(Debug, Clone)]
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 stdout: String,
pub stderr: String,
@@ -46,7 +46,7 @@ impl ExecOutput {
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> {
if self.success() {
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)]
pub struct ContainerRuntime {
program: String,
@@ -108,7 +108,7 @@ impl ContainerRuntime {
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 {
self.timeout = timeout;
self
@@ -122,7 +122,7 @@ impl ContainerRuntime {
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 {
Command::new(&self.program)
.arg("version")
@@ -135,15 +135,15 @@ impl ContainerRuntime {
.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
/// code is returned in the [`ExecOutput`] so callers can decide how to react.
/// Seuls les échecs de lancement (spawn) et les timeouts sont des erreurs ; un
/// code non nul est renvoyé dans [`ExecOutput`] afin que les appelants réagissent.
pub async fn run(&self, args: &[String]) -> Result<ExecOutput, ContainerError> {
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(
&self,
args: &[String],
@@ -174,7 +174,7 @@ impl ContainerRuntime {
}
}
/// A running devcontainer.
/// Un devcontainer en cours d'exécution.
#[derive(Debug, Clone)]
pub struct Container {
runtime: ContainerRuntime,
@@ -194,10 +194,10 @@ impl Container {
&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
/// interpolation is performed.
/// La commande est transmise sous forme de vecteur d'arguments (argv, sans shell),
/// donc aucun échappement ni interpolation n'est effectué.
pub async fn exec(&self, cmd: &[&str]) -> Result<ExecOutput, ContainerError> {
self.exec_with_timeout(cmd, DEFAULT_EXEC_TIMEOUT).await
}
@@ -220,12 +220,12 @@ impl Container {
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> {
self.exec(&["sh", "-c", script]).await
}
/// Stops the container.
/// Arrête le container.
pub async fn stop(&self) -> Result<(), ContainerError> {
let args = vec!["stop".to_string(), self.name.clone()];
self.runtime
@@ -235,9 +235,9 @@ impl Container {
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> {
let args = vec![
"rm".to_string(),
@@ -265,21 +265,21 @@ impl Container {
}
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 {
let base = self.name.as_deref().unwrap_or("devcontainer");
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
/// repositories sharing a devcontainer name) must not race on a shared tag.
/// L'unicité est importante : deux sandboxes concurrentes (éventuellement pour des
/// 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 {
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> {
let context = self
.container_file_path
@@ -302,7 +302,7 @@ impl DevContainer {
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(
&self,
workspace_dir: &Path,
@@ -340,27 +340,27 @@ impl DevContainer {
args.extend(self.run_args.iter().cloned());
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("infinity".to_string());
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 {
self.workspace_folder
.clone()
.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 {
let base = self.name.as_deref().unwrap_or("devcontainer");
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(
&self,
runtime: &ContainerRuntime,
@@ -377,12 +377,12 @@ impl DevContainer {
Ok(())
}
/// Builds the image, starts the container with the workspace mounted, runs
/// the `postCreateCommand` / `postStartCommand` hooks with network access,
/// then disconnects the container from the network.
/// Construit l'image, démarre le container avec le workspace monté, exécute les
/// hooks `postCreateCommand` / `postStartCommand` avec accès au réseau, puis
/// déconnecte le container du réseau.
///
/// On any failure the container, network and image are cleaned up before
/// returning, so no resource is leaked.
/// En cas d'échec, le container, le réseau et l'image sont nettoyés avant de
/// renvoyer l'erreur, afin qu'aucune ressource ne soit laissée en place.
pub async fn up(
&self,
runtime: &ContainerRuntime,
@@ -394,7 +394,7 @@ impl DevContainer {
let name = self.container_name();
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()];
if let Err(err) = runtime
.run(&args)
@@ -429,13 +429,13 @@ impl DevContainer {
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 {
let _ = container.remove().await;
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![
"network".to_string(),
"disconnect".to_string(),
@@ -455,8 +455,8 @@ impl DevContainer {
}
async fn run_hooks(&self, container: &Container) -> Result<(), ContainerError> {
// Hooks may install dependencies, so they get the long command timeout
// rather than the short one used for tool execution.
// Les hooks peuvent installer des dépendances, ils utilisent donc le timeout
// long des commandes plutôt que le court réservé à l'exécution des outils.
let timeout = container.runtime.timeout();
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 {
let sanitized: String = input
.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 {
let nanos = SystemTime::now()
.duration_since(UNIX_EPOCH)
@@ -507,8 +507,8 @@ fn unique_suffix() -> String {
format!("{}-{}", std::process::id(), nanos)
}
/// Lexically normalizes a path, resolving `.` and `..` without touching the
/// filesystem. Returns `None` if the path escapes its root.
/// Normalise lexicalement un chemin, en résolvant `.` et `..` sans toucher au
/// système de fichiers. Renvoie `None` si le chemin sort de sa racine.
pub fn normalize(path: &Path) -> Option<PathBuf> {
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
/// current process environment, as described by the devcontainer specification.
/// Unresolved variables without a default expand to an empty string.
/// Résout les références `${localEnv:VAR}` et `${localEnv:VAR:default}` à l'aide de
/// l'environnement du processus courant, comme décrit par la spécification devcontainer.
/// 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 {
const PREFIX: &str = "${localEnv:";