Skip to content

Commit

Permalink
Merge pull request #39 from MAIF/product_templates
Browse files Browse the repository at this point in the history
Product templates
  • Loading branch information
Zwiterrion authored Jun 25, 2024
2 parents b5f5879 + 0f1b49d commit 6fbfc8d
Show file tree
Hide file tree
Showing 187 changed files with 3,849 additions and 84 deletions.
35 changes: 33 additions & 2 deletions cli/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@ with wasm modules:
| `wasmo config set` | path | server | clientId | clientSecret | Globally configure the CLI with the path where the configuration file will be stored and the server to reach during the build. These parameters are optional and can be passed when running the build command. |
| `wasmo config get` | | Get the configuration from the configured path file or from `$HOME/.wasmo` |
| `wasmo config reset` | | Clean configuration and reset to default settings. The default file path configuration will be `$HOME/.wasmo` ||
| `wasmo init` | template | name | path | Initialize a WASM plugin to the specific path. You can choose between many templates, javascript/typescript (js/ts), Open Policy Agent (opa), Rust or Golang (go). |
| `wasmo init` | language | product | product_template | template | name | path | Initialize a WASM plugin to the specific path. You can choose between many templates, javascript/typescript (js/ts), Open Policy Agent (opa), Rust or Golang (go). |
| `wasmo build` | path | host | server | clientId | clientSecret | Build the plugin by sending the contents to the remote or local Wasmo server. As soon as the build is complete, the WASM binary is donwloaded and saved in the plugin folder. |

# Quick start
Expand All @@ -63,7 +63,38 @@ wasmo build --host=OneShotDocker --path=my-first-plugin

Then open the content of your `my-first-plugin` folder. You should find the generated WASM binary named `my-first-plugin-1.0.0.wasm`.

## Selecting a template
## Specifying language, product and product_template to create a plugin

With the newer version, you should create a plugin by specifying the language to be used, the target product, and the template.

```
wasmo init --name=my-first-plugin --language=js --product=otoroshi --product_template=ACCESS_CONTROL
```

The product template parameter accepts multiple values:
- REQUEST_TRANSFORMER: Transform the content of the request with a wasm plugin
- RESPONSE_TRANSFORMER: Transform the content of a response with a wasm plugin
- ACCESS_CONTROL: Delegate route access to a wasm plugin
- BACKEND: This plugin can be used to use a wasm plugin as backend
- ROUTE_MATCHER: This plugin can be used to use a wasm plugin as route matcher
- SINK: Handle unmatched requests with a wasm plugin
- PRE_ROUTE: This plugin can be used to use a wasm plugin as in pre-route phase

You can also create a Izanami plugin

```
wasmo init --name=my-first-plugin --language=js --product=izanami
```

For the moment, Izanami doesn't provide any templates.

If you want to start from scratch, without targeting any products

```
wasmo init --name=my-first-plugin --language=js
```

## Or selecting a template

You can now optionally start a new plugin from a template by appending `--template=[template-name]` to the creation command.

Expand Down
228 changes: 192 additions & 36 deletions cli/src/bin.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,12 +13,10 @@ use core::panic;
use hyper::{Body, Client, Method, Request};
use serde::Deserialize;
use std::{
collections::HashMap,
fs::{self, File},
io::Write,
path::{PathBuf, Path}, str::FromStr,
collections::HashMap, fs::{self, File}, io::Write, path::{Path, PathBuf}, str::FromStr
};


use dirs;

const WASMO_SERVER: &str = "WASMO_SERVER";
Expand All @@ -43,7 +41,73 @@ const OTOROSHI_ZIP_JS: &[u8] = include_bytes!("../templates/otoroshi/js.zip");
const OTOROSHI_ZIP_OPA: &[u8] = include_bytes!("../templates/otoroshi/opa.zip");
const OTOROSHI_ZIP_RUST: &[u8] = include_bytes!("../templates/otoroshi/rust.zip");
const OTOROSHI_ZIP_TS: &[u8] = include_bytes!("../templates/otoroshi/ts.zip");


use lazy_static::lazy_static;

