Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

application/vnd.ocipkg.v1.artifact as OCI Artifact #116

Merged
merged 11 commits into from
Apr 25, 2024
68 changes: 49 additions & 19 deletions ocipkg-cli/src/bin/ocipkg.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,13 @@ use base64::{engine::general_purpose::STANDARD, Engine};
use clap::Parser;
use flate2::read::GzDecoder;
use oci_spec::image::MediaType;
use ocipkg::error::*;
use std::{fs, path::*};
use ocipkg::{
error::*,
image::Config,
media_types::{artifact, config_json},
Digest,
};
use std::{fs, io::Read, path::*};

#[derive(Debug, Parser)]
#[command(version)]
Expand Down Expand Up @@ -189,25 +194,50 @@ fn main() -> Result<()> {
let mut f = fs::File::open(input)?;
let mut ar = ocipkg::image::Archive::new(&mut f);
for (name, manifest) in ar.get_manifests()? {
println!("[{}]", name);
for layer in manifest.layers() {
let digest = ocipkg::Digest::new(layer.digest())?;
let entry = ar.get_blob(&digest)?;
if let MediaType::ImageLayerGzip = layer.media_type() {
let buf = GzDecoder::new(entry);
let mut ar = tar::Archive::new(buf);
let paths: Vec<_> = ar
.entries()?
.filter_map(|entry| Some(entry.ok()?.path().ok()?.to_path_buf()))
.collect();
for (i, path) in paths.iter().enumerate() {
if i < paths.len() - 1 {
println!(" ├─ {}", path.display());
} else {
println!(" └─ {}", path.display());
}
let config = if manifest.artifact_type() == &Some(artifact()) {
let config = manifest.config();
if config.media_type() != &config_json() {
return Err(Error::InvalidConfigMediaType(config.media_type().clone()));
}
let mut buf = Vec::new();
ar.get_blob(&Digest::new(config.digest())?)?
.read_to_end(&mut buf)?;
Config::from_slice(&buf)?
} else {
log::warn!("ocipkg 0.2.x style artifact is deprecated.");
let mut config = Config::default();
for layer in manifest.layers() {
let digest = ocipkg::Digest::new(layer.digest())?;
let entry = ar.get_blob(&digest)?;
if let MediaType::ImageLayerGzip = layer.media_type() {
let buf = GzDecoder::new(entry);
let mut ar = tar::Archive::new(buf);
config.add_layer(
Digest::new(layer.digest())?,
ar.entries()?
.filter_map(|entry| {
Some(entry.ok()?.path().ok()?.to_path_buf())
})
.collect(),
)
}
}
config
};

println!("[{}]", name);
let paths: Vec<&PathBuf> = config
.layers()
.iter()
.flat_map(|(_digest, paths)| paths)
.collect();

for (i, path) in paths.iter().enumerate() {
if i < paths.len() - 1 {
println!(" ├─ {}", path.display());
} else {
println!(" └─ {}", path.display());
}
}
}
}
Expand Down
60 changes: 21 additions & 39 deletions ocipkg/src/digest.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
use crate::error::*;
use regex::Regex;
use serde::{Deserialize, Serialize};
use sha2::{Digest as _, Sha256};
use std::{fmt, io, path::PathBuf};
use std::{fmt, path::PathBuf};

/// Digest of contents
///
Expand Down Expand Up @@ -31,6 +32,25 @@ impl fmt::Display for Digest {
}
}

impl Serialize for Digest {
fn serialize<S>(&self, serializer: S) -> std::prelude::v1::Result<S::Ok, S::Error>
where
S: serde::Serializer,
{
serializer.serialize_str(&self.to_string())
}
}

impl<'de> Deserialize<'de> for Digest {
fn deserialize<D>(deserializer: D) -> std::prelude::v1::Result<Digest, D::Error>
where
D: serde::Deserializer<'de>,
{
let s = String::deserialize(deserializer)?;
Digest::new(&s).map_err(serde::de::Error::custom)
}
}

impl Digest {
pub fn new(input: &str) -> Result<Self> {
let mut iter = input.split(':');
Expand Down Expand Up @@ -65,41 +85,3 @@ impl Digest {
}
}
}

/// Wrapper for calculating hash
pub struct DigestBuf<W: io::Write> {
inner: W,
hasher: Sha256,
}

impl<W: io::Write> DigestBuf<W> {
pub fn new(inner: W) -> Self {
DigestBuf {
inner,
hasher: Sha256::new(),
}
}

pub fn finish(self) -> (W, Digest) {
let hash = self.hasher.finalize();
let digest = base16ct::lower::encode_string(&hash);
(
self.inner,
Digest {
algorithm: "sha256".to_string(),
encoded: digest,
},
)
}
}

impl<W: io::Write> io::Write for DigestBuf<W> {
fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
self.hasher.update(buf);
self.inner.write(buf)
}

fn flush(&mut self) -> io::Result<()> {
self.inner.flush()
}
}
2 changes: 1 addition & 1 deletion ocipkg/src/distribution/name.rs
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ impl fmt::Display for Name {
}

lazy_static::lazy_static! {
static ref NAME_RE: Regex = Regex::new(r"^[a-z0-9]+([._-][a-z0-9]+)*(/[a-z0-9]+([._-][a-z0-9]+)*)*$").unwrap();
static ref NAME_RE: Regex = Regex::new(r"^[A-Za-z0-9]+([._-][A-Za-z0-9]+)*(/[A-Za-z0-9]+([._-][A-Za-z0-9]+)*)*$").unwrap();
}

impl Name {
Expand Down
4 changes: 3 additions & 1 deletion ocipkg/src/error.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
use crate::Digest;
use oci_spec::{distribution::ErrorResponse, OciSpecError};
use oci_spec::{distribution::ErrorResponse, image::MediaType, OciSpecError};
use std::path::PathBuf;

#[derive(Debug, thiserror::Error)]
Expand Down Expand Up @@ -41,6 +41,8 @@ pub enum Error {
InvalidJson(#[from] serde_json::error::Error),
#[error(transparent)]
InvalidToml(#[from] toml::de::Error),
#[error("Invalid media type for config of ocipkg artifact: {0}")]
InvalidConfigMediaType(MediaType),

//
// Error from OCI registry
Expand Down
30 changes: 30 additions & 0 deletions ocipkg/src/image/config.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
use crate::{error::*, Digest};
use serde::{Deserialize, Serialize};
use std::{collections::HashMap, path::PathBuf};

/// The contents of `application/vnd.ocipkg.v1.config+json` media type.
///
/// This is a map from the layer digest to the list of relative paths of the files in the layer.
///
#[derive(Debug, Clone, Default, Serialize, Deserialize)]
pub struct Config {
layers: HashMap<Digest, Vec<PathBuf>>,
}

impl Config {
pub fn to_json(&self) -> Result<String> {
Ok(serde_json::to_string(self)?)
}

pub fn from_slice(slice: &[u8]) -> Result<Self> {
Ok(serde_json::from_slice(slice)?)
}

pub fn add_layer(&mut self, digest: Digest, paths: Vec<PathBuf>) {
self.layers.insert(digest, paths);
}

pub fn layers(&self) -> &HashMap<Digest, Vec<PathBuf>> {
&self.layers
}
}
4 changes: 2 additions & 2 deletions ocipkg/src/image/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,10 @@

pub mod annotations;

mod platform;
mod config;
mod read;
mod write;

pub use platform::*;
pub use config::*;
pub use read::*;
pub use write::*;
100 changes: 0 additions & 100 deletions ocipkg/src/image/platform.rs

This file was deleted.

Loading
Loading