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: run workers in installed language runtimes #81

Merged
merged 6 commits into from
Feb 7, 2023
Merged
Show file tree
Hide file tree
Changes from all 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
2 changes: 2 additions & 0 deletions examples/python-basic/worker.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
def worker(req):
return Response("Hello from Python!")
3 changes: 3 additions & 0 deletions examples/ruby-basic/worker.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
def worker(_req)
Response.new("Hello from Ruby!")
end
27 changes: 17 additions & 10 deletions src/config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -44,16 +44,7 @@ impl Config {
anyhow!("Error opening the .wws.toml file. The file format is not correct")
})
} else {
let new_repo = ConfigRepository {
name: DEFAULT_REPO_NAME.to_string(),
url: DEFAULT_REPO_URL.to_string(),
runtimes: Vec::new(),
};

Ok(Self {
version: 1,
repositories: vec![new_repo],
})
Ok(Self::default())
Angelmmiguel marked this conversation as resolved.
Show resolved Hide resolved
}
}

Expand Down Expand Up @@ -115,6 +106,22 @@ impl Config {
}
}

impl Default for Config {
// Initialize an empty repository by default
fn default() -> Self {
let new_repo = ConfigRepository {
name: DEFAULT_REPO_NAME.to_string(),
url: DEFAULT_REPO_URL.to_string(),
runtimes: Vec::new(),
};

Self {
version: 1,
repositories: vec![new_repo],
}
}
}