lazy_static! {
static ref OTOROSHI_WASM_TEMPLATES_ZIPNAME: HashMap<&'static str, &'static str> = {
let mut zip_names: HashMap<&'static str, &'static str> = HashMap::new();
zip_names.insert("ACCESS_CONTROL", "otoroshi_wasm_access_control");
zip_names.insert("BACKEND", "otoroshi_wasm_backend");
zip_names.insert("PRE_ROUTE", "otoroshi_wasm_pre_route");
zip_names.insert("REQUEST_TRANSFORMER", "otoroshi_wasm_request_transformer");
zip_names.insert("RESPONSE_TRANSFORMER", "otoroshi_wasm_response_transformer");
zip_names.insert("ROUTE_MATCHER", "otoroshi_wasm_route_matcher");
zip_names.insert("SINK", "otoroshi_wasm_sink");

zip_names
};
static ref OTOROSHI_WASM_TEMPLATES: HashMap<&'static str, HashMap<&'static str, &'static[u8]>> = {
let mut m: HashMap<&'static str, HashMap<&'static str, &'static[u8]>> = HashMap::new();

let mut js_plugins: HashMap<&'static str, &'static[u8]> = HashMap::new();
js_plugins.insert("ACCESS_CONTROL", include_bytes!("../templates/otoroshi/languages/js/otoroshi_wasm_access_control.zip"));
js_plugins.insert("BACKEND", include_bytes!("../templates/otoroshi/languages/js/otoroshi_wasm_backend.zip"));
js_plugins.insert("PRE_ROUTE", include_bytes!("../templates/otoroshi/languages/js/otoroshi_wasm_pre_route.zip"));
js_plugins.insert("REQUEST_TRANSFORMER", include_bytes!("../templates/otoroshi/languages/js/otoroshi_wasm_request_transformer.zip"));
js_plugins.insert("RESPONSE_TRANSFORMER", include_bytes!("../templates/otoroshi/languages/js/otoroshi_wasm_response_transformer.zip"));
js_plugins.insert("ROUTE_MATCHER", include_bytes!("../templates/otoroshi/languages/js/otoroshi_wasm_route_matcher.zip"));
js_plugins.insert("SINK", include_bytes!("../templates/otoroshi/languages/js/otoroshi_wasm_sink.zip"));
js_plugins.insert("EMPTY", include_bytes!("../templates/otoroshi/js.zip"));

let mut go_plugins: HashMap<&'static str, &'static[u8]> = HashMap::new();
go_plugins.insert("ACCESS_CONTROL", include_bytes!("../templates/otoroshi/languages/go/otoroshi_wasm_access_control.zip"));
go_plugins.insert("BACKEND", include_bytes!("../templates/otoroshi/languages/go/otoroshi_wasm_backend.zip"));
go_plugins.insert("PRE_ROUTE", include_bytes!("../templates/otoroshi/languages/go/otoroshi_wasm_pre_route.zip"));
go_plugins.insert("REQUEST_TRANSFORMER", include_bytes!("../templates/otoroshi/languages/go/otoroshi_wasm_request_transformer.zip"));
go_plugins.insert("RESPONSE_TRANSFORMER", include_bytes!("../templates/otoroshi/languages/go/otoroshi_wasm_response_transformer.zip"));
go_plugins.insert("ROUTE_MATCHER", include_bytes!("../templates/otoroshi/languages/go/otoroshi_wasm_route_matcher.zip"));
go_plugins.insert("SINK", include_bytes!("../templates/otoroshi/languages/go/otoroshi_wasm_sink.zip"));
go_plugins.insert("EMPTY", include_bytes!("../templates/otoroshi/go.zip"));

let mut ts_plugins: HashMap<&'static str, &'static[u8]> = HashMap::new();
ts_plugins.insert("ACCESS_CONTROL", include_bytes!("../templates/otoroshi/languages/ts/otoroshi_wasm_access_control.zip"));
ts_plugins.insert("BACKEND", include_bytes!("../templates/otoroshi/languages/ts/otoroshi_wasm_backend.zip"));
ts_plugins.insert("PRE_ROUTE", include_bytes!("../templates/otoroshi/languages/ts/otoroshi_wasm_pre_route.zip"));
ts_plugins.insert("REQUEST_TRANSFORMER", include_bytes!("../templates/otoroshi/languages/ts/otoroshi_wasm_request_transformer.zip"));
ts_plugins.insert("RESPONSE_TRANSFORMER", include_bytes!("../templates/otoroshi/languages/ts/otoroshi_wasm_response_transformer.zip"));
ts_plugins.insert("ROUTE_MATCHER", include_bytes!("../templates/otoroshi/languages/ts/otoroshi_wasm_route_matcher.zip"));
ts_plugins.insert("SINK", include_bytes!("../templates/otoroshi/languages/ts/otoroshi_wasm_sink.zip"));
ts_plugins.insert("EMPTY", include_bytes!("../templates/otoroshi/ts.zip"));

let mut rust_plugins: HashMap<&'static str, &'static[u8]> = HashMap::new();
rust_plugins.insert("ACCESS_CONTROL", include_bytes!("../templates/otoroshi/languages/rust/otoroshi_wasm_access_control.zip"));
rust_plugins.insert("BACKEND", include_bytes!("../templates/otoroshi/languages/rust/otoroshi_wasm_backend.zip"));
rust_plugins.insert("PRE_ROUTE", include_bytes!("../templates/otoroshi/languages/rust/otoroshi_wasm_pre_route.zip"));
rust_plugins.insert("REQUEST_TRANSFORMER", include_bytes!("../templates/otoroshi/languages/rust/otoroshi_wasm_request_transformer.zip"));
rust_plugins.insert("RESPONSE_TRANSFORMER", include_bytes!("../templates/otoroshi/languages/rust/otoroshi_wasm_response_transformer.zip"));
rust_plugins.insert("ROUTE_MATCHER", include_bytes!("../templates/otoroshi/languages/rust/otoroshi_wasm_route_matcher.zip"));
rust_plugins.insert("SINK", include_bytes!("../templates/otoroshi/languages/rust/otoroshi_wasm_sink.zip"));
rust_plugins.insert("EMPTY", include_bytes!("../templates/otoroshi/rust.zip"));

m.insert("js", js_plugins);
m.insert("go", go_plugins);
m.insert("ts", ts_plugins);
m.insert("rust", rust_plugins);

m
};
}

