ironflow_ops_docker/images/
manage.rs1use std::collections::HashMap;
4
5use async_trait::async_trait;
6use bollard::Docker;
7use bollard::query_parameters::{
8 CreateImageOptions, ListImagesOptions, PushImageOptions, RemoveImageOptions,
9};
10use ironflow_core::error::OperationError;
11use ironflow_core::operation::{Operation, OperationContext, TypedOperation};
12use serde::{Deserialize, Serialize};
13use serde_json::Value;
14use tokio_stream::StreamExt;
15
16use crate::containers::DockerRef;
17use crate::helpers::{docker_error, to_value};
18
19#[derive(Debug, Clone, Serialize, Deserialize)]
25pub struct ImageListEntry {
26 pub id: String,
28 pub repo_tags: Vec<String>,
30 pub size: i64,
32 pub created: i64,
34}
35
36#[derive(Debug, Clone, Serialize, Deserialize)]
38pub struct ImageListOutput {
39 pub images: Vec<ImageListEntry>,
41}
42
43pub struct ImageList {
57 docker: Docker,
58 all: bool,
59 filters: HashMap<String, Vec<String>>,
60}
61
62impl ImageList {
63 pub fn new(client: impl Into<DockerRef>) -> Self {
65 Self {
66 docker: client.into().0,
67 all: false,
68 filters: HashMap::new(),
69 }
70 }
71
72 pub fn all(mut self) -> Self {
74 self.all = true;
75 self
76 }
77
78 pub fn filter(mut self, key: impl Into<String>, value: impl Into<String>) -> Self {
80 self.filters
81 .entry(key.into())
82 .or_default()
83 .push(value.into());
84 self
85 }
86
87 pub async fn run(&self, _ctx: &OperationContext) -> Result<ImageListOutput, OperationError> {
94 let options = ListImagesOptions {
95 all: self.all,
96 filters: Some(self.filters.clone()),
97 ..Default::default()
98 };
99 let images = self
100 .docker
101 .list_images(Some(options))
102 .await
103 .map_err(docker_error)?;
104 let entries = images
105 .into_iter()
106 .map(|i| ImageListEntry {
107 id: i.id,
108 repo_tags: i.repo_tags,
109 size: i.size,
110 created: i.created,
111 })
112 .collect();
113 Ok(ImageListOutput { images: entries })
114 }
115}
116
117#[async_trait]
118impl Operation for ImageList {
119 fn kind(&self) -> &str {
120 "docker"
121 }
122
123 async fn execute(&self, ctx: &OperationContext) -> Result<Value, OperationError> {
124 to_value(&self.run(ctx).await?)
125 }
126
127 fn input(&self) -> Option<Value> {
128 Some(serde_json::json!({
129 "operation": "image_list",
130 "all": self.all,
131 }))
132 }
133}
134
135impl TypedOperation for ImageList {
136 type Output = ImageListOutput;
137}
138
139#[derive(Debug, Clone, Serialize, Deserialize)]
145pub struct ImagePullOutput {
146 pub image: String,
148}
149
150pub struct ImagePull {
164 docker: Docker,
165 image: String,
166}
167
168impl ImagePull {
169 pub fn new(client: impl Into<DockerRef>, image: impl Into<String>) -> Self {
171 Self {
172 docker: client.into().0,
173 image: image.into(),
174 }
175 }
176
177 pub async fn run(&self, _ctx: &OperationContext) -> Result<ImagePullOutput, OperationError> {
183 let options = CreateImageOptions {
184 from_image: Some(self.image.clone()),
185 ..Default::default()
186 };
187 let mut stream = self.docker.create_image(Some(options), None, None);
188 while let Some(result) = stream.next().await {
189 result.map_err(docker_error)?;
190 }
191 Ok(ImagePullOutput {
192 image: self.image.clone(),
193 })
194 }
195}
196
197#[async_trait]
198impl Operation for ImagePull {
199 fn kind(&self) -> &str {
200 "docker"
201 }
202
203 async fn execute(&self, ctx: &OperationContext) -> Result<Value, OperationError> {
204 to_value(&self.run(ctx).await?)
205 }
206
207 fn input(&self) -> Option<Value> {
208 Some(serde_json::json!({
209 "operation": "image_pull",
210 "image": self.image,
211 }))
212 }
213}
214
215impl TypedOperation for ImagePull {
216 type Output = ImagePullOutput;
217}
218
219#[derive(Debug, Clone, Serialize, Deserialize)]
225pub struct ImagePushOutput {
226 pub image: String,
228}
229
230pub struct ImagePush {
244 docker: Docker,
245 image: String,
246 tag: Option<String>,
247}
248
249impl ImagePush {
250 pub fn new(client: impl Into<DockerRef>, image: impl Into<String>) -> Self {
252 Self {
253 docker: client.into().0,
254 image: image.into(),
255 tag: None,
256 }
257 }
258
259 pub fn tag(mut self, tag: impl Into<String>) -> Self {
261 self.tag = Some(tag.into());
262 self
263 }
264
265 pub async fn run(&self, _ctx: &OperationContext) -> Result<ImagePushOutput, OperationError> {
271 let options = PushImageOptions {
272 tag: Some(self.tag.clone().unwrap_or_else(|| "latest".to_string())),
273 platform: None,
274 };
275 let mut stream = self.docker.push_image(&self.image, Some(options), None);
276 while let Some(result) = stream.next().await {
277 result.map_err(docker_error)?;
278 }
279 Ok(ImagePushOutput {
280 image: self.image.clone(),
281 })
282 }
283}
284
285#[async_trait]
286impl Operation for ImagePush {
287 fn kind(&self) -> &str {
288 "docker"
289 }
290
291 async fn execute(&self, ctx: &OperationContext) -> Result<Value, OperationError> {
292 to_value(&self.run(ctx).await?)
293 }
294
295 fn input(&self) -> Option<Value> {
296 Some(serde_json::json!({
297 "operation": "image_push",
298 "image": self.image,
299 }))
300 }
301}
302
303impl TypedOperation for ImagePush {
304 type Output = ImagePushOutput;
305}
306
307#[derive(Debug, Clone, Serialize, Deserialize)]
313pub struct ImageInspectOutput {
314 pub data: Value,
316}
317
318pub struct ImageInspect {
332 docker: Docker,
333 image: String,
334}
335
336impl ImageInspect {
337 pub fn new(client: impl Into<DockerRef>, image: impl Into<String>) -> Self {
339 Self {
340 docker: client.into().0,
341 image: image.into(),
342 }
343 }
344
345 pub async fn run(&self, _ctx: &OperationContext) -> Result<ImageInspectOutput, OperationError> {
351 let response = self
352 .docker
353 .inspect_image(&self.image)
354 .await
355 .map_err(docker_error)?;
356 let data = serde_json::to_value(&response).map_err(|e| OperationError::External {
357 origin: "docker".to_string(),
358 message: e.to_string(),
359 })?;
360 Ok(ImageInspectOutput { data })
361 }
362}
363
364#[async_trait]
365impl Operation for ImageInspect {
366 fn kind(&self) -> &str {
367 "docker"
368 }
369
370 async fn execute(&self, ctx: &OperationContext) -> Result<Value, OperationError> {
371 to_value(&self.run(ctx).await?)
372 }
373
374 fn input(&self) -> Option<Value> {
375 Some(serde_json::json!({
376 "operation": "image_inspect",
377 "image": self.image,
378 }))
379 }
380}
381
382impl TypedOperation for ImageInspect {
383 type Output = ImageInspectOutput;
384}
385
386#[derive(Debug, Clone, Serialize, Deserialize)]
392pub struct ImageRemoveOutput {
393 pub image: String,
395}
396
397pub struct ImageRemove {
411 docker: Docker,
412 image: String,
413 force: bool,
414 no_prune: bool,
415}
416
417impl ImageRemove {
418 pub fn new(client: impl Into<DockerRef>, image: impl Into<String>) -> Self {
420 Self {
421 docker: client.into().0,
422 image: image.into(),
423 force: false,
424 no_prune: false,
425 }
426 }
427
428 pub fn force(mut self) -> Self {
430 self.force = true;
431 self
432 }
433
434 pub fn no_prune(mut self) -> Self {
436 self.no_prune = true;
437 self
438 }
439
440 pub async fn run(&self, _ctx: &OperationContext) -> Result<ImageRemoveOutput, OperationError> {
446 let options = RemoveImageOptions {
447 force: self.force,
448 noprune: self.no_prune,
449 platforms: None,
450 };
451 self.docker
452 .remove_image(&self.image, Some(options), None)
453 .await
454 .map_err(docker_error)?;
455 Ok(ImageRemoveOutput {
456 image: self.image.clone(),
457 })
458 }
459}
460
461#[async_trait]
462impl Operation for ImageRemove {
463 fn kind(&self) -> &str {
464 "docker"
465 }
466
467 async fn execute(&self, ctx: &OperationContext) -> Result<Value, OperationError> {
468 to_value(&self.run(ctx).await?)
469 }
470
471 fn input(&self) -> Option<Value> {
472 Some(serde_json::json!({
473 "operation": "image_remove",
474 "image": self.image,
475 }))
476 }
477}
478
479impl TypedOperation for ImageRemove {
480 type Output = ImageRemoveOutput;
481}