Skip to content

Commit

Permalink
ref(annotations): convert to BTreeMap
Browse files Browse the repository at this point in the history
- eliminates the need for a specific Hash impl for ImageLayer

Signed-off-by: Vaughn Dice <vaughn.dice@fermyon.com>
  • Loading branch information
vdice committed Mar 29, 2024
1 parent bee9f8b commit 30a608e
Show file tree
Hide file tree
Showing 4 changed files with 27 additions and 42 deletions.
2 changes: 1 addition & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,6 @@ chrono = { version = "0.4.23", features = ["serde"] }
futures-util = "0.3"
http = "1.1"
http-auth = { version = "0.1", default_features = false }
itertools = "0.12.1"
jwt = "0.16"
lazy_static = "1.4"
olpc-cjson = "0.1"
Expand All @@ -62,6 +61,7 @@ clap = { version = "4.0", features = ["derive"] }
rstest = "0.18.1"
docker_credential = "1.0"
hmac = "0.12"
itertools = "0.12.1"
tracing-subscriber = { version = "0.3", features = ["env-filter"] }
tempfile = "3.3"
testcontainers = "0.15"
Expand Down
4 changes: 2 additions & 2 deletions examples/wasm/push.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,15 +4,15 @@ use oci_distribution::{
secrets::RegistryAuth,
Client, Reference,
};
use std::collections::HashMap;
use std::collections::BTreeMap;
use tracing::info;

