use super::{ClientCapabilities, ClientCapabilitiesDeps};
use crate::errors::{Result, SigstoreError};
use async_trait::async_trait;
use cached::proc_macro::cached;
use serde::Serialize;
use sha2::{Digest, Sha256};
use tracing::{debug, error};
pub(crate) struct OciCachingClient {
pub registry_client: oci_client::Client,
}
#[cached(
time = 60,
result = true,
sync_writes = "default",
key = "String",
convert = r#"{ format!("{}", image) }"#,
with_cached_flag = true
)]
async fn fetch_manifest_digest_cached(
client: &mut oci_client::Client,
image: &oci_client::Reference,
auth: &oci_client::secrets::RegistryAuth,
) -> Result<cached::Return<String>> {
client
.fetch_manifest_digest(image, auth)
.await
.map_err(|e| SigstoreError::RegistryFetchManifestError {
image: image.whole(),
error: e.to_string(),
})
.map(cached::Return::new)
}
#[derive(Serialize, Debug)]
struct PullSettings<'a> {
image: String,
auth: super::config::Auth,
pub accepted_media_types: Vec<&'a str>,
}
impl<'a> PullSettings<'a> {
fn new(
image: &oci_client::Reference,
auth: &oci_client::secrets::RegistryAuth,
accepted_media_types: Vec<&'a str>,
) -> PullSettings<'a> {
let image_str = image.whole();
let auth_sigstore: super::config::Auth = From::from(auth);
PullSettings {
image: image_str,
auth: auth_sigstore,
accepted_media_types,
}
}
#[allow(clippy::unwrap_used)]
pub fn image(&self) -> oci_client::Reference {
let reference: oci_client::Reference = self.image.parse().unwrap();
reference
}
pub fn auth(&self) -> oci_client::secrets::RegistryAuth {
let internal_auth: &super::config::Auth = &self.auth;
let a: oci_client::secrets::RegistryAuth = internal_auth.into();
a
}
pub fn hash(&self) -> String {
let buf = match serde_json_canonicalizer::to_vec(self) {
Ok(vec) => vec,
Err(e) => {
error!(err=?e, settings=?self, "Cannot perform canonical serialization");
return "0".to_string();
}
};
let mut hasher = Sha256::new();
hasher.update(&buf);
let result = hasher.finalize();
result
.iter()
.map(|v| format!("{v:x}"))
.collect::<Vec<String>>()
.join("")
}
}
#[cached(
time = 60,
result = true,
sync_writes = "default",
key = "String",
convert = r#"{ settings.hash() }"#,
with_cached_flag = true
)]
async fn pull_cached(
client: &mut oci_client::Client,
settings: PullSettings<'_>,
) -> Result<cached::Return<oci_client::client::ImageData>> {
let auth = settings.auth();
let image = settings.image();
client
.pull(&image, &auth, settings.accepted_media_types)
.await
.map_err(|e| SigstoreError::RegistryPullError {
image: image.whole(),
error: e.to_string(),
})
.map(cached::Return::new)
}
#[derive(Serialize, Debug)]
struct PullManifestSettings {
image: String,
auth: super::config::Auth,
}
impl PullManifestSettings {
fn new(
image: &oci_client::Reference,
auth: &oci_client::secrets::RegistryAuth,
) -> PullManifestSettings {
let image_str = image.whole();
let auth_sigstore: super::config::Auth = From::from(auth);
PullManifestSettings {
image: image_str,
auth: auth_sigstore,
}
}
#[allow(clippy::unwrap_used)]
pub fn image(&self) -> oci_client::Reference {
let reference: oci_client::Reference = self.image.parse().unwrap();
reference
}
pub fn auth(&self) -> oci_client::secrets::RegistryAuth {
let internal_auth: &super::config::Auth = &self.auth;
let a: oci_client::secrets::RegistryAuth = internal_auth.into();
a
}
pub fn hash(&self) -> String {
let buf = match serde_json_canonicalizer::to_vec(self) {
Ok(vec) => vec,
Err(e) => {
error!(err=?e, settings=?self, "Cannot perform canonical serialization");
return "0".to_string();
}
};
let mut hasher = Sha256::new();
hasher.update(&buf);
let result = hasher.finalize();
result
.iter()
.map(|v| format!("{v:x}"))
.collect::<Vec<String>>()
.join("")
}
}
#[cached(
time = 60,
result = true,
sync_writes = "default",
key = "String",
convert = r#"{ settings.hash() }"#,
with_cached_flag = true
)]
async fn pull_manifest_cached(
client: &mut oci_client::Client,
settings: PullManifestSettings,
) -> Result<cached::Return<(oci_client::manifest::OciManifest, String)>> {
let image = settings.image();
let auth = settings.auth();
client
.pull_manifest(&image, &auth)
.await
.map_err(|e| SigstoreError::RegistryPullManifestError {
image: image.whole(),
error: e.to_string(),
})
.map(cached::Return::new)
}
#[derive(Serialize, Debug)]
struct PullReferrersSettings {
image: String,
auth: super::config::Auth,
artifact_type: Option<String>,
}
impl PullReferrersSettings {
fn new(
image: &oci_client::Reference,
auth: &oci_client::secrets::RegistryAuth,
artifact_type: Option<&str>,
) -> PullReferrersSettings {
let image_str = image.whole();
let auth_sigstore: super::config::Auth = From::from(auth);
PullReferrersSettings {
image: image_str,
auth: auth_sigstore,
artifact_type: artifact_type.map(str::to_owned),
}
}
#[allow(clippy::unwrap_used)]
pub fn image(&self) -> oci_client::Reference {
let reference: oci_client::Reference = self.image.parse().unwrap();
reference
}
pub fn auth(&self) -> oci_client::secrets::RegistryAuth {
let internal_auth: &super::config::Auth = &self.auth;
let a: oci_client::secrets::RegistryAuth = internal_auth.into();
a
}
pub fn hash(&self) -> String {
let buf = match serde_json_canonicalizer::to_vec(self) {
Ok(vec) => vec,
Err(e) => {
error!(err=?e, settings=?self, "Cannot perform canonical serialization");
return "0".to_string();
}
};
let mut hasher = Sha256::new();
hasher.update(&buf);
let result = hasher.finalize();
result
.iter()
.map(|v| format!("{v:x}"))
.collect::<Vec<String>>()
.join("")
}
}
#[cached(
time = 60,
result = true,
sync_writes = "default",
key = "String",
convert = r#"{ settings.hash() }"#,
with_cached_flag = true
)]
async fn pull_referrers_cached(
client: &mut oci_client::Client,
settings: PullReferrersSettings,
) -> Result<cached::Return<oci_client::manifest::OciImageIndex>> {
let image = settings.image();
let auth = settings.auth();
let artifact_type = settings.artifact_type.as_deref();
client
.auth(&image, &auth, oci_client::RegistryOperation::Pull)
.await
.map_err(|e| SigstoreError::RegistryFetchManifestError {
image: image.whole(),
error: e.to_string(),
})?;
client
.pull_referrers(&image, artifact_type)
.await
.map_err(|e| SigstoreError::RegistryPullManifestError {
image: image.whole(),
error: e.to_string(),
})
.map(cached::Return::new)
}
impl ClientCapabilitiesDeps for OciCachingClient {}
#[async_trait]
impl ClientCapabilities for OciCachingClient {
async fn fetch_manifest_digest(
&mut self,
image: &oci_client::Reference,
auth: &oci_client::secrets::RegistryAuth,
) -> Result<String> {
fetch_manifest_digest_cached(&mut self.registry_client, image, auth)
.await
.map(|digest| {
if digest.was_cached {
debug!(?image, "Got image digest from cache");
} else {
debug!(?image, "Got image digest by querying remote registry");
}
digest.value
})
}
async fn pull(
&mut self,
image: &oci_client::Reference,
auth: &oci_client::secrets::RegistryAuth,
accepted_media_types: Vec<&str>,
) -> Result<oci_client::client::ImageData> {
let pull_settings = PullSettings::new(image, auth, accepted_media_types);
pull_cached(&mut self.registry_client, pull_settings)
.await
.map(|data| {
if data.was_cached {
debug!(?image, "Got image data from cache");
} else {
debug!(?image, "Got image data by querying remote registry");
}
data.value
})
}
async fn pull_manifest(
&mut self,
image: &oci_client::Reference,
auth: &oci_client::secrets::RegistryAuth,
) -> Result<(oci_client::manifest::OciManifest, String)> {
let pull_manifest_settings = PullManifestSettings::new(image, auth);
pull_manifest_cached(&mut self.registry_client, pull_manifest_settings)
.await
.map(|data| {
if data.was_cached {
debug!(?image, "Got image manifest from cache");
} else {
debug!(?image, "Got image manifest by querying remote registry");
}
data.value
})
}
async fn push(
&mut self,
image_ref: &oci_client::Reference,
layers: &[oci_client::client::ImageLayer],
config: oci_client::client::Config,
auth: &oci_client::secrets::RegistryAuth,
manifest: Option<oci_client::manifest::OciImageManifest>,
) -> Result<oci_client::client::PushResponse> {
self.registry_client
.push(image_ref, layers, config, auth, manifest)
.await
.map_err(|e| SigstoreError::RegistryPushError {
image: image_ref.whole(),
error: e.to_string(),
})
}
async fn pull_referrers(
&mut self,
image: &oci_client::Reference,
auth: &oci_client::secrets::RegistryAuth,
artifact_type: Option<&str>,
) -> Result<oci_client::manifest::OciImageIndex> {
let settings = PullReferrersSettings::new(image, auth, artifact_type);
pull_referrers_cached(&mut self.registry_client, settings)
.await
.map(|data| {
if data.was_cached {
debug!(?image, "Got image referrers from cache");
} else {
debug!(?image, "Got image referrers by querying remote registry");
}
data.value
})
}
}