From ea1459fcd0db7f0bcf045bbee734b2d34dbba2d4 Mon Sep 17 00:00:00 2001 From: Angel M De Miguel Date: Thu, 26 Jan 2023 13:05:07 +0100 Subject: [PATCH] feat: create and manage the .wws.toml file and its metadata --- .gitignore | 3 +- src/config.rs | 173 +++++++++++++++++++-------------------- src/router/files.rs | 16 +++- src/router/route.rs | 2 +- src/runtimes/metadata.rs | 9 ++ src/store.rs | 2 +- src/workers/config.rs | 109 ++++++++++++++++++++++++ src/workers/mod.rs | 1 + 8 files changed, 224 insertions(+), 91 deletions(-) create mode 100644 src/workers/config.rs diff --git a/.gitignore b/.gitignore index 10e1fd84..05ad14d6 100644 --- a/.gitignore +++ b/.gitignore @@ -3,4 +3,5 @@ target !tests/**/*.wasm examples/*.toml .DS_Store -.wws \ No newline at end of file +.wws +.wws.toml \ No newline at end of file diff --git a/src/config.rs b/src/config.rs index 4b252268..fecc4d62 100644 --- a/src/config.rs +++ b/src/config.rs @@ -1,109 +1,108 @@ // Copyright 2022 VMware, Inc. // SPDX-License-Identifier: Apache-2.0 -use crate::data::kv::KVConfigData; +use crate::runtimes::metadata::Runtime; use anyhow::{anyhow, Result}; -use serde::{Deserialize, Deserializer}; -use std::collections::HashMap; -use std::path::PathBuf; -use std::{env, fs}; -use toml::from_str; +use serde::{Deserialize, Serialize}; +use std::{ + fs, + path::{Path, PathBuf}, +}; -/// Workers configuration. These files are optional when no configuration change is required. -#[derive(Deserialize, Clone)] -pub struct Config { - /// Worker name. For logging purposes - pub name: Option, - /// Mandatory version of the file - pub version: String, - /// Optional data configuration - pub data: Option, - /// Optional environment configuration - #[serde(deserialize_with = "read_environment_variables", default)] - pub vars: HashMap, -} +/// Config file name +const CONFIG_FILENAME: &str = ".wws.toml"; -/// Configure a data plugin for the worker -#[derive(Deserialize, Clone)] -pub struct ConfigData { - /// Creates a Key/Value store associated to the given worker - pub kv: Option, +/// Loads the data from the Project definition file or .wws.toml. +/// This file contains information about the different runtimes +/// required for this project. You can think of those as dependencies. +/// +/// If your project requires to run workers using any interpreted +/// language (except Js, which it's embedded), you will need to install +/// a language runtime. +/// +/// For reproducibility, this file can be commited to the project +/// repository so other developers can download them directly. +#[derive(Deserialize, Serialize)] +pub struct Config { + /// Version of the .wws file + version: u32, + /// List of repositories + repositories: Vec, } +// TODO: Remove it when start adding the new subcommands +#[allow(dead_code)] impl Config { - /// Try to read the configuration from a TOML file. The path contains the local path - /// to the worker configuration. The file should use the same name as the worker, - /// with the .toml extension - /// - /// # Examples - /// - /// ``` - /// name = "todos" - /// version = "1" - /// - /// [data] - /// - /// [data.kv] - /// namespace = "todos" - /// ``` - pub fn try_from_file(path: PathBuf) -> Result { - let contents = fs::read_to_string(&path)?; + /// Load the config file if it's present. It not, it will create a + /// new empty config. + pub fn load(project_root: &Path) -> Result { + let config_path = Self::config_path(project_root); - let try_config: Result = from_str(&contents); + if config_path.exists() { + toml::from_str(&fs::read_to_string(config_path)?).map_err(|_| { + anyhow!("Error opening the .wws.toml file. The file format is not correct") + }) + } else { + let new_repo = ConfigRepository { + name: "wlr".to_string(), + runtimes: Vec::new(), + }; - match try_config { - Ok(c) => Ok(c), - Err(err) => Err(anyhow!( - "Error reading the configuration file at {}: {}", - &path.to_str().unwrap_or("?"), - err - )), + Ok(Self { + version: 1, + repositories: vec![new_repo], + }) } } - /// Returns a data Key/Value configuration if available - pub fn data_kv_config(&self) -> Option<&KVConfigData> { - self.data.as_ref()?.kv.as_ref() - } + /// Save a new installed runtime + pub fn save_runtime(&mut self, repository: &str, runtime: &Runtime) { + let repo = self.repositories.iter_mut().find(|r| r.name == repository); - /// Returns the data Key/Value namespace if available - pub fn data_kv_namespace(&self) -> Option { - Some(self.data_kv_config()?.namespace.clone()) + // Shadow to init an empty one if required + match repo { + Some(r) => r.runtimes.push(runtime.clone()), + None => { + let new_repo = ConfigRepository { + name: repository.to_string(), + runtimes: vec![runtime.clone()], + }; + + self.repositories.push(new_repo); + } + }; } -} -/// Deserialize the HashMap of variables. By default, this -/// function won't modify the K or the V of the HashMap. If -/// V starts with $, its value will be read from the server -/// environment variables -fn read_environment_variables<'de, D>(deserializer: D) -> Result, D::Error> -where - D: Deserializer<'de>, -{ - let result: Result>, D::Error> = - Deserialize::deserialize(deserializer); + /// Remove an existing runtime if it's present. + pub fn remove_runtime(&mut self, repository: &str, runtime: &Runtime) { + let repo = self.repositories.iter_mut().find(|r| r.name == repository); - match result { - Ok(value) => match value { - Some(mut options) => { - for (_, value) in options.iter_mut() { - // Read the value from the environment variables if available. - // If not, it will default to an empty string - if value.starts_with('$') && !value.contains(' ') { - // Remove the $ - value.remove(0); + // Shadow to init an empty one if required + if let Some(repo) = repo { + repo.runtimes.retain(|r| r != runtime); + }; + } - match env::var(&value) { - Ok(env_value) => *value = env_value, - Err(_) => *value = String::new(), - } - } - } + /// Write the current configuration into the `.wws.toml` file. It will + /// store it in the project root folder + pub fn save(&self, project_root: &Path) -> Result<()> { + let contents = toml::to_string_pretty(self)?; - Ok(options) - } - None => Ok(HashMap::new()), - }, - Err(err) => Err(err), + fs::write(Self::config_path(project_root), contents) + .map_err(|_| anyhow!("Error saving the .wws.toml file")) + } + + /// Retrieve the configuration path from the project root + fn config_path(project_root: &Path) -> PathBuf { + project_root.join(CONFIG_FILENAME) } } + +#[derive(Deserialize, Serialize)] +pub struct ConfigRepository { + /// Local name to identify the repository. It avoids collisions when installing + /// language runtimes + name: String, + /// Installed runtimes + runtimes: Vec, +} diff --git a/src/router/files.rs b/src/router/files.rs index bd4c9356..b5d9458f 100644 --- a/src/router/files.rs +++ b/src/router/files.rs @@ -1,6 +1,7 @@ // Copyright 2022 VMware, Inc. // SPDX-License-Identifier: Apache-2.0 +use crate::store::STORE_FOLDER; use std::ffi::OsStr; use std::path::{Component, Path, PathBuf}; use wax::{Glob, WalkEntry}; @@ -32,7 +33,12 @@ impl Files { glob.walk(&self.root) .filter_map(|el| match el { - Ok(entry) if !self.is_in_public_folder(entry.path()) => Some(entry), + Ok(entry) + if !self.is_in_public_folder(entry.path()) + && !self.is_in_wws_folder(entry.path()) => + { + Some(entry) + } _ => None, }) .collect() @@ -51,6 +57,14 @@ impl Files { _ => false, }) } + + /// Checks if the given filepath is inside the ".wws" special folder. + fn is_in_wws_folder(&self, path: &Path) -> bool { + path.components().any(|c| match c { + Component::Normal(os_str) => os_str == OsStr::new(STORE_FOLDER), + _ => false, + }) + } } #[cfg(test)] diff --git a/src/router/route.rs b/src/router/route.rs index 3bc1c0d9..feb8b49f 100644 --- a/src/router/route.rs +++ b/src/router/route.rs @@ -1,7 +1,7 @@ // Copyright 2022 VMware, Inc. // SPDX-License-Identifier: Apache-2.0 -use crate::{config::Config, workers::Worker}; +use crate::{workers::config::Config, workers::Worker}; use lazy_static::lazy_static; use regex::Regex; use std::{ diff --git a/src/runtimes/metadata.rs b/src/runtimes/metadata.rs index 1463a5ca..df140e2a 100644 --- a/src/runtimes/metadata.rs +++ b/src/runtimes/metadata.rs @@ -81,6 +81,15 @@ pub struct Runtime { pub template: Option, } +/// Implement comparison by checking the name and version of a given repository. +/// For now, we will rely on this simple comparison as a repository shouldn't +/// include two runtimes with the same name and version +impl PartialEq for Runtime { + fn eq(&self, other: &Self) -> bool { + self.name == other.name && self.version == other.version + } +} + /// Define the status of a runtime in a target repository #[derive(Deserialize, Serialize, Clone)] #[serde(rename_all = "lowercase")] diff --git a/src/store.rs b/src/store.rs index 363e10aa..7eeb18df 100644 --- a/src/store.rs +++ b/src/store.rs @@ -19,7 +19,7 @@ use std::{ /// to mount a folder with the source code. To avoid moun†ing /// a folder that may include multiple files, it stores in /// .wws/js/XXX/index.js the worker file. -const STORE_FOLDER: &str = ".wws"; +pub const STORE_FOLDER: &str = ".wws"; /// Struct to initialize, create and interact with files inside /// the store. All paths are considered &[&str] to ensure we diff --git a/src/workers/config.rs b/src/workers/config.rs new file mode 100644 index 00000000..4b252268 --- /dev/null +++ b/src/workers/config.rs @@ -0,0 +1,109 @@ +// Copyright 2022 VMware, Inc. +// SPDX-License-Identifier: Apache-2.0 + +use crate::data::kv::KVConfigData; +use anyhow::{anyhow, Result}; +use serde::{Deserialize, Deserializer}; +use std::collections::HashMap; +use std::path::PathBuf; +use std::{env, fs}; +use toml::from_str; + +/// Workers configuration. These files are optional when no configuration change is required. +#[derive(Deserialize, Clone)] +pub struct Config { + /// Worker name. For logging purposes + pub name: Option, + /// Mandatory version of the file + pub version: String, + /// Optional data configuration + pub data: Option, + /// Optional environment configuration + #[serde(deserialize_with = "read_environment_variables", default)] + pub vars: HashMap, +} + +/// Configure a data plugin for the worker +#[derive(Deserialize, Clone)] +pub struct ConfigData { + /// Creates a Key/Value store associated to the given worker + pub kv: Option, +} + +impl Config { + /// Try to read the configuration from a TOML file. The path contains the local path + /// to the worker configuration. The file should use the same name as the worker, + /// with the .toml extension + /// + /// # Examples + /// + /// ``` + /// name = "todos" + /// version = "1" + /// + /// [data] + /// + /// [data.kv] + /// namespace = "todos" + /// ``` + pub fn try_from_file(path: PathBuf) -> Result { + let contents = fs::read_to_string(&path)?; + + let try_config: Result = from_str(&contents); + + match try_config { + Ok(c) => Ok(c), + Err(err) => Err(anyhow!( + "Error reading the configuration file at {}: {}", + &path.to_str().unwrap_or("?"), + err + )), + } + } + + /// Returns a data Key/Value configuration if available + pub fn data_kv_config(&self) -> Option<&KVConfigData> { + self.data.as_ref()?.kv.as_ref() + } + + /// Returns the data Key/Value namespace if available + pub fn data_kv_namespace(&self) -> Option { + Some(self.data_kv_config()?.namespace.clone()) + } +} + +/// Deserialize the HashMap of variables. By default, this +/// function won't modify the K or the V of the HashMap. If +/// V starts with $, its value will be read from the server +/// environment variables +fn read_environment_variables<'de, D>(deserializer: D) -> Result, D::Error> +where + D: Deserializer<'de>, +{ + let result: Result>, D::Error> = + Deserialize::deserialize(deserializer); + + match result { + Ok(value) => match value { + Some(mut options) => { + for (_, value) in options.iter_mut() { + // Read the value from the environment variables if available. + // If not, it will default to an empty string + if value.starts_with('$') && !value.contains(' ') { + // Remove the $ + value.remove(0); + + match env::var(&value) { + Ok(env_value) => *value = env_value, + Err(_) => *value = String::new(), + } + } + } + + Ok(options) + } + None => Ok(HashMap::new()), + }, + Err(err) => Err(err), + } +} diff --git a/src/workers/mod.rs b/src/workers/mod.rs index 2d2f3216..f9193800 100644 --- a/src/workers/mod.rs +++ b/src/workers/mod.rs @@ -1,6 +1,7 @@ // Copyright 2022 VMware, Inc. // SPDX-License-Identifier: Apache-2.0 +pub mod config; pub mod wasm_io; mod worker;