use serde::{Deserialize, Serialize}; use crate::{Digest, EnvironmentResource}; #[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)] pub struct SelectedInput { pub path: String, pub digest: Digest, } #[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)] pub struct BundleIdentityInputs { pub wasm_code: Digest, pub task_abi: Digest, pub environments: Vec, pub source_provider_manifest: Digest, pub selected_inputs: Vec, } #[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)] pub struct BundleMetadata { pub identity: Digest, pub environments: Vec, pub selected_inputs: Vec, pub embeds_full_container_images: bool, } impl BundleIdentityInputs { pub fn identity(&self) -> Digest { let mut parts = vec![ b"bundle:v1".to_vec(), self.wasm_code.as_str().as_bytes().to_vec(), self.task_abi.as_str().as_bytes().to_vec(), self.source_provider_manifest.as_str().as_bytes().to_vec(), ]; let mut environments = self.environments.clone(); environments.sort_by(|left, right| left.name.cmp(&right.name)); for environment in environments { parts.push(environment.name.as_bytes().to_vec()); parts.push(format!("{:?}", environment.kind).into_bytes()); parts.push(environment.digest.as_str().as_bytes().to_vec()); } let mut inputs = self.selected_inputs.clone(); inputs.sort_by(|left, right| left.path.cmp(&right.path)); for input in inputs { parts.push(input.path.into_bytes()); parts.push(input.digest.as_str().as_bytes().to_vec()); } Digest::from_parts(parts) } pub fn inspectable_metadata(&self) -> BundleMetadata { BundleMetadata { identity: self.identity(), environments: self.environments.clone(), selected_inputs: self.selected_inputs.clone(), embeds_full_container_images: false, } } } #[cfg(test)] mod tests { use std::path::PathBuf; use crate::{EnvironmentKind, EnvironmentRequirements}; use super::*; fn env(digest: &str) -> EnvironmentResource { EnvironmentResource { name: "linux".to_owned(), kind: EnvironmentKind::Containerfile, recipe_path: PathBuf::from("envs/linux/Containerfile"), context_path: PathBuf::from("envs/linux"), digest: Digest::sha256(digest), requirements: EnvironmentRequirements::linux_container(), } } #[test] fn bundle_identity_changes_when_environment_recipe_changes() { let base = BundleIdentityInputs { wasm_code: Digest::sha256("wasm"), task_abi: Digest::sha256("abi"), environments: vec![env("recipe-a")], source_provider_manifest: Digest::sha256("source"), selected_inputs: vec![], }; let mut changed = base.clone(); changed.environments = vec![env("recipe-b")]; assert_ne!(base.identity(), changed.identity()); } #[test] fn bundle_metadata_is_inspectable_and_does_not_vendor_images_by_default() { let inputs = BundleIdentityInputs { wasm_code: Digest::sha256("wasm"), task_abi: Digest::sha256("abi"), environments: vec![env("recipe")], source_provider_manifest: Digest::sha256("source"), selected_inputs: vec![SelectedInput { path: "inputs/config.json".to_owned(), digest: Digest::sha256("config"), }], }; let metadata = inputs.inspectable_metadata(); assert_eq!(metadata.environments.len(), 1); assert!(!metadata.embeds_full_container_images); } }