use std::collections::HashMap;
use async_trait::async_trait;
use bollard::Docker;
use bollard::query_parameters::{PruneContainersOptions, StatsOptions};
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 ContainerStatsOutput {
pub data: Value,
}
pub struct ContainerStats {
docker: Docker,
container: String,
}
impl ContainerStats {
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<ContainerStatsOutput, OperationError> {
let options = StatsOptions {
stream: false,
one_shot: true,
};
let mut stream = self.docker.stats(&self.container, Some(options));
let stats = stream
.next()
.await
.ok_or_else(|| OperationError::External {
origin: "docker".to_string(),
message: "no stats returned".to_string(),
})?
.map_err(docker_error)?;
let data = serde_json::to_value(&stats).map_err(|e| OperationError::External {
origin: "docker".to_string(),
message: e.to_string(),
})?;
Ok(ContainerStatsOutput { data })
}
}
#[async_trait]
impl Operation for ContainerStats {
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_stats",
"container": self.container,
}))
}
}
impl TypedOperation for ContainerStats {
type Output = ContainerStatsOutput;
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ContainerChange {
pub path: String,
pub kind: i32,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ContainerChangesOutput {
pub changes: Vec<ContainerChange>,
}
pub struct ContainerChanges {
docker: Docker,
container: String,
}
impl ContainerChanges {
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<ContainerChangesOutput, OperationError> {
let response = self
.docker
.container_changes(&self.container)
.await
.map_err(docker_error)?;
let changes = response
.unwrap_or_default()
.into_iter()
.map(|c| ContainerChange {
path: c.path,
kind: c.kind as i32,
})
.collect();
Ok(ContainerChangesOutput { changes })
}
}
#[async_trait]
impl Operation for ContainerChanges {
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_changes",
"container": self.container,
}))
}
}
impl TypedOperation for ContainerChanges {
type Output = ContainerChangesOutput;
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ContainerPruneOutput {
pub containers_deleted: Vec<String>,
pub space_reclaimed: u64,
}
pub struct ContainerPrune {
docker: Docker,
filters: HashMap<String, Vec<String>>,
}
impl ContainerPrune {
pub fn new(client: impl Into<DockerRef>) -> Self {
Self {
docker: client.into().0,
filters: HashMap::new(),
}
}
pub fn filter(mut self, key: impl Into<String>, value: impl Into<String>) -> Self {
self.filters
.entry(key.into())
.or_default()
.push(value.into());
self
}
pub async fn run(
&self,
_ctx: &OperationContext,
) -> Result<ContainerPruneOutput, OperationError> {
let options = PruneContainersOptions {
filters: Some(self.filters.clone()),
};
let response = self
.docker
.prune_containers(Some(options))
.await
.map_err(docker_error)?;
Ok(ContainerPruneOutput {
containers_deleted: response.containers_deleted.unwrap_or_default(),
space_reclaimed: response.space_reclaimed.unwrap_or(0) as u64,
})
}
}
#[async_trait]
impl Operation for ContainerPrune {
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_prune",
}))
}
}
impl TypedOperation for ContainerPrune {
type Output = ContainerPruneOutput;
}