use crate::image::{Image, ImageBuilder, OciArchive, OciDir};
#[cfg(feature = "remote")]
use crate::{image::Remote, ImageName};
use anyhow::{Context, Result};
use chrono::{DateTime, TimeZone};
use oci_spec::image::{
Descriptor, DescriptorBuilder, ImageManifest, ImageManifestBuilder, MediaType,
};
use std::{
collections::HashMap,
ops::{Deref, DerefMut},
path::Path,
};
use url::Url;
pub struct OciArtifactBuilder<LayoutBuilder: ImageBuilder> {
manifest: ImageManifest,
layout: LayoutBuilder,
}
impl<LayoutBuilder: ImageBuilder> OciArtifactBuilder<LayoutBuilder> {
pub fn new(mut layout: LayoutBuilder, artifact_type: MediaType) -> Result<Self> {
let empty_config = layout.add_empty_json()?;
let manifest = ImageManifestBuilder::default()
.schema_version(2_u32)
.artifact_type(artifact_type)
.config(empty_config)
.layers(Vec::new())
.build()?;
Ok(Self { layout, manifest })
}
pub fn add_config(
&mut self,
config_type: MediaType,
config_blob: &[u8],
annotations: HashMap<String, String>,
) -> Result<Descriptor> {
let (digest, size) = self.layout.add_blob(config_blob)?;
let config = DescriptorBuilder::default()
.media_type(config_type)
.annotations(annotations)
.digest(digest)
.size(size)
.build()?;
self.manifest.set_config(config.clone());
Ok(config)
}
pub fn add_layer(
&mut self,
layer_type: MediaType,
layer_blob: &[u8],
annotations: HashMap<String, String>,
) -> Result<Descriptor> {
let (digest, size) = self.layout.add_blob(layer_blob)?;
let layer = DescriptorBuilder::default()
.media_type(layer_type)
.digest(digest)
.size(size)
.annotations(annotations)
.build()?;
self.manifest.layers_mut().push(layer.clone());
Ok(layer)
}
pub fn add_annotation(&mut self, key: String, value: String) {
self.manifest
.annotations_mut()
.get_or_insert(HashMap::new())
.insert(key, value);
}
pub fn add_description(&mut self, description: String) {
self.add_annotation(
"org.opencontainers.image.description".to_string(),
description,
)
}
pub fn add_source(&mut self, url: &Url) {
self.add_annotation(
"org.opencontainers.image.source".to_string(),
url.to_string(),
)
}
pub fn add_documentation(&mut self, url: &Url) {
self.add_annotation(
"org.opencontainers.image.documentation".to_string(),
url.to_string(),
)
}
pub fn add_url(&mut self, url: &Url) {
self.add_annotation("org.opencontainers.image.url".to_string(), url.to_string())
}
pub fn add_created<TZ: TimeZone>(&mut self, created: &DateTime<TZ>) {
self.add_annotation(
"org.opencontainers.image.created".to_string(),
created.to_rfc3339(),
)
}
pub fn add_revision(&mut self, revision: String) {
self.add_annotation("org.opencontainers.image.revision".to_string(), revision)
}
pub fn add_vendor(&mut self, vendor: String) {
self.add_annotation("org.opencontainers.image.vendor".to_string(), vendor)
}
pub fn add_title(&mut self, title: String) {
self.add_annotation("org.opencontainers.image.title".to_string(), title)
}
pub fn add_licenses(&mut self, licenses: String) {
self.add_annotation("org.opencontainers.image.licenses".to_string(), licenses)
}
pub fn add_authors(&mut self, authors: String) {
self.add_annotation("org.opencontainers.image.authors".to_string(), authors)
}
pub fn add_versions(&mut self, versions: String) {
self.add_annotation("org.opencontainers.image.versions".to_string(), versions)
}
pub fn build(self) -> Result<OciArtifact<LayoutBuilder::Image>> {
Ok(OciArtifact::new(self.layout.build(self.manifest)?))
}
}
pub struct OciArtifact<Layout: Image>(Layout);
impl<Base: Image> Deref for OciArtifact<Base> {
type Target = Base;
fn deref(&self) -> &Self::Target {
&self.0
}
}
impl<Layout: Image> DerefMut for OciArtifact<Layout> {
fn deref_mut(&mut self) -> &mut Self::Target {
&mut self.0
}
}
impl OciArtifact<OciArchive> {
pub fn from_oci_archive(path: &Path) -> Result<Self> {
let layout = OciArchive::new(path)?;
Ok(Self(layout))
}
}
impl OciArtifact<OciDir> {
pub fn from_oci_dir(path: &Path) -> Result<Self> {
let layout = OciDir::new(path)?;
Ok(Self(layout))
}
}
#[cfg(feature = "remote")]
impl OciArtifact<Remote> {
pub fn from_remote(image_name: ImageName) -> Result<Self> {
let layout = Remote::new(image_name)?;
Ok(Self(layout))
}
}
impl<Layout: Image> OciArtifact<Layout> {
pub fn new(layout: Layout) -> Self {
Self(layout)
}
pub fn artifact_type(&mut self) -> Result<MediaType> {
let manifest = self.get_manifest()?;
manifest
.artifact_type()
.clone()
.context("artifactType is not specified in manifest")
}
pub fn get_config(&mut self) -> Result<(Descriptor, Vec<u8>)> {
let manifest = self.get_manifest()?;
let config_desc = manifest.config();
if config_desc.media_type() == &MediaType::EmptyJSON {
return Ok((config_desc.clone(), "{}".as_bytes().to_vec()));
}
let blob = self.get_blob(config_desc.digest())?;
Ok((config_desc.clone(), blob))
}
pub fn get_layers(&mut self) -> Result<Vec<(Descriptor, Vec<u8>)>> {
let manifest = self.get_manifest()?;
manifest
.layers()
.iter()
.map(|layer| {
let blob = self.get_blob(layer.digest())?;
Ok((layer.clone(), blob))
})
.collect()
}
}