Skip to content

Commit

Permalink
Merge branch 'main' into refactor/wash-crate
Browse files Browse the repository at this point in the history
  • Loading branch information
Ahmed Tadde committed Feb 4, 2025
2 parents 470cf58 + 3655f8a commit c718547
Show file tree
Hide file tree
Showing 26 changed files with 1,052 additions and 340 deletions.
310 changes: 155 additions & 155 deletions Cargo.lock

Large diffs are not rendered by default.

16 changes: 8 additions & 8 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -265,7 +265,7 @@ notify = { version = "6", default-features = false }
nuid = { version = "0.5", default-features = false }
num = { version = "0.4", default-features = false }
oci-client = { version = "0.14", default-features = false }
oci-wasm = { version = "0.2.0", default-features = false }
oci-wasm = { version = "0.2.1", default-features = false }
once_cell = { version = "1", default-features = false }
opentelemetry = { version = "0.27", default-features = false }
opentelemetry-appender-tracing = { version = "0.27", default-features = false }
Expand All @@ -277,7 +277,7 @@ path-clean = { version = "1", default-features = false }
pg_bigdecimal = { version = "0.1", default-features = false }
pin-project-lite = { version = "0.2", default-features = false }
postgres-types = { version = "0.2", default-features = false }
provider-archive = { version = "^0.14.0", path = "./crates/provider-archive", default-features = false }
provider-archive = { version = "^0.15.0", path = "./crates/provider-archive", default-features = false }
quote = { version = "1", default-features = false }
rand = { version = "0.8", default-features = false }
redis = { version = "0.25", default-features = false }
Expand Down Expand Up @@ -362,7 +362,7 @@ wasmcloud-provider-keyvalue-nats = { version = "*", path = "./crates/provider-ke
wasmcloud-provider-keyvalue-redis = { version = "*", path = "./crates/provider-keyvalue-redis", default-features = false }
wasmcloud-provider-keyvalue-vault = { version = "*", path = "./crates/provider-keyvalue-vault", default-features = false }
wasmcloud-provider-messaging-kafka = { version = "*", path = "./crates/provider-messaging-kafka", default-features = false }
wasmcloud-provider-messaging-nats = { version = "^0.24.0", path = "./crates/provider-messaging-nats", default-features = false }
wasmcloud-provider-messaging-nats = { version = "0.25.0", path = "./crates/provider-messaging-nats", default-features = false }
wasmcloud-provider-sdk = { version = "^0.13.0", path = "./crates/provider-sdk", default-features = false }
wasmcloud-provider-sqldb-postgres = { version = "*", path = "./crates/provider-sqldb-postgres", default-features = false }
wasmcloud-runtime = { version = "^0.8.0", path = "./crates/runtime", default-features = false }
Expand All @@ -378,12 +378,12 @@ wasmtime-wit-bindgen = { version = "26", default-features = false }
wat = { version = "1", default-features = false }
webpki-roots = { version = "0.26", default-features = false }
which = { version = "4", default-features = false }
wit-bindgen = { version = "0.32", default-features = false }
wit-bindgen-core = { version = "0.33", default-features = false }
wit-bindgen-go = { version = "0.32", default-features = false }
wit-bindgen = { version = "0.38.0", default-features = false }
wit-bindgen-core = { version = "0.38.0", default-features = false }
wit-bindgen-go = { version = "0.38.0", default-features = false }
wit-bindgen-wrpc = { version = "0.9", default-features = false }
wit-component = { version = "0.219", default-features = false }
wit-parser = { version = "0.219", default-features = false }
wit-component = { version = "0.224", default-features = false }
wit-parser = { version = "0.224", default-features = false }
wrpc-interface-blobstore = { version = "0.21", default-features = false }
wrpc-interface-http = { version = "0.31", default-features = false }
wrpc-runtime-wasmtime = { version = "0.25", default-features = false }
Expand Down
2 changes: 1 addition & 1 deletion crates/provider-archive/Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[package]
name = "provider-archive"
version = "0.14.0"
version = "0.15.0"
description = "Library for reading and writing wasmCloud capability provider archive files"
documentation = "https://docs.rs/provider-archive"
readme = "README.md"
Expand Down
198 changes: 195 additions & 3 deletions crates/provider-archive/src/archive.rs
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ use wascap::{
};

const CLAIMS_JWT_FILE: &str = "claims.jwt";
const WIT_WORLD_FILE: &str = "world.wasm";

const GZIP_MAGIC: [u8; 2] = [0x1f, 0x8b];

Expand All @@ -36,6 +37,7 @@ pub struct ProviderArchive {
ver: Option<String>,
token: Option<Token<CapabilityProvider>>,
json_schema: Option<serde_json::Value>,
wit: Option<Vec<u8>>,
}

impl ProviderArchive {
Expand All @@ -50,6 +52,7 @@ impl ProviderArchive {
ver,
token: None,
json_schema: None,
wit: None,
}
}

Expand All @@ -60,6 +63,13 @@ impl ProviderArchive {
Ok(())
}

/// Adds a WIT file encoded as a wasm module to the archive
pub fn add_wit_world(&mut self, world: &[u8]) -> Result<()> {
self.wit = Some(world.to_vec());

Ok(())
}

/// Sets a JSON schema for this provider's link definition specification. This will be injected
/// into the claims written to a provider's PAR file, so you'll need to do this after instantiation
/// and prior to writing
Expand Down Expand Up @@ -102,6 +112,12 @@ impl ProviderArchive {
self.json_schema.clone()
}

/// Returns the WIT embedded in this provider archive.
#[must_use]
pub fn wit_world(&self) -> Option<&[u8]> {
self.wit.as_deref()
}

/// Attempts to read a Provider Archive (PAR) file's bytes to analyze and verify its contents.
///
/// The embedded claims in this archive will be validated, and the file hashes contained in
Expand Down Expand Up @@ -195,6 +211,7 @@ impl ProviderArchive {
target: Option<&str>,
) -> Result<ProviderArchive> {
let mut libraries = HashMap::new();
let mut wit_world = None;

let mut magic = [0; 2];
if let Err(e) = input.read_exact(&mut magic).await {
Expand Down Expand Up @@ -236,6 +253,9 @@ impl ProviderArchive {
jwt: jwt.to_string(),
claims,
});
} else if file_target == "world" {
tokio::io::copy(&mut entry, &mut bytes).await?;
wit_world = Some(bytes);
} else if let Some(t) = target {
// If loading only a specific target, only copy in bytes if it is the target. We still
// need to iterate through the rest so we can be sure to find the claims
Expand Down Expand Up @@ -267,7 +287,7 @@ impl ProviderArchive {
let ver = metadata.ver.clone();
let json_schema = metadata.config_schema.clone();

validate_hashes(&libraries, cl)?;
validate_hashes(&libraries, &wit_world, cl)?;

Ok(ProviderArchive {
libraries,
Expand All @@ -277,6 +297,7 @@ impl ProviderArchive {
ver,
token,
json_schema,
wit: wit_world,
})
} else {
Err("No claims found embedded in provider archive.".into())
Expand Down Expand Up @@ -320,7 +341,7 @@ impl ProviderArchive {
self.vendor.to_string(),
self.rev,
self.ver.clone(),
generate_hashes(&self.libraries),
generate_hashes(&self.libraries, &self.wit),
);
if let Some(schema) = self.json_schema.clone() {
claims.metadata.as_mut().unwrap().config_schema = Some(schema);
Expand All @@ -339,6 +360,15 @@ impl ProviderArchive {
par.append_data(&mut header, CLAIMS_JWT_FILE, Cursor::new(claims_jwt))
.await?;

if let Some(world) = &self.wit {
let mut header = tokio_tar::Header::new_gnu();
header.set_path(WIT_WORLD_FILE)?;
header.set_size(world.len() as u64);
header.set_cksum();
par.append_data(&mut header, WIT_WORLD_FILE, Cursor::new(world))
.await?;
}

for (tgt, lib) in &self.libraries {
let mut header = tokio_tar::Header::new_gnu();
let path = format!("{tgt}.bin");
Expand All @@ -361,6 +391,7 @@ impl ProviderArchive {

fn validate_hashes(
libraries: &HashMap<String, Vec<u8>>,
wit: &Option<Vec<u8>>,
claims: &Claims<CapabilityProvider>,
) -> Result<()> {
let file_hashes = claims.metadata.as_ref().unwrap().target_hashes.clone();
Expand All @@ -372,16 +403,35 @@ fn validate_hashes(
return Err(format!("File hash and verify hash do not match for '{tgt}'").into());
}
}

if let Some(interface) = wit {
if let Some(wit_hash) = file_hashes.get(WIT_WORLD_FILE) {
let check_hash = hash_bytes(interface);
if wit_hash != &check_hash {
return Err("WIT interface hash does not match".into());
}
} else if wit.is_some() {
return Err("WIT interface present but no hash found in claims".into());
}
}
Ok(())
}

fn generate_hashes(libraries: &HashMap<String, Vec<u8>>) -> HashMap<String, String> {
fn generate_hashes(
libraries: &HashMap<String, Vec<u8>>,
wit: &Option<Vec<u8>>,
) -> HashMap<String, String> {
let mut hm = HashMap::new();
for (target, lib) in libraries {
let hash = hash_bytes(lib);
hm.insert(target.to_string(), hash);
}

if let Some(interface) = wit {
let hash = hash_bytes(interface);
hm.insert(WIT_WORLD_FILE.to_string(), hash);
}

hm
}

Expand Down Expand Up @@ -595,6 +645,72 @@ mod test {
Ok(())
}

#[tokio::test]
async fn wit_compression_roundtrip() -> Result<()> {
let mut arch =
ProviderArchive::new("Testing", "wasmCloud", Some(4), Some("0.0.4".to_string()));

// Add both libraries and WIT data
arch.add_library("aarch64-linux", b"heylookimaraspberrypi")?;
arch.add_library("x86_64-linux", b"system76")?;
arch.add_library("x86_64-macos", b"16inchmacbookpro")?;
arch.add_wit_world(b"interface world example { resource config {} }")?;

let issuer = KeyPair::new_account();
let subject = KeyPair::new_service();

let filename = "wit_test";
let tempdir = tempfile::tempdir()?;

let parpath = tempdir.path().join(format!("{filename}.par"));
let cheezypath = tempdir.path().join(format!("{filename}.par.gz"));

// Write both compressed and uncompressed
arch.write(&parpath, &issuer, &subject, false).await?;
arch.write(&cheezypath, &issuer, &subject, true).await?;

// Read both files into memory
let mut buf2 = Vec::new();
let mut f2 = File::open(&parpath).await?;
f2.read_to_end(&mut buf2).await?;

let mut buf3 = Vec::new();
let mut f3 = File::open(&cheezypath).await?;
f3.read_to_end(&mut buf3).await?;

// Test uncompressed archive
let arch2 = ProviderArchive::try_load(&buf2).await?;
assert_eq!(
arch.libraries[&"aarch64-linux".to_string()],
arch2.libraries[&"aarch64-linux".to_string()]
);
assert_eq!(arch.wit_world(), arch2.wit_world());
assert_eq!(arch.claims().unwrap().subject, subject.public_key());

// Test compressed archive
let arch3 = ProviderArchive::try_load(&buf3).await?;
assert_eq!(
arch.libraries[&"aarch64-linux".to_string()],
arch3.libraries[&"aarch64-linux".to_string()]
);
assert_eq!(arch.wit_world(), arch3.wit_world());
assert_eq!(arch.claims().unwrap().subject, subject.public_key());

// Test loading directly from files
let arch4 = ProviderArchive::try_load_file(&parpath).await?;
assert_eq!(arch.wit_world(), arch4.wit_world());

let arch5 = ProviderArchive::try_load_file(&cheezypath).await?;
assert_eq!(arch.wit_world(), arch5.wit_world());

// Verify WIT hash in claims
let claims = arch5.claims().unwrap();
let hashes = claims.metadata.unwrap().target_hashes;
assert!(hashes.contains_key("world.wasm"));

Ok(())
}

#[tokio::test]
async fn valid_write_compressed() -> Result<()> {
let mut arch =
Expand Down Expand Up @@ -639,6 +755,55 @@ mod test {
Ok(())
}

#[tokio::test]
async fn valid_write_compressed_with_wit() -> Result<()> {
let mut arch =
ProviderArchive::new("Testing", "wasmCloud", Some(6), Some("0.0.6".to_string()));

// Add libraries and WIT
arch.add_library("x86_64-linux", b"linux")?;
arch.add_library("arm-macos", b"macos")?;
arch.add_library("mips64-freebsd", b"freebsd")?;
arch.add_wit_world(b"interface world capability { resource handler {} }")?;

let filename = "multi-os-wit";
let issuer = KeyPair::new_account();
let subject = KeyPair::new_service();

let tempdir = tempfile::tempdir()?;
arch.write(
tempdir.path().join(format!("{filename}.par")),
&issuer,
&subject,
true,
)
.await?;

let arch2 =
ProviderArchive::try_load_file(tempdir.path().join(format!("{filename}.par.gz")))
.await?;

// Verify libraries
assert_eq!(
arch.libraries[&"x86_64-linux".to_string()],
arch2.libraries[&"x86_64-linux".to_string()]
);
assert_eq!(
arch.libraries[&"arm-macos".to_string()],
arch2.libraries[&"arm-macos".to_string()]
);
assert_eq!(
arch.libraries[&"mips64-freebsd".to_string()],
arch2.libraries[&"mips64-freebsd".to_string()]
);

// Verify WIT and claims
assert_eq!(arch.wit_world(), arch2.wit_world());
assert_eq!(arch.claims(), arch2.claims());

Ok(())
}

#[tokio::test]
async fn valid_write_compressed_with_suffix() -> Result<()> {
let mut arch =
Expand Down Expand Up @@ -736,4 +901,31 @@ mod test {

Ok(())
}

/// Ensures backwards compatibility with PAR that do not contain WIT
#[tokio::test]
async fn witless_archive() -> Result<()> {
// First create an "old style" archive without WIT
let mut old_arch =
ProviderArchive::new("OldStyle", "wasmCloud", Some(1), Some("0.0.1".to_string()));
old_arch.add_library("x86_64-linux", b"oldbin")?;

let issuer = KeyPair::new_account();
let subject = KeyPair::new_service();

let tempdir = tempfile::tempdir()?;
let old_path = tempdir.path().join("old_style.par");

// Write old archive
old_arch.write(&old_path, &issuer, &subject, false).await?;

let loaded_arch = ProviderArchive::try_load_file(&old_path).await?;
assert_eq!(loaded_arch.wit_world(), None); // No WIT data
assert_eq!(
loaded_arch.libraries.get("x86_64-linux"),
old_arch.libraries.get("x86_64-linux")
);

Ok(())
}
}
2 changes: 1 addition & 1 deletion crates/provider-messaging-nats/Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[package]
name = "wasmcloud-provider-messaging-nats"
version = "0.24.0"
version = "0.25.0"
description = """
A capability provider that satisfies the 'wasmcloud:messaging' contract using NATS as a backend.
"""
Expand Down
9 changes: 4 additions & 5 deletions crates/provider-messaging-nats/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -297,15 +297,14 @@ impl Provider for NatsMessagingProvider {
#[instrument(level = "debug", skip_all, fields(target_id))]
async fn receive_link_config_as_source(
&self,
LinkConfig {
target_id, config, ..
}: LinkConfig<'_>,
link_config: LinkConfig<'_>,
) -> anyhow::Result<()> {
let config = if config.is_empty() {
let target_id = link_config.target_id;
let config = if link_config.config.is_empty() {
self.default_config.clone()
} else {
// create a config from the supplied values and merge that with the existing default
match ConnectionConfig::from_map(config) {
match ConnectionConfig::from_link_config(&link_config) {
Ok(cc) => self.default_config.merge(&cc),
Err(e) => {
error!("Failed to build connection configuration: {e:?}");
Expand Down
Loading

0 comments on commit c718547

Please sign in to comment.