use std::collections::BTreeMap;
use anyhow::Context;
use chrono::{DateTime, Utc};
use oci_client::client::{Config, ImageLayer};
use serde::{Deserialize, Serialize};
use sha2::Digest;
use crate::{
Component, COMPONENT_OS, MODULE_OS, WASM_ARCHITECTURE, WASM_LAYER_MEDIA_TYPE,
WASM_MANIFEST_CONFIG_MEDIA_TYPE,
};
pub trait ToConfig {
fn to_config(&self) -> anyhow::Result<Config>;
}
#[derive(Serialize, Deserialize, Debug)]
#[serde(rename_all = "camelCase")]
pub struct WasmConfig {
pub created: DateTime<Utc>,
pub author: Option<String>,
pub architecture: String,
pub os: String,
pub layer_digests: Vec<String>,
pub component: Option<Component>,
}
pub struct AnnotatedWasmConfig<'a> {
pub config: &'a WasmConfig,
pub annotations: BTreeMap<String, String>,
}
impl WasmConfig {
pub async fn from_component(
path: impl AsRef<std::path::Path>,
author: Option<String>,
) -> anyhow::Result<(Self, ImageLayer)> {
let raw = tokio::fs::read(path).await.context("Unable to read file")?;
Self::from_raw_component(raw, author)
}
pub fn from_raw_component(
raw: Vec<u8>,
author: Option<String>,
) -> anyhow::Result<(Self, ImageLayer)> {
let component = Component::from_raw_component(&raw)?;
let config = Self {
created: Utc::now(),
author,
architecture: WASM_ARCHITECTURE.to_string(),
os: COMPONENT_OS.to_string(),
layer_digests: vec![sha256_digest(&raw)],
component: Some(component),
};
Ok((
config,
ImageLayer {
data: raw.into(),
media_type: WASM_LAYER_MEDIA_TYPE.to_string(),
annotations: None,
},
))
}
pub async fn from_module(
path: impl AsRef<std::path::Path>,
author: Option<String>,
) -> anyhow::Result<(Self, ImageLayer)> {
let raw = tokio::fs::read(path).await.context("Unable to read file")?;
Self::from_raw_module(raw, author)
}
pub fn from_raw_module(
raw: Vec<u8>,
author: Option<String>,
) -> anyhow::Result<(Self, ImageLayer)> {
let config = Self {
created: Utc::now(),
author,
architecture: WASM_ARCHITECTURE.to_string(),
os: MODULE_OS.to_string(),
layer_digests: vec![sha256_digest(&raw)],
component: None,
};
Ok((
config,
ImageLayer {
data: raw.into(),
media_type: WASM_LAYER_MEDIA_TYPE.to_string(),
annotations: None,
},
))
}
#[must_use]
pub fn with_annotations(
&'_ self,
annotations: BTreeMap<String, String>,
) -> AnnotatedWasmConfig<'_> {
AnnotatedWasmConfig {
config: self,
annotations,
}
}
}
impl ToConfig for AnnotatedWasmConfig<'_> {
fn to_config(&self) -> anyhow::Result<Config> {
let mut config = self.config.to_config()?;
config.annotations = Some(self.annotations.clone());
Ok(config)
}
}
impl ToConfig for WasmConfig {
fn to_config(&self) -> anyhow::Result<Config> {
serde_json::to_vec(self)
.map(|data| Config {
data: data.into(),
media_type: WASM_MANIFEST_CONFIG_MEDIA_TYPE.to_string(),
annotations: None,
})
.map_err(Into::into)
}
}
impl TryFrom<String> for WasmConfig {
type Error = anyhow::Error;
fn try_from(value: String) -> Result<Self, Self::Error> {
serde_json::from_str(&value).map_err(Into::into)
}
}
impl TryFrom<Vec<u8>> for WasmConfig {
type Error = anyhow::Error;
fn try_from(value: Vec<u8>) -> Result<Self, Self::Error> {
serde_json::from_slice(&value).map_err(Into::into)
}
}
impl TryFrom<&str> for WasmConfig {
type Error = anyhow::Error;
fn try_from(value: &str) -> Result<Self, Self::Error> {
serde_json::from_str(value).map_err(Into::into)
}
}
impl TryFrom<&[u8]> for WasmConfig {
type Error = anyhow::Error;
fn try_from(value: &[u8]) -> Result<Self, Self::Error> {
serde_json::from_slice(value).map_err(Into::into)
}
}
fn sha256_digest(bytes: &[u8]) -> String {
use std::fmt::Write;
let digest = sha2::Sha256::digest(bytes);
let mut out = String::with_capacity("sha256:".len() + digest.len() * 2);
out.push_str("sha256:");
for byte in digest {
let _ = write!(out, "{byte:02x}");
}
out
}