use async_trait::async_trait;
use bollard::Docker;
use bollard::query_parameters::{SearchImagesOptions, TagImageOptions};
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 ImageTagOutput {
pub source: String,
pub repo: String,
pub tag: String,
}
pub struct ImageTag {
docker: Docker,
source: String,
repo: String,
tag: String,
}
impl ImageTag {
pub fn new(
client: impl Into<DockerRef>,
source: impl Into<String>,
repo: impl Into<String>,
tag: impl Into<String>,
) -> Self {
Self {
docker: client.into().0,
source: source.into(),
repo: repo.into(),
tag: tag.into(),
}
}
pub async fn run(&self, _ctx: &OperationContext) -> Result<ImageTagOutput, OperationError> {
let options = TagImageOptions {
repo: Some(self.repo.clone()),
tag: Some(self.tag.clone()),
};
self.docker
.tag_image(&self.source, Some(options))
.await
.map_err(docker_error)?;
Ok(ImageTagOutput {
source: self.source.clone(),
repo: self.repo.clone(),
tag: self.tag.clone(),
})
}
}
#[async_trait]
impl Operation for ImageTag {
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": "image_tag",
"source": self.source,
"repo": self.repo,
"tag": self.tag,
}))
}
}
impl TypedOperation for ImageTag {
type Output = ImageTagOutput;
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ImageHistoryEntry {
pub id: String,
pub created: i64,
pub created_by: String,
pub size: i64,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ImageHistoryOutput {
pub layers: Vec<ImageHistoryEntry>,
}
pub struct ImageHistory {
docker: Docker,
image: String,
}
impl ImageHistory {
pub fn new(client: impl Into<DockerRef>, image: impl Into<String>) -> Self {
Self {
docker: client.into().0,
image: image.into(),
}
}
pub async fn run(&self, _ctx: &OperationContext) -> Result<ImageHistoryOutput, OperationError> {
let history = self
.docker
.image_history(&self.image)
.await
.map_err(docker_error)?;
let layers = history
.into_iter()
.map(|h| ImageHistoryEntry {
id: h.id,
created: h.created,
created_by: h.created_by,
size: h.size,
})
.collect();
Ok(ImageHistoryOutput { layers })
}
}
#[async_trait]
impl Operation for ImageHistory {
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": "image_history",
"image": self.image,
}))
}
}
impl TypedOperation for ImageHistory {
type Output = ImageHistoryOutput;
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ImageSearchEntry {
pub name: String,
pub description: String,
pub star_count: i64,
pub is_official: bool,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ImageSearchOutput {
pub results: Vec<ImageSearchEntry>,
}
pub struct ImageSearch {
docker: Docker,
term: String,
limit: Option<i64>,
}
impl ImageSearch {
pub fn new(client: impl Into<DockerRef>, term: impl Into<String>) -> Self {
Self {
docker: client.into().0,
term: term.into(),
limit: None,
}
}
pub fn limit(mut self, limit: i64) -> Self {
self.limit = Some(limit);
self
}
pub async fn run(&self, _ctx: &OperationContext) -> Result<ImageSearchOutput, OperationError> {
let options = SearchImagesOptions {
term: self.term.clone(),
limit: self.limit.map(|l| l as i32),
..Default::default()
};
let results = self
.docker
.search_images(options)
.await
.map_err(docker_error)?;
let entries = results
.into_iter()
.map(|r| ImageSearchEntry {
name: r.name.unwrap_or_default(),
description: r.description.unwrap_or_default(),
star_count: r.star_count.unwrap_or(0),
is_official: r.is_official.unwrap_or(false),
})
.collect();
Ok(ImageSearchOutput { results: entries })
}
}
#[async_trait]
impl Operation for ImageSearch {
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": "image_search",
"term": self.term,
}))
}
}
impl TypedOperation for ImageSearch {
type Output = ImageSearchOutput;
}