diff --git a/README.md b/README.md index 7af1386a6..8d7ddbc6d 100644 --- a/README.md +++ b/README.md @@ -143,6 +143,9 @@ There are two types of configuration that `wrangler` uses: global user and per p This key is optional if you are using a workers.dev subdomain and is only required for `publish --release`. - `webpack_config`: This is the path to the webpack configuration file for your worker. This is optional and defaults to `webpack.config.js` + - `kv-namespaces`: Bind kv namespaces to your worker. Must be created in the dashboard before. An array of: + - `binding`: name that will be used to bind the kv-namespace to your script, in JavaScript. + - `id`: Identifer of the namespace (can be found on the Cloudflare dashboard). ## ⚓ Installation diff --git a/src/commands/build/wranglerjs/bundle.rs b/src/commands/build/wranglerjs/bundle.rs index d306ce976..e6b3defed 100644 --- a/src/commands/build/wranglerjs/bundle.rs +++ b/src/commands/build/wranglerjs/bundle.rs @@ -6,12 +6,13 @@ use std::fs::File; use std::io::prelude::*; use std::path::{Path, PathBuf}; +use crate::terminal::message; use log::info; use crate::commands::build::wranglerjs::output::WranglerjsOutput; use crate::settings::binding::Binding; use crate::settings::metadata; -use crate::terminal::message; +use crate::settings::project::KvNamespace; // Directory where we should write the {Bundle}. It represents the built // artifact. @@ -34,7 +35,11 @@ impl Bundle { Bundle { out } } - pub fn write(&self, wranglerjs_output: &WranglerjsOutput) -> Result<(), failure::Error> { + pub fn write( + &self, + wranglerjs_output: &WranglerjsOutput, + kv_namespaces: Vec, + ) -> Result<(), failure::Error> { let bundle_path = Path::new(&self.out); if !bundle_path.exists() { fs::create_dir(bundle_path)?; @@ -52,7 +57,7 @@ impl Bundle { script_file.write_all(script.as_bytes())?; - let metadata = create_metadata(self).expect("could not create metadata"); + let metadata = create_metadata(self, &kv_namespaces).expect("could not create metadata"); let mut metadata_file = File::create(self.metadata_path())?; metadata_file.write_all(metadata.as_bytes())?; @@ -112,13 +117,23 @@ pub fn create_prologue() -> String { } // This metadata describe the bindings on the Worker. -fn create_metadata(bundle: &Bundle) -> Result { +fn create_metadata( + bundle: &Bundle, + kv_namespaces: &Vec, +) -> Result { let mut bindings = vec![]; if bundle.has_wasm() { bindings.push(Binding::new_wasm_module( - bundle.get_wasm_binding(), - bundle.get_wasm_binding(), + bundle.get_wasm_binding(), // name + bundle.get_wasm_binding(), // part + )); + } + + for namespace in kv_namespaces { + bindings.push(Binding::new_kv_namespace( + namespace.binding.clone(), // local_binding + namespace.id.clone(), // namespace_id )); } @@ -154,7 +169,7 @@ mod tests { }; let bundle = Bundle::new_at(out.clone()); - bundle.write(&wranglerjs_output).unwrap(); + bundle.write(&wranglerjs_output, vec![]).unwrap(); assert!(Path::new(&bundle.metadata_path()).exists()); let contents = fs::read_to_string(&bundle.metadata_path()).expect("could not read metadata"); @@ -175,7 +190,7 @@ mod tests { }; let bundle = Bundle::new_at(out.clone()); - bundle.write(&wranglerjs_output).unwrap(); + bundle.write(&wranglerjs_output, vec![]).unwrap(); assert!(Path::new(&bundle.script_path()).exists()); assert!(!Path::new(&bundle.wasm_path()).exists()); @@ -193,7 +208,7 @@ mod tests { }; let bundle = Bundle::new_at(out.clone()); - bundle.write(&wranglerjs_output).unwrap(); + bundle.write(&wranglerjs_output, vec![]).unwrap(); assert!(Path::new(&bundle.wasm_path()).exists()); assert!(bundle.has_wasm()); @@ -211,7 +226,7 @@ mod tests { }; let bundle = Bundle::new_at(out.clone()); - bundle.write(&wranglerjs_output).unwrap(); + bundle.write(&wranglerjs_output, vec![]).unwrap(); assert!(Path::new(&bundle.metadata_path()).exists()); let contents = fs::read_to_string(&bundle.metadata_path()).expect("could not read metadata"); @@ -224,6 +239,32 @@ mod tests { cleanup(out); } + #[test] + fn it_writes_the_bundle_kv_metadata() { + let out = create_temp_dir("it_writes_the_bundle_kv_metadata"); + let wranglerjs_output = WranglerjsOutput { + errors: vec![], + script: "".to_string(), + wasm: None, + dist_to_clean: None, + }; + let bundle = Bundle::new_at(out.clone()); + + let kv_namespaces = vec![KvNamespace { + binding: "binding".to_string(), + id: "id".to_string(), + }]; + + bundle.write(&wranglerjs_output, kv_namespaces).unwrap(); + assert!(Path::new(&bundle.metadata_path()).exists()); + let contents = + fs::read_to_string(&bundle.metadata_path()).expect("could not read metadata"); + + assert_eq!(contents, r#"{"body_part":"script","bindings":[{"type":"kv_namespace","name":"binding","namespace_id":"id"}]}"#); + + cleanup(out); + } + #[test] fn it_has_errors() { let wranglerjs_output = WranglerjsOutput { diff --git a/src/commands/build/wranglerjs/mod.rs b/src/commands/build/wranglerjs/mod.rs index 8e079b24e..eb1cea1aa 100644 --- a/src/commands/build/wranglerjs/mod.rs +++ b/src/commands/build/wranglerjs/mod.rs @@ -45,8 +45,11 @@ pub fn run_build(project: &Project) -> Result<(), failure::Error> { failure::bail!("Webpack returned an error"); } + let kv_namespaces = project.kv_namespaces.clone().unwrap_or(Vec::new()); + let kv_namespaces_len = kv_namespaces.clone().len(); + bundle - .write(&wranglerjs_output) + .write(&wranglerjs_output, kv_namespaces) .expect("could not write bundle to disk"); let mut msg = format!( @@ -56,6 +59,9 @@ pub fn run_build(project: &Project) -> Result<(), failure::Error> { if bundle.has_wasm() { msg = format!("{} and Wasm size is {}", msg, wranglerjs_output.wasm_size()); } + if kv_namespaces_len > 0 { + msg = format!("{} and has {} kv namespaces", msg, kv_namespaces_len); + } message::success(&msg); Ok(()) } else { diff --git a/src/commands/publish/mod.rs b/src/commands/publish/mod.rs index 082286287..796ffdafb 100644 --- a/src/commands/publish/mod.rs +++ b/src/commands/publish/mod.rs @@ -26,7 +26,6 @@ pub fn publish(user: &GlobalUser, project: &Project, release: bool) -> Result<() validate_project(project, release)?; commands::build(&project)?; - create_kv_namespaces(user, &project)?; publish_script(&user, &project, release)?; if release { info!("release mode detected, making a route..."); @@ -43,43 +42,6 @@ pub fn publish(user: &GlobalUser, project: &Project, release: bool) -> Result<() Ok(()) } -pub fn create_kv_namespaces(user: &GlobalUser, project: &Project) -> Result<(), failure::Error> { - let kv_addr = format!( - "https://api.cloudflare.com/client/v4/accounts/{}/storage/kv/namespaces", - project.account_id, - ); - - let client = http::auth_client(user); - - if let Some(namespaces) = &project.kv_namespaces { - for namespace in namespaces { - info!("Attempting to create namespace '{}'", namespace); - - let mut map = HashMap::new(); - map.insert("title", namespace); - - let request = client.post(&kv_addr).json(&map).send(); - - if let Err(error) = request { - // A 400 is returned if the account already owns a namespace with this title. - // - // https://api.cloudflare.com/#workers-kv-namespace-create-a-namespace - match error.status() { - Some(code) if code == 400 => { - info!("Namespace '{}' already exists, continuing.", namespace) - } - _ => { - info!("Error when creating namespace '{}'", namespace); - failure::bail!("⛔ Something went wrong! Error: {}", error) - } - } - } - info!("Namespace '{}' exists now", namespace) - } - } - Ok(()) -} - fn publish_script( user: &GlobalUser, project: &Project, diff --git a/src/settings/binding.rs b/src/settings/binding.rs index e036559cf..5b3de6312 100644 --- a/src/settings/binding.rs +++ b/src/settings/binding.rs @@ -5,10 +5,18 @@ use serde::Serialize; pub enum Binding { #[allow(non_camel_case_types)] wasm_module { name: String, part: String }, + #[allow(non_camel_case_types)] + kv_namespace { name: String, namespace_id: String }, } impl Binding { pub fn new_wasm_module(name: String, part: String) -> Binding { Binding::wasm_module { name, part } } + pub fn new_kv_namespace(local_binding: String, namespace_id: String) -> Binding { + Binding::kv_namespace { + name: local_binding, + namespace_id, + } + } } diff --git a/src/settings/project.rs b/src/settings/project.rs index d3ec908b2..819d0313a 100644 --- a/src/settings/project.rs +++ b/src/settings/project.rs @@ -9,6 +9,14 @@ use log::info; use config::{Config, Environment, File}; use serde::{Deserialize, Serialize}; +#[derive(Deserialize, Serialize, Debug, Clone)] +pub struct KvNamespace { + // name that will be used to bind the JavaScript value to the worker + pub binding: String, + // identifier of the KV namespace + pub id: String, +} + #[derive(Clone, Debug, Deserialize, Serialize)] pub struct Project { pub name: String, @@ -21,7 +29,7 @@ pub struct Project { pub route: Option, pub routes: Option>, #[serde(rename = "kv-namespaces")] - pub kv_namespaces: Option>, + pub kv_namespaces: Option>, } #[derive(Clone, Debug, Deserialize, Serialize)]