diff --git a/Cargo.lock b/Cargo.lock index 3e5b357..ca5226a 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -504,15 +504,6 @@ version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "56ce8c6da7551ec6c462cbaf3bfbc75131ebbfa1c944aeaa9dab51ca1c5f0c3b" -[[package]] -name = "e2e" -version = "0.1.0" -dependencies = [ - "anyhow", - "nix 0.28.0", - "reqwest 0.11.27", -] - [[package]] name = "either" version = "1.12.0" @@ -1016,19 +1007,6 @@ dependencies = [ "tower-service", ] -[[package]] -name = "hyper-tls" -version = "0.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d6183ddfa99b85da61a140bea0efc93fdf56ceaa041b37d553518030827f9905" -dependencies = [ - "bytes", - "hyper 0.14.28", - "native-tls", - "tokio", - "tokio-native-tls", -] - [[package]] name = "hyper-tls" version = "0.6.0" @@ -1763,23 +1741,19 @@ dependencies = [ "http 0.2.12", "http-body 0.4.6", "hyper 0.14.28", - "hyper-tls 0.5.0", "ipnet", "js-sys", "log", "mime", - "native-tls", "once_cell", "percent-encoding", "pin-project-lite", - "rustls-pemfile 1.0.4", "serde", "serde_json", "serde_urlencoded", "sync_wrapper 0.1.2", "system-configuration", "tokio", - "tokio-native-tls", "tower-service", "url", "wasm-bindgen", @@ -1806,7 +1780,7 @@ dependencies = [ "http-body-util", "hyper 1.3.1", "hyper-rustls 0.26.0", - "hyper-tls 0.6.0", + "hyper-tls", "hyper-util", "ipnet", "js-sys", @@ -1817,7 +1791,7 @@ dependencies = [ "percent-encoding", "pin-project-lite", "rustls 0.22.4", - "rustls-pemfile 2.1.2", + "rustls-pemfile", "rustls-pki-types", "serde", "serde_json", @@ -1961,21 +1935,12 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8f1fb85efa936c42c6d5fc28d2629bb51e4b2f4b8a5211e297d599cc5a093792" dependencies = [ "openssl-probe", - "rustls-pemfile 2.1.2", + "rustls-pemfile", "rustls-pki-types", "schannel", "security-framework", ] -[[package]] -name = "rustls-pemfile" -version = "1.0.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1c74cae0a4cf6ccbbf5f359f08efdf8ee7e1dc532573bf0db71968cb56b1448c" -dependencies = [ - "base64 0.21.7", -] - [[package]] name = "rustls-pemfile" version = "2.1.2" diff --git a/Cargo.toml b/Cargo.toml index 1421b2c..500bdeb 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,4 +1,4 @@ [workspace] resolver = "2" -members = ["linkup-cli", "linkup", "e2e", "worker", "local-server", "server-tests"] +members = ["linkup-cli", "linkup", "worker", "local-server", "server-tests"] diff --git a/e2e/.env.dev b/e2e/.env.dev deleted file mode 100644 index db4130f..0000000 --- a/e2e/.env.dev +++ /dev/null @@ -1 +0,0 @@ -SOME_API_VAR=foobar.com \ No newline at end of file diff --git a/e2e/Cargo.toml b/e2e/Cargo.toml deleted file mode 100644 index ecca825..0000000 --- a/e2e/Cargo.toml +++ /dev/null @@ -1,9 +0,0 @@ -[package] -name = "e2e" -version = "0.1.0" - - -[dependencies] -anyhow = "1" -reqwest = { version = "0.11.24", features = ["blocking"] } -nix = {version = "0.28.0", features = ["signal"] } diff --git a/e2e/src/boot_cfworker.rs b/e2e/src/boot_cfworker.rs deleted file mode 100644 index 09eddd0..0000000 --- a/e2e/src/boot_cfworker.rs +++ /dev/null @@ -1,76 +0,0 @@ -use std::env; -use std::path::Path; -use std::process::{Child, Command, Stdio}; -use std::thread; -use std::time::Duration; - -use anyhow::Result; - -pub fn boot_worker() -> Result { - let original_cwd = env::current_dir()?; - env::set_current_dir(Path::new("../worker"))?; - - Command::new("npm") - .arg("install") - .arg("wrangler@latest") - .status()?; - - let cmd = Command::new("npx") - .arg("wrangler@latest") - .arg("dev") - .stdout(Stdio::null()) - // .stdout(Stdio::inherit()) - // DEBUG POINT, use inherit stderr to see wrangler output - .stderr(Stdio::null()) - // .stderr(Stdio::inherit()) - .spawn()?; - - thread::sleep(Duration::from_secs(5)); - - env::set_current_dir(original_cwd)?; - - Ok(cmd) -} - -pub fn wait_worker_started() -> Result<()> { - let mut count = 0; - - loop { - let output = Command::new("bash") - .arg("-c") - .arg("lsof -i tcp:8787") - .output() - .expect("Failed to execute command"); - - if output.status.success() { - println!("Worker started."); - break; - } else if count == 20 { - return Err(anyhow::anyhow!("Command failed after 20 retries")); - } else { - count += 1; - thread::sleep(Duration::from_millis(500)); - } - } - - Ok(()) -} - -pub fn kill_worker() -> Result<()> { - // Run pgrep to find the process ID of the wrangler process - let pgrep_output = Command::new("pgrep").arg("wrangler").output()?; - - // Check if pgrep was successful and the output is not empty - if pgrep_output.status.success() && !pgrep_output.stdout.is_empty() { - // Parse the process ID from the output - let pid_str = String::from_utf8_lossy(&pgrep_output.stdout); - let pid: i32 = pid_str.trim().parse()?; - - // Run the kill command with the process ID as an argument - Command::new("kill").arg(pid.to_string()).status()?; - } else { - println!("No wrangler process found."); - } - - Ok(()) -} diff --git a/e2e/src/boot_server.rs b/e2e/src/boot_server.rs deleted file mode 100644 index caeb3cc..0000000 --- a/e2e/src/boot_server.rs +++ /dev/null @@ -1,18 +0,0 @@ -use anyhow::Result; -use std::process::{Child, Command, Stdio}; - -pub fn boot_background_web_server(port: u16, name: String) -> Result { - let command = format!( - "while true; do echo 'HTTP/1.1 200 OK\r\n\r\n{}' | nc -l {}; done", - name, port - ); - - let c = Command::new("sh") - .arg("-c") - .arg(&command) - .stdout(Stdio::null()) - .stderr(Stdio::null()) - .spawn()?; - - Ok(c) -} diff --git a/e2e/src/main.rs b/e2e/src/main.rs deleted file mode 100644 index 3dcdc09..0000000 --- a/e2e/src/main.rs +++ /dev/null @@ -1,205 +0,0 @@ -extern crate anyhow; -extern crate nix; -extern crate reqwest; - -mod boot_cfworker; -mod boot_server; -mod run_cli; - -use anyhow::{anyhow, Result}; -use nix::sys::signal::{kill, Signal}; -use nix::unistd::Pid; -use reqwest::blocking::Client; -use reqwest::header::REFERER; -use std::fs::{self, read_to_string, File}; -use std::io::Write; -use std::str::FromStr; -use std::time::Duration; -use std::{env, thread}; - -use boot_cfworker::{boot_worker, kill_worker}; -use boot_server::boot_background_web_server; -use run_cli::{build_cli_project, run_cli_binary}; - -use crate::boot_cfworker::wait_worker_started; - -type CleanupFunc = Box Result<()> + 'static + Send>; - -struct Cleanup { - funcs: Vec, -} - -impl Cleanup { - fn new() -> Self { - Cleanup { funcs: Vec::new() } - } - - fn add Result<()> + 'static + Send>(&mut self, f: F) { - self.funcs.push(Box::new(f)); - } -} - -impl Drop for Cleanup { - fn drop(&mut self) { - for func in self.funcs.drain(..) { - if let Err(e) = func() { - eprintln!("Error during cleanup: {}", e); - } - } - } -} - -fn run_with_cleanup() -> Result<()> { - const CONF_STR: &str = r#" - linkup: - remote: http://localhost:8787 - services: - - name: frontend - remote: https://example.com - local: http://localhost:8901 - rewrites: - - source: /foo/(.*) - target: /bar/$1 - - name: backend - remote: http://menti.com - local: http://localhost:8911 - # Set dir to this to test env var copies - directory: ./ - domains: - - domain: somedomain.com - default_service: frontend - routes: - - path: /api/v1/.* - service: backend - - domain: api.somedomain.com - default_service: backend - "#; - - let mut cleanup = Cleanup::new(); - - build_cli_project()?; - - let mut file = File::create("e2e_conf.yml")?; - file.write_all(CONF_STR.as_bytes())?; - cleanup.add(|| std::fs::remove_file("e2e_conf.yml").map_err(anyhow::Error::from)); - - boot_worker()?; - cleanup.add(kill_worker); - wait_worker_started()?; - - let (out, err) = run_cli_binary(vec!["start", "-c", "e2e_conf.yml"])?; - println!("out: {}", out); - println!("err: {}", err); - cleanup.add(move || { - // print_linkup_files().expect("print_linkup_files failed"); - std::fs::remove_dir_all(format!("{}/.linkup", env::var("HOME").unwrap())) - .map_err(anyhow::Error::from) - }); - - let env_file = read_to_string(".env")?; - if !env_file.contains("SOME_API_VAR=foobar.com") { - return Err(anyhow::Error::msg( - "env file does not contain SOME_API_VAR=foobar.com after start", - )); - } - - let referer_to = format!("http://{}.somedomain.com", out.trim()); - println!("referer_to: {}", referer_to); - println!("referer_to: {}", referer_to); - - let mut front_remote = boot_background_web_server(8901, String::from("front_remote"))?; - cleanup.add(move || front_remote.kill().map_err(anyhow::Error::from)); - - let mut back_remote = boot_background_web_server(8911, String::from("back_remote"))?; - cleanup.add(move || back_remote.kill().map_err(anyhow::Error::from)); - - let client = Client::new(); - - let resp = client - .get("http://localhost:8787") - .header(REFERER, &referer_to) - .send()?; - - if resp.status().as_u16() != 200 { - return Err(anyhow::Error::msg("status code is not 200")); - } - if !String::from_utf8(resp.bytes().unwrap().to_vec()) - .unwrap() - .contains("Example Domain") - { - return Err(anyhow::Error::msg("body does not contain Example Domain")); - } - - let (out, err) = run_cli_binary(vec!["local", "frontend"])?; - println!("out: {}", out); - println!("err: {}", err); - - thread::sleep(Duration::from_secs(1)); - - let resp = client - .get("http://localhost:8787") - .header(REFERER, referer_to) - .send()?; - - if resp.status().as_u16() != 200 { - return Err(anyhow::Error::msg("status code is not 200")); - } - if !String::from_utf8(resp.bytes().unwrap().to_vec()) - .unwrap() - .contains("front_remote") - { - return Err(anyhow::Error::msg( - "session did not route to local domain after local switch", - )); - } - - let localserver_pid_file = fs::read_to_string(format!( - "{}/.linkup/localserver-pid", - env::var("HOME").unwrap() - )) - .unwrap(); - let cloudflared_pid_file = fs::read_to_string(format!( - "{}/.linkup/cloudflared-pid", - env::var("HOME").unwrap() - )) - .unwrap(); - let localserver_pid = localserver_pid_file.trim(); - let cloudflared_pid = cloudflared_pid_file.trim(); - - run_cli_binary(vec!["stop"])?; - - thread::sleep(Duration::from_secs(2)); - - check_process_dead(localserver_pid)?; - check_process_dead(cloudflared_pid)?; - - let env_file = read_to_string(".env")?; - if env_file.contains("SOME_API_VAR=foobar.com") { - return Err(anyhow::Error::msg( - "env file should not contain SOME_API_VAR=foobar.com after stop", - )); - } - - Ok(()) -} - -fn check_process_dead(pid_str: &str) -> Result<()> { - // Parse the PID string to a i32 - let pid_num = i32::from_str(pid_str).map_err(|e| anyhow!("Failed to parse PID: {}", e))?; - - // Create a Pid from the i32 - let pid = Pid::from_raw(pid_num); - - // Use the kill function with a signal of 0 to check if the process is alive - match kill(pid, Some(Signal::SIGCHLD)) { - Ok(_) => Err(anyhow!("Process is still alive")), - Err(_) => Ok(()), - } -} - -fn main() { - if let Err(e) = run_with_cleanup() { - println!("An error occurred: {}", e); - // Perform any additional error handling or logging here - } -} diff --git a/e2e/src/run_cli.rs b/e2e/src/run_cli.rs deleted file mode 100644 index 9c72b36..0000000 --- a/e2e/src/run_cli.rs +++ /dev/null @@ -1,86 +0,0 @@ -use anyhow::{anyhow, Result}; -use std::env; -use std::io::{BufReader, Read}; -use std::path::Path; -use std::process::{Command, Stdio}; -use std::thread; - -pub fn build_cli_project() -> Result<()> { - // Store the original current working directory - let original_cwd = env::current_dir()?; - - env::set_current_dir(Path::new("../linkup-cli"))?; - - let mut cmd = Command::new("cargo") - .arg("build") - .arg("--release") - .stdout(Stdio::inherit()) - .stderr(Stdio::inherit()) - .spawn()?; - - // Wait for the child process to finish - let status = cmd.wait()?; - - // Restore the original current working directory - env::set_current_dir(original_cwd)?; - - if !status.success() { - Err(anyhow!("Command failed: {}", status,))?; - } - - Ok(()) -} - -pub fn run_cli_binary(args: Vec<&str>) -> Result<(String, String)> { - // Build the path to the compiled binary - let binary_path = Path::new("../target/release/linkup"); - - // Run the compiled binary with the provided arguments and capture stdout and stderr - let mut cmd = Command::new(binary_path) - .args(args) - .stdout(Stdio::piped()) - .stderr(Stdio::piped()) - .spawn()?; - - // Separate variables for stdout and stderr before spawning threads - let stdout_pipe = cmd.stdout.take(); - let stderr_pipe = cmd.stderr.take(); - - // Spawn a thread to read stdout - let stdout_handle = thread::spawn(move || { - let mut stdout = String::new(); - if let Some(mut out) = stdout_pipe { - let mut reader = BufReader::new(&mut out); - reader.read_to_string(&mut stdout).unwrap(); - } - stdout - }); - - // Spawn a thread to read stderr - let stderr_handle = thread::spawn(move || { - let mut stderr = String::new(); - if let Some(mut err) = stderr_pipe { - let mut reader = BufReader::new(&mut err); - reader.read_to_string(&mut stderr).unwrap(); - } - stderr - }); - - // Wait for the child process to finish - let status = cmd.wait()?; - - // Join the stdout and stderr reading threads - let stdout = stdout_handle.join().unwrap(); - let stderr = stderr_handle.join().unwrap(); - - if !status.success() { - Err(anyhow!( - "Command failed: {}, {}, {}", - status, - stdout, - stderr - ))?; - } - - Ok((stdout, stderr)) -} diff --git a/server-tests/tests/helpers.rs b/server-tests/tests/helpers.rs index 081da49..c8e0e8a 100644 --- a/server-tests/tests/helpers.rs +++ b/server-tests/tests/helpers.rs @@ -1,9 +1,9 @@ use std::process::Command; -use linkup::{CreatePreviewRequest, StorableDomain, StorableService, UpdateSessionRequest}; +use linkup::{StorableDomain, StorableService, UpdateSessionRequest}; use linkup_local_server::linkup_router; use reqwest::Url; -use tokio::{net::TcpListener, sync::OnceCell}; +use tokio::net::TcpListener; #[derive(Debug)] pub enum ServerKind { @@ -69,26 +69,6 @@ pub fn create_session_request(name: String, fe_location: Option) -> Stri serde_json::to_string(&req).unwrap() } -pub fn create_preview_request(fe_location: Option) -> String { - let location = match fe_location { - Some(location) => location, - None => "http://example.com".to_string(), - }; - let req = CreatePreviewRequest { - domains: vec![StorableDomain { - domain: "example.com".to_string(), - default_service: "frontend".to_string(), - routes: None, - }], - services: vec![StorableService { - name: "frontend".to_string(), - location: Url::parse(&location).unwrap(), - rewrites: None, - }], - }; - serde_json::to_string(&req).unwrap() -} - pub fn check_worker_running() -> bool { let output = Command::new("bash") .arg("-c") diff --git a/server-tests/tests/server_test.rs b/server-tests/tests/server_test.rs index 1e8cfab..2920088 100644 --- a/server-tests/tests/server_test.rs +++ b/server-tests/tests/server_test.rs @@ -1,7 +1,9 @@ use helpers::ServerKind; +use linkup::{CreatePreviewRequest, StorableDomain, StorableService}; +use reqwest::Url; use rstest::rstest; -use crate::helpers::{create_preview_request, create_session_request, post, setup_server}; +use crate::helpers::{create_session_request, post, setup_server}; mod helpers; @@ -73,3 +75,23 @@ pub async fn get(url: String) -> reqwest::Response { .await .expect("Failed to send request") } + +pub fn create_preview_request(fe_location: Option) -> String { + let location = match fe_location { + Some(location) => location, + None => "http://example.com".to_string(), + }; + let req = CreatePreviewRequest { + domains: vec![StorableDomain { + domain: "example.com".to_string(), + default_service: "frontend".to_string(), + routes: None, + }], + services: vec![StorableService { + name: "frontend".to_string(), + location: Url::parse(&location).unwrap(), + rewrites: None, + }], + }; + serde_json::to_string(&req).unwrap() +} diff --git a/worker/src/http_util.rs b/worker/src/http_util.rs deleted file mode 100644 index 9da859d..0000000 --- a/worker/src/http_util.rs +++ /dev/null @@ -1,79 +0,0 @@ -use linkup::{unpack_cookie_header, HeaderMap as LinkupHeaderMap}; -// use reqwest::{Method as ReqwestMethod, Response as ReqwestResponse}; -use std::convert::TryFrom; -use worker::{ - console_log, Headers as CfHeaders, Method as CfMethod, Response as CfResponse, - Result as CfResult, -}; - -const SET_COOKIE: &str = "set-cookie"; - -pub fn plaintext_error(msg: impl Into, status: u16) -> CfResult { - let mut resp = CfResponse::error(msg, status)?; - let headers = resp.headers_mut(); - headers.set("Content-Type", "text/plain")?; - - Ok(resp) -} - -// pub fn convert_cf_method_to_reqwest( -// cf_method: &CfMethod, -// ) -> Result { -// let method_str = match cf_method { -// CfMethod::Get => "GET", -// CfMethod::Post => "POST", -// CfMethod::Put => "PUT", -// CfMethod::Delete => "DELETE", -// CfMethod::Options => "OPTIONS", -// CfMethod::Head => "HEAD", -// CfMethod::Patch => "PATCH", -// CfMethod::Connect => "CONNECT", -// CfMethod::Trace => "TRACE", -// }; - -// ReqwestMethod::try_from(method_str) -// } - -// pub async fn convert_reqwest_response_to_cf( -// response: ReqwestResponse, -// extra_headers: &LinkupHeaderMap, -// ) -> worker::Result { -// let status = response.status(); -// let headers = response.headers().clone(); - -// let body_bytes = match response.bytes().await { -// Ok(bytes) => bytes, -// Err(_) => return CfResponse::error("Error reading response body", 502), -// }; - -// let cf_response = match CfResponse::from_bytes(body_bytes.to_vec()) { -// Ok(response) => response, -// Err(_) => return CfResponse::error("Error creating response body", 500), -// }; - -// let mut cf_headers = CfHeaders::from(headers); - -// for (key, value) in extra_headers.into_iter() { -// let header_res = cf_headers.set(&key, &value); -// if header_res.is_err() { -// console_log!("failed to set response header: {}", header_res.unwrap_err()); -// } -// } - -// // WASM / web_sys does not support getSetCookie, so we unwrap cookies ourselves -// if let Ok(Some(folded_cookies)) = cf_headers.get(SET_COOKIE) { -// let cookies = unpack_cookie_header(folded_cookies); -// cf_headers.delete(SET_COOKIE).expect("set-cookie-valid"); - -// for cookie in cookies { -// cf_headers -// .append(SET_COOKIE, &cookie.to_string()) -// .expect("set-cookie-valid"); -// } -// } - -// let cf_response = cf_response.with_headers(cf_headers); -// let cf_response = cf_response.with_status(status.into()); - -// Ok(cf_response) -// }