Skip to content
Open
Show file tree
Hide file tree
Changes from 1 commit
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(())
}
}
140 changes: 23 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,15 @@ 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, setting `DOCKER_HOST` in the process environment when a
/// `--docker-host` (or `DOCKER_HOST` env) override is provided. 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.env("DOCKER_HOST", host);
Comment thread
fnando marked this conversation as resolved.
Outdated
}
cmd
}
}

Expand Down Expand Up @@ -137,42 +82,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