Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: create methods to manage runtimes #74

Merged
merged 4 commits into from
Jan 26, 2023
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
529 changes: 518 additions & 11 deletions Cargo.lock

Large diffs are not rendered by default.

5 changes: 4 additions & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -27,10 +27,13 @@ env_logger = "0.9.0"
serde = { version = "1.0", features = ["derive"] }
serde_json = "1.0.85"
wax = "0.5.0"
toml = "0.5.9"
toml = "0.6.0"
base64 = "0.13.1"
clap = { version = "4.0.10", features = ["derive"] }
regex = "1"
url = "2.3.1"
reqwest = { version = "0.11" }
sha256 = "1.1.1"

[workspace]
members = [
Expand Down
7 changes: 4 additions & 3 deletions src/config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ use serde::{Deserialize, Deserializer};
use std::collections::HashMap;
use std::path::PathBuf;
use std::{env, fs};
use toml::from_slice;
use toml::from_str;

/// Workers configuration. These files are optional when no configuration change is required.
#[derive(Deserialize, Clone)]
Expand Down Expand Up @@ -46,9 +46,10 @@ impl Config {
/// namespace = "todos"
/// ```
pub fn try_from_file(path: PathBuf) -> Result<Self, String> {
let contents = fs::read(&path).expect("The configuration file was not properly loaded");
let contents =
fs::read_to_string(&path).expect("The configuration file was not properly loaded");
Angelmmiguel marked this conversation as resolved.
Show resolved Hide resolved

let try_config: Result<Config, toml::de::Error> = from_slice(&contents);
let try_config: Result<Config, toml::de::Error> = from_str(&contents);

match try_config {
Ok(c) => Ok(c),
Expand Down
26 changes: 26 additions & 0 deletions src/fetch.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
// Copyright 2022 VMware, Inc.
// SPDX-License-Identifier: Apache-2.0

use crate::runtimes::metadata::Checksum;
use anyhow::Result;

// TODO: Remove it when implementing the manager
#[allow(dead_code)]
/// Fetch the contents of a given file and validates it
/// using the Sha256.
pub async fn fetch<T: AsRef<str>>(file: T) -> Result<Vec<u8>> {
let body: Vec<u8> = reqwest::get(file.as_ref()).await?.bytes().await?.into();

Ok(body)
}

// TODO: Remove it when implementing the manager
#[allow(dead_code)]
/// Fetch the contents of a given file and validates it
/// using the Sha256.
pub async fn fetch_and_validate<T: AsRef<str>>(file: T, checksum: &Checksum) -> Result<Vec<u8>> {
let body: Vec<u8> = fetch(file).await?;
checksum.validate(&body)?;

Ok(body)
}
2 changes: 2 additions & 0 deletions src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,10 @@ extern crate lazy_static;

mod config;
mod data;
mod fetch;
mod router;
mod runtimes;
mod store;
mod workers;

use actix_files::{Files, NamedFile};
Expand Down
59 changes: 59 additions & 0 deletions src/runtimes/manager.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
//// Copyright 2022 VMware, Inc.
// SPDX-License-Identifier: Apache-2.0

use super::{
metadata::{RemoteFile, Runtime as RuntimeMetadata},
modules::{javascript::JavaScriptRuntime, native::NativeRuntime},
runtime::Runtime,
};
use crate::{fetch::fetch_and_validate, store::Store};
use anyhow::{anyhow, Result};
use std::path::Path;

// A collection of methods to manage runtimes

/// Initializes a runtime based on the file extension. In the future,
/// This will contain a more complete struct that will identify local
/// runtimes.
pub fn init_runtime(path: &Path) -> Result<Box<dyn Runtime + Sync + Send>> {
if let Some(ext) = path.extension() {
let ext_as_str = ext.to_str().unwrap();

match ext_as_str {
"js" => Ok(Box::new(JavaScriptRuntime::new(path.to_path_buf())?)),
"wasm" => Ok(Box::new(NativeRuntime::new(path.to_path_buf()))),
_ => Err(anyhow!(format!(
"The '{}' extension does not have an associated runtime",
ext_as_str
))),
}
} else {
Err(anyhow!("The given file does not have a valid extension"))
}
}

// TODO: Remove it when implementing the full logic
#[allow(dead_code)]
// Install a given runtime based on its metadata
pub async fn install_runtime(repository: &str, metadata: &RuntimeMetadata) -> Result<()> {
let store = Store::new(&["runtimes", repository, &metadata.name, &metadata.version])?;

// Install the different files
download_file(&metadata.binary, &store).await?;

if let Some(polyfill) = &metadata.polyfill {
download_file(polyfill, &store).await?;
}

if let Some(template) = &metadata.template {
download_file(template, &store).await?;
}

Ok(())
}

// TODO: Remove it when implementing the full logic
async fn download_file(file: &RemoteFile, store: &Store) -> Result<()> {
let contents = fetch_and_validate(&file.url, &file.checksum).await?;
store.write(&[&file.filename], &contents)
}
158 changes: 131 additions & 27 deletions src/runtimes/metadata.rs
Original file line number Diff line number Diff line change
@@ -1,10 +1,50 @@
// Copyright 2022 VMware, Inc.
// SPDX-License-Identifier: Apache-2.0

use super::{remote_file::RemoteFile, runtime::RuntimeStatus};
use crate::fetch::fetch;
use anyhow::{anyhow, Result};
use serde::Deserialize;
use std::collections::HashMap;
use serde::{Deserialize, Serialize};
use sha256::digest as sha256_digest;
use std::{collections::HashMap, fmt};
use url::Url;

/// A Repository contains the list of runtimes available on it.
/// This file is used by wws to properly show the list of available
/// repos and install them.
///
/// By default, this repository class rely on the
/// [WebAssembly Language Runtimes](https://github.com/vmware-labs/webassembly-language-runtimes)
/// repository. It looks for a repository.toml file in the Git repo.
#[derive(Deserialize)]
pub struct Repository {
/// Version of the repository file
pub version: u32,
/// The list of runtimes available in the repository
pub runtimes: Vec<Runtime>,
}

// TODO: Remove it when implementing the manager
#[allow(dead_code)]
impl Repository {
/// Reads and parses the metadata from a slice of bytes. It will return
/// a result as the deserialization may fail.
pub fn from_str(data: &str) -> Result<Self> {
toml::from_str::<Repository>(data).map_err(|err| {
println!("Err: {:?}", err);
anyhow!("wws could not deserialize the repository metadata")
})
}

/// Retrieve a repository from a remote file. It will download the content
/// using reqwest and initializing the repository with it.
pub async fn from_remote_file(repository_url: &str) -> Result<Self> {
let url = Url::parse(repository_url)?;
let data = fetch(&url).await?;
let str_data = String::from_utf8(data)?;

Repository::from_str(&str_data)
}
}

// TODO: Remove it when implementing the manager
#[allow(dead_code)]
Expand All @@ -16,53 +56,117 @@ use std::collections::HashMap;
/// a source code as a worker. The configuration includes
/// different pieces like polyfills files, templates,
/// arguments, etc.
#[derive(Deserialize)]
pub struct RuntimeMetadata<'a> {
#[derive(Deserialize, Serialize, Clone)]
pub struct Runtime {
/// Name of the runtime (like ruby, python, etc)
name: &'a str,
pub name: String,
/// Specific version of the runtime
version: &'a str,
pub version: String,
/// Current status in the repository
status: RuntimeStatus,
pub status: RuntimeStatus,
/// Associated extensions
extensions: Vec<&'a str>,
pub extensions: Vec<String>,
/// Arguments to pass to the Wasm module via WASI
args: Vec<&'a str>,
pub args: Vec<String>,
/// A list of environment variables that must be configured
/// for the runtime to work.
envs: Option<HashMap<&'a str, &'a str>>,
pub envs: Option<HashMap<String, String>>,
/// The reference to a remote binary (url + checksum)
binary: RemoteFile<'a>,
pub binary: RemoteFile,
/// The reference to a remote polyfill file (url + checksum)
polyfill: Option<RemoteFile<'a>>,
/// The refernmece to a template file for the worker. It will wrap the
pub polyfill: Option<RemoteFile>,
/// The reference to a template file for the worker. It will wrap the
/// source code into a template that can include imports,
/// function calls, etc.
template: Option<RemoteFile<'a>>,
pub template: Option<RemoteFile>,
}

// TODO: Remove it when implementing the manager
#[allow(dead_code)]
impl<'a> RuntimeMetadata<'a> {
/// Reads and parses the metadata from a slice of bytes. It will return
/// a result as the deserialization may fail.
pub fn from_slice(data: &'a [u8]) -> Result<Self> {
toml::from_slice::<RuntimeMetadata>(data)
.map_err(|_| anyhow!("wws could not deserialize the runtime metadata"))
// Implement the Display trait to runtime metadata properly in the CLI
impl fmt::Display for Runtime {
/// Prints the runtime as a row for a list of runtimes
Angelmmiguel marked this conversation as resolved.
Show resolved Hide resolved
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(
f,
"{}\t{}\t{}\t{}",
&self.name,
&self.version,
self.extensions.join(", "),
&self.binary.url
)
}
}

/// Define the status of a runtime in a target repository
#[derive(Deserialize, Serialize, Clone)]
#[serde(rename_all = "lowercase")]
pub enum RuntimeStatus {
Active,
Yanked,
Deprecated,
Unknown,
}

impl From<&str> for RuntimeStatus {
/// Create a RuntimeStatus variant from a &str. It uses predefined
/// values
fn from(value: &str) -> Self {
match value {
"active" => RuntimeStatus::Active,
"yanked" => RuntimeStatus::Yanked,
"deprecated" => RuntimeStatus::Deprecated,
_ => RuntimeStatus::Unknown,
}
}
}

/// A file represents a combination of both a remote URL, filename
/// and checksum.
#[derive(Deserialize, Serialize, Clone)]
pub struct RemoteFile {
/// URL pointing to the file
pub url: String,
/// Checksum to validate the given file
pub checksum: Checksum,
/// Provide a filename
pub filename: String,
}

/// A list of available checksums. For now, we will support only sha256
#[derive(Deserialize, Serialize, Clone)]
#[serde(rename_all = "lowercase", tag = "type")]
pub enum Checksum {
Sha256 { value: String },
}

impl Checksum {
/// Validate the provided slice of bytes with the given checksum.
/// Depending on the type, it will calculate a different digest.
pub fn validate(&self, bytes: &[u8]) -> Result<()> {
match self {
Checksum::Sha256 { value } if value == &sha256_digest(bytes) => Ok(()),
_ => Err(anyhow!("The checksums dont not match")),
}
}
}

#[cfg(test)]
mod tests {
use crate::runtimes::remote_file::Checksum;

use super::*;
use std::{any::Any, fs};

#[test]
fn parse_index_toml() {
let contents = fs::read_to_string("tests/data/metadata/repository.toml").unwrap();
let repo = Repository::from_str(&contents).unwrap();

assert_eq!(repo.version, 1);
assert_eq!(repo.runtimes.len(), 1);
}

#[test]
fn parse_runtime_toml() {
let contents = fs::read("tests/data/metadata/runtime.toml").unwrap();
let metadata = RuntimeMetadata::from_slice(&contents).unwrap();
let contents = fs::read_to_string("tests/data/metadata/runtime.toml").unwrap();
let metadata = toml::from_str::<Runtime>(&contents).unwrap();

assert_eq!(metadata.name, "ruby");
assert_eq!(metadata.version, "3.2.0+20230118-8aec06d");
Expand Down
5 changes: 2 additions & 3 deletions src/runtimes/mod.rs
Original file line number Diff line number Diff line change
@@ -1,8 +1,7 @@
// Copyright 2022 VMware, Inc.
// SPDX-License-Identifier: Apache-2.0

mod metadata;
pub mod manager;
pub mod metadata;
mod modules;
mod remote_file;
mod repository;
pub mod runtime;
Loading