use core::fmt;
use bollard::{ClientVersion, Docker};
use clap::ValueEnum;
#[allow(unused_imports)]
use home::home_dir;
use crate::print;
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";
#[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";
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("URI scheme is not supported: {uri}")]
UnsupportedURISchemeError { uri: String },
}
#[derive(Debug, clap::Parser, Clone)]
pub struct Args {
#[arg(short = 'd', long, help = DOCKER_HOST_HELP, env = "DOCKER_HOST")]
pub docker_host: Option<String>,
}
impl Args {
pub(crate) fn get_additional_flags(&self) -> String {
self.docker_host
.as_ref()
.map(|docker_host| format!("--docker-host {docker_host}"))
.unwrap_or_default()
}
#[allow(unused_variables)]
pub(crate) async fn connect_to_docker(&self, print: &print::Print) -> Result<Docker, Error> {
let host = self.docker_host.as_ref().map_or_else(
|| DEFAULT_DOCKER_HOST.to_string(),
std::string::ToString::to_string,
);
let connection = match host.clone() {
h if h.starts_with("tcp://") || h.starts_with("http://") => {
Docker::connect_with_http_defaults()
}
#[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),
#[allow(unused_variables)]
Err(e) => {
#[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)?
}
}
}
}
}
#[derive(ValueEnum, Debug, Copy, Clone, PartialEq)]
pub enum Network {
Local,
Testnet,
Futurenet,
Pubnet,
}
impl fmt::Display for Network {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let variant_str = match self {
Network::Local => "local",
Network::Testnet => "testnet",
Network::Futurenet => "futurenet",
Network::Pubnet => "pubnet",
};
write!(f, "{variant_str}")
}
}
pub struct Name(pub String);
impl Name {
pub fn get_internal_container_name(&self) -> String {
format!("stellar-{}", self.0)
}
pub fn get_external_container_name(&self) -> String {
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."
);
})
}
async fn check_docker_connection(docker: &Docker) -> Result<(), bollard::errors::Error> {
match docker.version().await {
Ok(_version) => Ok(()),
Err(err) => Err(err),
}
}