use std::path::{Path, PathBuf};
use asset_container::AssetManager;
use sha256::digest;
use tokio::fs;
use wick_config::WickConfiguration;
use wick_oci_utils::package::annotations::Annotations;
use wick_oci_utils::package::{media_types, PackageFile};
use wick_oci_utils::OciOptions;
use crate::utils::{get_relative_path, metadata_to_annotations};
use crate::{Error, WickPackageKind};
#[derive(Debug, Clone)]
pub struct WickPackage {
#[allow(unused)]
kind: WickPackageKind,
#[allow(dead_code)]
name: String,
#[allow(dead_code)]
version: String,
files: Vec<PackageFile>,
#[allow(unused)]
annotations: Annotations,
}
impl WickPackage {
pub async fn from_path(path: &Path) -> Result<Self, Error> {
if path.is_dir() {
return Err(Error::Directory(path.to_path_buf()));
}
let options = wick_config::FetchOptions::default();
let config = WickConfiguration::fetch(&path.to_string_lossy(), options).await?;
if !matches!(config, WickConfiguration::App(_) | WickConfiguration::Component(_)) {
return Err(Error::InvalidWickConfig(path.to_string_lossy().to_string()));
}
let full_path = tokio::fs::canonicalize(path)
.await
.map_err(|e| Error::ReadFile(path.to_path_buf(), e))?;
let parent_dir = full_path
.parent()
.map_or_else(|| PathBuf::from("/"), |v| v.to_path_buf());
let (kind, name, version, annotations, parent_dir, media_type) = match &config {
WickConfiguration::App(app_config) => {
let name = app_config.name();
let version = app_config.version();
let annotations = metadata_to_annotations(app_config.metadata());
let media_type = media_types::APPLICATION;
let kind = WickPackageKind::APPLICATION;
(kind, name, version, annotations, parent_dir, media_type)
}
WickConfiguration::Component(component_config) => {
let name = component_config.name().clone().ok_or(Error::NoName)?;
let version = component_config.version();
let annotations = metadata_to_annotations(component_config.metadata());
let media_type = media_types::COMPONENT;
let kind = WickPackageKind::COMPONENT;
(kind, name, version, annotations, parent_dir, media_type)
}
_ => return Err(Error::InvalidWickConfig(path.to_string_lossy().to_string())),
};
let mut assets = config.assets();
let mut wick_files: Vec<PackageFile> = Vec::new();
let root_bytes = fs::read(path)
.await
.map_err(|e| Error::ReadFile(path.to_path_buf(), e))?;
let root_hash = format!("sha256:{}", digest(root_bytes.as_slice()));
let root_file = PackageFile::new(
PathBuf::from(path.file_name().unwrap()),
root_hash,
media_type.to_owned(),
root_bytes.into(),
);
wick_files.push(root_file);
for asset in assets.iter() {
let location = asset.location(); let asset_path = asset.path()?; let path = get_relative_path(&parent_dir, &asset_path)?;
let options = wick_config::FetchOptions::default();
let media_type: &str;
match path.extension().and_then(|os_str| os_str.to_str()) {
Some("yaml" | "yml" | "wick") => {
let config = WickConfiguration::fetch(asset_path, options.clone()).await;
match config {
Ok(WickConfiguration::App(_)) => {
media_type = media_types::APPLICATION;
}
Ok(WickConfiguration::Component(_)) => {
media_type = media_types::COMPONENT;
}
Ok(WickConfiguration::Tests(_)) => {
media_type = media_types::TESTS;
}
Ok(WickConfiguration::Types(_)) => {
media_type = media_types::TYPES;
}
Err(_) => {
media_type = media_types::OTHER;
}
}
}
Some("wasm") => {
media_type = media_types::WASM;
}
_ => {
media_type = media_types::OTHER;
}
}
let file_bytes = asset.bytes(&options).await?;
let hash = format!("sha256:{}", digest(file_bytes.as_ref()));
let wick_file = PackageFile::new(PathBuf::from(location), hash, media_type.to_owned(), file_bytes);
wick_files.push(wick_file);
}
Ok(Self {
kind,
name: name.clone(),
version: version.clone(),
files: wick_files,
annotations,
})
}
#[must_use]
pub fn list_files(&self) -> Vec<&PackageFile> {
self.files.iter().collect()
}
pub async fn push(&mut self, reference: &str, options: &OciOptions) -> Result<String, Error> {
let config = crate::WickConfig { kind: self.kind };
let image_config_contents = serde_json::to_string(&config).unwrap();
let files = self.files.drain(..).collect();
let push_response = wick_oci_utils::package::push(
reference,
image_config_contents,
files,
self.annotations.clone(),
options,
)
.await?;
println!("Image successfully pushed to the registry.");
println!("Config URL: {}", push_response.config_url);
println!("Manifest URL: {}", push_response.manifest_url);
Ok(push_response.manifest_url)
}
pub async fn pull(reference: &str, options: &OciOptions) -> Result<Self, Error> {
let result = wick_oci_utils::package::pull(reference, options).await?;
let package = Self::from_path(&result.base_dir.join(Path::new(&result.root_path))).await;
match package {
Ok(package) => Ok(package),
Err(e) => Err(Error::PackageReadFailed(e.to_string())),
}
}
}