Skip to content

Commit

Permalink
feat: create and manage the .wws.toml file and its metadata
Browse files Browse the repository at this point in the history
  • Loading branch information
Angelmmiguel committed Jan 27, 2023
1 parent 764a4ad commit e2bcf3f
Show file tree
Hide file tree
Showing 6 changed files with 208 additions and 89 deletions.
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>,
}
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
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

0 comments on commit e2bcf3f

Please sign in to comment.