Skip to content

Commit

Permalink
Upgrading to oci-spec 0.7 (#180)
Browse files Browse the repository at this point in the history
- Replace `ocipkg::Digest` by `oci_spec::image::Digest`
  - This is breaking change. Bump up to 0.4.0
  • Loading branch information
termoshtt authored Feb 15, 2025
1 parent f868093 commit 1b0b0f7
Show file tree
Hide file tree
Showing 16 changed files with 123 additions and 160 deletions.
51 changes: 35 additions & 16 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 2 additions & 2 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -18,8 +18,8 @@ git2 = "0.19.0"
lazy_static = "1.5.0"
log = "0.4.25"
maplit = "1.0.2"
oci-spec = "0.6.8"
regex = "1.10.6"
oci-spec = "0.7.1"
regex = "1.11.1"
serde = { version = "1.0.217", features = ["derive"] }
serde_json = "1.0.138"
sha2 = "0.10.8"
Expand Down
4 changes: 2 additions & 2 deletions ocipkg-cli/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
name = "ocipkg-cli"
authors = ["Toshiki Teramura <[email protected]>"]
license = "MIT OR Apache-2.0"
version = "0.3.10"
version = "0.4.0"
edition = "2021"
description = "CLI for ocipkg"
documentation = "https://docs.rs/ocipkg-cli"
Expand All @@ -25,7 +25,7 @@ tar.workspace = true
url.workspace = true

[dependencies.ocipkg]
version = "0.3.10"
version = "0.4.0"
path = "../ocipkg"

[[bin]]
Expand Down
2 changes: 1 addition & 1 deletion ocipkg/Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[package]
name = "ocipkg"
version = "0.3.10"
version = "0.4.0"
authors = ["Toshiki Teramura <[email protected]>"]
edition = "2021"
license = "MIT OR Apache-2.0"
Expand Down
95 changes: 12 additions & 83 deletions ocipkg/src/digest.rs
Original file line number Diff line number Diff line change
@@ -1,91 +1,20 @@
use anyhow::{bail, Result};
use regex::Regex;
use serde::{Deserialize, Serialize};
use oci_spec::image::Digest;
use sha2::{Digest as _, Sha256};
use std::{fmt, path::PathBuf};
use std::{path::PathBuf, str::FromStr};

/// Digest of contents
///
/// Digest is defined in [OCI image spec](https://github.com/opencontainers/image-spec/blob/v1.0.1/descriptor.md#digests)
/// as a string satisfies following EBNF:
///
/// ```text
/// digest ::= algorithm ":" encoded
/// algorithm ::= algorithm-component (algorithm-separator algorithm-component)*
/// algorithm-component ::= [a-z0-9]+
/// algorithm-separator ::= [+._-]
/// encoded ::= [a-zA-Z0-9=_-]+
/// ```
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub struct Digest {
pub algorithm: String,
pub encoded: String,
pub trait DigestExt {
fn eval_sha256_digest(buf: &[u8]) -> Self;
fn as_path(&self) -> PathBuf;
}

lazy_static::lazy_static! {
static ref ENCODED_RE: Regex = Regex::new(r"[a-zA-Z0-9=_-]+").unwrap();
}

impl fmt::Display for Digest {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}:{}", self.algorithm, self.encoded)
}
}

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(':');
match (iter.next(), iter.next(), iter.next()) {
(Some(algorithm), Some(encoded), None) => {
// FIXME: check algorithm part
if ENCODED_RE.is_match(encoded) {
Ok(Digest {
algorithm: algorithm.to_string(),
encoded: encoded.to_string(),
})
} else {
bail!("Invalid digest: {}", input);
}
}
_ => bail!("Invalid digest: {}", input),
}
}

pub fn from_descriptor(descriptor: &oci_spec::image::Descriptor) -> Result<Self> {
Self::new(descriptor.digest())
}

/// As a path used in oci-archive
pub fn as_path(&self) -> PathBuf {
PathBuf::from(format!("blobs/{}/{}", self.algorithm, self.encoded))
}

