Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 0 additions & 2 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 0 additions & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -86,7 +86,6 @@ escape-bytes = "0.1.1"
hex = "0.4.3"
itertools = "0.10.0"
async-trait = "0.1.76"
bollard = "0.20.2"
serde-aux = "4.1.2"
serde_json = "1.0.82"
serde = "1.0.82"
Expand Down
1 change: 0 additions & 1 deletion cmd/crates/stellar-ledger/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,6 @@ ledger-transport = "0.10.0"
tracing = { workspace = true }
hex.workspace = true
byteorder = "1.5.0"
bollard = { workspace = true }
home = "0.5.9"
tokio = { version = "1", features = ["full"] }
reqwest = { workspace = true, features = ["json"] }
Expand Down
1 change: 0 additions & 1 deletion cmd/soroban-cli/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -107,7 +107,6 @@ shell-escape = "0.1.5"
tempfile = "3.8.1"
toml_edit = { workspace = true }
rust-embed = { version = "8.2.0", features = ["debug-embed"] }
bollard = { workspace = true }
futures-util = "0.3.30"
futures = "0.3.30"
home = "0.5.9"
Expand Down
44 changes: 18 additions & 26 deletions cmd/soroban-cli/src/commands/container/logs.rs
Original file line number Diff line number Diff line change
@@ -1,20 +1,14 @@
use bollard::query_parameters::LogsOptions;
use futures_util::TryStreamExt;

use crate::{
commands::{container::shared::Error as ConnectionError, global},
print,
};
use crate::commands::{container::shared::Error as ConnectionError, global};

use super::shared::{Args, Name};

