add default image docker fallback #8

Merged
qpismont merged 5 commits from 1.2.1 into main 2026-09-29 20:50:36 +02:00
Showing only changes of commit 4999091f32 - Show all commits
+73 -53
View File
@@ -84,18 +84,20 @@ impl DevContainer {
workspace_dir: &Path, workspace_dir: &Path,
) -> Result<Container, ContainerError> { ) -> Result<Container, ContainerError> {
let id = unique_id(); let id = unique_id();
let image = self let image = self
.image .image
.clone() .clone()
.unwrap_or_else(|| self.local_image_tag(&id)); .unwrap_or_else(|| self.local_image_tag(&id));
let owns_image = self.image.is_none(); let owns_image = self.image.is_none();
let container_name = self.container_name(&id);
let network = format!("{container_name}-net");
let workspace_folder = self.workspace_folder();
info!(image = %image, "Starting sandbox container"); info!(image = %image, "Starting sandbox container");
match owns_image { let setup = async {
true => { if owns_image {
let container_file_path = self.container_file_path.as_ref().ok_or_else(|| { let container_file_path = self.container_file_path.as_ref().ok_or_else(|| {
ContainerError::Unexpected(String::from( ContainerError::Unexpected(String::from(
"the devcontainer has neither an image nor a container file", "the devcontainer has neither an image nor a container file",
@@ -103,74 +105,78 @@ impl DevContainer {
})?; })?;
self.build(runtime, container_file_path, &image).await?; self.build(runtime, container_file_path, &image).await?;
} } else {
false => {
runtime.pull_image(&image).await?; runtime.pull_image(&image).await?;
} }
}
let name = self.container_name(&id); runtime.create_network(&network).await?;
let network = format!("{name}-net");
runtime.create_network(&network).await?; let body = ContainerCreateBody {
image: Some(image.clone()),
let workspace_folder = self.workspace_folder(); cmd: Some(vec![String::from("sleep"), String::from("infinity")]),
let body = ContainerCreateBody { user: self.remote_user.clone(),
image: Some(image.clone()), env: Some(
cmd: Some(vec![String::from("sleep"), String::from("infinity")]), self.container_env
user: self.remote_user.clone(), .iter()
env: Some( .map(|(key, value)| format!("{key}={value}"))
self.container_env .collect(),
.iter() ),
.map(|(key, value)| format!("{key}={value}")) working_dir: Some(workspace_folder.clone()),
.collect(), host_config: Some(HostConfig {
), network_mode: Some(network.clone()),
working_dir: Some(workspace_folder.clone()), ..Default::default()
host_config: Some(HostConfig { }),
network_mode: Some(network.clone()),
..Default::default() ..Default::default()
}), };
..Default::default()
};
if let Err(err) = runtime.create_container(&name, body).await { runtime.create_container(&container_name, body).await?;
let _ = runtime.remove_network(&network).await; runtime.start_container(&container_name).await?;
return Err(err); runtime
.upload_directory(&container_name, &workspace_folder, workspace_dir)
.await?;
Ok::<(), ContainerError>(())
} }
.await;
if let Err(err) = runtime.start_container(&name).await { if let Err(err) = setup {
let _ = runtime.remove_container(&name).await; cleanup_failed_startup(
let _ = runtime.remove_network(&network).await; runtime,
return Err(err); &container_name,
} &network,
owns_image.then_some(image.as_str()),
if let Err(err) = runtime )
.upload_directory(&name, &workspace_folder, workspace_dir) .await;
.await
{
let _ = runtime.remove_container(&name).await;
let _ = runtime.remove_network(&network).await;
return Err(err); return Err(err);
} }
let container = Container::new( let container = Container::new(
runtime.clone(), runtime.clone(),
name, container_name.clone(),
self.workspace_folder(), workspace_folder,
self.remote_user.clone(), self.remote_user.clone(),
Some(network.clone()), Some(network.clone()),
owns_image.then_some(image), owns_image.then_some(image.clone()),
); );
// Les hooks s'exécutent avec accès au réseau (installation de dépendances, etc.). let post_setup = async {
Outdated
Review

[performance] Le tag d'une image construite n'est enregistré dans Container::owned_image qu'après la création, le démarrage et l'upload du workspace. Si le réseau, la création/le démarrage du container ou l'upload échoue avant cette ligne, l'image construite reste dans le daemon sans possibilité de nettoyage, ce qui laisse des images potentiellement volumineuses à chaque sandbox échouée. Nettoyez l'image dans chaque chemin d'erreur après le build, ou encapsulez sa propriété dans un garde-fou jusqu'à la création de Container.

**[performance]** Le tag d'une image construite n'est enregistré dans `Container::owned_image` qu'après la création, le démarrage et l'upload du workspace. Si le réseau, la création/le démarrage du container ou l'upload échoue avant cette ligne, l'image construite reste dans le daemon sans possibilité de nettoyage, ce qui laisse des images potentiellement volumineuses à chaque sandbox échouée. Nettoyez l'image dans chaque chemin d'erreur après le build, ou encapsulez sa propriété dans un garde-fou jusqu'à la création de `Container`.
if let Err(err) = self.run_hooks(&container, runtime.timeout()).await { self.run_hooks(&container, runtime.timeout()).await?;
let _ = container.remove().await; runtime
return Err(err); .disconnect_network(&network, &container_name)
} .await?;
// Coupe l'accès réseau pour le reste de la durée de vie de la sandbox. Ok::<(), ContainerError>(())
if let Err(err) = runtime.disconnect_network(&network, container.name()).await { }
let _ = container.remove().await; .await;
if let Err(err) = post_setup {
cleanup_failed_startup(
runtime,
&container_name,
&network,
owns_image.then_some(image.as_str()),
)
.await;
return Err(err); return Err(err);
} }
@@ -203,6 +209,20 @@ impl DevContainer {
} }
} }
async fn cleanup_failed_startup(
runtime: &ContainerRuntime,
container: &str,
network: &str,
owned_image: Option<&str>,
) {
Outdated
Review

[bug] L'image par défaut debian:stable-slim ne crée pas le répertoire /workspaces/workspace, qui est pourtant le chemin retourné par workspace_folder() lorsque cette valeur est absente. up() tente ensuite d'y téléverser le workspace et configure ce chemin comme répertoire de travail ; le fallback échouera donc probablement dès le démarrage ou l'upload. Il faut créer ce répertoire avant l'upload, ou choisir un chemin garanti d'exister comme /tmp/workspace.

**[bug]** L'image par défaut `debian:stable-slim` ne crée pas le répertoire `/workspaces/workspace`, qui est pourtant le chemin retourné par `workspace_folder()` lorsque cette valeur est absente. `up()` tente ensuite d'y téléverser le workspace et configure ce chemin comme répertoire de travail ; le fallback échouera donc probablement dès le démarrage ou l'upload. Il faut créer ce répertoire avant l'upload, ou choisir un chemin garanti d'exister comme `/tmp/workspace`.
let _ = runtime.remove_container(container).await;
let _ = runtime.remove_network(network).await;
if let Some(image) = owned_image {
let _ = runtime.remove_image(image).await;
}
}
impl Default for DevContainer { impl Default for DevContainer {
fn default() -> Self { fn default() -> Self {
Self { Self {
1