use async_trait::async_trait;
use bollard::Docker;
use bollard::exec::{CreateExecOptions, StartExecResults};
use bollard::query_parameters::{LogsOptions, WaitContainerOptions};
use ironflow_core::error::OperationError;
use ironflow_core::operation::{Operation, OperationContext, TypedOperation};
use serde::{Deserialize, Serialize};
use serde_json::Value;
use tokio_stream::StreamExt;
use crate::containers::DockerRef;
use crate::helpers::{docker_error, to_value};
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ContainerLogsOutput {
pub lines: Vec<String>,
}
pub struct ContainerLogs {
docker: Docker,
container: String,
stdout: bool,
stderr: bool,
tail: Option<String>,
}
impl ContainerLogs {
pub fn new(client: impl Into<DockerRef>, container: impl Into<String>) -> Self {
Self {
docker: client.into().0,
container: container.into(),
stdout: true,
stderr: true,
tail: None,
}
}
pub fn stdout_only(mut self) -> Self {
self.stderr = false;
self
}
pub fn stderr_only(mut self) -> Self {
self.stdout = false;
self
}
pub fn tail(mut self, n: u64) -> Self {
self.tail = Some(n.to_string());
self
}
pub async fn run(
&self,
_ctx: &OperationContext,
) -> Result<ContainerLogsOutput, OperationError> {
let options = LogsOptions {
stdout: self.stdout,
stderr: self.stderr,
tail: self.tail.clone().unwrap_or_else(|| "all".to_string()),
..Default::default()
};
let mut stream = self.docker.logs(&self.container, Some(options));
let mut lines = Vec::new();
while let Some(result) = stream.next().await {
let chunk = result.map_err(docker_error)?;
lines.push(chunk.to_string());
}
Ok(ContainerLogsOutput { lines })
}
}
#[async_trait]
impl Operation for ContainerLogs {
fn kind(&self) -> &str {
"docker"
}
async fn execute(&self, ctx: &OperationContext) -> Result<Value, OperationError> {
to_value(&self.run(ctx).await?)
}
fn input(&self) -> Option<Value> {
Some(serde_json::json!({
"operation": "container_logs",
"container": self.container,
}))
}
}
impl TypedOperation for ContainerLogs {
type Output = ContainerLogsOutput;
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ContainerExecOutput {
pub output: Vec<String>,
}
pub struct ContainerExec {
docker: Docker,
container: String,
cmd: Vec<String>,
}
impl ContainerExec {
pub fn new(
client: impl Into<DockerRef>,
container: impl Into<String>,
cmd: Vec<impl Into<String>>,
) -> Self {
Self {
docker: client.into().0,
container: container.into(),
cmd: cmd.into_iter().map(Into::into).collect(),
}
}
pub async fn run(
&self,
_ctx: &OperationContext,
) -> Result<ContainerExecOutput, OperationError> {
let exec_options = CreateExecOptions {
cmd: Some(self.cmd.clone()),
attach_stdout: Some(true),
attach_stderr: Some(true),
..Default::default()
};
let exec = self
.docker
.create_exec(&self.container, exec_options)
.await
.map_err(docker_error)?;
let start_result = self
.docker
.start_exec(&exec.id, None)
.await
.map_err(docker_error)?;
let mut output = Vec::new();
if let StartExecResults::Attached {
output: mut stream, ..
} = start_result
{
while let Some(result) = stream.next().await {
let chunk = result.map_err(docker_error)?;
output.push(chunk.to_string());
}
}
Ok(ContainerExecOutput { output })
}
}
#[async_trait]
impl Operation for ContainerExec {
fn kind(&self) -> &str {
"docker"
}
async fn execute(&self, ctx: &OperationContext) -> Result<Value, OperationError> {
to_value(&self.run(ctx).await?)
}
fn input(&self) -> Option<Value> {
Some(serde_json::json!({
"operation": "container_exec",
"container": self.container,
"cmd": self.cmd,
}))
}
}
impl TypedOperation for ContainerExec {
type Output = ContainerExecOutput;
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ContainerWaitOutput {
pub status_code: i64,
}
pub struct ContainerWait {
docker: Docker,
container: String,
}
impl ContainerWait {
pub fn new(client: impl Into<DockerRef>, container: impl Into<String>) -> Self {
Self {
docker: client.into().0,
container: container.into(),
}
}
pub async fn run(
&self,
_ctx: &OperationContext,
) -> Result<ContainerWaitOutput, OperationError> {
let options = WaitContainerOptions {
condition: "not-running".to_string(),
};
let mut stream = self.docker.wait_container(&self.container, Some(options));
let mut status_code = 0i64;
while let Some(result) = stream.next().await {
let response = result.map_err(docker_error)?;
status_code = response.status_code;
}
Ok(ContainerWaitOutput { status_code })
}
}
#[async_trait]
impl Operation for ContainerWait {
fn kind(&self) -> &str {
"docker"
}
async fn execute(&self, ctx: &OperationContext) -> Result<Value, OperationError> {
to_value(&self.run(ctx).await?)
}
fn input(&self) -> Option<Value> {
Some(serde_json::json!({
"operation": "container_wait",
"container": self.container,
}))
}
}
impl TypedOperation for ContainerWait {
type Output = ContainerWaitOutput;
}