docker_wrapper/command/manifest/
annotate.rs

1//! Docker manifest annotate command implementation.
2
3use crate::command::{CommandExecutor, CommandOutput, DockerCommand};
4use crate::error::Result;
5use async_trait::async_trait;
6
7/// Result of manifest annotate command
8#[derive(Debug, Clone)]
9pub struct ManifestAnnotateResult {
10    /// The manifest list that was annotated
11    pub manifest_list: String,
12    /// The manifest that was annotated
13    pub manifest: String,
14    /// Raw output from the command
15    pub output: String,
16    /// Whether the command succeeded
17    pub success: bool,
18}
19
20impl ManifestAnnotateResult {
21    /// Parse the manifest annotate output
22    fn parse(manifest_list: &str, manifest: &str, output: &CommandOutput) -> Self {
23        Self {
24            manifest_list: manifest_list.to_string(),
25            manifest: manifest.to_string(),
26            output: output.stdout.clone(),
27            success: output.success,
28        }
29    }
30}
31
32/// Docker manifest annotate command builder
33///
34/// Adds additional information to a local image manifest.
35///
36/// # Example
37///
38/// ```rust,no_run
39/// use docker_wrapper::{DockerCommand, ManifestAnnotateCommand};
40///
41/// # #[tokio::main]
42/// # async fn main() -> Result<(), Box<dyn std::error::Error>> {
43/// let result = ManifestAnnotateCommand::new("myapp:latest", "myapp:latest-arm64")
44///     .os("linux")
45///     .arch("arm64")
46///     .variant("v8")
47///     .execute()
48///     .await?;
49///
50/// println!("Annotated manifest: {}", result.manifest);
51/// # Ok(())
52/// # }
53/// ```
54#[derive(Debug, Clone)]
55pub struct ManifestAnnotateCommand {
56    /// The manifest list name
57    manifest_list: String,
58    /// The manifest to annotate
59    manifest: String,
60    /// Set architecture
61    arch: Option<String>,
62    /// Set operating system
63    os: Option<String>,
64    /// Set operating system features
65    os_features: Vec<String>,
66    /// Set operating system version
67    os_version: Option<String>,
68    /// Set architecture variant
69    variant: Option<String>,
70    /// Command executor
71    pub executor: CommandExecutor,
72}
73
74impl ManifestAnnotateCommand {
75    /// Create a new manifest annotate command
76    ///
77    /// # Arguments
78    ///
79    /// * `manifest_list` - The manifest list name (e.g., "myapp:latest")
80    /// * `manifest` - The manifest to annotate (e.g., "myapp:latest-arm64")
81    #[must_use]
82    pub fn new(manifest_list: impl Into<String>, manifest: impl Into<String>) -> Self {
83        Self {
84            manifest_list: manifest_list.into(),
85            manifest: manifest.into(),
86            arch: None,
87            os: None,
88            os_features: Vec::new(),
89            os_version: None,
90            variant: None,
91            executor: CommandExecutor::new(),
92        }
93    }
94
95    /// Set the architecture (e.g., "amd64", "arm64", "arm")
96    #[must_use]
97    pub fn arch(mut self, arch: impl Into<String>) -> Self {
98        self.arch = Some(arch.into());
99        self
100    }
101
102    /// Set the operating system (e.g., "linux", "windows")
103    #[must_use]
104    pub fn os(mut self, os: impl Into<String>) -> Self {
105        self.os = Some(os.into());
106        self
107    }
108
109    /// Add an operating system feature
110    #[must_use]
111    pub fn os_feature(mut self, feature: impl Into<String>) -> Self {
112        self.os_features.push(feature.into());
113        self
114    }
115
116    /// Set the operating system version
117    #[must_use]
118    pub fn os_version(mut self, version: impl Into<String>) -> Self {
119        self.os_version = Some(version.into());
120        self
121    }
122
123    /// Set the architecture variant (e.g., "v7", "v8")
124    #[must_use]
125    pub fn variant(mut self, variant: impl Into<String>) -> Self {
126        self.variant = Some(variant.into());
127        self
128    }
129
130    /// Build the command arguments
131    fn build_args(&self) -> Vec<String> {
132        let mut args = vec!["manifest".to_string(), "annotate".to_string()];
133
134        if let Some(ref arch) = self.arch {
135            args.push("--arch".to_string());
136            args.push(arch.clone());
137        }
138
139        if let Some(ref os) = self.os {
140            args.push("--os".to_string());
141            args.push(os.clone());
142        }
143
144        for feature in &self.os_features {
145            args.push("--os-features".to_string());
146            args.push(feature.clone());
147        }
148
149        if let Some(ref version) = self.os_version {
150            args.push("--os-version".to_string());
151            args.push(version.clone());
152        }
153
154        if let Some(ref variant) = self.variant {
155            args.push("--variant".to_string());
156            args.push(variant.clone());
157        }
158
159        args.push(self.manifest_list.clone());
160        args.push(self.manifest.clone());
161
162        args.extend(self.executor.raw_args.clone());
163
164        args
165    }
166}
167
168#[async_trait]
169impl DockerCommand for ManifestAnnotateCommand {
170    type Output = ManifestAnnotateResult;
171
172    fn get_executor(&self) -> &CommandExecutor {
173        &self.executor
174    }
175
176    fn get_executor_mut(&mut self) -> &mut CommandExecutor {
177        &mut self.executor
178    }
179
180    fn build_command_args(&self) -> Vec<String> {
181        self.build_args()
182    }
183
184    async fn execute(&self) -> Result<Self::Output> {
185        let args = self.build_args();
186        let output = self.execute_command(args).await?;
187        Ok(ManifestAnnotateResult::parse(
188            &self.manifest_list,
189            &self.manifest,
190            &output,
191        ))
192    }
193}
194
195#[cfg(test)]
196mod tests {
197    use super::*;
198
199    #[test]
200    fn test_manifest_annotate_basic() {
201        let cmd = ManifestAnnotateCommand::new("myapp:latest", "myapp:latest-amd64");
202        let args = cmd.build_args();
203        assert_eq!(
204            args,
205            vec!["manifest", "annotate", "myapp:latest", "myapp:latest-amd64"]
206        );
207    }
208
209    #[test]
210    fn test_manifest_annotate_with_arch() {
211        let cmd = ManifestAnnotateCommand::new("myapp:latest", "myapp:latest-arm64").arch("arm64");
212        let args = cmd.build_args();
213        assert!(args.contains(&"--arch".to_string()));
214        assert!(args.contains(&"arm64".to_string()));
215    }
216
217    #[test]
218    fn test_manifest_annotate_with_os() {
219        let cmd = ManifestAnnotateCommand::new("myapp:latest", "myapp:latest-amd64").os("linux");
220        let args = cmd.build_args();
221        assert!(args.contains(&"--os".to_string()));
222        assert!(args.contains(&"linux".to_string()));
223    }
224
225    #[test]
226    fn test_manifest_annotate_with_variant() {
227        let cmd = ManifestAnnotateCommand::new("myapp:latest", "myapp:latest-arm64").variant("v8");
228        let args = cmd.build_args();
229        assert!(args.contains(&"--variant".to_string()));
230        assert!(args.contains(&"v8".to_string()));
231    }
232
233    #[test]
234    fn test_manifest_annotate_all_options() {
235        let cmd = ManifestAnnotateCommand::new("myapp:latest", "myapp:latest-arm64")
236            .arch("arm64")
237            .os("linux")
238            .os_feature("sse4")
239            .os_version("1.0")
240            .variant("v8");
241        let args = cmd.build_args();
242        assert!(args.contains(&"--arch".to_string()));
243        assert!(args.contains(&"--os".to_string()));
244        assert!(args.contains(&"--os-features".to_string()));
245        assert!(args.contains(&"--os-version".to_string()));
246        assert!(args.contains(&"--variant".to_string()));
247    }
248}