use std::net::IpAddr;
use tracing::{info, instrument, warn};
use crate::adapters::ssh::SshClient;
use crate::adapters::ssh::SshConfig;
use crate::infrastructure::remote_actions::{RemoteAction, RemoteActionError};
pub struct DockerComposeValidator {
ssh_client: SshClient,
}
impl DockerComposeValidator {
#[must_use]
pub fn new(ssh_config: SshConfig) -> Self {
let ssh_client = SshClient::new(ssh_config);
Self { ssh_client }
}
}
impl RemoteAction for DockerComposeValidator {
fn name(&self) -> &'static str {
"docker-compose-validation"
}
#[allow(clippy::too_many_lines)]
#[instrument(
name = "docker_compose_validation",
skip(self),
fields(
action_type = "validation",
component = "docker_compose",
server_ip = %server_ip
)
)]
async fn execute(&self, server_ip: &IpAddr) -> Result<(), RemoteActionError> {
info!(
action = "docker_compose_validation",
"Validating Docker Compose installation"
);
let compose_version =
self.ssh_client
.execute("docker compose version")
.map_err(|source| RemoteActionError::SshCommandFailed {
action_name: self.name().to_string(),
source,
})?;
let compose_version = compose_version.trim();
info!(
action = "docker_compose_validation",
status = "success",
"Docker Compose installation validated"
);
info!(
action = "docker_compose_validation",
version = compose_version,
"Docker Compose version detected"
);
let test_compose_content = r"services:
test:
image: hello-world
";
let create_test_success = self
.ssh_client
.check_command(&format!(
"echo '{test_compose_content}' > /tmp/test-docker-compose.yml"
))
.map_err(|source| RemoteActionError::SshCommandFailed {
action_name: self.name().to_string(),
source,
})?;
if !create_test_success {
warn!(
action = "docker_compose_validation",
check = "test_file_creation",
status = "failed",
"Could not create test docker-compose.yml file"
);
return Ok(()); }
let validate_success = self
.ssh_client
.check_command("cd /tmp && docker compose -f test-docker-compose.yml config")
.map_err(|source| RemoteActionError::SshCommandFailed {
action_name: self.name().to_string(),
source,
})?;
if validate_success {
info!(
action = "docker_compose_validation",
check = "configuration_validation",
status = "success",
"Docker Compose configuration validation passed"
);
} else {
warn!(
action = "docker_compose_validation",
check = "configuration_validation",
status = "skipped",
"Docker Compose configuration validation skipped"
);
}
drop(
self.ssh_client
.check_command("rm -f /tmp/test-docker-compose.yml"),
);
Ok(())
}
}