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 and manage the .wws.toml file and its metadata #75

Merged
merged 1 commit into from
Jan 27, 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
3 changes: 2 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -3,4 +3,5 @@ target
!tests/**/*.wasm
examples/*.toml
.DS_Store
.wws
.wws
.wws.toml
173 changes: 86 additions & 87 deletions src/config.rs
Original file line number Diff line number Diff line change
@@ -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<String>,
/// Mandatory version of the file
pub version: String,
/// Optional data configuration
pub data: Option<ConfigData>,
/// Optional environment configuration
#[serde(deserialize_with = "read_environment_variables", default)]
pub vars: HashMap<String, String>,
}
/// 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<KVConfigData>,
/// 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<ConfigRepository>,
}

// 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<Self> {
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<Self> {
let config_path = Self::config_path(project_root);

let try_config: Result<Config, toml::de::Error> = 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<String> {
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<HashMap<String, String>, D::Error>
where
D: Deserializer<'de>,
{
let result: Result<Option<HashMap<String, String>>, 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<Runtime>,
}
16 changes: 15 additions & 1 deletion 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::store::STORE_FOLDER;
use std::ffi::OsStr;
use std::path::{Component, Path, PathBuf};
use wax::{Glob, WalkEntry};
Expand Down Expand Up @@ -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()
Expand All @@ -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)]
Expand Down
2 changes: 1 addition & 1 deletion 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::{config::Config, workers::Worker};
use crate::{workers::config::Config, workers::Worker};
use lazy_static::lazy_static;
use regex::Regex;
use std::{
Expand Down
9 changes: 9 additions & 0 deletions src/runtimes/metadata.rs
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,15 @@ pub struct Runtime {
pub template: Option<RemoteFile>,
}

/// 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")]
Expand Down
2 changes: 1 addition & 1 deletion src/store.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
109 changes: 109 additions & 0 deletions src/workers/config.rs
Original file line number Diff line number Diff line change
@@ -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<String>,
/// Mandatory version of the file
pub version: String,
/// Optional data configuration
pub data: Option<ConfigData>,
/// Optional environment configuration
#[serde(deserialize_with = "read_environment_variables", default)]
pub vars: HashMap<String, String>,
}

/// 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<KVConfigData>,
}

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<Self> {
let contents = fs::read_to_string(&path)?;

let try_config: Result<Config, toml::de::Error> = 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<String> {
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<HashMap<String, String>, D::Error>
where
D: Deserializer<'de>,
{
let result: Result<Option<HashMap<String, String>>, 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),
}
}
1 change: 1 addition & 0 deletions src/workers/mod.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

pub mod config;
pub mod wasm_io;
mod worker;

Expand Down