pub(crate) async fn push_wasm(
client: &mut Client,
auth: &RegistryAuth,
reference: &Reference,
module: &str,
annotations: Option<HashMap<String, String>>,
annotations: Option<BTreeMap<String, String>>,
) {
info!(?reference, ?module, "pushing wasm module");

Expand Down
51 changes: 18 additions & 33 deletions src/client.rs
Original file line number Diff line number Diff line change
Expand Up @@ -23,16 +23,15 @@ use futures_util::stream::{self, StreamExt, TryStreamExt};
use futures_util::Stream;
use http::HeaderValue;
use http_auth::{parser::ChallengeParser, ChallengeRef};
use itertools::Itertools;
use olpc_cjson::CanonicalFormatter;
use reqwest::header::HeaderMap;
use reqwest::{RequestBuilder, Url};
use serde::Deserialize;
use serde::Serialize;
use sha2::Digest;
use std::collections::HashMap;
use std::collections::{BTreeMap, HashMap};
use std::convert::TryFrom;
use std::hash::{Hash, Hasher};
use std::hash::Hash;
use std::sync::Arc;
use tokio::io::{AsyncWrite, AsyncWriteExt};
use tokio::sync::RwLock;
Expand Down Expand Up @@ -88,23 +87,23 @@ pub struct TagResponse {
}

/// The data and media type for an image layer
#[derive(Clone, Debug, Eq, PartialEq)]
#[derive(Clone, Debug, Eq, Hash, PartialEq)]
pub struct ImageLayer {
/// The data of this layer
pub data: Vec<u8>,
/// The media type of this layer
pub media_type: String,
/// This OPTIONAL property contains arbitrary metadata for this descriptor.
/// This OPTIONAL property MUST use the [annotation rules](https://github.com/opencontainers/image-spec/blob/main/annotations.md#rules)
pub annotations: Option<HashMap<String, String>>,
pub annotations: Option<BTreeMap<String, String>>,
}

impl ImageLayer {
/// Constructs a new ImageLayer struct with provided data and media type
pub fn new(
data: Vec<u8>,
media_type: String,
annotations: Option<HashMap<String, String>>,
annotations: Option<BTreeMap<String, String>>,
) -> Self {
ImageLayer {
data,
Expand All @@ -115,12 +114,12 @@ impl ImageLayer {

/// Constructs a new ImageLayer struct with provided data and
/// media type application/vnd.oci.image.layer.v1.tar
pub fn oci_v1(data: Vec<u8>, annotations: Option<HashMap<String, String>>) -> Self {
pub fn oci_v1(data: Vec<u8>, annotations: Option<BTreeMap<String, String>>) -> Self {
Self::new(data, IMAGE_LAYER_MEDIA_TYPE.to_string(), annotations)
}
/// Constructs a new ImageLayer struct with provided data and
/// media type application/vnd.oci.image.layer.v1.tar+gzip
pub fn oci_v1_gzip(data: Vec<u8>, annotations: Option<HashMap<String, String>>) -> Self {
pub fn oci_v1_gzip(data: Vec<u8>, annotations: Option<BTreeMap<String, String>>) -> Self {
Self::new(data, IMAGE_LAYER_GZIP_MEDIA_TYPE.to_string(), annotations)
}

Expand All @@ -130,22 +129,6 @@ impl ImageLayer {
}
}

/// Implementing Hash to enable determining uniqueness
/// eg via https://docs.rs/itertools/latest/itertools/structs/struct.Unique.html
impl Hash for ImageLayer {
fn hash<H: Hasher>(&self, state: &mut H) {
self.data.hash(state);
self.media_type.hash(state);
if let Some(annotations) = self.annotations.clone() {
let sorted = annotations.iter().sorted();
for (k, v) in sorted {
k.hash(state);
v.hash(state);
}
}
}
}

/// The data and media type for a configuration object
#[derive(Clone)]
pub struct Config {
Expand All @@ -155,15 +138,15 @@ pub struct Config {
pub media_type: String,
/// This OPTIONAL property contains arbitrary metadata for this descriptor.
/// This OPTIONAL property MUST use the [annotation rules](https://github.com/opencontainers/image-spec/blob/main/annotations.md#rules)
pub annotations: Option<HashMap<String, String>>,
pub annotations: Option<BTreeMap<String, String>>,
}

impl Config {
/// Constructs a new Config struct with provided data and media type
pub fn new(
data: Vec<u8>,
media_type: String,
annotations: Option<HashMap<String, String>>,
annotations: Option<BTreeMap<String, String>>,
) -> Self {
Config {
data,
Expand All @@ -174,15 +157,15 @@ impl Config {

/// Constructs a new Config struct with provided data and
/// media type application/vnd.oci.image.config.v1+json
pub fn oci_v1(data: Vec<u8>, annotations: Option<HashMap<String, String>>) -> Self {
pub fn oci_v1(data: Vec<u8>, annotations: Option<BTreeMap<String, String>>) -> Self {
Self::new(data, IMAGE_CONFIG_MEDIA_TYPE.to_string(), annotations)
}

/// Construct a new Config struct with provided [`ConfigFile`] and
/// media type `application/vnd.oci.image.config.v1+json`
pub fn oci_v1_from_config_file(
config_file: ConfigFile,
annotations: Option<HashMap<String, String>>,
annotations: Option<BTreeMap<String, String>>,
) -> Result<Self> {
let data = serde_json::to_vec(&config_file)?;
Ok(Self::new(
Expand Down Expand Up @@ -2950,44 +2933,46 @@ mod test {

#[tokio::test]
async fn test_hashable_image_layer() {
use itertools::Itertools;

// First two should be identical; others differ
let image_layers = Vec::from([
ImageLayer {
data: Vec::from([0, 1, 2, 3]),
media_type: "media_type".to_owned(),
annotations: Some(HashMap::from([
annotations: Some(BTreeMap::from([
("0".to_owned(), "1".to_owned()),
("2".to_owned(), "3".to_owned()),
])),
},
ImageLayer {
data: Vec::from([0, 1, 2, 3]),
media_type: "media_type".to_owned(),
annotations: Some(HashMap::from([
annotations: Some(BTreeMap::from([
("2".to_owned(), "3".to_owned()),
("0".to_owned(), "1".to_owned()),
])),
},
ImageLayer {
data: Vec::from([0, 1, 2, 3]),
media_type: "different_media_type".to_owned(),
annotations: Some(HashMap::from([
annotations: Some(BTreeMap::from([
("0".to_owned(), "1".to_owned()),
("2".to_owned(), "3".to_owned()),
])),
},
ImageLayer {
data: Vec::from([0, 1, 2]),
media_type: "media_type".to_owned(),
annotations: Some(HashMap::from([
annotations: Some(BTreeMap::from([
("0".to_owned(), "1".to_owned()),
("2".to_owned(), "3".to_owned()),
])),
},
ImageLayer {
data: Vec::from([0, 1, 2, 3]),
media_type: "media_type".to_owned(),
annotations: Some(HashMap::from([
annotations: Some(BTreeMap::from([
("1".to_owned(), "0".to_owned()),
("2".to_owned(), "3".to_owned()),
])),
Expand Down
12 changes: 6 additions & 6 deletions src/manifest.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
//! OCI Manifest
use std::collections::HashMap;
use std::collections::BTreeMap;

use crate::{
client::{Config, ImageLayer},
Expand Down Expand Up @@ -113,7 +113,7 @@ pub struct OciImageManifest {
/// MUST either be absent or be an empty map."
/// TO accomodate either, this is optional.
#[serde(skip_serializing_if = "Option::is_none")]
pub annotations: Option<HashMap<String, String>>,
pub annotations: Option<BTreeMap<String, String>>,
}

impl Default for OciImageManifest {
Expand All @@ -137,7 +137,7 @@ impl OciImageManifest {
pub fn build(
layers: &[ImageLayer],
config: &Config,
annotations: Option<HashMap<String, String>>,
annotations: Option<BTreeMap<String, String>>,
) -> Self {
let mut manifest = OciImageManifest::default();

Expand Down Expand Up @@ -276,7 +276,7 @@ pub struct OciDescriptor {
/// This OPTIONAL property MUST use the annotation rules.
/// <https://github.com/opencontainers/image-spec/blob/main/annotations.md#rules>
#[serde(skip_serializing_if = "Option::is_none")]
pub annotations: Option<HashMap<String, String>>,
pub annotations: Option<BTreeMap<String, String>>,
}

impl std::fmt::Display for OciDescriptor {
Expand Down Expand Up @@ -334,7 +334,7 @@ pub struct OciImageIndex {
/// MUST either be absent or be an empty map."
/// TO accomodate either, this is optional.
#[serde(skip_serializing_if = "Option::is_none")]
pub annotations: Option<HashMap<String, String>>,
pub annotations: Option<BTreeMap<String, String>>,
}

/// The manifest entry of an `ImageIndex`.
Expand Down Expand Up @@ -375,7 +375,7 @@ pub struct ImageIndexEntry {
/// This OPTIONAL property contains arbitrary metadata for the image index.
/// This OPTIONAL property MUST use the [annotation rules](https://github.com/opencontainers/image-spec/blob/main/annotations.md#rules).
#[serde(skip_serializing_if = "Option::is_none")]
pub annotations: Option<HashMap<String, String>>,
pub annotations: Option<BTreeMap<String, String>>,
}

impl std::fmt::Display for ImageIndexEntry {
Expand Down

0 comments on commit 30a608e

Please sign in to comment.