#[derive(Debug, PartialEq)]
pub enum Host {
Expand Down Expand Up @@ -125,7 +189,41 @@ enum Commands {
],
require_equals = true,
)]
template: String,
template: Option<String>,
#[arg(
value_name = "PRODUCT",
// short = 'p',
long = "product",
value_parser = [
"izanami",
"otoroshi",
"other"
],
)]
product: Option<String>,
/// The product template
#[arg(
value_name = "PRODUCT_TEMPLATE",
// short = 'p',
long = "product_template",
value_parser = [
"ACCESS_CONTROL",
"BACKEND",
"PRE_ROUTE",
"REQUEST_TRANSFORMER",
"RESPONSE_TRANSFORMER",
"ROUTE_MATCHER",
"SINK",
]
)]
product_template: Option<String>,
/// The language
#[arg(
value_name = "LANGUAGE",
// short = 'p',
long = "language"
)]
language: Option<String>,
/// The plugin name
#[arg(
value_name = "NAME",
Expand Down Expand Up @@ -281,35 +379,83 @@ fn update_metadata_file(path: &PathBuf, name: &String, template: &String) -> Was
}
}

fn initialize(template: String, name: String, path: Option<String>) -> WasmoResult<()> {
logger::loading("<yellow>Creating</> plugin ...".to_string());
fn initialize_empty_project(language: String) -> &'static [u8] {
match language.as_str() {
"go" => EMPTY_ZIP_GO,
"js" => EMPTY_ZIP_JS,
"opa" => EMPTY_ZIP_OPA,
"rust" => EMPTY_ZIP_RUST,
"ts" => EMPTY_ZIP_TS,
_ => EMPTY_ZIP_TS
}
}

let manifest_dir = std::env::temp_dir();
fn get_otoroshi_template(language: String, product_template: String) -> &'static [u8] {
OTOROSHI_WASM_TEMPLATES.get(language.to_uppercase().as_str())
.unwrap()
.get(product_template.to_uppercase().as_str()).unwrap()
}

