use crate::command::{CommandExecutor, CommandOutput, DockerCommand};
use crate::error::Result;
use async_trait::async_trait;
use serde::{Deserialize, Serialize};
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ManifestPlatform {
pub architecture: Option<String>,
pub os: Option<String>,
#[serde(rename = "os.version")]
pub os_version: Option<String>,
pub variant: Option<String>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ManifestInfo {
#[serde(rename = "schemaVersion")]
pub schema_version: Option<i32>,
#[serde(rename = "mediaType")]
pub media_type: Option<String>,
pub digest: Option<String>,
pub size: Option<i64>,
pub platform: Option<ManifestPlatform>,
pub manifests: Option<Vec<ManifestInfo>>,
#[serde(skip)]
pub raw_json: String,
}
impl ManifestInfo {
fn parse(output: &CommandOutput) -> Self {
let stdout = output.stdout.trim();
if stdout.is_empty() {
return Self {
schema_version: None,
media_type: None,
digest: None,
size: None,
platform: None,
manifests: None,
raw_json: String::new(),
};
}
let mut info: ManifestInfo =
serde_json::from_str(stdout).unwrap_or_else(|_| ManifestInfo {
schema_version: None,
media_type: None,
digest: None,
size: None,
platform: None,
manifests: None,
raw_json: String::new(),
});
info.raw_json = stdout.to_string();
info
}
}
#[derive(Debug, Clone)]
pub struct ManifestInspectCommand {
manifest_list: Option<String>,
manifest: String,
insecure: bool,
verbose: bool,
pub executor: CommandExecutor,
}
impl ManifestInspectCommand {
#[must_use]
pub fn new(manifest: impl Into<String>) -> Self {
Self {
manifest_list: None,
manifest: manifest.into(),
insecure: false,
verbose: false,
executor: CommandExecutor::new(),
}
}
#[must_use]
pub fn manifest_list(mut self, manifest_list: impl Into<String>) -> Self {
self.manifest_list = Some(manifest_list.into());
self
}
#[must_use]
pub fn insecure(mut self) -> Self {
self.insecure = true;
self
}
#[must_use]
pub fn verbose(mut self) -> Self {
self.verbose = true;
self
}
fn build_args(&self) -> Vec<String> {
let mut args = vec!["manifest".to_string(), "inspect".to_string()];
if self.insecure {
args.push("--insecure".to_string());
}
if self.verbose {
args.push("--verbose".to_string());
}
if let Some(ref list) = self.manifest_list {
args.push(list.clone());
}
args.push(self.manifest.clone());
args.extend(self.executor.raw_args.clone());
args
}
}
#[async_trait]
impl DockerCommand for ManifestInspectCommand {
type Output = ManifestInfo;
fn get_executor(&self) -> &CommandExecutor {
&self.executor
}
fn get_executor_mut(&mut self) -> &mut CommandExecutor {
&mut self.executor
}
fn build_command_args(&self) -> Vec<String> {
self.build_args()
}
async fn execute(&self) -> Result<Self::Output> {
let args = self.build_args();
let output = self.execute_command(args).await?;
Ok(ManifestInfo::parse(&output))
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_manifest_inspect_basic() {
let cmd = ManifestInspectCommand::new("myapp:latest");
let args = cmd.build_args();
assert_eq!(args, vec!["manifest", "inspect", "myapp:latest"]);
}
#[test]
fn test_manifest_inspect_with_list() {
let cmd = ManifestInspectCommand::new("myapp:latest-amd64").manifest_list("myapp:latest");
let args = cmd.build_args();
assert_eq!(
args,
vec!["manifest", "inspect", "myapp:latest", "myapp:latest-amd64"]
);
}
#[test]
fn test_manifest_inspect_with_insecure() {
let cmd = ManifestInspectCommand::new("myapp:latest").insecure();
let args = cmd.build_args();
assert!(args.contains(&"--insecure".to_string()));
}
#[test]
fn test_manifest_inspect_with_verbose() {
let cmd = ManifestInspectCommand::new("myapp:latest").verbose();
let args = cmd.build_args();
assert!(args.contains(&"--verbose".to_string()));
}
#[test]
fn test_manifest_inspect_all_options() {
let cmd = ManifestInspectCommand::new("myapp:latest")
.insecure()
.verbose();
let args = cmd.build_args();
assert!(args.contains(&"--insecure".to_string()));
assert!(args.contains(&"--verbose".to_string()));
}
#[test]
fn test_manifest_info_parse_empty() {
let output = CommandOutput {
stdout: String::new(),
stderr: String::new(),
exit_code: 0,
success: true,
};
let info = ManifestInfo::parse(&output);
assert!(info.schema_version.is_none());
}
#[test]
fn test_manifest_info_parse_json() {
let json = r#"{"schemaVersion": 2, "mediaType": "application/vnd.docker.distribution.manifest.list.v2+json"}"#;
let output = CommandOutput {
stdout: json.to_string(),
stderr: String::new(),
exit_code: 0,
success: true,
};
let info = ManifestInfo::parse(&output);
assert_eq!(info.schema_version, Some(2));
assert_eq!(
info.media_type,
Some("application/vnd.docker.distribution.manifest.list.v2+json".to_string())
);
}
}