diff --git a/crates/oci/src/client.rs b/crates/oci/src/client.rs index 145d795147..408c934f87 100644 --- a/crates/oci/src/client.rs +++ b/crates/oci/src/client.rs @@ -1,6 +1,6 @@ //! Spin's client for distributing applications via OCI registries -use std::collections::HashMap; +use std::collections::{BTreeMap, HashMap}; use std::path::{Path, PathBuf}; use anyhow::{bail, Context, Result}; @@ -97,6 +97,7 @@ impl Client { &mut self, manifest_path: &Path, reference: impl AsRef, + annotations: Option>, ) -> Result> { let reference: Reference = reference .as_ref() @@ -115,7 +116,8 @@ impl Client { ) .await?; - self.push_locked_core(locked, auth, reference).await + self.push_locked_core(locked, auth, reference, annotations) + .await } /// Push a Spin application to an OCI registry and return the digest (or None @@ -124,6 +126,7 @@ impl Client { &mut self, locked: LockedApp, reference: impl AsRef, + annotations: Option>, ) -> Result> { let reference: Reference = reference .as_ref() @@ -131,7 +134,8 @@ impl Client { .with_context(|| format!("cannot parse reference {}", reference.as_ref()))?; let auth = Self::auth(&reference).await?; - self.push_locked_core(locked, auth, reference).await + self.push_locked_core(locked, auth, reference, annotations) + .await } /// Push a Spin application to an OCI registry and return the digest (or None @@ -141,6 +145,7 @@ impl Client { locked: LockedApp, auth: RegistryAuth, reference: Reference, + annotations: Option>, ) -> Result> { let mut locked_app = locked.clone(); let mut layers = self @@ -193,7 +198,7 @@ impl Client { }; let oci_config = oci_distribution::client::Config::oci_v1_from_config_file(oci_config_file, None)?; - let manifest = OciImageManifest::build(&layers, &oci_config, None); + let manifest = OciImageManifest::build(&layers, &oci_config, annotations); let response = self .oci diff --git a/src/commands/registry.rs b/src/commands/registry.rs index 2bcda4744c..49d5839285 100644 --- a/src/commands/registry.rs +++ b/src/commands/registry.rs @@ -2,6 +2,7 @@ use crate::opts::*; use anyhow::{Context, Result}; use clap::{Parser, Subcommand}; use indicatif::{ProgressBar, ProgressStyle}; +use spin_common::arg_parser::parse_kv; use spin_oci::Client; use std::{io::Read, path::PathBuf, time::Duration}; @@ -61,6 +62,11 @@ pub struct Push { /// Cache directory for downloaded registry data. #[clap(long)] pub cache_dir: Option, + + /// Specifies the OCI image manifest annotations (in key=value format). + /// Any existing value will be overwritten. Can be used multiple times. + #[clap(long = "annotation", parse(try_from_str = parse_kv))] + pub annotations: Vec<(String, String)>, } impl Push { @@ -70,11 +76,17 @@ impl Push { spin_build::build(&app_file, &[]).await?; } + let annotations = if self.annotations.is_empty() { + None + } else { + Some(self.annotations.iter().cloned().collect()) + }; + let mut client = spin_oci::Client::new(self.insecure, self.cache_dir.clone()).await?; let _spinner = create_dotted_spinner(2000, "Pushing app to the Registry".to_owned()); - let digest = client.push(&app_file, &self.reference).await?; + let digest = client.push(&app_file, &self.reference, annotations).await?; match digest { Some(digest) => println!("Pushed with digest {digest}"), None => println!("Pushed; the registry did not return the digest"),