let zip_bytes = match template.as_str() {
"go" => EMPTY_ZIP_GO,
"js" => EMPTY_ZIP_JS,
"opa" => EMPTY_ZIP_OPA,
"rust" => EMPTY_ZIP_RUST,
"ts" => EMPTY_ZIP_TS,

"izanami_go" => IZANAMI_ZIP_GO,
"izanami_js" => IZANAMI_ZIP_JS,
"izanami_opa" => IZANAMI_ZIP_OPA,
"izanami_rust" => IZANAMI_ZIP_RUST,
"izanami_ts" => IZANAMI_ZIP_TS,

"otoroshi_go" => OTOROSHI_ZIP_GO,
"otoroshi_js" => OTOROSHI_ZIP_JS,
"otoroshi_opa" => OTOROSHI_ZIP_OPA,
"otoroshi_rust" => OTOROSHI_ZIP_RUST,
"otoroshi_ts" => OTOROSHI_ZIP_TS,

_ => EMPTY_ZIP_JS
};
fn initialize(
language: Option<String>,
product: Option<String>,
template: Option<String>,
product_template: Option<String>,
name: String,
path: Option<String>) -> WasmoResult<()> {
logger::loading("<yellow>Creating</> plugin ...".to_string());

let language_used = template.replace("izanami_", "").replace("otoroshi_", "");
let zip_bytes = match (language.clone(), product, template.clone(), product_template.clone()) {
(Some(language), None, None, None) => initialize_empty_project(language),
(Some(language), Some(product), None, product_template) => {
match product.as_str() {
"otoroshi" => get_otoroshi_template(language, product_template.unwrap_or("empty".to_string())),
"izanami" => match language.as_str() {
"go" => IZANAMI_ZIP_GO,
"js" => IZANAMI_ZIP_JS,
"opa" => IZANAMI_ZIP_OPA,
"rust" => IZANAMI_ZIP_RUST,
"ts" => IZANAMI_ZIP_TS,
_ => return Err(WasmoError::Raw("Unsupported language".to_string()))
},
_ => return Err(WasmoError::Raw("Only otoroshi or izanami values are allowed as product value".to_string()))
}
},
(None, None, Some(template), None) =>
match template.as_str() {
"go" => EMPTY_ZIP_GO,
"js" => EMPTY_ZIP_JS,
"opa" => EMPTY_ZIP_OPA,
"rust" => EMPTY_ZIP_RUST,
"ts" => EMPTY_ZIP_TS,

"izanami_go" => IZANAMI_ZIP_GO,
"izanami_js" => IZANAMI_ZIP_JS,
"izanami_opa" => IZANAMI_ZIP_OPA,
"izanami_rust" => IZANAMI_ZIP_RUST,
"izanami_ts" => IZANAMI_ZIP_TS,

"otoroshi_go" => OTOROSHI_ZIP_GO,
"otoroshi_js" => OTOROSHI_ZIP_JS,
"otoroshi_opa" => OTOROSHI_ZIP_OPA,
"otoroshi_rust" => OTOROSHI_ZIP_RUST,
"otoroshi_ts" => OTOROSHI_ZIP_TS,

_ => EMPTY_ZIP_JS
},
(_, _, _, _) => return Err(WasmoError::Raw("You should provide language, product, and product_template parameters, or use only the deprecated template parameter.".to_string()))
};

let language_used = product_template.clone()
.map(|product| OTOROSHI_WASM_TEMPLATES_ZIPNAME.get(product.as_str()).unwrap_or(&"FAILED").to_string())
.unwrap_or(language
.unwrap_or(template.map(|template| template.replace("izanami_", "").replace("otoroshi_", "")).unwrap_or("FAILED".to_string())));

if language_used == "FAILED" {
return Err(WasmoError::Raw("Invalid language".to_string()));
}

let manifest_dir = std::env::temp_dir();
let zip_path = Path::new(&manifest_dir).join(format!("{}.zip", &language_used));

if std::path::Path::new(&zip_path).exists() {
Expand All @@ -326,10 +472,17 @@ fn initialize(template: String, name: String, path: Option<String>) -> WasmoResu
};

logger::indent_println("<yellow>Unzipping</> the template ...".to_string());
let zip_action = zip_extensions::read::zip_extract(
&PathBuf::from(zip_path),
&manifest_dir,
);

let zip_action = match zip_extensions::read::zip_extract(
&PathBuf::from(zip_path.clone()),
&PathBuf::from(manifest_dir.clone())
) {
Ok(()) => Ok(()),
Err(_) => zip_extensions::read::zip_extract(
&PathBuf::from(zip_path),
&PathBuf::from(manifest_dir.clone()).join(language_used.clone())
)
};

match zip_action {
Ok(()) => rename_plugin(language_used, name, path),
Expand Down Expand Up @@ -729,9 +882,12 @@ async fn main() {
},
Commands::Init {
template,
language,
product,
product_template,
name,
path,
} => initialize(template, name, path.map(absolute_path)),
} => initialize(language, product, template, product_template, name, path.map(absolute_path)),
Commands::Build {
server,
path,
Expand Down
6 changes: 5 additions & 1 deletion cli/src/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,8 @@ pub enum WasmoError {
FileSystem(String),
NoDockerRunning(String),
DockerContainer(String),
Configuration(String)
Configuration(String),
Raw(String)
}

impl fmt::Display for WasmoError {
Expand All @@ -34,6 +35,9 @@ impl fmt::Display for WasmoError {
},
WasmoError::Configuration(err) => {
write!(f,"something happened with the configuration, {}", &err)
},
WasmoError::Raw(err) => {
write!(f, "{}", &err)
}
}
}
Expand Down
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
const esbuild = require('esbuild')

esbuild
.build({
entryPoints: ['index.js'],
outdir: 'dist',
bundle: true,
sourcemap: true,
minify: false, // might want to use true for production build
format: 'cjs', // needs to be CJS for now
target: ['es2020'] // don't go over es2020 because quickjs doesn't support it
})
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
export function execute() {
let context = JSON.parse(Host.inputString())

if (context.request.headers["foo"] === "bar") {
const out = {
result: true
}
Host.outputString(JSON.stringify(out))
} else {
const error = {
result: false,
error: {
message: "you're not authorized",
status: 401
}
}
Host.outputString(JSON.stringify(error))
}

return 0
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
{
"name": "@@PLUGIN_NAME@@",
"version": "@@PLUGIN_VERSION@@",
"devDependencies": {
"esbuild": "^0.17.9"
}
}
Loading

0 comments on commit 6fbfc8d

Please sign in to comment.