#[derive(Deserialize, Serialize)]
pub struct ConfigRepository {
/// Local name to identify the repository. It avoids collisions when installing
Expand Down
15 changes: 14 additions & 1 deletion src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ mod runtimes;
mod store;
mod workers;

use crate::config::Config;
use actix_files::{Files, NamedFile};
use actix_web::dev::{fn_service, ServiceRequest, ServiceResponse};
use actix_web::{
Expand Down Expand Up @@ -257,8 +258,20 @@ async fn main() -> std::io::Result<()> {
} else {
// TODO(Angelmmiguel): refactor this into a separate command!
// Initialize the routes

// Loading the local configuration if available.
let config = match Config::load(&args.path) {
Ok(c) => c,
Err(err) => {
println!("⚠️ There was an error reading the .wws.toml file. It will be ignored");
println!("⚠️ Error: {err}");

Config::default()
}
};

println!("⚙️ Loading routes from: {}", &args.path.display());
let routes = Data::new(Routes::new(&args.path, &args.prefix));
let routes = Data::new(Routes::new(&args.path, &args.prefix, &config));

let data = Data::new(RwLock::new(DataConnectors { kv: KV::new() }));

Expand Down
30 changes: 26 additions & 4 deletions src/router/files.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
// Copyright 2022 VMware, Inc.
// SPDX-License-Identifier: Apache-2.0

use crate::config::Config;
use crate::store::STORE_FOLDER;
use std::ffi::OsStr;
use std::path::{Component, Path, PathBuf};
Expand All @@ -11,25 +12,43 @@ use wax::{Glob, WalkEntry};
/// provide utilities to work with public folders and
/// other related resources.
pub struct Files {
/// Root path
root: PathBuf,
/// Available extensions based on the config
extensions: Vec<String>,
/// Check if the public folder exists
has_public: bool,
}

impl Files {
/// Initializes a new files instance. It will detect
/// relevant resources for WWS like the public folder.
pub fn new(root: &Path) -> Self {
pub fn new(root: &Path, config: &Config) -> Self {
let mut extensions = vec![String::from("js"), String::from("wasm")];

for repo in &config.repositories {
for runtime in &repo.runtimes {
for ext in &runtime.extensions {
if !extensions.contains(ext) {
Angelmmiguel marked this conversation as resolved.
Show resolved Hide resolved
extensions.push(ext.clone());
}
}
}
}

Self {
root: root.to_path_buf(),
extensions,
has_public: root.join(Path::new("public")).exists(),
}
}

/// Walk through all the different files associated to this
/// project using a Glob pattern
pub fn walk(&self) -> Vec<WalkEntry> {
let glob_pattern = format!("**/*.{{{}}}", self.extensions.join(","));
let glob =
Glob::new("**/*.{wasm,js}").expect("Failed to read the files in the current directory");
Glob::new(&glob_pattern).expect("Failed to read the files in the current directory");

glob.walk(&self.root)
.filter_map(|el| match el {
Expand Down Expand Up @@ -85,8 +104,9 @@ mod tests {
];

for t in tests {
let config = Config::default();
assert_eq!(
Files::new(Path::new("./tests/data")).is_in_public_folder(Path::new(t.0)),
Files::new(Path::new("./tests/data"), &config).is_in_public_folder(Path::new(t.0)),
t.1
)
}
Expand All @@ -106,8 +126,10 @@ mod tests {
];

for t in tests {
let config = Config::default();
assert_eq!(
Files::new(Path::new(".\\tests\\data")).is_in_public_folder(Path::new(t.0)),
Files::new(Path::new(".\\tests\\data"), &config)
.is_in_public_folder(Path::new(t.0)),
t.1
)
}
Expand Down
6 changes: 3 additions & 3 deletions src/router/route.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
// Copyright 2022 VMware, Inc.
// SPDX-License-Identifier: Apache-2.0

use crate::{workers::config::Config, workers::Worker};
use crate::{config::Config as ProjectConfig, workers::config::Config, workers::Worker};
use lazy_static::lazy_static;
use regex::Regex;
use std::{
Expand Down Expand Up @@ -53,8 +53,8 @@ impl Route {
/// proper URL path based on the filename.
///
/// This method also initializes the Runner and loads the Config if available.
pub fn new(base_path: &Path, filepath: PathBuf, prefix: &str) -> Self {
let worker = Worker::new(base_path, &filepath).unwrap();
pub fn new(base_path: &Path, filepath: PathBuf, prefix: &str, config: &ProjectConfig) -> Self {
let worker = Worker::new(base_path, &filepath, config).unwrap();

// Load configuration
let mut config_path = filepath.clone();
Expand Down
11 changes: 8 additions & 3 deletions src/router/routes.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ use std::path::Path;

use super::files::Files;
use super::route::{Route, RouteAffinity};
use crate::config::Config;

/// Contains all registered routes
pub struct Routes {
Expand All @@ -20,13 +21,13 @@ impl Routes {
/// Initialize the list of routes from the given folder. This method will look for
/// different files and will create the associated routes. This routing approach
/// is pretty popular in web development and static sites.
pub fn new(path: &Path, base_prefix: &str) -> Self {
pub fn new(path: &Path, base_prefix: &str, config: &Config) -> Self {
let mut routes = Vec::new();
let prefix = Self::format_prefix(base_prefix);
let files = Files::new(path);
let files = Files::new(path, config);

for entry in files.walk() {
routes.push(Route::new(path, entry.into_path(), &prefix));
routes.push(Route::new(path, entry.into_path(), &prefix, config));
}

Self { routes, prefix }
Expand Down Expand Up @@ -91,10 +92,12 @@ mod tests {
#[test]
fn route_path_affinity() {
let build_route = |file: &str| -> Route {
let project_config = Config::default();
Route::new(
Path::new("./tests/data/params"),
PathBuf::from(format!("./tests/data/params{file}")),
"",
&project_config,
)
};

Expand Down Expand Up @@ -123,10 +126,12 @@ mod tests {
#[test]
fn best_route_by_affinity() {
let build_route = |file: &str| -> Route {
let project_config = Config::default();
Route::new(
Path::new("./tests/data/params"),
PathBuf::from(format!("./tests/data/params{file}")),
"",
&project_config,
)
};

Expand Down
50 changes: 44 additions & 6 deletions src/runtimes/manager.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,10 @@

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

Expand All @@ -15,7 +15,11 @@ use std::path::Path;
/// 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(project_root: &Path, path: &Path) -> Result<Box<dyn Runtime + Sync + Send>> {
pub fn init_runtime(
project_root: &Path,
path: &Path,
config: &Config,
) -> Result<Box<dyn Runtime + Sync + Send>> {
if let Some(ext) = path.extension() {
let ext_as_str = ext.to_str().unwrap();

Expand All @@ -25,15 +29,49 @@ pub fn init_runtime(project_root: &Path, path: &Path) -> Result<Box<dyn Runtime
path.to_path_buf(),
)?)),
"wasm" => Ok(Box::new(NativeRuntime::new(path.to_path_buf()))),
_ => Err(anyhow!(format!(
"The '{ext_as_str}' extension does not have an associated runtime"
))),
other => init_external_runtime(project_root, config, path, other),
}
} else {
Err(anyhow!("The given file does not have a valid extension"))
}
}

/// Initialize an external runtime. It looks for the right runtime in the configuration
/// metadata. Then, it will init the runtime with it.
fn init_external_runtime(
project_root: &Path,
config: &Config,
path: &Path,
extension: &str,
) -> Result<Box<dyn Runtime + Sync + Send>> {
let mut runtime_config = None;
let mut repo_name = "";
let other_string = extension.to_string();

'outer: for repo in &config.repositories {
for r in &repo.runtimes {
if r.extensions.contains(&other_string) {
runtime_config = Some(r);
repo_name = &repo.name;
break 'outer;
}
}
}

if let Some(runtime_config) = runtime_config {
Ok(Box::new(ExternalRuntime::new(
project_root,
path.to_path_buf(),
repo_name,
runtime_config.clone(),
)?))
} else {
Err(anyhow!(format!(
"The '{extension}' extension does not have an associated runtime"
)))
}
}

// Install a given runtime based on its metadata
pub async fn install_runtime(
project_root: &Path,
Expand Down
Loading