ironflow_ops_docker/images/
tag.rs1use async_trait::async_trait;
4use bollard::Docker;
5use bollard::query_parameters::{SearchImagesOptions, TagImageOptions};
6use ironflow_core::error::OperationError;
7use ironflow_core::operation::{Operation, OperationContext, TypedOperation};
8use serde::{Deserialize, Serialize};
9use serde_json::Value;
10
11use crate::containers::DockerRef;
12use crate::helpers::{docker_error, to_value};
13
14#[derive(Debug, Clone, Serialize, Deserialize)]
20pub struct ImageTagOutput {
21 pub source: String,
23 pub repo: String,
25 pub tag: String,
27}
28
29pub struct ImageTag {
43 docker: Docker,
44 source: String,
45 repo: String,
46 tag: String,
47}
48
49impl ImageTag {
50 pub fn new(
52 client: impl Into<DockerRef>,
53 source: impl Into<String>,
54 repo: impl Into<String>,
55 tag: impl Into<String>,
56 ) -> Self {
57 Self {
58 docker: client.into().0,
59 source: source.into(),
60 repo: repo.into(),
61 tag: tag.into(),
62 }
63 }
64
65 pub async fn run(&self, _ctx: &OperationContext) -> Result<ImageTagOutput, OperationError> {
71 let options = TagImageOptions {
72 repo: Some(self.repo.clone()),
73 tag: Some(self.tag.clone()),
74 };
75 self.docker
76 .tag_image(&self.source, Some(options))
77 .await
78 .map_err(docker_error)?;
79 Ok(ImageTagOutput {
80 source: self.source.clone(),
81 repo: self.repo.clone(),
82 tag: self.tag.clone(),
83 })
84 }
85}
86
87#[async_trait]
88impl Operation for ImageTag {
89 fn kind(&self) -> &str {
90 "docker"
91 }
92
93 async fn execute(&self, ctx: &OperationContext) -> Result<Value, OperationError> {
94 to_value(&self.run(ctx).await?)
95 }
96
97 fn input(&self) -> Option<Value> {
98 Some(serde_json::json!({
99 "operation": "image_tag",
100 "source": self.source,
101 "repo": self.repo,
102 "tag": self.tag,
103 }))
104 }
105}
106
107impl TypedOperation for ImageTag {
108 type Output = ImageTagOutput;
109}
110
111#[derive(Debug, Clone, Serialize, Deserialize)]
117pub struct ImageHistoryEntry {
118 pub id: String,
120 pub created: i64,
122 pub created_by: String,
124 pub size: i64,
126}
127
128#[derive(Debug, Clone, Serialize, Deserialize)]
130pub struct ImageHistoryOutput {
131 pub layers: Vec<ImageHistoryEntry>,
133}
134
135pub struct ImageHistory {
149 docker: Docker,
150 image: String,
151}
152
153impl ImageHistory {
154 pub fn new(client: impl Into<DockerRef>, image: impl Into<String>) -> Self {
156 Self {
157 docker: client.into().0,
158 image: image.into(),
159 }
160 }
161
162 pub async fn run(&self, _ctx: &OperationContext) -> Result<ImageHistoryOutput, OperationError> {
168 let history = self
169 .docker
170 .image_history(&self.image)
171 .await
172 .map_err(docker_error)?;
173 let layers = history
174 .into_iter()
175 .map(|h| ImageHistoryEntry {
176 id: h.id,
177 created: h.created,
178 created_by: h.created_by,
179 size: h.size,
180 })
181 .collect();
182 Ok(ImageHistoryOutput { layers })
183 }
184}
185
186#[async_trait]
187impl Operation for ImageHistory {
188 fn kind(&self) -> &str {
189 "docker"
190 }
191
192 async fn execute(&self, ctx: &OperationContext) -> Result<Value, OperationError> {
193 to_value(&self.run(ctx).await?)
194 }
195
196 fn input(&self) -> Option<Value> {
197 Some(serde_json::json!({
198 "operation": "image_history",
199 "image": self.image,
200 }))
201 }
202}
203
204impl TypedOperation for ImageHistory {
205 type Output = ImageHistoryOutput;
206}
207
208#[derive(Debug, Clone, Serialize, Deserialize)]
214pub struct ImageSearchEntry {
215 pub name: String,
217 pub description: String,
219 pub star_count: i64,
221 pub is_official: bool,
223}
224
225#[derive(Debug, Clone, Serialize, Deserialize)]
227pub struct ImageSearchOutput {
228 pub results: Vec<ImageSearchEntry>,
230}
231
232pub struct ImageSearch {
246 docker: Docker,
247 term: String,
248 limit: Option<i64>,
249}
250
251impl ImageSearch {
252 pub fn new(client: impl Into<DockerRef>, term: impl Into<String>) -> Self {
254 Self {
255 docker: client.into().0,
256 term: term.into(),
257 limit: None,
258 }
259 }
260
261 pub fn limit(mut self, limit: i64) -> Self {
263 self.limit = Some(limit);
264 self
265 }
266
267 pub async fn run(&self, _ctx: &OperationContext) -> Result<ImageSearchOutput, OperationError> {
273 let options = SearchImagesOptions {
274 term: self.term.clone(),
275 limit: self.limit.map(|l| l as i32),
276 ..Default::default()
277 };
278 let results = self
279 .docker
280 .search_images(options)
281 .await
282 .map_err(docker_error)?;
283 let entries = results
284 .into_iter()
285 .map(|r| ImageSearchEntry {
286 name: r.name.unwrap_or_default(),
287 description: r.description.unwrap_or_default(),
288 star_count: r.star_count.unwrap_or(0),
289 is_official: r.is_official.unwrap_or(false),
290 })
291 .collect();
292 Ok(ImageSearchOutput { results: entries })
293 }
294}
295
296#[async_trait]
297impl Operation for ImageSearch {
298 fn kind(&self) -> &str {
299 "docker"
300 }
301
302 async fn execute(&self, ctx: &OperationContext) -> Result<Value, OperationError> {
303 to_value(&self.run(ctx).await?)
304 }
305
306 fn input(&self) -> Option<Value> {
307 Some(serde_json::json!({
308 "operation": "image_search",
309 "term": self.term,
310 }))
311 }
312}
313
314impl TypedOperation for ImageSearch {
315 type Output = ImageSearchOutput;
316}