diff --git a/cmd/soroban-cli/src/commands/network/shared.rs b/cmd/soroban-cli/src/commands/network/shared.rs index f219b41c21..cf1d1889e6 100644 --- a/cmd/soroban-cli/src/commands/network/shared.rs +++ b/cmd/soroban-cli/src/commands/network/shared.rs @@ -31,13 +31,47 @@ impl fmt::Display for Network { } } -pub fn connect_to_docker( +pub async fn connect_to_docker( docker_socket_path: &Option, ) -> Result { - if docker_socket_path.is_some() { + let docker = if docker_socket_path.is_some() { let socket = docker_socket_path.as_ref().unwrap(); - Docker::connect_with_socket(socket, DEFAULT_TIMEOUT, API_DEFAULT_VERSION) + let connection = Docker::connect_with_socket(socket, DEFAULT_TIMEOUT, API_DEFAULT_VERSION)?; + check_docker_connection(&connection).await?; + connection } else { - Docker::connect_with_socket_defaults() + let connection = Docker::connect_with_socket_defaults()?; + check_docker_connection(&connection).await?; + connection + }; + Ok(docker) +} + +// When bollard is not able to connect to the docker daemon, it returns a generic ConnectionRefused error +// This method attempts to connect to the docker daemon and returns a more specific error message +pub async fn check_docker_connection(docker: &Docker) -> Result<(), bollard::errors::Error> { + // This is a bit hacky, but the `client_addr` field is not directly accessible from the `Docker` struct, but we can access it from the debug string representation of the `Docker` struct + let docker_debug_string = format!("{:#?}", docker); + let start_of_client_addr = docker_debug_string.find("client_addr: ").unwrap(); + let end_of_client_addr = docker_debug_string[start_of_client_addr..] + .find(',') + .unwrap(); + // Extract the substring containing the value of client_addr + let client_addr = &docker_debug_string + [start_of_client_addr + "client_addr: ".len()..start_of_client_addr + end_of_client_addr] + .trim() + .trim_matches('"'); + + match docker.version().await { + Ok(_version) => Ok(()), + Err(err) => { + println!( + "⛔️ Failed to connect to the Docker daemon at {:?}. Is the docker daemon running?\n + Running a local Stellar network requires a Docker-compatible container runtime.\n + Also, please note that if you are using Docker Desktop, you may need to utilize the `--docker-socket-path` flag to pass in the location of the docker socket on your machine.", + client_addr + ); + Err(err) + } } } diff --git a/cmd/soroban-cli/src/commands/network/start.rs b/cmd/soroban-cli/src/commands/network/start.rs index 1a8a097db1..3eaad97345 100644 --- a/cmd/soroban-cli/src/commands/network/start.rs +++ b/cmd/soroban-cli/src/commands/network/start.rs @@ -1,10 +1,11 @@ +use std::collections::HashMap; + use bollard::{ container::{Config, CreateContainerOptions, StartContainerOptions}, image::CreateImageOptions, service::{HostConfig, PortBinding}, }; use futures_util::TryStreamExt; -use std::collections::HashMap; use crate::commands::network::shared::connect_to_docker; use crate::commands::network::shared::Network; @@ -14,7 +15,7 @@ const DOCKER_IMAGE: &str = "docker.io/stellar/quickstart"; #[derive(thiserror::Error, Debug)] pub enum Error { - #[error("Failed to start container: {0}")] + #[error("⛔ ️Failed to start container: {0}")] StartContainerError(#[from] bollard::errors::Error), } @@ -50,13 +51,13 @@ pub struct Cmd { impl Cmd { pub async fn run(&self) -> Result<(), Error> { - println!("Starting {} network", &self.network); + println!("ℹ️ Starting {} network", &self.network); run_docker_command(self).await } } async fn run_docker_command(cmd: &Cmd) -> Result<(), Error> { - let docker = connect_to_docker(&cmd.docker_socket_path)?; + let docker = connect_to_docker(&cmd.docker_socket_path).await?; let image = get_image_name(cmd); docker @@ -91,20 +92,34 @@ async fn run_docker_command(cmd: &Cmd) -> Result<(), Error> { let create_container_response = docker .create_container( Some(CreateContainerOptions { - name: container_name, + name: container_name.clone(), ..Default::default() }), config, ) - .await - .unwrap(); + .await?; + docker .start_container( &create_container_response.id, None::>, ) - .await - .map_err(Error::StartContainerError) + .await?; + println!("✅ Container started: {container_name}"); + let stop_message = format!( + "ℹ️ To stop this container run: soroban network stop {network} {additional_flags}", + network = &cmd.network, + additional_flags = if cmd.docker_socket_path.is_some() { + format!( + "--docker-socket-path {}", + cmd.docker_socket_path.as_ref().unwrap() + ) + } else { + String::new() + } + ); + println!("{stop_message}"); + Ok(()) } fn get_container_args(cmd: &Cmd) -> Vec { diff --git a/cmd/soroban-cli/src/commands/network/stop.rs b/cmd/soroban-cli/src/commands/network/stop.rs index 934b072c2e..1e1baeab0e 100644 --- a/cmd/soroban-cli/src/commands/network/stop.rs +++ b/cmd/soroban-cli/src/commands/network/stop.rs @@ -20,10 +20,10 @@ pub struct Cmd { impl Cmd { pub async fn run(&self) -> Result<(), Error> { let container_name = format!("stellar-{}", self.network); - let docker = connect_to_docker(&self.docker_socket_path)?; - println!("Stopping container: {container_name}"); + let docker = connect_to_docker(&self.docker_socket_path).await?; + println!("ℹ️ Stopping container: {container_name}"); docker.stop_container(&container_name, None).await.unwrap(); - + println!("✅ Container stopped: {container_name}"); Ok(()) } }