#[derive(thiserror::Error, Debug)]
pub enum Error {
#[error(transparent)]
ConnectionError(#[from] ConnectionError),
Docker(#[from] ConnectionError),

#[error("⛔ ️Failed to tail container: {0}")]
TailContainerError(#[from] bollard::errors::Error),
#[error("failed to tail container logs")]
TailContainerError,
}

#[derive(Debug, clap::Parser, Clone)]
Expand All @@ -28,24 +22,22 @@ pub struct Cmd {
}

impl Cmd {
pub async fn run(&self, global_args: &global::Args) -> Result<(), Error> {
let print = print::Print::new(global_args.quiet);
pub async fn run(&self, _global_args: &global::Args) -> Result<(), Error> {
let container_name = Name(self.name.clone()).get_internal_container_name();
let docker = self.container_args.connect_to_docker(&print).await?;
let logs_stream = &mut docker.logs(
&container_name,
Some(LogsOptions {
follow: true,
stdout: true,
stderr: true,
tail: "all".to_owned(),
..Default::default()
}),
);

while let Some(log) = logs_stream.try_next().await? {
print!("{log}");

// Stream logs straight to the terminal by inheriting stdio.
let status = self
.container_args
.docker_command()
.args(["logs", "-f", "--tail", "all", &container_name])
.status()
.await
.map_err(ConnectionError::from)?;

if !status.success() {
return Err(Error::TailContainerError);
}

Ok(())
}
}
141 changes: 24 additions & 117 deletions cmd/soroban-cli/src/commands/container/shared.rs
Original file line number Diff line number Diff line change
@@ -1,36 +1,27 @@
use core::fmt;

use bollard::{ClientVersion, Docker};
use clap::ValueEnum;
#[allow(unused_imports)]
// Need to add this for windows, since we are only using this crate for the unix fn try_docker_desktop_socket
use home::home_dir;

use crate::print;
use tokio::process::Command;

pub const DOCKER_HOST_HELP: &str = "Optional argument to override the default docker host. This is useful when you are using a non-standard docker host 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_DOCKER_HOST is from the bollard crate on the main branch, which has not been released yet: https://github.com/fussybeaver/bollard/blob/0972b1aac0ad5c08798e100319ddd0d2ee010365/src/docker.rs#L64
#[cfg(unix)]
pub const DEFAULT_DOCKER_HOST: &str = "unix:///var/run/docker.sock";

#[cfg(windows)]
pub const DEFAULT_DOCKER_HOST: &str = "npipe:////./pipe/docker_engine";

// DEFAULT_TIMEOUT and API_DEFAULT_VERSION are from the bollard crate
const DEFAULT_TIMEOUT: u64 = 120;
const API_DEFAULT_VERSION: &ClientVersion = &ClientVersion {
major_version: 1,
minor_version: 40,
};

#[derive(thiserror::Error, Debug)]
pub enum Error {
#[error("⛔ ️Failed to start container: {0}")]
BollardErr(#[from] bollard::errors::Error),
#[error("failed to run docker: {0}; is docker installed and on your PATH?")]
DockerNotFound(std::io::Error),

#[error("URI scheme is not supported: {uri}")]
UnsupportedURISchemeError { uri: String },
#[error("failed to run docker: {0}")]
DockerCommand(std::io::Error),
}

impl From<std::io::Error> for Error {
fn from(err: std::io::Error) -> Self {
if err.kind() == std::io::ErrorKind::NotFound {
Error::DockerNotFound(err)
} else {
Error::DockerCommand(err)
}
}
}

#[derive(Debug, clap::Parser, Clone)]
Expand All @@ -48,61 +39,16 @@ impl Args {
.unwrap_or_default()
}

#[allow(unused_variables)]
pub(crate) async fn connect_to_docker(&self, print: &print::Print) -> Result<Docker, Error> {
// if no docker_host is provided, use the default docker host:
// "unix:///var/run/docker.sock" on unix machines
// "npipe:////./pipe/docker_engine" on windows machines
let host = self.docker_host.as_ref().map_or_else(
|| DEFAULT_DOCKER_HOST.to_string(),
std::string::ToString::to_string,
);

// this is based on the `connect_with_defaults` method which has not yet been released in the bollard crate
// https://github.com/fussybeaver/bollard/blob/0972b1aac0ad5c08798e100319ddd0d2ee010365/src/docker.rs#L660
let connection = match host.clone() {
// if tcp or http, connect to the specified host directly
// if unix and host starts with "unix://" use connect_with_unix
// if windows and host starts with "npipe://", use connect_with_named_pipe
// else default to connect_with_unix
h if h.starts_with("tcp://") || h.starts_with("http://") => {
Docker::connect_with_http(&h, DEFAULT_TIMEOUT, API_DEFAULT_VERSION)
}
#[cfg(unix)]
h if h.starts_with("unix://") => {
Docker::connect_with_unix(&h, DEFAULT_TIMEOUT, API_DEFAULT_VERSION)
}
#[cfg(windows)]
h if h.starts_with("npipe://") => {
Docker::connect_with_named_pipe(&h, DEFAULT_TIMEOUT, API_DEFAULT_VERSION)
}
_ => {
return Err(Error::UnsupportedURISchemeError { uri: host.clone() });
}
}?;

match check_docker_connection(&connection).await {
Ok(()) => Ok(connection),
// If we aren't able to connect with the defaults, or with the provided docker_host
// try to connect with the default docker desktop socket since that is a common use case for devs
#[allow(unused_variables)]
Err(e) => {
// if on unix, try to connect to the default docker desktop socket
#[cfg(unix)]
{
let docker_desktop_connection = try_docker_desktop_socket(&host, print)?;
match check_docker_connection(&docker_desktop_connection).await {
Ok(()) => Ok(docker_desktop_connection),
Err(err) => Err(err)?,
}
}

#[cfg(windows)]
{
Err(e)?
}
}
/// Builds a `docker` command, passing a `-H <host>` override when a `--docker-host` (or
/// `DOCKER_HOST` env) value is provided. The `-H` flag outranks `DOCKER_CONTEXT`, so the
/// override is honored even when a docker context is active. Host resolution is otherwise
/// left to the docker CLI itself.
pub(crate) fn docker_command(&self) -> Command {
let mut cmd = Command::new("docker");
if let Some(host) = &self.docker_host {
cmd.args(["-H", host]);
}
cmd
}
}

Expand Down Expand Up @@ -137,42 +83,3 @@ impl Name {
self.0.clone()
}
}

#[cfg(unix)]
fn try_docker_desktop_socket(
host: &str,
print: &print::Print,
) -> Result<Docker, bollard::errors::Error> {
let default_docker_desktop_host =
format!("{}/.docker/run/docker.sock", home_dir().unwrap().display());
print.warnln(format!("Failed to connect to Docker daemon at {host}."));

print.infoln(format!(
"Attempting to connect to the default Docker Desktop socket at {default_docker_desktop_host} instead."
));

Docker::connect_with_unix(
&default_docker_desktop_host,
DEFAULT_TIMEOUT,
API_DEFAULT_VERSION,
).inspect_err(|_| {
print.errorln(format!(
"Failed to connect to the Docker daemon at {host:?}. Is the docker daemon running?"
));
print.infoln(
"Running a local Stellar network requires a Docker-compatible container runtime."
);
print.infoln(
"Please note that if you are using Docker Desktop, you may need to utilize the `--docker-host` flag to pass in the location of the docker socket on your machine."
);
})
}

// 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
async fn check_docker_connection(docker: &Docker) -> Result<(), bollard::errors::Error> {
match docker.version().await {
Ok(_version) => Ok(()),
Err(err) => Err(err),
}
}
Loading
Loading