From c0c43326cbd8d9bc8a4989d2d3b6d5ddd0bd5911 Mon Sep 17 00:00:00 2001 From: Ashley Lewis Date: Mon, 12 Aug 2019 16:31:17 -0500 Subject: [PATCH 01/11] feat: authenticate calls to preview service when possible --- src/commands/publish/preview/mod.rs | 77 ++++++++++++++++++++++++++++- src/main.rs | 3 +- 2 files changed, 77 insertions(+), 3 deletions(-) diff --git a/src/commands/publish/preview/mod.rs b/src/commands/publish/preview/mod.rs index e696810cd..97bd79f77 100644 --- a/src/commands/publish/preview/mod.rs +++ b/src/commands/publish/preview/mod.rs @@ -10,6 +10,7 @@ use uuid::Uuid; use crate::commands; use crate::http; +use crate::settings::global_user::GlobalUser; use crate::settings::project::Project; use crate::terminal::message; @@ -18,7 +19,63 @@ struct Preview { pub id: String, } -pub fn preview( +fn preview_with_auth( + project: &Project, + user: &GlobalUser, + method: Result, + body: Option, +) -> Result<(), failure::Error> { + let create_address = format!( + "https://api.cloudflare.com/client/v4/accounts/{}/workers/scripts/{}/preview", + project.account_id, project.name + ); + + let client = http::auth_client(user); + + commands::build(&project)?; + + let script_upload_form = publish::build_script_upload_form(project)?; + + log::info!("💩"); + let res = client + .post(&create_address) + .multipart(script_upload_form) + .send()? + .error_for_status(); + + let text = &res?.text()?; + log::info!("Response from preview: {:?}", text); + + let p: Preview = + serde_json::from_str(text).expect("could not create a script on cloudflareworkers.com"); + + let session = Uuid::new_v4().to_simple(); + + let preview_host = "example.com"; + let https = 1; + let script_id = &p.id; + + let preview_address = "https://00000000000000000000000000000000.cloudflareworkers.com"; + let cookie = format!( + "__ew_fiddle_preview={}{}{}{}", + script_id, session, https, preview_host + ); + + let method = method.unwrap_or_default(); + + let worker_res = match method { + HTTPMethod::Get => get(preview_address, cookie, client)?, + HTTPMethod::Post => post(preview_address, cookie, client, body)?, + }; + let msg = format!("Your worker responded with: {}", worker_res); + message::preview(&msg); + + open(preview_host, https, script_id)?; + + Ok(()) +} + +fn preview_without_auth( project: &Project, method: Result, body: Option, @@ -69,6 +126,24 @@ pub fn preview( Ok(()) } +pub fn preview( + method: Result, + body: Option, +) -> Result<(), failure::Error> { + let project = Project::new()?; + + match GlobalUser::new() { + Ok(user) => { + log::info!("running in authenticated mode"); + return preview_with_auth(&project, &user, method, body); + } + Err(_e) => { + log::info!("running in unauthenticated mode"); + return preview_without_auth(&project, method, body); + } + } +} + fn open(preview_host: &str, https: u8, script_id: &str) -> Result<(), failure::Error> { let https_str = match https { 1 => "https://", diff --git a/src/main.rs b/src/main.rs index dc5ca9305..90a4a78da 100644 --- a/src/main.rs +++ b/src/main.rs @@ -194,7 +194,6 @@ fn run() -> Result<(), failure::Error> { commands::build(&project)?; } else if let Some(matches) = matches.subcommand_matches("preview") { info!("Getting project settings"); - let project = settings::project::Project::new()?; let method = HTTPMethod::from_str(matches.value_of("method").unwrap_or("get")); @@ -203,7 +202,7 @@ fn run() -> Result<(), failure::Error> { None => None, }; - commands::preview(&project, method, body)?; + commands::preview(method, body)?; } else if matches.subcommand_matches("whoami").is_some() { info!("Getting User settings"); let user = settings::global_user::GlobalUser::new()?; From 6e3ea47cfb3d1b0874312b77e7ef97f1ded32dbe Mon Sep 17 00:00:00 2001 From: Ashley Lewis Date: Mon, 12 Aug 2019 18:09:17 -0500 Subject: [PATCH 02/11] refactor: switch on global user for preview upload --- src/commands/publish/preview/mod.rs | 112 +++++++--------------------- src/main.rs | 4 +- 2 files changed, 32 insertions(+), 84 deletions(-) diff --git a/src/commands/publish/preview/mod.rs b/src/commands/publish/preview/mod.rs index 97bd79f77..400565c81 100644 --- a/src/commands/publish/preview/mod.rs +++ b/src/commands/publish/preview/mod.rs @@ -19,41 +19,26 @@ struct Preview { pub id: String, } -fn preview_with_auth( +pub fn preview( project: &Project, - user: &GlobalUser, + user: Option, method: Result, body: Option, ) -> Result<(), failure::Error> { - let create_address = format!( - "https://api.cloudflare.com/client/v4/accounts/{}/workers/scripts/{}/preview", - project.account_id, project.name - ); - - let client = http::auth_client(user); - commands::build(&project)?; - let script_upload_form = publish::build_script_upload_form(project)?; - - log::info!("💩"); - let res = client - .post(&create_address) - .multipart(script_upload_form) - .send()? - .error_for_status(); - - let text = &res?.text()?; - log::info!("Response from preview: {:?}", text); + let client = match &user { + Some(user) => http::auth_client(&user), + None => http::client(), + }; - let p: Preview = - serde_json::from_str(text).expect("could not create a script on cloudflareworkers.com"); + let preview = upload_to_preview(&project, user, &client)?; let session = Uuid::new_v4().to_simple(); let preview_host = "example.com"; let https = 1; - let script_id = &p.id; + let script_id = &preview.id; let preview_address = "https://00000000000000000000000000000000.cloudflareworkers.com"; let cookie = format!( @@ -64,8 +49,8 @@ fn preview_with_auth( let method = method.unwrap_or_default(); let worker_res = match method { - HTTPMethod::Get => get(preview_address, cookie, client)?, - HTTPMethod::Post => post(preview_address, cookie, client, body)?, + HTTPMethod::Get => get(preview_address, cookie, &client)?, + HTTPMethod::Post => post(preview_address, cookie, &client, body)?, }; let msg = format!("Your worker responded with: {}", worker_res); message::preview(&msg); @@ -75,73 +60,34 @@ fn preview_with_auth( Ok(()) } -fn preview_without_auth( +fn upload_to_preview( project: &Project, - method: Result, - body: Option, -) -> Result<(), failure::Error> { - let create_address = "https://cloudflareworkers.com/script"; - - let client = http::client(); - - commands::build(&project)?; + user: Option, + client: &reqwest::Client, +) -> Result { + let create_address = match &user { + Some(_user) => format!( + "https://api.cloudflare.com/client/v4/accounts/{}/workers/scripts/{}/preview", + project.account_id, project.name + ), + None => "https://cloudflareworkers.com/script".to_string(), + }; let script_upload_form = publish::build_script_upload_form(project)?; - let res = client - .post(create_address) + let mut res = client + .post(&create_address) .multipart(script_upload_form) .send()? - .error_for_status(); + .error_for_status()?; - let text = &res?.text()?; + let text = &res.text()?; log::info!("Response from preview: {:?}", text); - let p: Preview = + let preview: Preview = serde_json::from_str(text).expect("could not create a script on cloudflareworkers.com"); - let session = Uuid::new_v4().to_simple(); - - let preview_host = "example.com"; - let https = 1; - let script_id = &p.id; - - let preview_address = "https://00000000000000000000000000000000.cloudflareworkers.com"; - let cookie = format!( - "__ew_fiddle_preview={}{}{}{}", - script_id, session, https, preview_host - ); - - let method = method.unwrap_or_default(); - - let worker_res = match method { - HTTPMethod::Get => get(preview_address, cookie, client)?, - HTTPMethod::Post => post(preview_address, cookie, client, body)?, - }; - let msg = format!("Your worker responded with: {}", worker_res); - message::preview(&msg); - - open(preview_host, https, script_id)?; - - Ok(()) -} - -pub fn preview( - method: Result, - body: Option, -) -> Result<(), failure::Error> { - let project = Project::new()?; - - match GlobalUser::new() { - Ok(user) => { - log::info!("running in authenticated mode"); - return preview_with_auth(&project, &user, method, body); - } - Err(_e) => { - log::info!("running in unauthenticated mode"); - return preview_without_auth(&project, method, body); - } - } + Ok(preview) } fn open(preview_host: &str, https: u8, script_id: &str) -> Result<(), failure::Error> { @@ -174,7 +120,7 @@ fn open(preview_host: &str, https: u8, script_id: &str) -> Result<(), failure::E fn get( preview_address: &str, cookie: String, - client: reqwest::Client, + client: &reqwest::Client, ) -> Result { let res = client.get(preview_address).header("Cookie", cookie).send(); let msg = format!("GET {}", preview_address); @@ -185,7 +131,7 @@ fn get( fn post( preview_address: &str, cookie: String, - client: reqwest::Client, + client: &reqwest::Client, body: Option, ) -> Result { let res = match body { diff --git a/src/main.rs b/src/main.rs index 90a4a78da..90aaf91ad 100644 --- a/src/main.rs +++ b/src/main.rs @@ -194,6 +194,8 @@ fn run() -> Result<(), failure::Error> { commands::build(&project)?; } else if let Some(matches) = matches.subcommand_matches("preview") { info!("Getting project settings"); + let project = settings::project::Project::new()?; + let user = settings::global_user::GlobalUser::new().ok(); let method = HTTPMethod::from_str(matches.value_of("method").unwrap_or("get")); @@ -202,7 +204,7 @@ fn run() -> Result<(), failure::Error> { None => None, }; - commands::preview(method, body)?; + commands::preview(&project, user, method, body)?; } else if matches.subcommand_matches("whoami").is_some() { info!("Getting User settings"); let user = settings::global_user::GlobalUser::new()?; From 091d6c9a270561609925c11b4fb0bbd81d51b0f6 Mon Sep 17 00:00:00 2001 From: Ashley Lewis Date: Mon, 12 Aug 2019 19:45:47 -0500 Subject: [PATCH 03/11] properly parse preview response --- src/commands/publish/preview/mod.rs | 39 ++++++++++++++++++++++++----- 1 file changed, 33 insertions(+), 6 deletions(-) diff --git a/src/commands/publish/preview/mod.rs b/src/commands/publish/preview/mod.rs index 400565c81..f93a87a45 100644 --- a/src/commands/publish/preview/mod.rs +++ b/src/commands/publish/preview/mod.rs @@ -19,6 +19,24 @@ struct Preview { pub id: String, } +impl From for Preview { + fn from(api_preview: ApiPreview) -> Preview { + Preview { + id: api_preview.preview_id, + } + } +} + +#[derive(Debug, Deserialize)] +struct ApiPreview { + pub preview_id: String, +} + +#[derive(Debug, Deserialize)] +struct V4ApiResponse { + pub result: ApiPreview, +} + pub fn preview( project: &Project, user: Option, @@ -72,6 +90,7 @@ fn upload_to_preview( ), None => "https://cloudflareworkers.com/script".to_string(), }; + log::info!("address: {}", create_address); let script_upload_form = publish::build_script_upload_form(project)?; @@ -82,12 +101,20 @@ fn upload_to_preview( .error_for_status()?; let text = &res.text()?; - log::info!("Response from preview: {:?}", text); - - let preview: Preview = - serde_json::from_str(text).expect("could not create a script on cloudflareworkers.com"); - - Ok(preview) + log::info!("Response from preview: {:#?}", text); + + match user { + Some(_user) => { + let response: V4ApiResponse = serde_json::from_str(text) + .expect("could not create a script on cloudflareworkers.com"); + + Ok(Preview::from(response.result)) + } + None => { + Ok(serde_json::from_str(text) + .expect("could not create a script on cloudflareworkers.com")) + } + } } fn open(preview_host: &str, https: u8, script_id: &str) -> Result<(), failure::Error> { From 1351784444c93a37595d3eb9194e9726e089975e Mon Sep 17 00:00:00 2001 From: Ashley Lewis Date: Mon, 12 Aug 2019 19:46:33 -0500 Subject: [PATCH 04/11] refactor: make preview_address a const --- src/commands/publish/preview/mod.rs | 24 ++++++++++-------------- 1 file changed, 10 insertions(+), 14 deletions(-) diff --git a/src/commands/publish/preview/mod.rs b/src/commands/publish/preview/mod.rs index f93a87a45..31c7394d8 100644 --- a/src/commands/publish/preview/mod.rs +++ b/src/commands/publish/preview/mod.rs @@ -14,6 +14,8 @@ use crate::settings::global_user::GlobalUser; use crate::settings::project::Project; use crate::terminal::message; +const PREVIEW_ADDRESS: &str = "https://00000000000000000000000000000000.cloudflareworkers.com"; + #[derive(Debug, Deserialize)] struct Preview { pub id: String, @@ -58,7 +60,6 @@ pub fn preview( let https = 1; let script_id = &preview.id; - let preview_address = "https://00000000000000000000000000000000.cloudflareworkers.com"; let cookie = format!( "__ew_fiddle_preview={}{}{}{}", script_id, session, https, preview_host @@ -67,8 +68,8 @@ pub fn preview( let method = method.unwrap_or_default(); let worker_res = match method { - HTTPMethod::Get => get(preview_address, cookie, &client)?, - HTTPMethod::Post => post(preview_address, cookie, &client, body)?, + HTTPMethod::Get => get(cookie, &client)?, + HTTPMethod::Post => post(cookie, &client, body)?, }; let msg = format!("Your worker responded with: {}", worker_res); message::preview(&msg); @@ -144,32 +145,27 @@ fn open(preview_host: &str, https: u8, script_id: &str) -> Result<(), failure::E Ok(()) } -fn get( - preview_address: &str, - cookie: String, - client: &reqwest::Client, -) -> Result { - let res = client.get(preview_address).header("Cookie", cookie).send(); - let msg = format!("GET {}", preview_address); +fn get(cookie: String, client: &reqwest::Client) -> Result { + let res = client.get(PREVIEW_ADDRESS).header("Cookie", cookie).send(); + let msg = format!("GET {}", PREVIEW_ADDRESS); message::preview(&msg); Ok(res?.text()?) } fn post( - preview_address: &str, cookie: String, client: &reqwest::Client, body: Option, ) -> Result { let res = match body { Some(s) => client - .post(preview_address) + .post(PREVIEW_ADDRESS) .header("Cookie", cookie) .body(s) .send(), - None => client.post(preview_address).header("Cookie", cookie).send(), + None => client.post(PREVIEW_ADDRESS).header("Cookie", cookie).send(), }; - let msg = format!("POST {}", preview_address); + let msg = format!("POST {}", PREVIEW_ADDRESS); message::preview(&msg); Ok(res?.text()?) } From 60be1340f72aeddb07e373f1583ae058c525930a Mon Sep 17 00:00:00 2001 From: Ashley Lewis Date: Tue, 13 Aug 2019 17:36:18 -0500 Subject: [PATCH 05/11] refactor: pass method instead of result --- src/commands/publish/preview/mod.rs | 5 ++--- src/main.rs | 2 +- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/src/commands/publish/preview/mod.rs b/src/commands/publish/preview/mod.rs index 31c7394d8..ab566dd8b 100644 --- a/src/commands/publish/preview/mod.rs +++ b/src/commands/publish/preview/mod.rs @@ -14,6 +14,7 @@ use crate::settings::global_user::GlobalUser; use crate::settings::project::Project; use crate::terminal::message; +// Using this instead of just `https://cloudflareworkers.com` returns just the worker response to the CLI const PREVIEW_ADDRESS: &str = "https://00000000000000000000000000000000.cloudflareworkers.com"; #[derive(Debug, Deserialize)] @@ -42,7 +43,7 @@ struct V4ApiResponse { pub fn preview( project: &Project, user: Option, - method: Result, + method: HTTPMethod, body: Option, ) -> Result<(), failure::Error> { commands::build(&project)?; @@ -65,8 +66,6 @@ pub fn preview( script_id, session, https, preview_host ); - let method = method.unwrap_or_default(); - let worker_res = match method { HTTPMethod::Get => get(cookie, &client)?, HTTPMethod::Post => post(cookie, &client, body)?, diff --git a/src/main.rs b/src/main.rs index 90aaf91ad..b139f29a2 100644 --- a/src/main.rs +++ b/src/main.rs @@ -197,7 +197,7 @@ fn run() -> Result<(), failure::Error> { let project = settings::project::Project::new()?; let user = settings::global_user::GlobalUser::new().ok(); - let method = HTTPMethod::from_str(matches.value_of("method").unwrap_or("get")); + let method = HTTPMethod::from_str(matches.value_of("method").unwrap_or("get"))?; let body = match matches.value_of("body") { Some(s) => Some(s.to_string()), From d9c1460524902982c9d7d66929f9a4384828b2a8 Mon Sep 17 00:00:00 2001 From: Ashley Lewis Date: Tue, 13 Aug 2019 18:18:40 -0500 Subject: [PATCH 06/11] refactor: make validate a method on project --- src/commands/publish/mod.rs | 66 +------------------------------------ src/settings/project/mod.rs | 59 +++++++++++++++++++++++++++++++++ 2 files changed, 60 insertions(+), 65 deletions(-) diff --git a/src/commands/publish/mod.rs b/src/commands/publish/mod.rs index 6dab662b9..0a9032edf 100644 --- a/src/commands/publish/mod.rs +++ b/src/commands/publish/mod.rs @@ -20,7 +20,7 @@ use crate::terminal::message; pub fn publish(user: &GlobalUser, project: &Project, release: bool) -> Result<(), failure::Error> { info!("release = {}", release); - validate_project(project, release)?; + project.validate(release)?; commands::build(&project)?; publish_script(&user, &project, release)?; if release { @@ -115,67 +115,3 @@ fn make_public_on_subdomain(project: &Project, user: &GlobalUser) -> Result<(), } Ok(()) } - -fn validate_project(project: &Project, release: bool) -> Result<(), failure::Error> { - let mut missing_fields = Vec::new(); - - if project.account_id.is_empty() { - missing_fields.push("account_id") - }; - if project.name.is_empty() { - missing_fields.push("name") - }; - - match &project.kv_namespaces { - Some(kv_namespaces) => { - for kv in kv_namespaces { - if kv.binding.is_empty() { - missing_fields.push("kv-namespace binding") - } - - if kv.id.is_empty() { - missing_fields.push("kv-namespace id") - } - } - } - None => {} - } - - let destination = if release { - //check required fields for release - if project - .zone_id - .as_ref() - .unwrap_or(&"".to_string()) - .is_empty() - { - missing_fields.push("zone_id") - }; - if project.route.as_ref().unwrap_or(&"".to_string()).is_empty() { - missing_fields.push("route") - }; - //zoned deploy destination - "a route" - } else { - //zoneless deploy destination - "your subdomain" - }; - - let (field_pluralization, is_are) = match missing_fields.len() { - n if n >= 2 => ("fields", "are"), - 1 => ("field", "is"), - _ => ("", ""), - }; - - if !missing_fields.is_empty() { - failure::bail!( - "Your wrangler.toml is missing the {} {:?} which {} required to publish to {}!", - field_pluralization, - missing_fields, - is_are, - destination - ); - }; - - Ok(()) -} diff --git a/src/settings/project/mod.rs b/src/settings/project/mod.rs index 9ef0e589b..e08736142 100644 --- a/src/settings/project/mod.rs +++ b/src/settings/project/mod.rs @@ -71,6 +71,65 @@ impl Project { pub fn kv_namespaces(&self) -> Vec { self.kv_namespaces.clone().unwrap_or_else(Vec::new) } + + pub fn validate(&self, release: bool) -> Result<(), failure::Error> { + let mut missing_fields = Vec::new(); + + if self.account_id.is_empty() { + missing_fields.push("account_id") + }; + if self.name.is_empty() { + missing_fields.push("name") + }; + + match &self.kv_namespaces { + Some(kv_namespaces) => { + for kv in kv_namespaces { + if kv.binding.is_empty() { + missing_fields.push("kv-namespace binding") + } + + if kv.id.is_empty() { + missing_fields.push("kv-namespace id") + } + } + } + None => {} + } + + let destination = if release { + //check required fields for release + if self.zone_id.as_ref().unwrap_or(&"".to_string()).is_empty() { + missing_fields.push("zone_id") + }; + if self.route.as_ref().unwrap_or(&"".to_string()).is_empty() { + missing_fields.push("route") + }; + //zoned deploy destination + "a route" + } else { + //zoneless deploy destination + "your subdomain" + }; + + let (field_pluralization, is_are) = match missing_fields.len() { + n if n >= 2 => ("fields", "are"), + 1 => ("field", "is"), + _ => ("", ""), + }; + + if !missing_fields.is_empty() { + failure::bail!( + "Your wrangler.toml is missing the {} {:?} which {} required to publish to {}!", + field_pluralization, + missing_fields, + is_are, + destination + ); + }; + + Ok(()) + } } fn get_project_config(config_path: &Path) -> Result { From 908aade08a006366d6cae16b642cf880cb888f88 Mon Sep 17 00:00:00 2001 From: Ashley Lewis Date: Tue, 13 Aug 2019 18:33:20 -0500 Subject: [PATCH 07/11] feat: add messaging, validation, warnings * refactor: split authenticated upload and unauthenticated upload * feat: validate project config before preview * feat: warn on unauthenticated preview upload with kv bindings * feat: omit kv bindings from unauthenticated preview upload --- src/commands/publish/preview/mod.rs | 137 +++++++++++++++++++--------- src/main.rs | 5 +- 2 files changed, 96 insertions(+), 46 deletions(-) diff --git a/src/commands/publish/preview/mod.rs b/src/commands/publish/preview/mod.rs index ab566dd8b..bfb7e3cba 100644 --- a/src/commands/publish/preview/mod.rs +++ b/src/commands/publish/preview/mod.rs @@ -13,6 +13,7 @@ use crate::http; use crate::settings::global_user::GlobalUser; use crate::settings::project::Project; use crate::terminal::message; +use reqwest::Client; // Using this instead of just `https://cloudflareworkers.com` returns just the worker response to the CLI const PREVIEW_ADDRESS: &str = "https://00000000000000000000000000000000.cloudflareworkers.com"; @@ -30,6 +31,9 @@ impl From for Preview { } } +// When making authenticated preview requests, we go through the v4 Workers API rather than +// hitting the preview service directly, so its response is formatted like a v4 API response. +// These structs are here to convert from this format into the Preview defined above. #[derive(Debug, Deserialize)] struct ApiPreview { pub preview_id: String, @@ -41,58 +45,61 @@ struct V4ApiResponse { } pub fn preview( - project: &Project, + mut project: Project, user: Option, method: HTTPMethod, body: Option, ) -> Result<(), failure::Error> { - commands::build(&project)?; + let client: Client; - let client = match &user { - Some(user) => http::auth_client(&user), - None => http::client(), - }; + let preview = match &user { + Some(user) => { + log::info!("Running in Auth'd mode"); - let preview = upload_to_preview(&project, user, &client)?; + project.validate(false)?; - let session = Uuid::new_v4().to_simple(); + commands::build(&project)?; + client = http::auth_client(&user); - let preview_host = "example.com"; - let https = 1; - let script_id = &preview.id; + authenticated_upload(&client, &project)? + } + None => { + log::info!("Running in Un-auth'd mode"); + + // KV namespaces are not supported by the preview service unless you authenticate + // so we omit them and provide the user with a little guidance. We don't error out, though, + // because there are valid workarounds for this for testing purposes. + if project.kv_namespaces.is_some() { + message::warn("KV Namespaces are not supported in unauthenticated mode"); + message::help( + "Run wrangler config or set $CF_API_KEY and $CF_EMAIL to configure your user.", + ); + project.kv_namespaces = None; + } + + commands::build(&project)?; + client = http::client(); + + unauthenticated_upload(&client, &project)? + } + }; - let cookie = format!( - "__ew_fiddle_preview={}{}{}{}", - script_id, session, https, preview_host - ); + let worker_res = call_worker(&client, preview, method, body)?; - let worker_res = match method { - HTTPMethod::Get => get(cookie, &client)?, - HTTPMethod::Post => post(cookie, &client, body)?, - }; let msg = format!("Your worker responded with: {}", worker_res); message::preview(&msg); - open(preview_host, https, script_id)?; - Ok(()) } -fn upload_to_preview( - project: &Project, - user: Option, - client: &reqwest::Client, -) -> Result { - let create_address = match &user { - Some(_user) => format!( - "https://api.cloudflare.com/client/v4/accounts/{}/workers/scripts/{}/preview", - project.account_id, project.name - ), - None => "https://cloudflareworkers.com/script".to_string(), - }; +fn authenticated_upload(client: &Client, project: &Project) -> Result { + let create_address = format!( + "https://api.cloudflare.com/client/v4/accounts/{}/workers/scripts/{}/preview", + project.account_id, project.name + ); log::info!("address: {}", create_address); - let script_upload_form = publish::build_script_upload_form(project)?; + let script_upload_form = publish::build_script_upload_form(&project)?; let mut res = client .post(&create_address) @@ -103,18 +110,58 @@ fn upload_to_preview( let text = &res.text()?; log::info!("Response from preview: {:#?}", text); - match user { - Some(_user) => { - let response: V4ApiResponse = serde_json::from_str(text) - .expect("could not create a script on cloudflareworkers.com"); + let response: V4ApiResponse = + serde_json::from_str(text).expect("could not create a script on cloudflareworkers.com"); - Ok(Preview::from(response.result)) - } - None => { - Ok(serde_json::from_str(text) - .expect("could not create a script on cloudflareworkers.com")) - } - } + Ok(Preview::from(response.result)) +} + +fn unauthenticated_upload(client: &Client, project: &Project) -> Result { + let create_address = "https://cloudflareworkers.com/script"; + log::info!("address: {}", create_address); + + let script_upload_form = publish::build_script_upload_form(project)?; + + let mut res = client + .post(create_address) + .multipart(script_upload_form) + .send()? + .error_for_status()?; + + let text = &res.text()?; + log::info!("Response from preview: {:#?}", text); + + let preview: Preview = + serde_json::from_str(text).expect("could not create a script on cloudflareworkers.com"); + + Ok(preview) +} + +fn call_worker( + client: &Client, + preview: Preview, + method: HTTPMethod, + body: Option, +) -> Result { + let session = Uuid::new_v4().to_simple(); + + let preview_host = "example.com"; + let https = 1; + let script_id = &preview.id; + + let cookie = format!( + "__ew_fiddle_preview={}{}{}{}", + script_id, session, https, preview_host + ); + + let res = match method { + HTTPMethod::Get => get(cookie, &client)?, + HTTPMethod::Post => post(cookie, &client, body)?, + }; + + open(preview_host, https, script_id)?; + + Ok(res) } fn open(preview_host: &str, https: u8, script_id: &str) -> Result<(), failure::Error> { diff --git a/src/main.rs b/src/main.rs index b139f29a2..437f27f58 100644 --- a/src/main.rs +++ b/src/main.rs @@ -195,6 +195,9 @@ fn run() -> Result<(), failure::Error> { } else if let Some(matches) = matches.subcommand_matches("preview") { info!("Getting project settings"); let project = settings::project::Project::new()?; + + // the preview command can be called with or without a Global User having been config'd + // so we convert this Result into an Option let user = settings::global_user::GlobalUser::new().ok(); let method = HTTPMethod::from_str(matches.value_of("method").unwrap_or("get"))?; @@ -204,7 +207,7 @@ fn run() -> Result<(), failure::Error> { None => None, }; - commands::preview(&project, user, method, body)?; + commands::preview(project, user, method, body)?; } else if matches.subcommand_matches("whoami").is_some() { info!("Getting User settings"); let user = settings::global_user::GlobalUser::new()?; From 982761805d2a68528a138089414394f22f7c7a0b Mon Sep 17 00:00:00 2001 From: Ashley Lewis Date: Wed, 14 Aug 2019 10:19:47 -0500 Subject: [PATCH 08/11] nit: clean up messaging, logging --- src/commands/publish/preview/mod.rs | 8 ++++---- src/settings/project/mod.rs | 4 ++-- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/src/commands/publish/preview/mod.rs b/src/commands/publish/preview/mod.rs index bfb7e3cba..854267d8f 100644 --- a/src/commands/publish/preview/mod.rs +++ b/src/commands/publish/preview/mod.rs @@ -54,7 +54,7 @@ pub fn preview( let preview = match &user { Some(user) => { - log::info!("Running in Auth'd mode"); + log::info!("GlobalUser set, running with authentication"); project.validate(false)?; @@ -64,15 +64,15 @@ pub fn preview( authenticated_upload(&client, &project)? } None => { - log::info!("Running in Un-auth'd mode"); + log::info!("GlobalUser not set, running without authentication"); // KV namespaces are not supported by the preview service unless you authenticate // so we omit them and provide the user with a little guidance. We don't error out, though, // because there are valid workarounds for this for testing purposes. if project.kv_namespaces.is_some() { - message::warn("KV Namespaces are not supported in unauthenticated mode"); + message::warn("KV Namespaces are not supported without setting API credentials"); message::help( - "Run wrangler config or set $CF_API_KEY and $CF_EMAIL to configure your user.", + "Run `wrangler config` or set $CF_API_KEY and $CF_EMAIL to configure your user.", ); project.kv_namespaces = None; } diff --git a/src/settings/project/mod.rs b/src/settings/project/mod.rs index e08736142..572b42608 100644 --- a/src/settings/project/mod.rs +++ b/src/settings/project/mod.rs @@ -86,11 +86,11 @@ impl Project { Some(kv_namespaces) => { for kv in kv_namespaces { if kv.binding.is_empty() { - missing_fields.push("kv-namespace binding") + missing_fields.push("kv-namespaces binding") } if kv.id.is_empty() { - missing_fields.push("kv-namespace id") + missing_fields.push("kv-namespaces id") } } } From 31aac5eaf4628c8888416f55983d93cd87f85402 Mon Sep 17 00:00:00 2001 From: Ashley Lewis Date: Wed, 14 Aug 2019 15:36:57 -0500 Subject: [PATCH 09/11] Revert "refactor: make validate a method on project" This reverts commit d9c1460524902982c9d7d66929f9a4384828b2a8. --- src/commands/publish/mod.rs | 66 ++++++++++++++++++++++++++++- src/commands/publish/preview/mod.rs | 2 +- src/settings/project/mod.rs | 59 -------------------------- 3 files changed, 66 insertions(+), 61 deletions(-) diff --git a/src/commands/publish/mod.rs b/src/commands/publish/mod.rs index 0a9032edf..6dab662b9 100644 --- a/src/commands/publish/mod.rs +++ b/src/commands/publish/mod.rs @@ -20,7 +20,7 @@ use crate::terminal::message; pub fn publish(user: &GlobalUser, project: &Project, release: bool) -> Result<(), failure::Error> { info!("release = {}", release); - project.validate(release)?; + validate_project(project, release)?; commands::build(&project)?; publish_script(&user, &project, release)?; if release { @@ -115,3 +115,67 @@ fn make_public_on_subdomain(project: &Project, user: &GlobalUser) -> Result<(), } Ok(()) } + +fn validate_project(project: &Project, release: bool) -> Result<(), failure::Error> { + let mut missing_fields = Vec::new(); + + if project.account_id.is_empty() { + missing_fields.push("account_id") + }; + if project.name.is_empty() { + missing_fields.push("name") + }; + + match &project.kv_namespaces { + Some(kv_namespaces) => { + for kv in kv_namespaces { + if kv.binding.is_empty() { + missing_fields.push("kv-namespace binding") + } + + if kv.id.is_empty() { + missing_fields.push("kv-namespace id") + } + } + } + None => {} + } + + let destination = if release { + //check required fields for release + if project + .zone_id + .as_ref() + .unwrap_or(&"".to_string()) + .is_empty() + { + missing_fields.push("zone_id") + }; + if project.route.as_ref().unwrap_or(&"".to_string()).is_empty() { + missing_fields.push("route") + }; + //zoned deploy destination + "a route" + } else { + //zoneless deploy destination + "your subdomain" + }; + + let (field_pluralization, is_are) = match missing_fields.len() { + n if n >= 2 => ("fields", "are"), + 1 => ("field", "is"), + _ => ("", ""), + }; + + if !missing_fields.is_empty() { + failure::bail!( + "Your wrangler.toml is missing the {} {:?} which {} required to publish to {}!", + field_pluralization, + missing_fields, + is_are, + destination + ); + }; + + Ok(()) +} diff --git a/src/commands/publish/preview/mod.rs b/src/commands/publish/preview/mod.rs index 854267d8f..30dd3b61b 100644 --- a/src/commands/publish/preview/mod.rs +++ b/src/commands/publish/preview/mod.rs @@ -56,7 +56,7 @@ pub fn preview( Some(user) => { log::info!("GlobalUser set, running with authentication"); - project.validate(false)?; + super::validate_project(&project, false)?; commands::build(&project)?; client = http::auth_client(&user); diff --git a/src/settings/project/mod.rs b/src/settings/project/mod.rs index 572b42608..9ef0e589b 100644 --- a/src/settings/project/mod.rs +++ b/src/settings/project/mod.rs @@ -71,65 +71,6 @@ impl Project { pub fn kv_namespaces(&self) -> Vec { self.kv_namespaces.clone().unwrap_or_else(Vec::new) } - - pub fn validate(&self, release: bool) -> Result<(), failure::Error> { - let mut missing_fields = Vec::new(); - - if self.account_id.is_empty() { - missing_fields.push("account_id") - }; - if self.name.is_empty() { - missing_fields.push("name") - }; - - match &self.kv_namespaces { - Some(kv_namespaces) => { - for kv in kv_namespaces { - if kv.binding.is_empty() { - missing_fields.push("kv-namespaces binding") - } - - if kv.id.is_empty() { - missing_fields.push("kv-namespaces id") - } - } - } - None => {} - } - - let destination = if release { - //check required fields for release - if self.zone_id.as_ref().unwrap_or(&"".to_string()).is_empty() { - missing_fields.push("zone_id") - }; - if self.route.as_ref().unwrap_or(&"".to_string()).is_empty() { - missing_fields.push("route") - }; - //zoned deploy destination - "a route" - } else { - //zoneless deploy destination - "your subdomain" - }; - - let (field_pluralization, is_are) = match missing_fields.len() { - n if n >= 2 => ("fields", "are"), - 1 => ("field", "is"), - _ => ("", ""), - }; - - if !missing_fields.is_empty() { - failure::bail!( - "Your wrangler.toml is missing the {} {:?} which {} required to publish to {}!", - field_pluralization, - missing_fields, - is_are, - destination - ); - }; - - Ok(()) - } } fn get_project_config(config_path: &Path) -> Result { From fdd2e2cec67b15d8873eadcd984bf6ba335dd97c Mon Sep 17 00:00:00 2001 From: Ashley Lewis Date: Wed, 14 Aug 2019 15:55:06 -0500 Subject: [PATCH 10/11] Fallback to unauthenticated preview * use custom validate fn for preview --- src/commands/publish/preview/mod.rs | 44 +++++++++++++++++++++++++++-- 1 file changed, 41 insertions(+), 3 deletions(-) diff --git a/src/commands/publish/preview/mod.rs b/src/commands/publish/preview/mod.rs index 30dd3b61b..4229b6ea0 100644 --- a/src/commands/publish/preview/mod.rs +++ b/src/commands/publish/preview/mod.rs @@ -56,12 +56,22 @@ pub fn preview( Some(user) => { log::info!("GlobalUser set, running with authentication"); - super::validate_project(&project, false)?; - commands::build(&project)?; client = http::auth_client(&user); - authenticated_upload(&client, &project)? + let missing_fields = validate(&project); + + if !missing_fields.is_empty() { + message::warn(&format!( + "Your wrangler.toml is missing the following fields: {:?}", + missing_fields + )); + message::info("Falling back to unauthenticated preview."); + + unauthenticated_upload(&client, &project)? + } else { + authenticated_upload(&client, &project)? + } } None => { log::info!("GlobalUser not set, running without authentication"); @@ -92,6 +102,34 @@ pub fn preview( Ok(()) } +fn validate(project: &Project) -> Vec<&str> { + let mut missing_fields = Vec::new(); + + if project.account_id.is_empty() { + missing_fields.push("account_id") + }; + if project.name.is_empty() { + missing_fields.push("name") + }; + + match &project.kv_namespaces { + Some(kv_namespaces) => { + for kv in kv_namespaces { + if kv.binding.is_empty() { + missing_fields.push("kv-namespace binding") + } + + if kv.id.is_empty() { + missing_fields.push("kv-namespace id") + } + } + } + None => {} + } + + missing_fields +} + fn authenticated_upload(client: &Client, project: &Project) -> Result { let create_address = format!( "https://api.cloudflare.com/client/v4/accounts/{}/workers/scripts/{}/preview", From dbbd2098a0a8bc960420dd59300891e79c855183 Mon Sep 17 00:00:00 2001 From: Ashley Lewis Date: Thu, 15 Aug 2019 10:32:52 -0500 Subject: [PATCH 11/11] Add more messaging output --- src/commands/publish/preview/mod.rs | 49 +++++++++++++++++------------ src/terminal/emoji.rs | 4 +-- 2 files changed, 31 insertions(+), 22 deletions(-) diff --git a/src/commands/publish/preview/mod.rs b/src/commands/publish/preview/mod.rs index 4229b6ea0..8009c74e8 100644 --- a/src/commands/publish/preview/mod.rs +++ b/src/commands/publish/preview/mod.rs @@ -57,40 +57,36 @@ pub fn preview( log::info!("GlobalUser set, running with authentication"); commands::build(&project)?; - client = http::auth_client(&user); let missing_fields = validate(&project); - if !missing_fields.is_empty() { + if missing_fields.is_empty() { + client = http::auth_client(&user); + + authenticated_upload(&client, &project)? + } else { message::warn(&format!( "Your wrangler.toml is missing the following fields: {:?}", missing_fields )); - message::info("Falling back to unauthenticated preview."); + message::warn("Falling back to unauthenticated preview."); - unauthenticated_upload(&client, &project)? - } else { - authenticated_upload(&client, &project)? + client = http::client(); + unauthenticated_upload(&client, &mut project)? } } None => { - log::info!("GlobalUser not set, running without authentication"); - - // KV namespaces are not supported by the preview service unless you authenticate - // so we omit them and provide the user with a little guidance. We don't error out, though, - // because there are valid workarounds for this for testing purposes. - if project.kv_namespaces.is_some() { - message::warn("KV Namespaces are not supported without setting API credentials"); - message::help( - "Run `wrangler config` or set $CF_API_KEY and $CF_EMAIL to configure your user.", - ); - project.kv_namespaces = None; - } + message::warn( + "You haven't run `wrangler config`. Running preview without authentication", + ); + message::help( + "Run `wrangler config` or set $CF_API_KEY and $CF_EMAIL to configure your user.", + ); commands::build(&project)?; client = http::client(); - unauthenticated_upload(&client, &project)? + unauthenticated_upload(&client, &mut project)? } }; @@ -154,10 +150,23 @@ fn authenticated_upload(client: &Client, project: &Project) -> Result Result { +fn unauthenticated_upload( + client: &Client, + project: &mut Project, +) -> Result { let create_address = "https://cloudflareworkers.com/script"; log::info!("address: {}", create_address); + // KV namespaces are not supported by the preview service unless you authenticate + // so we omit them and provide the user with a little guidance. We don't error out, though, + // because there are valid workarounds for this for testing purposes. + if project.kv_namespaces.is_some() { + message::warn( + "KV Namespaces are not supported in preview without setting API credentials and account_id", + ); + project.kv_namespaces = None; + } + let script_upload_form = publish::build_script_upload_form(project)?; let mut res = client diff --git a/src/terminal/emoji.rs b/src/terminal/emoji.rs index da62fa835..f316f54ea 100644 --- a/src/terminal/emoji.rs +++ b/src/terminal/emoji.rs @@ -20,8 +20,8 @@ pub static INBOX: Emoji = Emoji("📥 ", ""); pub static INFO: Emoji = Emoji("💁‍ ", ""); pub static MICROSCOPE: Emoji = Emoji("🔬 ", ""); pub static SHEEP: Emoji = Emoji("🐑 ", ""); -pub static SLEUTH: Emoji = Emoji("🕵️‍♂️", ""); -pub static SPARKLES: Emoji = Emoji("✨ ", ""); +pub static SLEUTH: Emoji = Emoji("🕵️‍♂️ ", ""); +pub static SPARKLES: Emoji = Emoji("✨ ", ""); pub static SWIRL: Emoji = Emoji("🌀 ", ""); pub static UP: Emoji = Emoji("🆙 ", ""); pub static WARN: Emoji = Emoji("⚠️ ", "");