Skip to content

Commit f35667d

Browse files
authored
Shell out to docker instead of using bollard (#2629)
1 parent e328acf commit f35667d

8 files changed

Lines changed: 135 additions & 278 deletions

File tree

Cargo.lock

Lines changed: 0 additions & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -86,7 +86,6 @@ escape-bytes = "0.1.1"
8686
hex = "0.4.3"
8787
itertools = "0.10.0"
8888
async-trait = "0.1.76"
89-
bollard = "0.20.2"
9089
serde-aux = "4.1.2"
9190
serde_json = "1.0.82"
9291
serde = "1.0.82"

cmd/crates/stellar-ledger/Cargo.toml

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,6 @@ ledger-transport = "0.10.0"
2323
tracing = { workspace = true }
2424
hex.workspace = true
2525
byteorder = "1.5.0"
26-
bollard = { workspace = true }
2726
home = "0.5.9"
2827
tokio = { version = "1", features = ["full"] }
2928
reqwest = { workspace = true, features = ["json"] }

cmd/soroban-cli/Cargo.toml

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -110,7 +110,6 @@ shell-escape = "0.1.5"
110110
tempfile = "3.8.1"
111111
toml_edit = { workspace = true }
112112
rust-embed = { version = "8.2.0", features = ["debug-embed"] }
113-
bollard = { workspace = true }
114113
futures-util = "0.3.30"
115114
futures = "0.3.30"
116115
home = "0.5.9"
Lines changed: 18 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,14 @@
1-
use bollard::query_parameters::LogsOptions;
2-
use futures_util::TryStreamExt;
3-
4-
use crate::{
5-
commands::{container::shared::Error as ConnectionError, global},
6-
print,
7-
};
1+
use crate::commands::{container::shared::Error as ConnectionError, global};
82

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

115
#[derive(thiserror::Error, Debug)]
126
pub enum Error {
137
#[error(transparent)]
14-
ConnectionError(#[from] ConnectionError),
8+
Docker(#[from] ConnectionError),
159

16-
#[error("⛔ ️Failed to tail container: {0}")]
17-
TailContainerError(#[from] bollard::errors::Error),
10+
#[error("failed to tail container logs")]
11+
TailContainerError,
1812
}
1913

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

3024
impl Cmd {
31-
pub async fn run(&self, global_args: &global::Args) -> Result<(), Error> {
32-
let print = print::Print::new(global_args.quiet);
25+
pub async fn run(&self, _global_args: &global::Args) -> Result<(), Error> {
3326
let container_name = Name(self.name.clone()).get_internal_container_name();
34-
let docker = self.container_args.connect_to_docker(&print).await?;
35-
let logs_stream = &mut docker.logs(
36-
&container_name,
37-
Some(LogsOptions {
38-
follow: true,
39-
stdout: true,
40-
stderr: true,
41-
tail: "all".to_owned(),
42-
..Default::default()
43-
}),
44-
);
45-
46-
while let Some(log) = logs_stream.try_next().await? {
47-
print!("{log}");
27+
28+
// Stream logs straight to the terminal by inheriting stdio.
29+
let status = self
30+
.container_args
31+
.docker_command()
32+
.args(["logs", "-f", "--tail", "all", &container_name])
33+
.status()
34+
.await
35+
.map_err(ConnectionError::from)?;
36+
37+
if !status.success() {
38+
return Err(Error::TailContainerError);
4839
}
40+
4941
Ok(())
5042
}
5143
}
Lines changed: 24 additions & 117 deletions
Original file line numberDiff line numberDiff line change
@@ -1,36 +1,27 @@
11
use core::fmt;
22

3-
use bollard::{ClientVersion, Docker};
43
use clap::ValueEnum;
5-
#[allow(unused_imports)]
6-
// Need to add this for windows, since we are only using this crate for the unix fn try_docker_desktop_socket
7-
use home::home_dir;
8-
9-
use crate::print;
4+
use tokio::process::Command;
105

116
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";
127

13-
// 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
14-
#[cfg(unix)]
15-
pub const DEFAULT_DOCKER_HOST: &str = "unix:///var/run/docker.sock";
16-
17-
#[cfg(windows)]
18-
pub const DEFAULT_DOCKER_HOST: &str = "npipe:////./pipe/docker_engine";
19-
20-
// DEFAULT_TIMEOUT and API_DEFAULT_VERSION are from the bollard crate
21-
const DEFAULT_TIMEOUT: u64 = 120;
22-
const API_DEFAULT_VERSION: &ClientVersion = &ClientVersion {
23-
major_version: 1,
24-
minor_version: 40,
25-
};
26-
278
#[derive(thiserror::Error, Debug)]
289
pub enum Error {
29-
#[error("⛔ ️Failed to start container: {0}")]
30-
BollardErr(#[from] bollard::errors::Error),
10+
#[error("failed to run docker: {0}; is docker installed and on your PATH?")]
11+
DockerNotFound(std::io::Error),
3112

32-
#[error("URI scheme is not supported: {uri}")]
33-
UnsupportedURISchemeError { uri: String },
13+
#[error("failed to run docker: {0}")]
14+
DockerCommand(std::io::Error),
15+
}
16+
17+
impl From<std::io::Error> for Error {
18+
fn from(err: std::io::Error) -> Self {
19+
if err.kind() == std::io::ErrorKind::NotFound {
20+
Error::DockerNotFound(err)
21+
} else {
22+
Error::DockerCommand(err)
23+
}
24+
}
3425
}
3526

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

51-
#[allow(unused_variables)]
52-
pub(crate) async fn connect_to_docker(&self, print: &print::Print) -> Result<Docker, Error> {
53-
// if no docker_host is provided, use the default docker host:
54-
// "unix:///var/run/docker.sock" on unix machines
55-
// "npipe:////./pipe/docker_engine" on windows machines
56-
let host = self.docker_host.as_ref().map_or_else(
57-
|| DEFAULT_DOCKER_HOST.to_string(),
58-
std::string::ToString::to_string,
59-
);
60-
61-
// this is based on the `connect_with_defaults` method which has not yet been released in the bollard crate
62-
// https://github.com/fussybeaver/bollard/blob/0972b1aac0ad5c08798e100319ddd0d2ee010365/src/docker.rs#L660
63-
let connection = match host.clone() {
64-
// if tcp or http, connect to the specified host directly
65-
// if unix and host starts with "unix://" use connect_with_unix
66-
// if windows and host starts with "npipe://", use connect_with_named_pipe
67-
// else default to connect_with_unix
68-
h if h.starts_with("tcp://") || h.starts_with("http://") => {
69-
Docker::connect_with_http(&h, DEFAULT_TIMEOUT, API_DEFAULT_VERSION)
70-
}
71-
#[cfg(unix)]
72-
h if h.starts_with("unix://") => {
73-
Docker::connect_with_unix(&h, DEFAULT_TIMEOUT, API_DEFAULT_VERSION)
74-
}
75-
#[cfg(windows)]
76-
h if h.starts_with("npipe://") => {
77-
Docker::connect_with_named_pipe(&h, DEFAULT_TIMEOUT, API_DEFAULT_VERSION)
78-
}
79-
_ => {
80-
return Err(Error::UnsupportedURISchemeError { uri: host.clone() });
81-
}
82-
}?;
83-
84-
match check_docker_connection(&connection).await {
85-
Ok(()) => Ok(connection),
86-
// If we aren't able to connect with the defaults, or with the provided docker_host
87-
// try to connect with the default docker desktop socket since that is a common use case for devs
88-
#[allow(unused_variables)]
89-
Err(e) => {
90-
// if on unix, try to connect to the default docker desktop socket
91-
#[cfg(unix)]
92-
{
93-
let docker_desktop_connection = try_docker_desktop_socket(&host, print)?;
94-
match check_docker_connection(&docker_desktop_connection).await {
95-
Ok(()) => Ok(docker_desktop_connection),
96-
Err(err) => Err(err)?,
97-
}
98-
}
99-
100-
#[cfg(windows)]
101-
{
102-
Err(e)?
103-
}
104-
}
42+
/// Builds a `docker` command, passing a `-H <host>` override when a `--docker-host` (or
43+
/// `DOCKER_HOST` env) value is provided. The `-H` flag outranks `DOCKER_CONTEXT`, so the
44+
/// override is honored even when a docker context is active. Host resolution is otherwise
45+
/// left to the docker CLI itself.
46+
pub(crate) fn docker_command(&self) -> Command {
47+
let mut cmd = Command::new("docker");
48+
if let Some(host) = &self.docker_host {
49+
cmd.args(["-H", host]);
10550
}
51+
cmd
10652
}
10753
}
10854

@@ -137,42 +83,3 @@ impl Name {
13783
self.0.clone()
13884
}
13985
}
140-
141-
#[cfg(unix)]
142-
fn try_docker_desktop_socket(
143-
host: &str,
144-
print: &print::Print,
145-
) -> Result<Docker, bollard::errors::Error> {
146-
let default_docker_desktop_host =
147-
format!("{}/.docker/run/docker.sock", home_dir().unwrap().display());
148-
print.warnln(format!("Failed to connect to Docker daemon at {host}."));
149-
150-
print.infoln(format!(
151-
"Attempting to connect to the default Docker Desktop socket at {default_docker_desktop_host} instead."
152-
));
153-
154-
Docker::connect_with_unix(
155-
&default_docker_desktop_host,
156-
DEFAULT_TIMEOUT,
157-
API_DEFAULT_VERSION,
158-
).inspect_err(|_| {
159-
print.errorln(format!(
160-
"Failed to connect to the Docker daemon at {host:?}. Is the docker daemon running?"
161-
));
162-
print.infoln(
163-
"Running a local Stellar network requires a Docker-compatible container runtime."
164-
);
165-
print.infoln(
166-
"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."
167-
);
168-
})
169-
}
170-
171-
// When bollard is not able to connect to the docker daemon, it returns a generic ConnectionRefused error
172-
// This method attempts to connect to the docker daemon and returns a more specific error message
173-
async fn check_docker_connection(docker: &Docker) -> Result<(), bollard::errors::Error> {
174-
match docker.version().await {
175-
Ok(_version) => Ok(()),
176-
Err(err) => Err(err),
177-
}
178-
}

0 commit comments

Comments
 (0)