Skip to content

Commit

Permalink
Improve messaging for start and stop
Browse files Browse the repository at this point in the history
  • Loading branch information
elizabethengelman committed Feb 13, 2024
1 parent 7a86f06 commit bfb5551
Show file tree
Hide file tree
Showing 3 changed files with 75 additions and 31 deletions.
41 changes: 37 additions & 4 deletions cmd/soroban-cli/src/commands/network/shared.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@ use core::fmt;
use bollard::{ClientVersion, Docker};
use clap::ValueEnum;

pub const DOCKER_SOCKET_PATH_HELP: &str = "Optional argument to override the default docker socket path. This is useful when you are using a non-standard docker socket path for your Docker-compatible container runtime, e.g. Docker Desktop defaults to $HOME/.docker/run/docker.sock instead of /var/run/docker.sock";

// DEFAULT_TIMEOUT and API_DEFAULT_VERSION are from the bollard crate
const DEFAULT_TIMEOUT: u64 = 120;
const API_DEFAULT_VERSION: &ClientVersion = &ClientVersion {
Expand Down Expand Up @@ -31,13 +33,44 @@ impl fmt::Display for Network {
}
}

pub fn connect_to_docker(
pub async fn connect_to_docker(
docker_socket_path: &Option<String>,
) -> Result<Docker, bollard::errors::Error> {
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 {client_addr:?}. Is the docker daemon running?\nℹ️ Running a local Stellar network requires a Docker-compatible container runtime.\nℹ️ 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.\n"
);
Err(err)
}
}
}
51 changes: 32 additions & 19 deletions cmd/soroban-cli/src/commands/network/start.rs
Original file line number Diff line number Diff line change
@@ -1,62 +1,61 @@
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;
use crate::commands::network::shared::{connect_to_docker, Network, DOCKER_SOCKET_PATH_HELP};

const DEFAULT_PORT_MAPPING: &str = "8000:8000";
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),
}

#[derive(Debug, clap::Parser, Clone)]
pub struct Cmd {
/// network to start
/// Network to start
pub network: Network,

/// optional argument to override the default docker socket path
#[arg(short = 'd', long)]
#[arg(short = 'd', long, help = DOCKER_SOCKET_PATH_HELP)]
pub docker_socket_path: Option<String>,

/// optional argument to specify the limits for the local network only
/// Optional argument to specify the limits for the local network only
#[arg(short = 'l', long)]
pub limit: Option<String>,

/// argument to specify the HOST_PORT:CONTAINER_PORT mapping
/// Argument to specify the HOST_PORT:CONTAINER_PORT mapping
#[arg(short = 'p', long, num_args = 1.., default_value = DEFAULT_PORT_MAPPING)]
pub ports_mapping: Vec<String>,

/// optional argument to turn off soroban rpc
/// Optional argument to turn off soroban rpc
#[arg(short = 'r', long)]
pub disable_soroban_rpc: bool,

/// optional argument to override the default docker image tag for the given network
/// Optional argument to override the default docker image tag for the given network
#[arg(short = 't', long)]
pub image_tag_override: Option<String>,

/// optional argument to specify the protocol version for the local network only
/// Optional argument to specify the protocol version for the local network only
#[arg(short = 'v', long)]
pub protocol_version: Option<String>,
}

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
Expand Down Expand Up @@ -91,20 +90,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::<StartContainerOptions<String>>,
)
.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<String> {
Expand Down
14 changes: 6 additions & 8 deletions cmd/soroban-cli/src/commands/network/stop.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
use crate::commands::network::shared::connect_to_docker;
use crate::commands::network::shared::Network;
use crate::commands::network::shared::{connect_to_docker, Network, DOCKER_SOCKET_PATH_HELP};

#[derive(thiserror::Error, Debug)]
pub enum Error {
Expand All @@ -9,21 +8,20 @@ pub enum Error {

#[derive(Debug, clap::Parser, Clone)]
pub struct Cmd {
/// network container to stop
/// Network to stop
pub network: Network,

/// optional argument to override the default docker socket path
#[arg(short = 'd', long)]
#[arg(short = 'd', long, help = DOCKER_SOCKET_PATH_HELP)]
pub docker_socket_path: Option<String>,
}

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(())
}
}

0 comments on commit bfb5551

Please sign in to comment.