mirror of
https://github.com/logos-blockchain/logos-blockchain-testing.git
synced 2026-01-06 23:33:07 +00:00
196 lines
5.9 KiB
Rust
196 lines
5.9 KiB
Rust
use std::{
|
|
env,
|
|
process::{Command as StdCommand, Stdio},
|
|
time::Duration,
|
|
};
|
|
|
|
use tokio::{process::Command, time::timeout};
|
|
use tracing::warn;
|
|
|
|
use crate::{commands::ComposeCommandError, errors::ComposeRunnerError, template::repository_root};
|
|
|
|
const IMAGE_BUILD_TIMEOUT: Duration = Duration::from_secs(600);
|
|
const DOCKER_INFO_TIMEOUT: Duration = Duration::from_secs(15);
|
|
const IMAGE_INSPECT_TIMEOUT: Duration = Duration::from_secs(60);
|
|
|
|
/// Checks that `docker info` succeeds within a timeout.
|
|
pub async fn ensure_docker_available() -> Result<(), ComposeRunnerError> {
|
|
let mut command = Command::new("docker");
|
|
command
|
|
.arg("info")
|
|
.stdout(Stdio::null())
|
|
.stderr(Stdio::null());
|
|
|
|
let available = timeout(
|
|
testing_framework_core::adjust_timeout(DOCKER_INFO_TIMEOUT),
|
|
command.status(),
|
|
)
|
|
.await
|
|
.ok()
|
|
.and_then(Result::ok)
|
|
.map(|status| status.success())
|
|
.unwrap_or(false);
|
|
|
|
if available {
|
|
Ok(())
|
|
} else {
|
|
Err(ComposeRunnerError::DockerUnavailable)
|
|
}
|
|
}
|
|
|
|
/// Ensure the configured compose image exists, building a local one if needed.
|
|
pub async fn ensure_compose_image() -> Result<(), ComposeRunnerError> {
|
|
let (image, platform) = crate::platform::resolve_image();
|
|
ensure_image_present(&image, platform.as_deref()).await
|
|
}
|
|
|
|
/// Verify an image exists locally, optionally building it for the default tag.
|
|
pub async fn ensure_image_present(
|
|
image: &str,
|
|
platform: Option<&str>,
|
|
) -> Result<(), ComposeRunnerError> {
|
|
if docker_image_exists(image).await? {
|
|
return Ok(());
|
|
}
|
|
|
|
if image != "nomos-testnet:local" {
|
|
return Err(ComposeRunnerError::MissingImage {
|
|
image: image.to_owned(),
|
|
});
|
|
}
|
|
|
|
build_local_image(image, platform).await
|
|
}
|
|
|
|
/// Returns true when `docker image inspect` succeeds for the image.
|
|
pub async fn docker_image_exists(image: &str) -> Result<bool, ComposeRunnerError> {
|
|
let mut cmd = Command::new("docker");
|
|
cmd.arg("image")
|
|
.arg("inspect")
|
|
.arg(image)
|
|
.stdout(Stdio::null())
|
|
.stderr(Stdio::null());
|
|
|
|
match timeout(
|
|
testing_framework_core::adjust_timeout(IMAGE_INSPECT_TIMEOUT),
|
|
cmd.status(),
|
|
)
|
|
.await
|
|
{
|
|
Ok(Ok(status)) => Ok(status.success()),
|
|
Ok(Err(source)) => Err(ComposeRunnerError::Compose(ComposeCommandError::Spawn {
|
|
command: format!("docker image inspect {image}"),
|
|
source,
|
|
})),
|
|
Err(_) => Err(ComposeRunnerError::Compose(ComposeCommandError::Timeout {
|
|
command: format!("docker image inspect {image}"),
|
|
timeout: testing_framework_core::adjust_timeout(IMAGE_INSPECT_TIMEOUT),
|
|
})),
|
|
}
|
|
}
|
|
|
|
/// Build the local testnet image with optional platform override.
|
|
pub async fn build_local_image(
|
|
image: &str,
|
|
platform: Option<&str>,
|
|
) -> Result<(), ComposeRunnerError> {
|
|
let repo_root =
|
|
repository_root().map_err(|source| ComposeRunnerError::ImageBuild { source })?;
|
|
let dockerfile = repo_root.join("testing-framework/runners/docker/runner.Dockerfile");
|
|
|
|
tracing::info!(image, "building compose runner docker image");
|
|
|
|
let mut cmd = Command::new("docker");
|
|
cmd.arg("build");
|
|
|
|
if let Some(build_platform) = select_build_platform(platform)? {
|
|
cmd.arg("--platform").arg(&build_platform);
|
|
}
|
|
|
|
let circuits_platform = env::var("COMPOSE_CIRCUITS_PLATFORM")
|
|
.ok()
|
|
.filter(|value| !value.is_empty())
|
|
.unwrap_or_else(|| String::from("linux-x86_64"));
|
|
|
|
cmd.arg("--build-arg")
|
|
.arg(format!("NOMOS_CIRCUITS_PLATFORM={circuits_platform}"));
|
|
|
|
if let Some(value) = env::var("CIRCUITS_OVERRIDE")
|
|
.ok()
|
|
.filter(|val| !val.is_empty())
|
|
{
|
|
cmd.arg("--build-arg")
|
|
.arg(format!("CIRCUITS_OVERRIDE={value}"));
|
|
}
|
|
|
|
cmd.arg("-t")
|
|
.arg(image)
|
|
.arg("-f")
|
|
.arg(&dockerfile)
|
|
.arg(&repo_root);
|
|
|
|
run_docker_command(cmd, "docker build compose image", IMAGE_BUILD_TIMEOUT).await
|
|
}
|
|
|
|
/// Run a docker command with a timeout, mapping errors into runner errors.
|
|
pub async fn run_docker_command(
|
|
mut command: Command,
|
|
description: &str,
|
|
timeout_duration: Duration,
|
|
) -> Result<(), ComposeRunnerError> {
|
|
match timeout(timeout_duration, command.status()).await {
|
|
Ok(Ok(status)) if status.success() => Ok(()),
|
|
Ok(Ok(status)) => Err(ComposeRunnerError::Compose(ComposeCommandError::Failed {
|
|
command: description.to_owned(),
|
|
status,
|
|
})),
|
|
Ok(Err(source)) => Err(ComposeRunnerError::Compose(ComposeCommandError::Spawn {
|
|
command: description.to_owned(),
|
|
source,
|
|
})),
|
|
Err(_) => Err(ComposeRunnerError::Compose(ComposeCommandError::Timeout {
|
|
command: description.to_owned(),
|
|
timeout: timeout_duration,
|
|
})),
|
|
}
|
|
}
|
|
|
|
fn detect_docker_platform() -> Result<Option<String>, ComposeRunnerError> {
|
|
let output = StdCommand::new("docker")
|
|
.arg("info")
|
|
.arg("-f")
|
|
.arg("{{.Architecture}}")
|
|
.output()
|
|
.map_err(|source| ComposeRunnerError::ImageBuild {
|
|
source: source.into(),
|
|
})?;
|
|
|
|
if !output.status.success() {
|
|
return Ok(None);
|
|
}
|
|
|
|
let arch = String::from_utf8_lossy(&output.stdout).trim().to_owned();
|
|
if arch.is_empty() {
|
|
return Ok(None);
|
|
}
|
|
|
|
Ok(Some(format!("linux/{arch}")))
|
|
}
|
|
|
|
/// Choose the build platform from user override or docker host architecture.
|
|
pub fn select_build_platform(
|
|
requested: Option<&str>,
|
|
) -> Result<Option<String>, ComposeRunnerError> {
|
|
if let Some(value) = requested {
|
|
return Ok(Some(value.to_owned()));
|
|
}
|
|
|
|
detect_docker_platform()?.map_or_else(
|
|
|| {
|
|
warn!("docker host architecture unavailable; letting docker choose default platform");
|
|
Ok(None)
|
|
},
|
|
|host_platform| Ok(Some(host_platform)),
|
|
)
|
|
}
|