/// Calc digest using SHA-256 algorithm
pub fn from_buf_sha256(buf: &[u8]) -> Self {
impl DigestExt for Digest {
fn eval_sha256_digest(buf: &[u8]) -> Self {
let hash = Sha256::digest(buf);
let digest = base16ct::lower::encode_string(&hash);
Self {
algorithm: "sha256".to_string(),
encoded: digest,
}
oci_spec::image::Digest::from_str(&format!("sha256:{}", digest)).unwrap()
}

fn as_path(&self) -> PathBuf {
PathBuf::from(format!("blobs/{}/{}", self.algorithm(), self.digest()))
}
}
15 changes: 9 additions & 6 deletions ocipkg/src/distribution/client.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
use crate::distribution::*;
use crate::{digest::DigestExt, distribution::*};
use anyhow::{bail, ensure, Result};
use oci_spec::{distribution::*, image::*};
use oci_spec::{
distribution::TagList,
image::{ImageManifest, ToDockerV2S2},
};
use url::Url;

/// A client for `/v2/<name>/` API endpoint
Expand Down Expand Up @@ -132,7 +135,7 @@ impl Client {
.join(&format!("/v2/{}/manifests/{}", self.name, reference))?;
let mut req = self
.put(&url)
.set("Content-Type", &MediaType::ImageManifest.to_string());
.set("Content-Type", MediaType::ImageManifest.as_ref());
if let Some(token) = self.token.as_ref() {
// Authorization must be done while blobs push
req = req.set("Authorization", &format!("Bearer {}", token));
Expand Down Expand Up @@ -186,10 +189,10 @@ impl Client {
};
let url = Url::parse(loc).or_else(|_| self.url.join(loc))?;

let digest = Digest::from_buf_sha256(blob);
let digest = Digest::eval_sha256_digest(blob);
let mut req = self
.put(&url)
.query("digest", &digest.to_string())
.query("digest", digest.as_ref())
.set("Content-Length", &blob.len().to_string())
.set("Content-Type", "application/octet-stream");
if let Some(token) = self.token.as_ref() {
Expand Down Expand Up @@ -244,7 +247,7 @@ mod tests {
for tag in ["tag1", "tag2", "tag3"] {
let manifest = client.get_manifest(&Reference::new(tag)?)?;
for layer in manifest.layers() {
let buf = client.get_blob(&Digest::new(layer.digest())?)?;
let buf = client.get_blob(layer.digest())?;
dbg!(buf.len());
}
}
Expand Down
3 changes: 2 additions & 1 deletion ocipkg/src/distribution/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,9 +13,10 @@ pub use reference::Reference;

use crate::{
image::{copy, Artifact, Image, OciArchive, RemoteBuilder},
Digest, ImageName,
ImageName,
};
use anyhow::Result;
use oci_spec::image::Digest;
use std::{io::Read, path::Path};

/// Push image to registry
Expand Down
22 changes: 15 additions & 7 deletions ocipkg/src/distribution/reference.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
use crate::Digest;
use anyhow::{bail, Result};
use oci_spec::image::Digest;
use regex::Regex;
use std::fmt;
use std::{fmt, str::FromStr};

/// Reference of container image stored in the repository
///
Expand Down Expand Up @@ -56,7 +56,7 @@ impl Reference {
if REF_RE.is_match(name) {
Ok(Reference(name.to_string()))
} else if name.contains(':') {
_ = Digest::new(name)?;
_ = Digest::from_str(name)?;
Ok(Reference(name.to_string()))
} else {
bail!("Invalid reference {name}");
Expand All @@ -72,8 +72,12 @@ mod tests {
fn reference() {
assert_eq!(Reference::new("latest").unwrap().as_str(), "latest");
assert_eq!(
Reference::new("sha256:a1b2c3").unwrap().as_str(),
"sha256:a1b2c3"
Reference::new(
"sha256:a1b2c3d4a1b2c3d4a1b2c3d4a1b2c3d4a1b2c3d4a1b2c3d4a1b2c3d4a1b2c3d4"
)
.unwrap()
.as_str(),
"sha256:a1b2c3d4a1b2c3d4a1b2c3d4a1b2c3d4a1b2c3d4a1b2c3d4a1b2c3d4a1b2c3d4"
);
// @ is not allowed
assert!(Reference::new("my_super_tag@2").is_err());
Expand All @@ -84,8 +88,12 @@ mod tests {
"%53uper%54ag"
);
assert_eq!(
Reference::new("sha256:a1b2c3").unwrap().encoded(),
"sha256%3Aa1b2c3"
Reference::new(
"sha256:a1b2c3d4a1b2c3d4a1b2c3d4a1b2c3d4a1b2c3d4a1b2c3d4a1b2c3d4a1b2c3d4"
)
.unwrap()
.encoded(),
"sha256%3Aa1b2c3d4a1b2c3d4a1b2c3d4a1b2c3d4a1b2c3d4a1b2c3d4a1b2c3d4a1b2c3d4"
);
}
}
Loading

0 comments on commit 1b0b0f7

Please sign in to comment.