From 76a9f0dd23ec0d41817e912a48cbedc38999d515 Mon Sep 17 00:00:00 2001 From: "malonso@cloudflare.com" Date: Mon, 21 Dec 2020 12:39:54 -0600 Subject: [PATCH] support implementing + using durable object namespaces --- src/commands/dev/mod.rs | 12 +- src/commands/kv/mod.rs | 1 + src/commands/publish.rs | 69 ++++-- src/deploy/durable_objects.rs | 353 +++++++++++++++++++++++++++ src/deploy/mod.rs | 31 ++- src/preview/mod.rs | 5 + src/settings/binding.rs | 5 + src/settings/toml/durable_objects.rs | 41 ++++ src/settings/toml/environment.rs | 2 + src/settings/toml/manifest.rs | 36 ++- src/settings/toml/mod.rs | 2 + src/settings/toml/target.rs | 2 + src/sites/mod.rs | 1 + src/upload/form/mod.rs | 6 + src/upload/form/project_assets.rs | 16 +- 15 files changed, 548 insertions(+), 34 deletions(-) create mode 100644 src/deploy/durable_objects.rs create mode 100644 src/settings/toml/durable_objects.rs diff --git a/src/commands/dev/mod.rs b/src/commands/dev/mod.rs index 0750fed6e..04f45d4f1 100644 --- a/src/commands/dev/mod.rs +++ b/src/commands/dev/mod.rs @@ -9,7 +9,7 @@ pub use server_config::Protocol; pub use server_config::ServerConfig; use crate::build::build_target; -use crate::deploy::{DeployTarget, DeploymentSet}; +use crate::deploy::{self, DeployTarget, DeploymentSet}; use crate::settings::global_user::GlobalUser; use crate::settings::toml::Target; use crate::terminal::message::{Message, StdOut}; @@ -18,7 +18,7 @@ use crate::terminal::styles; /// `wrangler dev` starts a server on a dev machine that routes incoming HTTP requests /// to a Cloudflare Workers runtime and returns HTTP responses pub fn dev( - target: Target, + mut target: Target, deployments: DeploymentSet, user: Option, server_config: ServerConfig, @@ -29,6 +29,14 @@ pub fn dev( // before serving requests we must first build the Worker build_target(&target)?; + if let Some(user) = &user { + deploy::pre_upload(user, &mut target, &deployments, true)?; + } else { + failure::bail!( + "Previewing a script that binds to a Durable Object namespace is not supported using unauthenticated preview. Please use wrangler login or wrangler config." + ); + } + let deploy_target = { let valid_targets = deployments .into_iter() diff --git a/src/commands/kv/mod.rs b/src/commands/kv/mod.rs index d3ee322b9..58da0d3e4 100644 --- a/src/commands/kv/mod.rs +++ b/src/commands/kv/mod.rs @@ -119,6 +119,7 @@ mod tests { binding: "KV".to_string(), }, ], + used_durable_object_namespaces: vec![], name: "test-target".to_string(), target_type: TargetType::Webpack, webpack_config: None, diff --git a/src/commands/publish.rs b/src/commands/publish.rs index 920b64db8..47cd534e1 100644 --- a/src/commands/publish.rs +++ b/src/commands/publish.rs @@ -21,6 +21,7 @@ pub struct PublishOutput { pub name: String, pub urls: Vec, pub schedules: Vec, + pub durable_object_namespaces: Vec, } pub fn publish( @@ -31,33 +32,9 @@ pub fn publish( ) -> Result<(), failure::Error> { validate_target_required_fields_present(target)?; - let deploy = |target: &Target| match deploy::worker(&user, &deployments) { - Ok(deploy::DeployResults { urls, schedules }) => { - let result_msg = match (urls.as_slice(), schedules.as_slice()) { - ([], []) => "Successfully published your script".to_owned(), - ([], schedules) => format!( - "Successfully published your script with this schedule\n {}", - schedules.join("\n ") - ), - (urls, []) => format!( - "Successfully published your script to\n {}", - urls.join("\n ") - ), - (urls, schedules) => format!( - "Successfully published your script to\n {}\nwith this schedule\n {}", - urls.join("\n "), - schedules.join("\n ") - ), - }; - StdErr::success(&result_msg); - if out == Output::Json { - StdOut::as_json(&PublishOutput { - success: true, - name: target.name.clone(), - urls, - schedules, - }); - } + let deploy = |target: &Target| match deploy::deploy(&user, &deployments) { + Ok(results) => { + build_output_message(results, target.name.clone(), out); Ok(()) } Err(e) => Err(e), @@ -110,6 +87,10 @@ pub fn publish( pb.finish_with_message("Done Uploading"); } + // Workers Sites are currently limited to type = "webpack", and therefore service-worker format + // So there's no point in performing the full set of pre_upload tasks for Durable Objects. + deploy::pre_upload(user, target, &deployments, true)?; + let upload_client = http::featured_legacy_auth_client(user, Feature::Sites); // Next, upload and deploy the worker with the updated asset_manifest @@ -146,6 +127,7 @@ pub fn publish( } else { let upload_client = http::legacy_auth_client(user); + deploy::pre_upload(user, target, &deployments, false)?; upload::script(&upload_client, &target, None)?; deploy(target)?; } @@ -153,6 +135,39 @@ pub fn publish( Ok(()) } +fn build_output_message(deploy_results: deploy::DeployResults, target_name: String, out: Output) { + let deploy::DeployResults { + urls, + schedules, + durable_object_namespaces, + } = deploy_results; + + let mut msg = "Successfully published your script ".to_owned(); + if !urls.is_empty() { + msg.push_str(&format!("to\n {}\n", urls.join("\n "))); + } + if !schedules.is_empty() { + msg.push_str(&format!("with this schedule\n {}\n", schedules.join("\n "))); + } + if !durable_object_namespaces.is_empty() { + msg.push_str(&format!( + "implementing these Durable Object namespaces\n {}\n", + durable_object_namespaces.join("\n ") + )); + } + + StdErr::success(&msg); + if out == Output::Json { + StdOut::as_json(&PublishOutput { + success: true, + name: target_name, + urls, + schedules, + durable_object_namespaces, + }); + } +} + // We don't want folks setting their bucket to the top level directory, // which is where wrangler commands are always called from. pub fn validate_bucket_location(bucket: &PathBuf) -> Result<(), failure::Error> { diff --git a/src/deploy/durable_objects.rs b/src/deploy/durable_objects.rs new file mode 100644 index 000000000..81e5fdc7c --- /dev/null +++ b/src/deploy/durable_objects.rs @@ -0,0 +1,353 @@ +use std::{cell::RefCell, collections::HashMap}; + +use crate::settings::toml::DurableObjects; +use crate::settings::{global_user::GlobalUser, toml::ScriptFormat}; +use crate::{http, settings::toml::Target}; + +use reqwest::blocking::Client; +use serde::{Deserialize, Serialize}; + +/* +Durable Objects have a fun chicken and egg problem that makes them rather tricky to deploy, +in the case of scripts that both implement and bind to a namespace... you must have a namespace +id in order to create the binding to upload the script, but you need the script to create the +namespace. What we did to get around this, was allow creating a namespace without a script, +that just returns an error when you try to access it. + +When you use/implement a DO in the same script, we'll first initialize the namespace (if it does +not exist already) with no script/class so we have an ID to use for the binding. After the script is +uploaded, the namespace is finalized with the script and class filled in. + +There's only a single DurableObjectsTarget even if there are multiple used durable object +namespaces, or multiple implemented durable object namespaces. This is because we must handle the +(quite common) special case where a single script both implements and uses a durable object +namespace, and it's much easier to handle the initialization/finalization dance if all of the data +necessary to do that is available in one struct. +*/ + +#[derive(Clone, Debug, PartialEq)] +pub struct DurableObjectsTarget { + pub account_id: String, + pub script_name: String, + pub durable_objects: DurableObjects, + existing_namespaces: RefCell>, +} + +#[derive(Serialize, Debug)] +struct DurableObjectCreateNSRequest { + pub name: String, + #[serde(flatten)] + pub implementation: Option, +} + +#[derive(Serialize, Debug)] +struct DurableObjectNSImpl { + pub script: String, + pub class: String, +} + +impl DurableObjectsTarget { + pub fn new(account_id: String, script_name: String, durable_objects: DurableObjects) -> Self { + Self { + account_id, + script_name, + durable_objects, + existing_namespaces: RefCell::new(HashMap::new()), + } + } + + pub fn pre_upload(&self, user: &GlobalUser, target: &mut Target) -> Result<(), failure::Error> { + if self + .durable_objects + .implements + .as_ref() + .map_or(false, |i| !i.is_empty()) + { + match &target.build { + Some(build) if build.upload_format != ScriptFormat::Modules => { + failure::bail!("Implementing a Durable Object namespace requires that the upload_format in your [build] section is set to \"modules\". Please update your wrangler.toml") + }, + None => failure::bail!("Implementing a Durable Object namespace requires a [build] section to be present in your wrangler.toml with the upload_format set to \"modules\". Please update your wrangler.toml"), + _ => {} + } + } + + self.get_existing_namespaces(user)?; + self.init_self_referential_namespaces(user)?; + self.hydrate_target_with_ns_ids(target)?; + Ok(()) + } + + pub fn only_hydrate( + &self, + user: &GlobalUser, + target: &mut Target, + ) -> Result<(), failure::Error> { + self.get_existing_namespaces(user)?; + self.hydrate_target_with_ns_ids(target)?; + Ok(()) + } + + pub fn deploy(&self, user: &GlobalUser) -> Result, failure::Error> { + let existing_namespaces = self.existing_namespaces.borrow(); + let new_namespaces = self + .durable_objects + .implements + .iter() + .flatten() + .filter(|ns| !existing_namespaces.contains_key(&ns.namespace_name)); + + let update_namespaces = self + .durable_objects + .implements + .iter() + .flatten() + .filter_map(|ns| { + if let Some(current) = existing_namespaces.get(&ns.namespace_name) { + if current.script.as_ref() != Some(&self.script_name) + || current.class.as_ref() != Some(&ns.class_name) + { + Some((current.id.clone(), ns)) + } else { + None + } + } else { + None + } + }); + + let mut updated_namespaces = vec![]; + + for ns in new_namespaces { + updated_namespaces.push(ns.namespace_name.clone()); + create_ns( + &DurableObjectCreateNSRequest { + name: ns.namespace_name.clone(), + implementation: Some(DurableObjectNSImpl { + script: self.script_name.clone(), + class: ns.class_name.clone(), + }), + }, + &self.account_id, + user, + )?; + } + + for (id, ns) in update_namespaces { + updated_namespaces.push(ns.namespace_name.clone()); + update_ns( + &id, + &DurableObjectNSImpl { + script: self.script_name.clone(), + class: ns.class_name.clone(), + }, + &self.account_id, + user, + )?; + } + + Ok(updated_namespaces) + } + + fn get_existing_namespaces(&self, user: &GlobalUser) -> Result<(), failure::Error> { + log::info!("getting existing Durable Objects"); + let client = http::legacy_auth_client(user); + self.existing_namespaces + .replace(list_namespaces_by_name_to_id(&client, &self.account_id)?); + Ok(()) + } + + fn init_self_referential_namespaces(&self, user: &GlobalUser) -> Result<(), failure::Error> { + log::info!("initializing self-referential namespaces"); + let implemented_namespace_names = self + .durable_objects + .uses + .iter() + .flatten() + .filter_map(|ns| ns.namespace_name.as_ref()) + .collect::>(); + + let existing_namespace_names = { + let existing_namespaces = self.existing_namespaces.borrow(); + existing_namespaces + .keys() + .map(|s| s.to_owned()) + .collect::>() + }; + + let new_self_referential_namespace_names = + self.durable_objects.uses.iter().flatten().filter_map(|ns| { + ns.namespace_name.as_ref().and_then(|name| { + if implemented_namespace_names.contains(&name) + && !existing_namespace_names.contains(&name) + { + Some(name) + } else { + None + } + }) + }); + + for name in new_self_referential_namespace_names { + log::info!("creating error namespace {}", name); + let new_ns = create_ns( + &DurableObjectCreateNSRequest { + name: name.clone(), + implementation: None, + }, + &self.account_id, + user, + )?; + self.existing_namespaces + .borrow_mut() + .insert(new_ns.name.clone(), new_ns); + } + + Ok(()) + } + + fn hydrate_target_with_ns_ids(&self, target: &mut Target) -> Result<(), failure::Error> { + for ns in target.used_durable_object_namespaces.iter_mut() { + if let Some(name) = &ns.namespace_name { + if ns.namespace_id.is_none() { + ns.namespace_id = self + .existing_namespaces + .borrow() + .get(name) + .cloned() + .map(|ns| ns.id); + if ns.namespace_id.is_none() { + failure::bail!(format!( + "Durable Object namespace with name {} was not found in your account, please check that the name is correct and that you have created the namespace.", + name, + )) + } + } + } + } + Ok(()) + } +} + +fn create_ns( + body: &DurableObjectCreateNSRequest, + account_id: &str, + user: &GlobalUser, +) -> Result { + let durable_object_namespace_addr = format!( + "https://api.cloudflare.com/client/v4/accounts/{}/workers/durable_objects/namespaces", + account_id + ); + + let client = http::legacy_auth_client(user); + let res = client + .post(&durable_object_namespace_addr) + .header("Content-Type", "application/json") + .body(serde_json::to_string(body)?) + .send()?; + + if !res.status().is_success() { + failure::bail!( + "Failed to create a Durable Object namespace! Status: {}, Details {}, Body: {:?}", + res.status(), + res.text()?, + body + ) + } + + match res.json::()?.result { + Some(result) => Ok(result), + None => Err(failure::err_msg( + "Durable Object not returned from create call despite success status", + )), + } +} + +fn update_ns( + namespace_id: &str, + body: &DurableObjectNSImpl, + account_id: &str, + user: &GlobalUser, +) -> Result<(), failure::Error> { + let durable_object_namespace_addr = format!( + "https://api.cloudflare.com/client/v4/accounts/{}/workers/durable_objects/namespaces/{}", + account_id, namespace_id + ); + + let client = http::legacy_auth_client(user); + let res = client + .put(&durable_object_namespace_addr) + .header("Content-Type", "application/json") + .body(serde_json::to_string(body)?) + .send()?; + + if !res.status().is_success() { + failure::bail!( + "Failed to update a Durable Object namespace! Status: {}, Details {}, Body: {:?}", + res.status(), + res.text()?, + body + ) + } + + Ok(()) +} + +#[derive(Serialize, Clone, Deserialize, Debug, PartialEq)] +struct ApiDurableObjectNSResponse { + pub name: String, + pub id: String, + pub script: Option, + pub class: Option, +} + +#[derive(Deserialize)] +struct ApiSingleDurableObjectNSResponse { + pub result: Option, +} + +#[derive(Deserialize)] +struct ApiListDurableObjectNSResponse { + pub result: Option>, +} + +fn list_namespaces_by_name_to_id( + client: &Client, + account_id: &str, +) -> Result, failure::Error> { + let mut map = HashMap::new(); + let namespaces = list_namespaces(client, account_id)?; + for namespace in namespaces { + map.insert(namespace.name.clone(), namespace); + } + + Ok(map) +} + +fn list_namespaces( + client: &Client, + account_id: &str, +) -> Result, failure::Error> { + let list_addr = format!( + "https://api.cloudflare.com/client/v4/accounts/{}/workers/durable_objects/namespaces", + account_id, + ); + + let res = client + .get(&list_addr) + .header("Content-type", "application/json") + .send()?; + + if !res.status().is_success() { + failure::bail!( + "Failed to list Durable Object namespaces! Status: {}, Details {}", + res.status(), + res.text()? + ) + } + + Ok(res + .json::()? + .result + .unwrap_or_default()) +} diff --git a/src/deploy/mod.rs b/src/deploy/mod.rs index fa3d4d07a..70dab0106 100644 --- a/src/deploy/mod.rs +++ b/src/deploy/mod.rs @@ -1,12 +1,14 @@ +mod durable_objects; mod schedule; mod zoned; mod zoneless; +pub use durable_objects::DurableObjectsTarget; pub use schedule::ScheduleTarget; pub use zoned::ZonedTarget; pub use zoneless::ZonelessTarget; -use crate::settings::global_user::GlobalUser; +use crate::settings::{global_user::GlobalUser, toml::Target}; /// A set of deploy targets. pub type DeploymentSet = Vec; @@ -16,9 +18,29 @@ pub enum DeployTarget { Zoned(ZonedTarget), Zoneless(ZonelessTarget), Schedule(ScheduleTarget), + DurableObjects(DurableObjectsTarget), } -pub fn worker( +pub fn pre_upload( + user: &GlobalUser, + target: &mut Target, + deploy_targets: &[DeployTarget], + only_hydrate: bool, +) -> Result<(), failure::Error> { + for deploy_target in deploy_targets { + if let DeployTarget::DurableObjects(durable_objects) = deploy_target { + if only_hydrate { + durable_objects.only_hydrate(user, target)?; + } else { + durable_objects.pre_upload(user, target)?; + } + } + } + + Ok(()) +} + +pub fn deploy( user: &GlobalUser, deploy_targets: &[DeployTarget], ) -> Result { @@ -37,6 +59,10 @@ pub fn worker( let schedules = schedule.deploy(user)?; results.schedules.extend(schedules); } + DeployTarget::DurableObjects(durable_objects) => { + let namespaces = durable_objects.deploy(user)?; + results.durable_object_namespaces.extend(namespaces); + } } } @@ -47,4 +73,5 @@ pub fn worker( pub struct DeployResults { pub urls: Vec, pub schedules: Vec, + pub durable_object_namespaces: Vec, } diff --git a/src/preview/mod.rs b/src/preview/mod.rs index b3111f6ea..257119921 100644 --- a/src/preview/mod.rs +++ b/src/preview/mod.rs @@ -36,6 +36,11 @@ pub fn preview( failure::bail!("wrangler preview does not support previewing modules scripts. Please use wrangler dev instead."); } } + + if !target.used_durable_object_namespaces.is_empty() { + failure::bail!("wrangler preview does not support previewing scripts that bind to durable object namespaces. Please use wrangler dev instead."); + } + build_target(&target)?; let sites_preview: bool = target.site.is_some(); diff --git a/src/settings/binding.rs b/src/settings/binding.rs index 8834003f7..d3012c0bd 100644 --- a/src/settings/binding.rs +++ b/src/settings/binding.rs @@ -6,6 +6,7 @@ use serde::Serialize; pub enum Binding { WasmModule { name: String, part: String }, KvNamespace { name: String, namespace_id: String }, + DurableObjectNamespace { name: String, namespace_id: String }, TextBlob { name: String, part: String }, PlainText { name: String, text: String }, } @@ -19,6 +20,10 @@ impl Binding { Binding::KvNamespace { name, namespace_id } } + pub fn new_durable_object_namespace(name: String, namespace_id: String) -> Binding { + Binding::DurableObjectNamespace { name, namespace_id } + } + pub fn new_text_blob(name: String, part: String) -> Binding { Binding::TextBlob { name, part } } diff --git a/src/settings/toml/durable_objects.rs b/src/settings/toml/durable_objects.rs new file mode 100644 index 000000000..c22714dba --- /dev/null +++ b/src/settings/toml/durable_objects.rs @@ -0,0 +1,41 @@ +use serde::{Deserialize, Serialize}; + +use crate::settings::binding::Binding; + +#[derive(Clone, Debug, Default, Deserialize, PartialEq, Serialize)] +pub struct DurableObjects { + pub uses: Option>, + pub implements: Option>, +} + +// TODO(now): Are there reasonable defaults we can come up with for namespace_name/binding? +// The bash script uses - for the namespace name, +// and for the binding. + +#[derive(Clone, Debug, Default, Deserialize, PartialEq, Serialize)] +pub struct DurableObjectNamespace { + pub binding: String, + pub namespace_name: Option, + pub namespace_id: Option, +} + +impl DurableObjectNamespace { + pub fn binding(&self) -> Result { + match &self.namespace_id { + Some(namespace_id) => Ok(Binding::new_durable_object_namespace( + self.binding.clone(), + namespace_id.clone(), + )), + None => Err(failure::err_msg(format!( + "id not found or provided for durable object namespace bound to {}", + self.binding + ))), + } + } +} + +#[derive(Clone, Debug, Default, Deserialize, PartialEq, Serialize)] +pub struct DurableObjectNamespaceImpl { + pub namespace_name: String, + pub class_name: String, +} diff --git a/src/settings/toml/environment.rs b/src/settings/toml/environment.rs index 1f85a6f11..487190598 100644 --- a/src/settings/toml/environment.rs +++ b/src/settings/toml/environment.rs @@ -5,6 +5,7 @@ use serde::{Deserialize, Serialize}; use serde_with::rust::string_empty_as_none; use crate::settings::toml::builder::Builder; +use crate::settings::toml::durable_objects::DurableObjects; use crate::settings::toml::kv_namespace::ConfigKvNamespace; use crate::settings::toml::route::RouteConfig; use crate::settings::toml::site::Site; @@ -30,6 +31,7 @@ pub struct Environment { pub vars: Option>, pub text_blobs: Option>, pub triggers: Option, + pub durable_objects: Option, } impl Environment { diff --git a/src/settings/toml/manifest.rs b/src/settings/toml/manifest.rs index 924c6c37e..94a4afeaa 100644 --- a/src/settings/toml/manifest.rs +++ b/src/settings/toml/manifest.rs @@ -13,6 +13,7 @@ use crate::commands::{validate_worker_name, DEFAULT_CONFIG_PATH}; use crate::deploy::{self, DeployTarget, DeploymentSet}; use crate::settings::toml::builder::Builder; use crate::settings::toml::dev::Dev; +use crate::settings::toml::durable_objects::DurableObjects; use crate::settings::toml::environment::Environment; use crate::settings::toml::kv_namespace::{ConfigKvNamespace, KvNamespace}; use crate::settings::toml::route::RouteConfig; @@ -53,6 +54,7 @@ pub struct Manifest { pub vars: Option>, pub text_blobs: Option>, pub triggers: Option, + pub durable_objects: Option, } impl Manifest { @@ -243,6 +245,24 @@ impl Manifest { deployments.push(DeployTarget::Schedule(scheduled)); } + let (account_id, durable_objects) = match env { + Some(e) => ( + e.account_id.as_ref().unwrap_or(&self.account_id), + e.durable_objects.as_ref(), + ), + None => (&self.account_id, self.durable_objects.as_ref()), + }; + + if let Some(durable_objects) = durable_objects { + deployments.push(DeployTarget::DurableObjects( + deploy::DurableObjectsTarget::new( + account_id.clone(), + script.clone(), + durable_objects.clone(), + ), + )); + } + if deployments.is_empty() { failure::bail!("No deployments specified!") } @@ -297,8 +317,13 @@ impl Manifest { // to include the name of the environment name: self.name.clone(), // Inherited kv_namespaces: get_namespaces(self.kv_namespaces.clone(), preview)?, // Not inherited - site: self.site.clone(), // Inherited - vars: self.vars.clone(), // Not inherited + used_durable_object_namespaces: self // Not inherited + .durable_objects + .clone() + .and_then(|d| d.uses) + .unwrap_or_default(), + site: self.site.clone(), // Inherited + vars: self.vars.clone(), // Not inherited text_blobs: self.text_blobs.clone(), // Inherited }; @@ -319,6 +344,13 @@ impl Manifest { // don't inherit kv namespaces because it is an anti-pattern to use the same namespaces across multiple environments target.kv_namespaces = get_namespaces(environment.kv_namespaces.clone(), preview)?; + // don't inherit durable object namespaces + target.used_durable_object_namespaces = environment + .durable_objects + .clone() + .and_then(|d| d.uses) + .unwrap_or_default(); + if let Some(site) = &environment.site { target.site = Some(site.clone()); } diff --git a/src/settings/toml/mod.rs b/src/settings/toml/mod.rs index 3008c299a..49c49444e 100644 --- a/src/settings/toml/mod.rs +++ b/src/settings/toml/mod.rs @@ -1,5 +1,6 @@ mod builder; mod dev; +mod durable_objects; mod environment; mod kv_namespace; mod manifest; @@ -11,6 +12,7 @@ mod target_type; mod triggers; pub use builder::Builder; +pub use durable_objects::{DurableObjectNamespace, DurableObjectNamespaceImpl, DurableObjects}; pub use environment::Environment; pub use kv_namespace::{ConfigKvNamespace, KvNamespace}; pub use manifest::Manifest; diff --git a/src/settings/toml/target.rs b/src/settings/toml/target.rs index 8cda79767..8fb9ac974 100644 --- a/src/settings/toml/target.rs +++ b/src/settings/toml/target.rs @@ -1,4 +1,5 @@ use super::builder::Builder; +use super::durable_objects::DurableObjectNamespace; use super::kv_namespace::KvNamespace; use super::site::Site; use super::target_type::TargetType; @@ -12,6 +13,7 @@ use std::path::PathBuf; pub struct Target { pub account_id: String, pub kv_namespaces: Vec, + pub used_durable_object_namespaces: Vec, pub name: String, pub target_type: TargetType, pub webpack_config: Option, diff --git a/src/sites/mod.rs b/src/sites/mod.rs index 0425ae0fd..52d5202d1 100644 --- a/src/sites/mod.rs +++ b/src/sites/mod.rs @@ -315,6 +315,7 @@ mod tests { Target { account_id: "".to_string(), kv_namespaces: Vec::new(), + used_durable_object_namespaces: Vec::new(), name: "".to_string(), target_type: TargetType::JavaScript, webpack_config: None, diff --git a/src/upload/form/mod.rs b/src/upload/form/mod.rs index b2b7ec077..9bcdd97b5 100644 --- a/src/upload/form/mod.rs +++ b/src/upload/form/mod.rs @@ -35,6 +35,7 @@ pub fn build( ) -> Result { let target_type = &target.target_type; let kv_namespaces = &target.kv_namespaces; + let used_durable_object_namespaces = &target.used_durable_object_namespaces; let mut text_blobs: Vec = Vec::new(); let mut plain_texts: Vec = Vec::new(); let mut wasm_modules: Vec<WasmModule> = Vec::new(); @@ -70,6 +71,7 @@ pub fn build( script_path, wasm_modules, kv_namespaces.to_vec(), + used_durable_object_namespaces.to_vec(), text_blobs, plain_texts, )?; @@ -88,6 +90,7 @@ pub fn build( script_path, wasm_modules, kv_namespaces.to_vec(), + used_durable_object_namespaces.to_vec(), text_blobs, plain_texts, )?; @@ -123,6 +126,7 @@ pub fn build( main_module_name, modules, kv_namespaces.to_vec(), + used_durable_object_namespaces.to_vec(), plain_texts, )?; @@ -139,6 +143,7 @@ pub fn build( script_path, wasm_modules, kv_namespaces.to_vec(), + used_durable_object_namespaces.to_vec(), text_blobs, plain_texts, )?; @@ -173,6 +178,7 @@ pub fn build( script_path, wasm_modules, kv_namespaces.to_vec(), + used_durable_object_namespaces.to_vec(), text_blobs, plain_texts, )?; diff --git a/src/upload/form/project_assets.rs b/src/upload/form/project_assets.rs index a833dc69a..961bce19b 100644 --- a/src/upload/form/project_assets.rs +++ b/src/upload/form/project_assets.rs @@ -8,7 +8,7 @@ use super::text_blob::TextBlob; use super::wasm_module::WasmModule; use super::{filename_from_path, filestem_from_path}; -use crate::settings::toml::KvNamespace; +use crate::settings::toml::{DurableObjectNamespace, KvNamespace}; #[derive(Debug)] pub struct ServiceWorkerAssets { @@ -16,6 +16,7 @@ pub struct ServiceWorkerAssets { script_path: PathBuf, pub wasm_modules: Vec<WasmModule>, pub kv_namespaces: Vec<KvNamespace>, + pub used_durable_object_namespaces: Vec<DurableObjectNamespace>, pub text_blobs: Vec<TextBlob>, pub plain_texts: Vec<PlainText>, } @@ -25,6 +26,7 @@ impl ServiceWorkerAssets { script_path: PathBuf, wasm_modules: Vec<WasmModule>, kv_namespaces: Vec<KvNamespace>, + used_durable_object_namespaces: Vec<DurableObjectNamespace>, text_blobs: Vec<TextBlob>, plain_texts: Vec<PlainText>, ) -> Result<Self, failure::Error> { @@ -37,6 +39,7 @@ impl ServiceWorkerAssets { script_path, wasm_modules, kv_namespaces, + used_durable_object_namespaces, text_blobs, plain_texts, }) @@ -53,6 +56,10 @@ impl ServiceWorkerAssets { let binding = kv.binding(); bindings.push(binding); } + for do_ns in &self.used_durable_object_namespaces { + let binding = do_ns.binding().expect("namespace id to be provided"); + bindings.push(binding); + } for blob in &self.text_blobs { let binding = blob.binding(); bindings.push(binding); @@ -131,6 +138,7 @@ pub struct ModulesAssets { pub main_module: String, pub modules: Vec<Module>, pub kv_namespaces: Vec<KvNamespace>, + pub used_durable_object_namespaces: Vec<DurableObjectNamespace>, pub plain_texts: Vec<PlainText>, } @@ -139,12 +147,14 @@ impl ModulesAssets { main_module: String, modules: Vec<Module>, kv_namespaces: Vec<KvNamespace>, + used_durable_object_namespaces: Vec<DurableObjectNamespace>, plain_texts: Vec<PlainText>, ) -> Result<Self, failure::Error> { Ok(Self { main_module, modules, kv_namespaces, + used_durable_object_namespaces, plain_texts, }) } @@ -159,6 +169,10 @@ impl ModulesAssets { let binding = kv.binding(); bindings.push(binding); } + for do_ns in &self.used_durable_object_namespaces { + let binding = do_ns.binding().expect("namespace id to be provided"); + bindings.push(binding); + } for plain_text in &self.plain_texts { let binding = plain_text.binding(); bindings.push(binding);