docker_wrapper/command/manifest/
push.rs

1//! Docker manifest push command implementation.
2
3use crate::command::{CommandExecutor, CommandOutput, DockerCommand};
4use crate::error::Result;
5use async_trait::async_trait;
6
7/// Result of manifest push command
8#[derive(Debug, Clone)]
9pub struct ManifestPushResult {
10    /// The manifest list that was pushed
11    pub manifest_list: String,
12    /// The digest of the pushed manifest (if available)
13    pub digest: Option<String>,
14    /// Raw output from the command
15    pub output: String,
16    /// Whether the command succeeded
17    pub success: bool,
18}
19
20impl ManifestPushResult {
21    /// Parse the manifest push output
22    fn parse(manifest_list: &str, output: &CommandOutput) -> Self {
23        // The output typically contains the digest of the pushed manifest
24        let digest = output
25            .stdout
26            .lines()
27            .find(|line| line.starts_with("sha256:"))
28            .map(|s| s.trim().to_string());
29
30        Self {
31            manifest_list: manifest_list.to_string(),
32            digest,
33            output: output.stdout.clone(),
34            success: output.success,
35        }
36    }
37}
38
39/// Docker manifest push command builder
40///
41/// Pushes a manifest list to a repository.
42///
43/// # Example
44///
45/// ```rust,no_run
46/// use docker_wrapper::{DockerCommand, ManifestPushCommand};
47///
48/// # #[tokio::main]
49/// # async fn main() -> Result<(), Box<dyn std::error::Error>> {
50/// let result = ManifestPushCommand::new("myapp:latest")
51///     .purge()
52///     .execute()
53///     .await?;
54///
55/// if let Some(digest) = &result.digest {
56///     println!("Pushed manifest with digest: {}", digest);
57/// }
58/// # Ok(())
59/// # }
60/// ```
61#[derive(Debug, Clone)]
62pub struct ManifestPushCommand {
63    /// The manifest list to push
64    manifest_list: String,
65    /// Allow push to an insecure registry
66    insecure: bool,
67    /// Remove the local manifest list after push
68    purge: bool,
69    /// Command executor
70    pub executor: CommandExecutor,
71}
72
73impl ManifestPushCommand {
74    /// Create a new manifest push command
75    ///
76    /// # Arguments
77    ///
78    /// * `manifest_list` - The manifest list to push (e.g., "myapp:latest")
79    #[must_use]
80    pub fn new(manifest_list: impl Into<String>) -> Self {
81        Self {
82            manifest_list: manifest_list.into(),
83            insecure: false,
84            purge: false,
85            executor: CommandExecutor::new(),
86        }
87    }
88
89    /// Allow push to an insecure registry
90    #[must_use]
91    pub fn insecure(mut self) -> Self {
92        self.insecure = true;
93        self
94    }
95
96    /// Remove the local manifest list after push
97    #[must_use]
98    pub fn purge(mut self) -> Self {
99        self.purge = true;
100        self
101    }
102
103    /// Build the command arguments
104    fn build_args(&self) -> Vec<String> {
105        let mut args = vec!["manifest".to_string(), "push".to_string()];
106
107        if self.insecure {
108            args.push("--insecure".to_string());
109        }
110
111        if self.purge {
112            args.push("--purge".to_string());
113        }
114
115        args.push(self.manifest_list.clone());
116
117        args.extend(self.executor.raw_args.clone());
118
119        args
120    }
121}
122
123#[async_trait]
124impl DockerCommand for ManifestPushCommand {
125    type Output = ManifestPushResult;
126
127    fn get_executor(&self) -> &CommandExecutor {
128        &self.executor
129    }
130
131    fn get_executor_mut(&mut self) -> &mut CommandExecutor {
132        &mut self.executor
133    }
134
135    fn build_command_args(&self) -> Vec<String> {
136        self.build_args()
137    }
138
139    async fn execute(&self) -> Result<Self::Output> {
140        let args = self.build_args();
141        let output = self.execute_command(args).await?;
142        Ok(ManifestPushResult::parse(&self.manifest_list, &output))
143    }
144}
145
146#[cfg(test)]
147mod tests {
148    use super::*;
149
150    #[test]
151    fn test_manifest_push_basic() {
152        let cmd = ManifestPushCommand::new("myapp:latest");
153        let args = cmd.build_args();
154        assert_eq!(args, vec!["manifest", "push", "myapp:latest"]);
155    }
156
157    #[test]
158    fn test_manifest_push_with_insecure() {
159        let cmd = ManifestPushCommand::new("myapp:latest").insecure();
160        let args = cmd.build_args();
161        assert!(args.contains(&"--insecure".to_string()));
162    }
163
164    #[test]
165    fn test_manifest_push_with_purge() {
166        let cmd = ManifestPushCommand::new("myapp:latest").purge();
167        let args = cmd.build_args();
168        assert!(args.contains(&"--purge".to_string()));
169    }
170
171    #[test]
172    fn test_manifest_push_all_options() {
173        let cmd = ManifestPushCommand::new("myapp:latest").insecure().purge();
174        let args = cmd.build_args();
175        assert!(args.contains(&"--insecure".to_string()));
176        assert!(args.contains(&"--purge".to_string()));
177    }
178
179    #[test]
180    fn test_manifest_push_result_parse_with_digest() {
181        let output = CommandOutput {
182            stdout: "sha256:abc123def456".to_string(),
183            stderr: String::new(),
184            exit_code: 0,
185            success: true,
186        };
187        let result = ManifestPushResult::parse("myapp:latest", &output);
188        assert_eq!(result.digest, Some("sha256:abc123def456".to_string()));
189    }
190}