diff --git a/crates/devcontainer-rs/src/devcontainer.rs b/crates/devcontainer-rs/src/devcontainer.rs index 2e96a19..eff0ca6 100644 --- a/crates/devcontainer-rs/src/devcontainer.rs +++ b/crates/devcontainer-rs/src/devcontainer.rs @@ -84,18 +84,20 @@ impl DevContainer { workspace_dir: &Path, ) -> Result { let id = unique_id(); - let image = self .image .clone() .unwrap_or_else(|| self.local_image_tag(&id)); 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"); - match owns_image { - true => { + let setup = async { + if owns_image { let container_file_path = self.container_file_path.as_ref().ok_or_else(|| { ContainerError::Unexpected(String::from( "the devcontainer has neither an image nor a container file", @@ -103,74 +105,78 @@ impl DevContainer { })?; self.build(runtime, container_file_path, &image).await?; - } - false => { + } else { runtime.pull_image(&image).await?; } - } - let name = self.container_name(&id); - let network = format!("{name}-net"); + runtime.create_network(&network).await?; - runtime.create_network(&network).await?; - - let workspace_folder = self.workspace_folder(); - let body = ContainerCreateBody { - image: Some(image.clone()), - cmd: Some(vec![String::from("sleep"), String::from("infinity")]), - user: self.remote_user.clone(), - env: Some( - self.container_env - .iter() - .map(|(key, value)| format!("{key}={value}")) - .collect(), - ), - working_dir: Some(workspace_folder.clone()), - host_config: Some(HostConfig { - network_mode: Some(network.clone()), + let body = ContainerCreateBody { + image: Some(image.clone()), + cmd: Some(vec![String::from("sleep"), String::from("infinity")]), + user: self.remote_user.clone(), + env: Some( + self.container_env + .iter() + .map(|(key, value)| format!("{key}={value}")) + .collect(), + ), + working_dir: Some(workspace_folder.clone()), + 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 { - let _ = runtime.remove_network(&network).await; - return Err(err); + runtime.create_container(&container_name, body).await?; + runtime.start_container(&container_name).await?; + runtime + .upload_directory(&container_name, &workspace_folder, workspace_dir) + .await?; + + Ok::<(), ContainerError>(()) } + .await; - if let Err(err) = runtime.start_container(&name).await { - let _ = runtime.remove_container(&name).await; - let _ = runtime.remove_network(&network).await; - return Err(err); - } - - if let Err(err) = runtime - .upload_directory(&name, &workspace_folder, workspace_dir) - .await - { - let _ = runtime.remove_container(&name).await; - let _ = runtime.remove_network(&network).await; + if let Err(err) = setup { + cleanup_failed_startup( + runtime, + &container_name, + &network, + owns_image.then_some(image.as_str()), + ) + .await; return Err(err); } let container = Container::new( runtime.clone(), - name, - self.workspace_folder(), + container_name.clone(), + workspace_folder, self.remote_user.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.). - if let Err(err) = self.run_hooks(&container, runtime.timeout()).await { - let _ = container.remove().await; - return Err(err); - } + let post_setup = async { + self.run_hooks(&container, runtime.timeout()).await?; + runtime + .disconnect_network(&network, &container_name) + .await?; - // Coupe l'accès réseau pour le reste de la durée de vie de la sandbox. - if let Err(err) = runtime.disconnect_network(&network, container.name()).await { - let _ = container.remove().await; + Ok::<(), ContainerError>(()) + } + .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); } @@ -203,6 +209,20 @@ impl DevContainer { } } +async fn cleanup_failed_startup( + runtime: &ContainerRuntime, + container: &str, + network: &str, + owned_image: Option<&str>, +) { + 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 { fn default() -> Self { Self {