use super::{Descriptor, MediaType};
use crate::{
error::{OciSpecError, Result},
from_file, from_reader, to_file, to_string, to_writer,
};
use derive_builder::Builder;
use getset::{CopyGetters, Getters, Setters};
use serde::{Deserialize, Serialize};
use std::{
collections::HashMap,
fmt::Display,
io::{Read, Write},
path::Path,
};
pub const SCHEMA_VERSION: u32 = 2;
#[derive(
Builder, Clone, CopyGetters, Debug, Deserialize, Eq, Getters, Setters, PartialEq, Serialize,
)]
#[serde(rename_all = "camelCase")]
#[builder(
pattern = "owned",
setter(into, strip_option),
build_fn(error = "OciSpecError")
)]
pub struct ImageIndex {
#[getset(get_copy = "pub", set = "pub")]
schema_version: u32,
#[serde(skip_serializing_if = "Option::is_none")]
#[getset(get = "pub", set = "pub")]
#[builder(default)]
media_type: Option<MediaType>,
#[serde(skip_serializing_if = "Option::is_none")]
#[getset(get = "pub", set = "pub")]
#[builder(default)]
artifact_type: Option<MediaType>,
#[getset(get_mut = "pub", get = "pub", set = "pub")]
manifests: Vec<Descriptor>,
#[serde(skip_serializing_if = "Option::is_none")]
#[getset(get = "pub", set = "pub")]
#[builder(default)]
subject: Option<Descriptor>,
#[serde(skip_serializing_if = "Option::is_none")]
#[getset(get_mut = "pub", get = "pub", set = "pub")]
#[builder(default)]
annotations: Option<HashMap<String, String>>,
}
impl ImageIndex {
pub fn from_file<P: AsRef<Path>>(path: P) -> Result<ImageIndex> {
from_file(path)
}
pub fn from_reader<R: Read>(reader: R) -> Result<ImageIndex> {
from_reader(reader)
}
pub fn to_file<P: AsRef<Path>>(&self, path: P) -> Result<()> {
to_file(&self, path, false)
}
pub fn to_file_pretty<P: AsRef<Path>>(&self, path: P) -> Result<()> {
to_file(&self, path, true)
}
pub fn to_writer<W: Write>(&self, writer: &mut W) -> Result<()> {
to_writer(&self, writer, false)
}
pub fn to_writer_pretty<W: Write>(&self, writer: &mut W) -> Result<()> {
to_writer(&self, writer, true)
}
pub fn to_string(&self) -> Result<String> {
to_string(&self, false)
}
pub fn to_string_pretty(&self) -> Result<String> {
to_string(&self, true)
}
}
impl Default for ImageIndex {
fn default() -> Self {
Self {
schema_version: SCHEMA_VERSION,
media_type: Default::default(),
manifests: Default::default(),
annotations: Default::default(),
artifact_type: Default::default(),
subject: Default::default(),
}
}
}
impl Display for ImageIndex {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(
f,
"{}",
self.to_string_pretty()
.expect("ImageIndex to JSON conversion failed")
)
}
}
#[cfg(test)]
mod tests {
use std::str::FromStr;
use std::{fs, path::PathBuf};
use super::*;
use crate::image::{Arch, Os, Sha256Digest};
use crate::image::{DescriptorBuilder, PlatformBuilder};
fn create_index() -> ImageIndex {
let ppc_manifest = DescriptorBuilder::default()
.media_type(MediaType::ImageManifest)
.digest(
Sha256Digest::from_str(
"e692418e4cbaf90ca69d05a66403747baa33ee08806650b51fab815ad7fc331f",
)
.unwrap(),
)
.size(7143u64)
.platform(
PlatformBuilder::default()
.architecture(Arch::PowerPC64le)
.os(Os::Linux)
.build()
.expect("build ppc64le platform"),
)
.build()
.expect("build ppc manifest descriptor");
let amd64_manifest = DescriptorBuilder::default()
.media_type(MediaType::ImageManifest)
.digest(
Sha256Digest::from_str(
"5b0bcabd1ed22e9fb1310cf6c2dec7cdef19f0ad69efa1f392e94a4333501270",
)
.unwrap(),
)
.size(7682u64)
.platform(
PlatformBuilder::default()
.architecture(Arch::Amd64)
.os(Os::Linux)
.build()
.expect("build amd64 platform"),
)
.build()
.expect("build amd64 manifest descriptor");
ImageIndexBuilder::default()
.schema_version(SCHEMA_VERSION)
.manifests(vec![ppc_manifest, amd64_manifest])
.build()
.expect("build image index")
}
fn get_index_path() -> PathBuf {
PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("test/data/index.json")
}
#[test]
fn load_index_from_file() {
let index_path = get_index_path();
let actual = ImageIndex::from_file(index_path).expect("from file");
let expected = create_index();
assert_eq!(actual, expected);
}
#[test]
fn load_index_from_reader() {
let reader = fs::read(get_index_path()).expect("read index");
let actual = ImageIndex::from_reader(&*reader).expect("from reader");
let expected = create_index();
assert_eq!(actual, expected);
}
#[test]
fn save_index_to_file() {
let tmp = std::env::temp_dir().join("save_index_to_file");
fs::create_dir_all(&tmp).expect("create test directory");
let index = create_index();
let index_path = tmp.join("index.json");
index
.to_file_pretty(&index_path)
.expect("write index to file");
let actual = fs::read_to_string(index_path).expect("read actual");
let expected = fs::read_to_string(get_index_path()).expect("read expected");
assert_eq!(actual, expected);
}
#[test]
fn save_index_to_writer() {
let mut actual = Vec::new();
let index = create_index();
index.to_writer_pretty(&mut actual).expect("to writer");
let expected = fs::read(get_index_path()).expect("read expected");
assert_eq!(actual, expected);
}
#[test]
fn save_index_to_string() {
let index = create_index();
let actual = index.to_string_pretty().expect("to string");
let expected = fs::read_to_string(get_index_path()).expect("read expected");
assert_eq!(actual, expected);
}
}