use std::collections::HashMap;
use async_trait::async_trait;
use bollard::Docker;
use bollard::query_parameters::{
InspectContainerOptions, KillContainerOptions, ListContainersOptions, RemoveContainerOptions,
};
use ironflow_core::error::OperationError;
use ironflow_core::operation::{Operation, OperationContext, TypedOperation};
use serde::{Deserialize, Serialize};
use serde_json::Value;
use crate::containers::DockerRef;
use crate::helpers::{docker_error, to_value};
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ContainerKillOutput {
pub container: String,
}
pub struct ContainerKill {
docker: Docker,
container: String,
signal: Option<String>,
}
impl ContainerKill {
pub fn new(client: impl Into<DockerRef>, container: impl Into<String>) -> Self {
Self {
docker: client.into().0,
container: container.into(),
signal: None,
}
}
pub fn signal(mut self, signal: impl Into<String>) -> Self {
self.signal = Some(signal.into());
self
}
pub async fn run(
&self,
_ctx: &OperationContext,
) -> Result<ContainerKillOutput, OperationError> {
let options = KillContainerOptions {
signal: self.signal.clone().unwrap_or_else(|| "SIGKILL".to_string()),
};
self.docker
.kill_container(&self.container, Some(options))
.await
.map_err(docker_error)?;
Ok(ContainerKillOutput {
container: self.container.clone(),
})
}
}
#[async_trait]
impl Operation for ContainerKill {
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_kill",
"container": self.container,
}))
}
}
impl TypedOperation for ContainerKill {
type Output = ContainerKillOutput;
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ContainerRemoveOutput {
pub container: String,
}
pub struct ContainerRemove {
docker: Docker,
container: String,
force: bool,
volumes: bool,
}
impl ContainerRemove {
pub fn new(client: impl Into<DockerRef>, container: impl Into<String>) -> Self {
Self {
docker: client.into().0,
container: container.into(),
force: false,
volumes: false,
}
}
pub fn force(mut self) -> Self {
self.force = true;
self
}
pub fn volumes(mut self) -> Self {
self.volumes = true;
self
}
pub async fn run(
&self,
_ctx: &OperationContext,
) -> Result<ContainerRemoveOutput, OperationError> {
let options = RemoveContainerOptions {
force: self.force,
v: self.volumes,
..Default::default()
};
self.docker
.remove_container(&self.container, Some(options))
.await
.map_err(docker_error)?;
Ok(ContainerRemoveOutput {
container: self.container.clone(),
})
}
}
#[async_trait]
impl Operation for ContainerRemove {
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_remove",
"container": self.container,
"force": self.force,
}))
}
}
impl TypedOperation for ContainerRemove {
type Output = ContainerRemoveOutput;
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ContainerInspectOutput {
pub data: Value,
}
pub struct ContainerInspect {
docker: Docker,
container: String,
}
impl ContainerInspect {
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<ContainerInspectOutput, OperationError> {
let options = InspectContainerOptions { size: false };
let response = self
.docker
.inspect_container(&self.container, Some(options))
.await
.map_err(docker_error)?;
let data = serde_json::to_value(&response).map_err(|e| OperationError::External {
origin: "docker".to_string(),
message: e.to_string(),
})?;
Ok(ContainerInspectOutput { data })
}
}
#[async_trait]
impl Operation for ContainerInspect {
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_inspect",
"container": self.container,
}))
}
}
impl TypedOperation for ContainerInspect {
type Output = ContainerInspectOutput;
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ContainerListEntry {
pub id: String,
pub names: Vec<String>,
pub image: String,
pub state: String,
pub status: String,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ContainerListOutput {
pub containers: Vec<ContainerListEntry>,
}
pub struct ContainerList {
docker: Docker,
all: bool,
filters: HashMap<String, Vec<String>>,
}
impl ContainerList {
pub fn new(client: impl Into<DockerRef>) -> Self {
Self {
docker: client.into().0,
all: false,
filters: HashMap::new(),
}
}
pub fn all(mut self) -> Self {
self.all = true;
self
}
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<ContainerListOutput, OperationError> {
let options = ListContainersOptions {
all: self.all,
filters: Some(self.filters.clone()),
..Default::default()
};
let containers = self
.docker
.list_containers(Some(options))
.await
.map_err(docker_error)?;
let entries = containers
.into_iter()
.map(|c| ContainerListEntry {
id: c.id.unwrap_or_default(),
names: c.names.unwrap_or_default(),
image: c.image.unwrap_or_default(),
state: c.state.map(|s| s.to_string()).unwrap_or_default(),
status: c.status.unwrap_or_default(),
})
.collect();
Ok(ContainerListOutput {
containers: entries,
})
}
}
#[async_trait]
impl Operation for ContainerList {
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_list",
"all": self.all,
}))
}
}
impl TypedOperation for ContainerList {
type Output = ContainerListOutput;
}