From 33c4669bd9cc254848bf38f7570103fa81078fff Mon Sep 17 00:00:00 2001 From: Brooks Townsend Date: Mon, 15 May 2023 14:45:01 -0400 Subject: [PATCH 1/9] initial working commit launching burritos Signed-off-by: Brooks Townsend --- crates/wash-lib/src/start/wasmcloud.rs | 169 +++++++++---------------- src/down/mod.rs | 26 ++-- src/up/config.rs | 2 +- 3 files changed, 75 insertions(+), 122 deletions(-) diff --git a/crates/wash-lib/src/start/wasmcloud.rs b/crates/wash-lib/src/start/wasmcloud.rs index e4f2f642..e0c2329f 100644 --- a/crates/wash-lib/src/start/wasmcloud.rs +++ b/crates/wash-lib/src/start/wasmcloud.rs @@ -6,15 +6,12 @@ use std::path::{Path, PathBuf}; use std::process::Stdio; use anyhow::{anyhow, Result}; -use async_compression::tokio::bufread::GzipDecoder; #[cfg(target_family = "unix")] use command_group::AsyncCommandGroup; use futures::future::join_all; use log::warn; use tokio::fs::{create_dir_all, metadata, File}; use tokio::process::{Child, Command}; -use tokio_stream::StreamExt; -use tokio_tar::Archive; const WASMCLOUD_GITHUB_RELEASE_URL: &str = "https://github.com/wasmCloud/wasmcloud-otp/releases/download"; @@ -23,15 +20,16 @@ pub const WASMCLOUD_HOST_BIN: &str = "bin/wasmcloud_host"; #[cfg(target_family = "windows")] pub const WASMCLOUD_HOST_BIN: &str = "bin\\wasmcloud_host.bat"; -// Any version of wasmCloud under 0.57.0 uses distillery releases and is incompatible -const MINIMUM_WASMCLOUD_VERSION: &str = "0.57.0"; +// Any version of wasmCloud under 0.63.0 uses Elixir releases and is incompatible +// See https://github.com/wasmCloud/wasmcloud-otp/pull/616 for the move to burrito releases +const MINIMUM_WASMCLOUD_VERSION: &str = "0.63.0"; /// A wrapper around the [ensure_wasmcloud_for_os_arch_pair] function that uses the /// architecture and operating system of the current host machine. /// /// # Arguments /// -/// * `version` - Specifies the version of the binary to download in the form of `vX.Y.Z`. Must be at least v0.57.0. +/// * `version` - Specifies the version of the binary to download in the form of `vX.Y.Z`. Must be at least v0.63.0. /// * `dir` - Where to unpack the wasmCloud host contents into /// # Examples /// @@ -39,9 +37,9 @@ const MINIMUM_WASMCLOUD_VERSION: &str = "0.57.0"; /// # #[tokio::main] /// # async fn main() { /// use wash_lib::start::ensure_wasmcloud; -/// let res = ensure_wasmcloud("v0.57.1", "/tmp/wasmcloud/").await; +/// let res = ensure_wasmcloud("v0.63.0", "/tmp/wasmcloud/").await; /// assert!(res.is_ok()); -/// assert!(res.unwrap().to_string_lossy() == "/tmp/wasmcloud/bin/wasmcloud_host".to_string()); +/// assert!(res.unwrap().to_string_lossy() == "/tmp/wasmcloud/wasmcloud_host".to_string()); /// # } /// ``` pub async fn ensure_wasmcloud

(version: &str, dir: P) -> Result @@ -55,17 +53,17 @@ where /// Ensures the `wasmcloud_host` application is installed, returning the path to the executable /// early if it exists or downloading the specified GitHub release version of the wasmCloud host /// from and unpacking the contents for a -/// specified OS/ARCH pair to a directory. Returns the path to the Elixir executable. +/// specified OS/ARCH pair to a directory. Returns the path to the executable. /// /// # Arguments /// /// * `os` - Specifies the operating system of the binary to download, e.g. `linux` /// * `arch` - Specifies the architecture of the binary to download, e.g. `amd64` /// * `version` - Specifies the version of the binary to download in the form of `vX.Y.Z`. Must be -/// at least v0.57.0. +/// at least v0.63.0. /// * `dir` - Where to unpack the wasmCloud host contents into. This should be the root level /// directory where to store hosts. Each host will be stored in a directory maching its version -/// (e.g. "/tmp/wasmcloud/v0.59.0") +/// (e.g. "/tmp/wasmcloud/v0.63.0") /// # Examples /// /// ```no_run @@ -74,7 +72,8 @@ where /// use wash_lib::start::ensure_wasmcloud_for_os_arch_pair; /// let os = std::env::consts::OS; /// let arch = std::env::consts::ARCH; -/// let res = ensure_wasmcloud_for_os_arch_pair(os, arch, "v0.57.1", "/tmp/wasmcloud/").await; +/// TODO: Check to see if this really always stores in a version folder +/// let res = ensure_wasmcloud_for_os_arch_pair(os, arch, "v0.63.0", "/tmp/wasmcloud/").await; /// assert!(res.is_ok()); /// assert!(res.unwrap().to_string_lossy() == "/tmp/wasmcloud/bin/wasmcloud_host".to_string()); /// # } @@ -88,7 +87,8 @@ pub async fn ensure_wasmcloud_for_os_arch_pair

( where P: AsRef, { - check_version(version)?; + //TODO: check version with 0.63.0 + // check_version(version)?; if let Some(dir) = find_wasmcloud_binary(&dir, version).await { // wasmCloud already exists, return early return Ok(dir); @@ -134,7 +134,7 @@ where /// * `arch` - Specifies the architecture of the binary to download, e.g. `amd64` /// * `version` - Specifies the version of the binary to download in the form of `vX.Y.Z` /// * `dir` - Where to unpack the wasmCloud host contents into. This should be the root level -/// directory where to store hosts. Each host will be stored in a directory maching its version +/// directory where to store hosts. Each host will be stored in a directory maching its version TODO: verifty this /// # Examples /// /// ```no_run @@ -143,9 +143,9 @@ where /// use wash_lib::start::download_wasmcloud_for_os_arch_pair; /// let os = std::env::consts::OS; /// let arch = std::env::consts::ARCH; -/// let res = download_wasmcloud_for_os_arch_pair(os, arch, "v0.57.1", "/tmp/wasmcloud/").await; +/// let res = download_wasmcloud_for_os_arch_pair(os, arch, "v0.63.0", "/tmp/wasmcloud/").await; /// assert!(res.is_ok()); -/// assert!(res.unwrap().to_string_lossy() == "/tmp/wasmcloud/bin/wasmcloud_host".to_string()); +/// assert!(res.unwrap().to_string_lossy() == "/tmp/wasmcloud/wasmcloud_host".to_string()); /// # } /// ``` pub async fn download_wasmcloud_for_os_arch_pair

( @@ -158,50 +158,29 @@ where P: AsRef, { let url = wasmcloud_url(os, arch, version); - let body = reqwest::get(url).await?.bytes().await?; - let cursor = Cursor::new(body); - let mut wasmcloud_host = Archive::new(Box::new(GzipDecoder::new(cursor))); - let mut entries = wasmcloud_host.entries()?; + println!("Downloading from {url}"); + //TODO: why need do this many conversions + let wasmcloud_host_burrito = reqwest::get(url).await?.bytes().await?.to_vec(); let version_dir = dir.as_ref().join(version); - // Copy all of the files out of the tarball into the bin directory - while let Some(res) = entries.next().await { - let mut entry = res.map_err(|_e| { - anyhow!( - "Failed to retrieve file from archive, ensure wasmcloud_host version '{}' exists", - version - ) - })?; - if let Ok(path) = entry.path() { - let file_path = version_dir.join(path); - if let Some(parent_folder) = file_path.parent() { - // If the user doesn't have permission to create files in the provided directory, - // this will bubble the error up noting permission denied - create_dir_all(parent_folder).await?; - } - if let Ok(mut wasmcloud_file) = File::create(&file_path).await { - // This isn't an `if let` to avoid a Windows lint warning - if file_path.file_name().is_some() { - // Set permissions of executable files and binaries to allow executing - #[cfg(target_family = "unix")] - { - let file_name = file_path.file_name().unwrap().to_string_lossy(); - if file_path.to_string_lossy().contains("bin") - || file_name.contains(".sh") - || file_name.contains(".bat") - || file_name.eq("iex") - || file_name.eq("elixir") - || file_name.eq("wasmcloud_host") - || file_name.eq("mac_listener") - { - let mut perms = wasmcloud_file.metadata().await?.permissions(); - perms.set_mode(0o755); - wasmcloud_file.set_permissions(perms).await?; - } - } - } - tokio::io::copy(&mut entry, &mut wasmcloud_file).await?; + //TODO: diff for windows, exe, constant + let file_path = version_dir.join("wasmcloud_host"); + if let Some(parent_folder) = file_path.parent() { + // If the user doesn't have permission to create files in the provided directory, + // this will bubble the error up noting permission denied + create_dir_all(parent_folder).await?; + } + if let Ok(mut wasmcloud_file) = File::create(&file_path).await { + // This isn't an `if let` to avoid a Windows lint warning + if file_path.file_name().is_some() { + // Set permissions of executable files and binaries to allow executing + #[cfg(target_family = "unix")] + { + let mut perms = wasmcloud_file.metadata().await?.permissions(); + perms.set_mode(0o755); + wasmcloud_file.set_permissions(perms).await?; } } + tokio::io::copy(&mut wasmcloud_host_burrito.as_slice(), &mut wasmcloud_file).await?; } // Return success if wasmCloud components exist, error otherwise @@ -212,10 +191,11 @@ where )), } } -/// Helper function to start a wasmCloud host given the path to the elixir release script + +/// Helper function to start a wasmCloud host given the path to the burrito release application /// /// # Arguments /// -/// * `bin_path` - Path to the wasmcloud_host script to execute +/// * `bin_path` - Path to the wasmcloud_host burrito application /// * `stdout` - Specify where wasmCloud stdout logs should be written to. Logs can be written to stdout by the erlang process /// * `stderr` - Specify where wasmCloud stderr logs should be written to. Logs are written to stderr that are generated by wasmCloud /// * `env_vars` - Environment variables to pass to the host, see for details @@ -245,23 +225,6 @@ where )); } - #[cfg(target_family = "unix")] - if let Ok(output) = Command::new(bin_path.as_ref()) - .envs(&env_vars) - .arg("pid") - .output() - .await - { - // Stderr will include :nodedown if no other host is running, otherwise - // stdout will contain the PID - if !String::from_utf8_lossy(&output.stderr).contains(":nodedown") { - return Err(anyhow!( - "Another wasmCloud host is already running on this machine with PID {}", - String::from_utf8_lossy(&output.stdout) - )); - } - } - // Constructing this object in one step results in a temporary value that's dropped let mut cmd = Command::new(bin_path.as_ref()); let cmd = cmd @@ -269,8 +232,7 @@ where .stderr(stderr) .stdout(stdout) .stdin(Stdio::null()) - .envs(&env_vars) - .arg("start"); + .envs(&env_vars); #[cfg(target_family = "unix")] { @@ -289,17 +251,8 @@ where P: AsRef, { let versioned_dir = dir.as_ref().join(version); - let bin_dir = versioned_dir.join("bin"); - let bin_file = versioned_dir.join(WASMCLOUD_HOST_BIN); - let lib_dir = versioned_dir.join("lib"); - let releases_dir = versioned_dir.join("releases"); - let file_checks = vec![ - metadata(versioned_dir), - metadata(bin_dir), - metadata(bin_file.clone()), - metadata(lib_dir), - metadata(releases_dir), - ]; + let bin_file = versioned_dir.join("wasmcloud_host"); + let file_checks = vec![metadata(bin_file.clone())]; join_all(file_checks) .await .iter() @@ -309,7 +262,8 @@ where /// Helper function to determine the wasmCloud host release path given an os/arch and version fn wasmcloud_url(os: &str, arch: &str, version: &str) -> String { - format!("{WASMCLOUD_GITHUB_RELEASE_URL}/{version}/{arch}-{os}.tar.gz") + let os = os.replace("macos", "darwin"); + format!("{WASMCLOUD_GITHUB_RELEASE_URL}/{version}/wasmcloud_host_{arch}_{os}") } /// Helper function to ensure the version of wasmCloud is above the minimum @@ -460,7 +414,7 @@ mod test { } const NATS_SERVER_VERSION: &str = "v2.8.4"; - const WASMCLOUD_HOST_VERSION: &str = "v0.61.0"; + const WASMCLOUD_HOST_VERSION: &str = "v0.62.2-burrito-test"; #[tokio::test] async fn can_download_and_start_wasmcloud() -> anyhow::Result<()> { @@ -563,24 +517,21 @@ mod test { .await .expect_err("Starting a second process should error"); - // Should fail because another erlang wasmcloud_host node is running - #[cfg(target_family = "unix")] - // Windows is unable to properly check running erlang nodes with `pid` - { - let mut host_env = HashMap::new(); - host_env.insert("WASMCLOUD_DASHBOARD_PORT".to_string(), "4002".to_string()); - host_env.insert("WASMCLOUD_RPC_PORT".to_string(), nats_port.to_string()); - host_env.insert("WASMCLOUD_CTL_PORT".to_string(), nats_port.to_string()); - host_env.insert("WASMCLOUD_PROV_RPC_PORT".to_string(), nats_port.to_string()); - let child_res = start_wasmcloud_host( - &wasmcloud_binary, - std::process::Stdio::null(), - std::process::Stdio::null(), - host_env, - ) - .await; - assert!(child_res.is_err()); - } + // Burrito releases (0.63.0+) do support multiple hosts, so this should work fine + let mut host_env = HashMap::new(); + host_env.insert("WASMCLOUD_DASHBOARD_PORT".to_string(), "4002".to_string()); + host_env.insert("WASMCLOUD_RPC_PORT".to_string(), nats_port.to_string()); + host_env.insert("WASMCLOUD_CTL_PORT".to_string(), nats_port.to_string()); + host_env.insert("WASMCLOUD_PROV_RPC_PORT".to_string(), nats_port.to_string()); + let child_res = start_wasmcloud_host( + &wasmcloud_binary, + std::process::Stdio::null(), + std::process::Stdio::null(), + host_env, + ) + .await; + assert!(child_res.is_ok()); + child_res.unwrap().kill().await?; host_child.kill().await?; nats_child.kill().await?; diff --git a/src/down/mod.rs b/src/down/mod.rs index a804aa01..02ca6df7 100644 --- a/src/down/mod.rs +++ b/src/down/mod.rs @@ -33,6 +33,8 @@ pub(crate) async fn handle_down( let mut out_json = HashMap::new(); let mut out_text = String::from(""); + + //TODO: stop host via nats instead of local let version = tokio::fs::read_to_string(install_dir.join(WASMCLOUD_PID_FILE)) .await .map_err(|e| { @@ -59,6 +61,18 @@ pub(crate) async fn handle_down( } } + match stop_wadm(&install_dir).await { + Ok(_) => { + tokio::fs::remove_file(&install_dir.join(WADM_PID)).await?; + out_json.insert("wadm_stopped".to_string(), json!(true)); + out_text.push_str("✅ wadm stopped successfully\n"); + } + Err(e) => { + out_json.insert("wadm_stopped".to_string(), json!(false)); + out_text.push_str(&format!("❌ wadm did not stop successfully: {e:?}\n")); + } + } + let nats_bin = install_dir.join(NATS_SERVER_BINARY); if nats_bin.is_file() { sp.update_spinner_message(" Stopping NATS server ...".to_string()); @@ -73,18 +87,6 @@ pub(crate) async fn handle_down( } } - match stop_wadm(&install_dir).await { - Ok(_) => { - tokio::fs::remove_file(&install_dir.join(WADM_PID)).await?; - out_json.insert("wadm_stopped".to_string(), json!(true)); - out_text.push_str("✅ wadm stopped successfully\n"); - } - Err(e) => { - out_json.insert("wadm_stopped".to_string(), json!(false)); - out_text.push_str(&format!("❌ wadm did not stop successfully: {e:?}\n")); - } - } - out_json.insert("success".to_string(), json!(true)); out_text.push_str("🛁 wash down completed successfully"); diff --git a/src/up/config.rs b/src/up/config.rs index 2f67a41f..f2511421 100644 --- a/src/up/config.rs +++ b/src/up/config.rs @@ -11,7 +11,7 @@ pub(crate) const DEFAULT_NATS_PORT: &str = "4222"; // wadm configuration values pub(crate) const WADM_VERSION: &str = "v0.4.0"; // wasmCloud configuration values, https://wasmcloud.dev/reference/host-runtime/host_configure/ -pub(crate) const WASMCLOUD_HOST_VERSION: &str = "v0.62.1"; +pub(crate) const WASMCLOUD_HOST_VERSION: &str = "v0.62.2-burrito-test"; pub(crate) const WASMCLOUD_DASHBOARD_PORT: &str = "WASMCLOUD_DASHBOARD_PORT"; pub(crate) const DEFAULT_DASHBOARD_PORT: &str = "4000"; // NATS isolation configuration variables From a0c8205be8fbb93c3f0cf564418904f5ac501024 Mon Sep 17 00:00:00 2001 From: Brooks Townsend Date: Tue, 16 May 2023 14:34:24 -0400 Subject: [PATCH 2/9] implement starting multiple hosts in quick succession Signed-off-by: Brooks Townsend --- .gitignore | 2 + crates/wash-lib/src/start/nats.rs | 8 ++ crates/wash-lib/src/start/wasmcloud.rs | 4 +- src/down/mod.rs | 12 +-- src/up/config.rs | 2 +- src/up/mod.rs | 119 ++++++++++++++++++------- 6 files changed, 104 insertions(+), 43 deletions(-) diff --git a/.gitignore b/.gitignore index b0e4ad47..98c533fe 100644 --- a/.gitignore +++ b/.gitignore @@ -23,3 +23,5 @@ docker-compose.yml # Host config files host_config.json +# No dumps in git +*.dump \ No newline at end of file diff --git a/crates/wash-lib/src/start/nats.rs b/crates/wash-lib/src/start/nats.rs index fe9db5d4..e8974b3d 100644 --- a/crates/wash-lib/src/start/nats.rs +++ b/crates/wash-lib/src/start/nats.rs @@ -268,6 +268,14 @@ where .map(|_| child) } +/// Helper function to get the path to the NATS server pid file +pub fn nats_pid_path

(install_dir: P) -> PathBuf +where + P: AsRef, +{ + install_dir.as_ref().join(NATS_SERVER_PID) +} + /// Helper function to determine the NATS server release path given an os/arch and version fn nats_url(os: &str, arch: &str, version: &str) -> String { // Replace "macos" with "darwin" to match NATS release scheme diff --git a/crates/wash-lib/src/start/wasmcloud.rs b/crates/wash-lib/src/start/wasmcloud.rs index e0c2329f..19eaa6c8 100644 --- a/crates/wash-lib/src/start/wasmcloud.rs +++ b/crates/wash-lib/src/start/wasmcloud.rs @@ -1,5 +1,4 @@ use std::collections::HashMap; -use std::io::Cursor; #[cfg(target_family = "unix")] use std::os::unix::prelude::PermissionsExt; use std::path::{Path, PathBuf}; @@ -23,6 +22,7 @@ pub const WASMCLOUD_HOST_BIN: &str = "bin\\wasmcloud_host.bat"; // Any version of wasmCloud under 0.63.0 uses Elixir releases and is incompatible // See https://github.com/wasmCloud/wasmcloud-otp/pull/616 for the move to burrito releases const MINIMUM_WASMCLOUD_VERSION: &str = "0.63.0"; +const DEFAULT_DASHBOARD_PORT: u16 = 4000; /// A wrapper around the [ensure_wasmcloud_for_os_arch_pair] function that uses the /// architecture and operating system of the current host machine. @@ -214,7 +214,7 @@ where let port = env_vars .get("WASMCLOUD_DASHBOARD_PORT") .cloned() - .unwrap_or_else(|| "4000".to_string()); + .unwrap_or_else(|| DEFAULT_DASHBOARD_PORT.to_string()); if tokio::net::TcpStream::connect(format!("localhost:{port}")) .await .is_ok() diff --git a/src/down/mod.rs b/src/down/mod.rs index 02ca6df7..11b48822 100644 --- a/src/down/mod.rs +++ b/src/down/mod.rs @@ -1,5 +1,5 @@ use std::collections::HashMap; -use std::path::{Path, PathBuf}; +use std::path::Path; use std::process::{Output, Stdio}; use std::time::Duration; @@ -8,7 +8,7 @@ use clap::Parser; use serde_json::json; use tokio::process::Command; use wash_lib::cli::{CommandOutput, OutputKind}; -use wash_lib::start::{find_wasmcloud_binary, NATS_SERVER_BINARY, NATS_SERVER_PID, WADM_PID}; +use wash_lib::start::{find_wasmcloud_binary, nats_pid_path, NATS_SERVER_BINARY, WADM_PID}; use crate::appearance::spinner::Spinner; use crate::cfg::cfg_dir; @@ -134,14 +134,6 @@ where output } -/// Helper function to get the path to the NATS server pid file -pub(crate) fn nats_pid_path

(install_dir: P) -> PathBuf -where - P: AsRef, -{ - install_dir.as_ref().join(NATS_SERVER_PID) -} - /// Helper function to kill the wadm process pub(crate) async fn stop_wadm

(install_dir: P) -> Result where diff --git a/src/up/config.rs b/src/up/config.rs index f2511421..16237987 100644 --- a/src/up/config.rs +++ b/src/up/config.rs @@ -50,7 +50,7 @@ pub(crate) const WASMCLOUD_PROV_RPC_TLS: &str = "WASMCLOUD_PROV_RPC_TLS"; pub(crate) const WASMCLOUD_OCI_ALLOWED_INSECURE: &str = "WASMCLOUD_OCI_ALLOWED_INSECURE"; pub(crate) const WASMCLOUD_OCI_ALLOW_LATEST: &str = "WASMCLOUD_OCI_ALLOW_LATEST"; // Extra configuration (logs, IPV6, config service) -pub(crate) const WASMCLOUD_STRUCTURED_LOG_LEVEL: &str = "WASMCLOUD_STRUCTURED_LOG_LEVEL"; +pub(crate) const WASMCLOUD_STRUCTURED_LOG_LEVEL: &str = "WASMCLOUD_LOG_LEVEL"; pub(crate) const DEFAULT_STRUCTURED_LOG_LEVEL: &str = "info"; pub(crate) const WASMCLOUD_ENABLE_IPV6: &str = "WASMCLOUD_ENABLE_IPV6"; pub(crate) const WASMCLOUD_STRUCTURED_LOGGING_ENABLED: &str = diff --git a/src/up/mod.rs b/src/up/mod.rs index 6f0afe6a..b1c9dc8a 100644 --- a/src/up/mod.rs +++ b/src/up/mod.rs @@ -9,6 +9,7 @@ use std::sync::{ }; use anyhow::{anyhow, Result}; +use async_nats::Client; use clap::Parser; use serde_json::json; @@ -19,6 +20,7 @@ use tokio::{ }; use wash_lib::cli::{CommandOutput, OutputKind}; use wash_lib::start::ensure_wadm; +use wash_lib::start::nats_pid_path; use wash_lib::start::start_wadm; use wash_lib::start::WadmConfig; use wash_lib::start::{ @@ -38,6 +40,8 @@ pub use config::DOWNLOADS_DIR; pub use config::WASMCLOUD_PID_FILE; use config::*; +const LOCALHOST: &str = "127.0.0.1"; + #[derive(Parser, Debug, Clone)] pub(crate) struct UpCommand { /// Launch NATS and wasmCloud detached from the current terminal as background processes @@ -244,7 +248,7 @@ pub(crate) struct WasmcloudOpts { pub(crate) enable_structured_logging: bool, /// Controls the verbosity of JSON structured logs from the wasmCloud host - #[clap(long = "structured-log-level", default_value = DEFAULT_STRUCTURED_LOG_LEVEL, env = WASMCLOUD_STRUCTURED_LOG_LEVEL)] + #[clap(long = "log-level", alias = "structured-log-level", default_value = DEFAULT_STRUCTURED_LOG_LEVEL, env = WASMCLOUD_STRUCTURED_LOG_LEVEL)] pub(crate) structured_log_level: String, /// Port to listen on for the wasmCloud dashboard, defaults to 4000 @@ -281,13 +285,18 @@ pub(crate) async fn handle_up(cmd: UpCommand, output_kind: OutputKind) -> Result let install_dir = cfg_dir()?.join(DOWNLOADS_DIR); create_dir_all(&install_dir).await?; let spinner = Spinner::new(&output_kind)?; + + // Find an open port for the host, and if the user specified a port, ensure it's open + let host_port = ensure_open_port(cmd.wasmcloud_opts.dashboard_port).await?; + // Capture listen address to keep the value after the nats_opts are moved let nats_listen_address = format!("{}:{}", cmd.nats_opts.nats_host, cmd.nats_opts.nats_port); - // Avoid downloading + starting NATS if the user already runs their own server. Ignore connect_only - // if this server has a remote and credsfile as we have to start a leafnode in that scenario + let nats_client = nats_client_from_wasmcloud_opts(&cmd.wasmcloud_opts).await; let nats_opts = cmd.nats_opts.clone(); - let nats_bin = if !cmd.nats_opts.connect_only + // Avoid downloading + starting NATS if the user already runs their own server and we can connect. + // Ignore connect_only if this server has a remote and credsfile as we have to start a leafnode in that scenario + let nats_bin = if (!cmd.nats_opts.connect_only && nats_client.is_err()) || cmd.nats_opts.nats_remote_url.is_some() && cmd.nats_opts.nats_credsfile.is_some() { // Download NATS if not already installed @@ -298,17 +307,16 @@ pub(crate) async fn handle_up(cmd: UpCommand, output_kind: OutputKind) -> Result start_nats(&install_dir, &nats_binary, cmd.nats_opts.clone()).await?; Some(nats_binary) } else { - // If we can connect to NATS, return None as we aren't managing the child process. - // Otherwise, exit with error since --nats-connect-only was specified - tokio::net::TcpStream::connect(&nats_listen_address) - .await - .map(|_| None) - .map_err(|_| { - anyhow!( - "Could not connect to NATS at {}, exiting since --nats-connect-only was set", - nats_listen_address - ) - })? + // The user is running their own NATS server, so we don't need to download or start one + None + }; + + // Based on the options provided for wasmCloud, form a client connection to NATS. + // If this fails, we should return early since wasmCloud wouldn't be able to connect either + let nats_client = if let Ok(client) = nats_client { + client + } else { + nats_client_from_wasmcloud_opts(&cmd.wasmcloud_opts).await? }; let wadm_process = if !cmd.wadm_opts.disable_wadm @@ -364,7 +372,7 @@ pub(crate) async fn handle_up(cmd: UpCommand, output_kind: OutputKind) -> Result // Redirect output (which is on stderr) to a log file in detached mode, or use the terminal spinner.update_spinner_message(" Starting wasmCloud ...".to_string()); - let wasmcloud_log_path = install_dir.join("wasmcloud.log"); + let wasmcloud_log_path = install_dir.join(format!("wasmcloud_{host_port}.log")); let stderr: Stdio = if cmd.detached { tokio::fs::File::create(&wasmcloud_log_path) .await? @@ -374,13 +382,13 @@ pub(crate) async fn handle_up(cmd: UpCommand, output_kind: OutputKind) -> Result } else { Stdio::piped() }; - let dashboard_port = cmd - .wasmcloud_opts - .dashboard_port - .map(|p| p.to_string()) - .unwrap_or_else(|| DEFAULT_DASHBOARD_PORT.to_string()); let version = cmd.wasmcloud_opts.wasmcloud_version.clone(); - let host_env = configure_host_env(nats_opts, cmd.wasmcloud_opts).await; + // Ensure we use the open dashboard port + let wasmcloud_opts = WasmcloudOpts { + dashboard_port: Some(host_port), + ..cmd.wasmcloud_opts + }; + let host_env = configure_host_env(nats_opts, wasmcloud_opts).await; let wasmcloud_child = match start_wasmcloud_host( &wasmcloud_executable, std::process::Stdio::null(), @@ -392,17 +400,18 @@ pub(crate) async fn handle_up(cmd: UpCommand, output_kind: OutputKind) -> Result Ok(child) => child, Err(e) => { // Ensure we clean up the NATS server and wadm if we can't start wasmCloud - if !cmd.nats_opts.connect_only { - stop_nats(install_dir).await?; - } if let Some(mut child) = wadm_process { child.kill().await?; } + //TODO: only kill NATS if there are no running hosts + if !cmd.nats_opts.connect_only { + stop_nats(install_dir).await?; + } return Err(e); } }; - let url = format!("{}:{}", "127.0.0.1", dashboard_port); + let url = format!("{LOCALHOST}:{}", host_port); if wait_for_server(&url, "Washboard").await.is_err() { if nats_bin.is_some() { stop_nats(install_dir).await?; @@ -412,7 +421,7 @@ pub(crate) async fn handle_up(cmd: UpCommand, output_kind: OutputKind) -> Result spinner.finish_and_clear(); if !cmd.detached { - run_wasmcloud_interactive(wasmcloud_child, output_kind).await?; + run_wasmcloud_interactive(wasmcloud_child, host_port, output_kind).await?; let spinner = Spinner::new(&output_kind)?; spinner.update_spinner_message( @@ -443,7 +452,7 @@ pub(crate) async fn handle_up(cmd: UpCommand, output_kind: OutputKind) -> Result if cmd.detached { // Write the pid file with the selected version tokio::fs::write(install_dir.join(config::WASMCLOUD_PID_FILE), version).await?; - let url = format!("http://localhost:{}", dashboard_port); + let url = format!("http://localhost:{}", host_port); out_json.insert("wasmcloud_url".to_string(), json!(url)); out_json.insert("wasmcloud_log".to_string(), json!(wasmcloud_log_path)); out_json.insert("kill_cmd".to_string(), json!("wash down")); @@ -495,12 +504,21 @@ async fn start_nats(install_dir: &Path, nats_binary: &Path, nats_opts: NatsOpts) .await? .into_std() .await; - start_nats_server(nats_binary, nats_log_file, nats_opts.into()).await + let nats_process = start_nats_server(nats_binary, nats_log_file, nats_opts.into()).await?; + + // save the PID so we can kill it later + if let Some(pid) = nats_process.id() { + let pid_file = nats_pid_path(install_dir); + tokio::fs::write(&pid_file, pid.to_string()).await?; + } + + Ok(nats_process) } /// Helper function to run wasmCloud in interactive mode async fn run_wasmcloud_interactive( mut wasmcloud_child: Child, + port: u16, output_kind: OutputKind, ) -> Result<()> { use std::sync::mpsc::channel; @@ -518,7 +536,7 @@ async fn run_wasmcloud_interactive( .expect("Error setting Ctrl-C handler, please file a bug issue https://github.com/wasmCloud/wash/issues/new/choose"); if output_kind != OutputKind::Json { - println!("🏃 Running in interactive mode, your host is running at http://localhost:4000",); + println!("🏃 Running in interactive mode, your host is running at http://localhost:{port}",); println!("🚪 Press `CTRL+c` at any time to exit"); } @@ -560,6 +578,47 @@ async fn is_wadm_running(nats_opts: &NatsOpts, lattice_prefix: &str) -> Result) -> Result { + if let Some(port) = supplied_port { + tokio::net::TcpStream::connect((LOCALHOST, port)) + .await + .map(|_tcp_stream| port) + .map_err(|e| anyhow!(e)) + } else { + for i in 4000..=5000 { + if tokio::net::TcpStream::connect((LOCALHOST, i)) + .await + .is_err() + { + return Ok(i); + } + } + Err(anyhow!("Failed to find open port for host")) + } +} + +/// Helper function to create a NATS client from the same arguments wasmCloud will use +async fn nats_client_from_wasmcloud_opts(wasmcloud_opts: &WasmcloudOpts) -> Result { + nats_client_from_opts( + &wasmcloud_opts + .ctl_host + .clone() + .unwrap_or(DEFAULT_NATS_HOST.to_string()), + &wasmcloud_opts + .ctl_port + .map(|port| port.to_string()) + .unwrap_or(DEFAULT_NATS_PORT.to_string()), + wasmcloud_opts.ctl_jwt.clone(), + wasmcloud_opts.ctl_seed.clone(), + wasmcloud_opts.ctl_credsfile.clone(), + ) + .await +} + #[cfg(test)] mod tests { use super::UpCommand; From 4ef90dd3af831f9ea1c38b3c5403f935f6a4780f Mon Sep 17 00:00:00 2001 From: Brooks Townsend Date: Wed, 17 May 2023 11:43:51 -0400 Subject: [PATCH 3/9] implement down functionality Signed-off-by: Brooks Townsend --- Cargo.toml | 1 + crates/wash-lib/src/start/wasmcloud.rs | 7 +- src/down/mod.rs | 167 ++++++++++++++++++++----- src/up/config.rs | 2 +- src/up/mod.rs | 18 +-- 5 files changed, 145 insertions(+), 50 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index d8011122..fb8a4786 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -27,6 +27,7 @@ ctrlc = { workspace = true } dirs = { workspace = true } env_logger = { workspace = true } envmnt = { workspace = true } +futures = { workspace = true } indicatif = { workspace = true } log = { workspace = true } nkeys = { workspace = true } diff --git a/crates/wash-lib/src/start/wasmcloud.rs b/crates/wash-lib/src/start/wasmcloud.rs index 19eaa6c8..14046ee0 100644 --- a/crates/wash-lib/src/start/wasmcloud.rs +++ b/crates/wash-lib/src/start/wasmcloud.rs @@ -87,8 +87,7 @@ pub async fn ensure_wasmcloud_for_os_arch_pair

( where P: AsRef, { - //TODO: check version with 0.63.0 - // check_version(version)?; + check_version(version)?; if let Some(dir) = find_wasmcloud_binary(&dir, version).await { // wasmCloud already exists, return early return Ok(dir); @@ -267,7 +266,7 @@ fn wasmcloud_url(os: &str, arch: &str, version: &str) -> String { } /// Helper function to ensure the version of wasmCloud is above the minimum -/// supported version (v0.57.0) that runs mix releases +/// supported version (v0.63.0) that runs burrito releases fn check_version(version: &str) -> Result<()> { let version_req = semver::VersionReq::parse(&format!(">={MINIMUM_WASMCLOUD_VERSION}"))?; match semver::Version::parse(version.trim_start_matches('v')) { @@ -414,7 +413,7 @@ mod test { } const NATS_SERVER_VERSION: &str = "v2.8.4"; - const WASMCLOUD_HOST_VERSION: &str = "v0.62.2-burrito-test"; + const WASMCLOUD_HOST_VERSION: &str = "v0.63.0"; #[tokio::test] async fn can_download_and_start_wasmcloud() -> anyhow::Result<()> { diff --git a/src/down/mod.rs b/src/down/mod.rs index 11b48822..a82dc8e0 100644 --- a/src/down/mod.rs +++ b/src/down/mod.rs @@ -1,21 +1,63 @@ use std::collections::HashMap; -use std::path::Path; +use std::path::{Path, PathBuf}; use std::process::{Output, Stdio}; -use std::time::Duration; -use anyhow::Result; +use anyhow::{anyhow, Result}; +use async_nats::Client; use clap::Parser; +use log::warn; use serde_json::json; use tokio::process::Command; use wash_lib::cli::{CommandOutput, OutputKind}; -use wash_lib::start::{find_wasmcloud_binary, nats_pid_path, NATS_SERVER_BINARY, WADM_PID}; +use wash_lib::config::{DEFAULT_NATS_HOST, DEFAULT_NATS_PORT}; +use wash_lib::id::ServerId; +use wash_lib::start::{nats_pid_path, NATS_SERVER_BINARY, WADM_PID}; use crate::appearance::spinner::Spinner; use crate::cfg::cfg_dir; -use crate::up::{DOWNLOADS_DIR, WASMCLOUD_PID_FILE}; +use crate::up::{ + DEFAULT_LATTICE_PREFIX, DOWNLOADS_DIR, WASMCLOUD_CTL_CREDSFILE, WASMCLOUD_CTL_HOST, + WASMCLOUD_CTL_JWT, WASMCLOUD_CTL_PORT, WASMCLOUD_CTL_SEED, WASMCLOUD_LATTICE_PREFIX, +}; +use crate::util::nats_client_from_opts; #[derive(Parser, Debug, Clone)] -pub(crate) struct DownCommand {} +pub(crate) struct DownCommand { + /// A lattice prefix is a unique identifier for a lattice, and is frequently used within NATS topics to isolate messages from different lattices + #[clap( + short = 'x', + long = "lattice-prefix", + default_value = DEFAULT_LATTICE_PREFIX, + env = WASMCLOUD_LATTICE_PREFIX, + )] + pub(crate) lattice_prefix: String, + + /// An IP address or DNS name to use to connect to NATS for Control Interface (CTL) messages, defaults to the value supplied to --nats-host if not supplied + #[clap(long = "ctl-host", env = WASMCLOUD_CTL_HOST)] + pub(crate) ctl_host: Option, + + /// A port to use to connect to NATS for CTL messages, defaults to the value supplied to --nats-port if not supplied + #[clap(long = "ctl-port", env = WASMCLOUD_CTL_PORT)] + pub(crate) ctl_port: Option, + + /// Convenience flag for CTL authentication, internally this parses the JWT and seed from the credsfile + #[clap(long = "ctl-credsfile", env = WASMCLOUD_CTL_CREDSFILE)] + pub(crate) ctl_credsfile: Option, + + /// A seed nkey to use to authenticate to NATS for CTL messages + #[clap(long = "ctl-seed", env = WASMCLOUD_CTL_SEED, requires = "ctl_jwt")] + pub(crate) ctl_seed: Option, + + /// A user JWT to use to authenticate to NATS for CTL messages + #[clap(long = "ctl-jwt", env = WASMCLOUD_CTL_JWT, requires = "ctl_seed")] + pub(crate) ctl_jwt: Option, + + #[clap(long = "host-id")] + pub host_id: Option, + + #[clap(long = "all")] + pub all: bool, +} pub(crate) async fn handle_command( command: DownCommand, @@ -25,40 +67,41 @@ pub(crate) async fn handle_command( } pub(crate) async fn handle_down( - _cmd: DownCommand, + cmd: DownCommand, output_kind: OutputKind, ) -> Result { let install_dir = cfg_dir()?.join(DOWNLOADS_DIR); let sp = Spinner::new(&output_kind)?; + sp.update_spinner_message(" Stopping wasmCloud ...".to_string()); let mut out_json = HashMap::new(); let mut out_text = String::from(""); - //TODO: stop host via nats instead of local - let version = tokio::fs::read_to_string(install_dir.join(WASMCLOUD_PID_FILE)) - .await - .map_err(|e| { - anyhow::anyhow!("Unable to find wasmcloud pid file for stopping process: {e}") - })?; - let host_bin = find_wasmcloud_binary(&install_dir, &version) - .await - .ok_or_else(|| anyhow::anyhow!("Couldn't find path to wasmCloud binary. Is it running?"))?; - if host_bin.is_file() { - sp.update_spinner_message(" Stopping host ...".to_string()); - if let Ok(output) = stop_wasmcloud(host_bin).await { - if output.stderr.is_empty() && output.stdout.is_empty() { - // if there was a host running, 'stop' has no output. - // Give it time to stop before stopping nats - tokio::time::sleep(Duration::from_secs(6)).await; - out_json.insert("host_stopped".to_string(), json!(true)); - out_text.push_str("✅ wasmCloud host stopped successfully\n"); - } else { - out_json.insert("host_stopped".to_string(), json!(true)); - out_text.push_str( - "🤔 Host did not appear to be running, assuming it's already stopped\n", - ); - } + if let Ok(client) = nats_client_from_opts( + &cmd.ctl_host.unwrap_or(DEFAULT_NATS_HOST.to_string()), + &cmd.ctl_port + .map(|port| port.to_string()) + .unwrap_or(DEFAULT_NATS_PORT.to_string()), + cmd.ctl_jwt, + cmd.ctl_seed, + cmd.ctl_credsfile, + ) + .await + { + let (hosts, hosts_remain) = + stop_hosts(client, &cmd.lattice_prefix, &cmd.host_id, cmd.all).await?; + out_json.insert("hosts_stopped".to_string(), json!(hosts)); + out_text.push_str("✅ wasmCloud hosts stopped successfully\n"); + if hosts_remain { + out_json.insert("nats_stopped".to_string(), json!(false)); + out_json.insert("wadm_stopped".to_string(), json!(false)); + out_text.push_str( + "🛁 Exiting without stopping NATS or wadm, there are still hosts running", + ); + return Ok(CommandOutput::new(out_text, out_json)); } + } else { + warn!("Couldn't connect to NATS, unable to stop running hosts") } match stop_wadm(&install_dir).await { @@ -69,7 +112,7 @@ pub(crate) async fn handle_down( } Err(e) => { out_json.insert("wadm_stopped".to_string(), json!(false)); - out_text.push_str(&format!("❌ wadm did not stop successfully: {e:?}\n")); + out_text.push_str(&format!("❌ Could not stop wadm: {e:?}\n")); } } @@ -94,6 +137,66 @@ pub(crate) async fn handle_down( Ok(CommandOutput::new(out_text, out_json)) } +/// Stop running wasmCloud hosts, returns a vector of host IDs that were stopped and +/// a boolean indicating whether any hosts remain running +async fn stop_hosts( + nats_client: Client, + lattice_prefix: &str, + host_id: &Option, + all: bool, +) -> Result<(Vec, bool)> { + let client = wasmcloud_control_interface::ClientBuilder::new(nats_client) + .lattice_prefix(lattice_prefix) + .auction_timeout(std::time::Duration::from_secs(2)) + .build() + .await + .map_err(|e| anyhow!(e))?; + + let hosts = client.get_hosts().await.map_err(|e| anyhow!(e))?; + + // If a host ID was supplied, stop only that host + if let Some(host_id) = host_id { + let host_id_string = host_id.to_string(); + client.stop_host(&host_id_string, None).await.map_err(|e| { + anyhow!( + "Could not stop host, ensure a host with that ID is running: {:?}", + e + ) + })?; + + Ok((vec![host_id_string], hosts.len() > 1)) + } else { + if hosts.is_empty() { + Ok((vec![], false)) + } else if hosts.len() == 1 { + let host_id = &hosts[0].id; + client + .stop_host(host_id, None) + .await + .map_err(|e| anyhow!(e))?; + Ok((vec![host_id.to_string()], false)) + } else if all { + let host_stops = hosts + .iter() + .map(|host| async { + let host_id = &host.id; + let _ = client + .stop_host(&host_id, None) + .await + // TODO: deal with this error + .map_err(|e| anyhow!(e)); + host_id.to_owned() + }) + .collect::>(); + Ok((futures::future::join_all(host_stops).await, false)) + } else { + Err(anyhow!( + "More than one host is running, please specify a host ID or use --all\nRunning hosts: {:?}", hosts.into_iter().map(|h| h.id).collect::>() + )) + } + } +} + /// Helper function to send wasmCloud the `stop` command and wait for it to clean up pub(crate) async fn stop_wasmcloud

(bin_path: P) -> Result where diff --git a/src/up/config.rs b/src/up/config.rs index 16237987..b273abdf 100644 --- a/src/up/config.rs +++ b/src/up/config.rs @@ -11,7 +11,7 @@ pub(crate) const DEFAULT_NATS_PORT: &str = "4222"; // wadm configuration values pub(crate) const WADM_VERSION: &str = "v0.4.0"; // wasmCloud configuration values, https://wasmcloud.dev/reference/host-runtime/host_configure/ -pub(crate) const WASMCLOUD_HOST_VERSION: &str = "v0.62.2-burrito-test"; +pub(crate) const WASMCLOUD_HOST_VERSION: &str = "v0.63.0"; pub(crate) const WASMCLOUD_DASHBOARD_PORT: &str = "WASMCLOUD_DASHBOARD_PORT"; pub(crate) const DEFAULT_DASHBOARD_PORT: &str = "4000"; // NATS isolation configuration variables diff --git a/src/up/mod.rs b/src/up/mod.rs index b1c9dc8a..165992bd 100644 --- a/src/up/mod.rs +++ b/src/up/mod.rs @@ -36,9 +36,7 @@ use crate::util::nats_client_from_opts; mod config; mod credsfile; -pub use config::DOWNLOADS_DIR; -pub use config::WASMCLOUD_PID_FILE; -use config::*; +pub use config::*; const LOCALHOST: &str = "127.0.0.1"; @@ -425,18 +423,10 @@ pub(crate) async fn handle_up(cmd: UpCommand, output_kind: OutputKind) -> Result let spinner = Spinner::new(&output_kind)?; spinner.update_spinner_message( - "CTRL+c received, gracefully stopping wasmCloud, wadm, and NATS...".to_string(), + // wasmCloud and NATS both exit immediately when sent SIGINT + "CTRL+c received, stopping wasmCloud, wadm, and NATS...".to_string(), ); - // Terminate wasmCloud and NATS processes - let output = stop_wasmcloud(wasmcloud_executable.clone()).await?; - if !output.status.success() { - log::warn!("wasmCloud exited with a non-zero exit status, processes may need to be cleaned up manually") - } - if !cmd.nats_opts.connect_only { - stop_nats(&install_dir).await?; - } - // remove wadm pidfile, the process is stopped automatically by CTRL+c tokio::fs::remove_file(&install_dir.join("wadm.pid")).await?; @@ -558,6 +548,8 @@ async fn run_wasmcloud_interactive( if let Some(handle) = handle { handle.abort() }; + + wasmcloud_child.kill().await?; Ok(()) } From 94c4ace324f1ded482b28efbdd08c7728c84e32c Mon Sep 17 00:00:00 2001 From: Brooks Townsend Date: Wed, 17 May 2023 11:48:25 -0400 Subject: [PATCH 4/9] addressed test differences and code linting Signed-off-by: Brooks Townsend cleanup TODOs, satisfy clippy Signed-off-by: Brooks Townsend addressed test failures Signed-off-by: Brooks Townsend --- crates/wash-lib/src/start/wasmcloud.rs | 108 ++++++++---------- .../parser/files/withcargotoml/Cargo.toml | 3 + src/down/mod.rs | 68 +++++------ src/up/mod.rs | 11 +- 4 files changed, 80 insertions(+), 110 deletions(-) diff --git a/crates/wash-lib/src/start/wasmcloud.rs b/crates/wash-lib/src/start/wasmcloud.rs index 14046ee0..6d19df5f 100644 --- a/crates/wash-lib/src/start/wasmcloud.rs +++ b/crates/wash-lib/src/start/wasmcloud.rs @@ -9,15 +9,16 @@ use anyhow::{anyhow, Result}; use command_group::AsyncCommandGroup; use futures::future::join_all; use log::warn; +use reqwest::StatusCode; use tokio::fs::{create_dir_all, metadata, File}; use tokio::process::{Child, Command}; const WASMCLOUD_GITHUB_RELEASE_URL: &str = "https://github.com/wasmCloud/wasmcloud-otp/releases/download"; #[cfg(target_family = "unix")] -pub const WASMCLOUD_HOST_BIN: &str = "bin/wasmcloud_host"; +pub const WASMCLOUD_HOST_BIN: &str = "wasmcloud_host"; #[cfg(target_family = "windows")] -pub const WASMCLOUD_HOST_BIN: &str = "bin\\wasmcloud_host.bat"; +pub const WASMCLOUD_HOST_BIN: &str = "wasmcloud_host.exe"; // Any version of wasmCloud under 0.63.0 uses Elixir releases and is incompatible // See https://github.com/wasmCloud/wasmcloud-otp/pull/616 for the move to burrito releases @@ -39,7 +40,7 @@ const DEFAULT_DASHBOARD_PORT: u16 = 4000; /// use wash_lib::start::ensure_wasmcloud; /// let res = ensure_wasmcloud("v0.63.0", "/tmp/wasmcloud/").await; /// assert!(res.is_ok()); -/// assert!(res.unwrap().to_string_lossy() == "/tmp/wasmcloud/wasmcloud_host".to_string()); +/// assert!(res.unwrap().to_string_lossy() == "/tmp/wasmcloud/v0.63.0/wasmcloud_host".to_string()); /// # } /// ``` pub async fn ensure_wasmcloud

(version: &str, dir: P) -> Result @@ -72,10 +73,9 @@ where /// use wash_lib::start::ensure_wasmcloud_for_os_arch_pair; /// let os = std::env::consts::OS; /// let arch = std::env::consts::ARCH; -/// TODO: Check to see if this really always stores in a version folder /// let res = ensure_wasmcloud_for_os_arch_pair(os, arch, "v0.63.0", "/tmp/wasmcloud/").await; /// assert!(res.is_ok()); -/// assert!(res.unwrap().to_string_lossy() == "/tmp/wasmcloud/bin/wasmcloud_host".to_string()); +/// assert!(res.unwrap().to_string_lossy() == "/tmp/wasmcloud/v0.63.0/wasmcloud_host".to_string()); /// # } /// ``` pub async fn ensure_wasmcloud_for_os_arch_pair

( @@ -112,7 +112,7 @@ where /// use wash_lib::start::download_wasmcloud; /// let res = download_wasmcloud("v0.57.1", "/tmp/wasmcloud/").await; /// assert!(res.is_ok()); -/// assert!(res.unwrap().to_string_lossy() == "/tmp/wasmcloud/bin/wasmcloud_host".to_string()); +/// assert!(res.unwrap().to_string_lossy() == "/tmp/wasmcloud/v0.63.0/wasmcloud_host".to_string()); /// # } /// ``` pub async fn download_wasmcloud

(version: &str, dir: P) -> Result @@ -133,7 +133,7 @@ where /// * `arch` - Specifies the architecture of the binary to download, e.g. `amd64` /// * `version` - Specifies the version of the binary to download in the form of `vX.Y.Z` /// * `dir` - Where to unpack the wasmCloud host contents into. This should be the root level -/// directory where to store hosts. Each host will be stored in a directory maching its version TODO: verifty this +/// directory where to store hosts. Each host will be stored in a directory maching its version /// # Examples /// /// ```no_run @@ -144,7 +144,7 @@ where /// let arch = std::env::consts::ARCH; /// let res = download_wasmcloud_for_os_arch_pair(os, arch, "v0.63.0", "/tmp/wasmcloud/").await; /// assert!(res.is_ok()); -/// assert!(res.unwrap().to_string_lossy() == "/tmp/wasmcloud/wasmcloud_host".to_string()); +/// assert!(res.unwrap().to_string_lossy() == "/tmp/wasmcloud/v0.63.0/wasmcloud_host".to_string()); /// # } /// ``` pub async fn download_wasmcloud_for_os_arch_pair

( @@ -157,12 +157,19 @@ where P: AsRef, { let url = wasmcloud_url(os, arch, version); - println!("Downloading from {url}"); - //TODO: why need do this many conversions + // NOTE(brooksmtownsend): This seems like a lot of work when I really just want to use AsyncRead + // to pipe the response body into a file. I'm not sure if there's a better way to do this. + let download_response = reqwest::get(url.clone()).await?; + if download_response.status() != StatusCode::OK { + return Err(anyhow!( + "Failed to download wasmCloud host from {}. Status code: {}", + url, + download_response.status() + )); + } let wasmcloud_host_burrito = reqwest::get(url).await?.bytes().await?.to_vec(); let version_dir = dir.as_ref().join(version); - //TODO: diff for windows, exe, constant - let file_path = version_dir.join("wasmcloud_host"); + let file_path = version_dir.join(WASMCLOUD_HOST_BIN); if let Some(parent_folder) = file_path.parent() { // If the user doesn't have permission to create files in the provided directory, // this will bubble the error up noting permission denied @@ -250,7 +257,7 @@ where P: AsRef, { let versioned_dir = dir.as_ref().join(version); - let bin_file = versioned_dir.join("wasmcloud_host"); + let bin_file = versioned_dir.join(WASMCLOUD_HOST_BIN); let file_checks = vec![metadata(bin_file.clone())]; join_all(file_checks) .await @@ -261,7 +268,13 @@ where /// Helper function to determine the wasmCloud host release path given an os/arch and version fn wasmcloud_url(os: &str, arch: &str, version: &str) -> String { - let os = os.replace("macos", "darwin"); + // NOTE(brooksmtownsend): I'm hardcoding `gnu` here because I'm not sure how to determine + // that programmatically. This essentially is what we had before (gnu only) but we do have a musl + // release that we should consider. + let os = os + .replace("macos", "darwin") + .replace("linux", "linux_gnu") + .replace("windows", "windows.exe"); format!("{WASMCLOUD_GITHUB_RELEASE_URL}/{version}/wasmcloud_host_{arch}_{os}") } @@ -298,7 +311,7 @@ mod test { use reqwest::StatusCode; use std::{collections::HashMap, env::temp_dir}; use tokio::fs::{create_dir_all, remove_dir_all}; - const WASMCLOUD_VERSION: &str = "v0.60.0"; + const WASMCLOUD_VERSION: &str = "v0.63.0"; #[tokio::test] async fn can_request_supported_wasmcloud_urls() { @@ -317,12 +330,9 @@ mod test { } } - #[cfg(target_family = "unix")] - use std::os::unix::prelude::PermissionsExt; - #[tokio::test] - async fn can_download_wasmcloud_tarball() { - let download_dir = temp_dir().join("can_download_wasmcloud_tarball"); + async fn can_download_wasmcloud_burrito() { + let download_dir = temp_dir().join("can_download_wasmcloud_burrito"); let res = ensure_wasmcloud_for_os_arch_pair("macos", "aarch64", WASMCLOUD_VERSION, &download_dir) .await @@ -336,39 +346,13 @@ mod test { res ); - // Permit execution of file-watching on macos. - #[cfg(target_family = "unix")] - { - let mut fs_dir: Option = None; - let mut entries = tokio::fs::read_dir(download_dir.join(WASMCLOUD_VERSION).join("lib")) - .await - .unwrap(); - - while let Some(entry) = entries.next_entry().await.unwrap() { - let dir = entry.path(); - let file_name = dir.file_name().unwrap().to_string_lossy(); - - if file_name.contains("file_system") { - fs_dir = Some(dir); - break; - } - } - - let path = fs_dir.unwrap().join("priv/mac_listener"); - let file = tokio::fs::File::open(path).await.unwrap(); - let metadata = file.metadata().await.unwrap(); - let perms = metadata.permissions(); - - assert_eq!(perms.mode(), 0o100755); - } - let _ = remove_dir_all(download_dir).await; } #[tokio::test] async fn can_handle_missing_wasmcloud_version() { let download_dir = temp_dir().join("can_handle_missing_wasmcloud_version"); - let res = ensure_wasmcloud("v010233.123.3.4", &download_dir).await; + let res = ensure_wasmcloud("v10233.123.3.4", &download_dir).await; assert!(res.is_err()); let _ = remove_dir_all(download_dir).await; @@ -388,12 +372,12 @@ mod test { "wasmCloud should be installed" ); - ensure_wasmcloud_for_os_arch_pair("macos", "aarch64", "v0.59.0", &download_dir) + ensure_wasmcloud_for_os_arch_pair("macos", "aarch64", "v0.63.1", &download_dir) .await .expect("Should be able to download host"); assert!( - find_wasmcloud_binary(&download_dir, "v0.59.0") + find_wasmcloud_binary(&download_dir, "v0.63.1") .await .is_some(), "wasmCloud should be installed" @@ -405,7 +389,7 @@ mod test { "Directory should exist" ); assert!( - download_dir.join("v0.59.0").exists(), + download_dir.join("v0.63.1").exists(), "Directory should exist" ); @@ -539,28 +523,30 @@ mod test { } #[tokio::test] - async fn can_properly_deny_distillery_release_hosts() -> anyhow::Result<()> { - // Ensure we allow versions >= 0.57.0 + async fn can_properly_deny_elixir_release_hosts() -> anyhow::Result<()> { + // Ensure we allow versions >= 0.63.0 assert!(check_version("v1.56.0").is_ok()); - assert!(check_version("v0.57.0").is_ok()); - assert!(check_version("v0.57.1").is_ok()); - assert!(check_version("v0.57.2").is_ok()); - assert!(check_version("v0.58.0").is_ok()); + assert!(check_version("v0.63.0").is_ok()); + assert!(check_version("v0.63.1").is_ok()); + assert!(check_version("v0.63.2").is_ok()); + assert!(check_version("v0.64.0").is_ok()); assert!(check_version("v0.100.0").is_ok()); assert!(check_version("v0.203.0").is_ok()); // Ensure we allow prerelease tags for testing - assert!(check_version("v0.60.0-rc.1").is_ok()); - assert!(check_version("v0.60.0-alpha.23").is_ok()); - assert!(check_version("v0.60.0-beta.0").is_ok()); + assert!(check_version("v0.64.0-rc.1").is_ok()); + assert!(check_version("v0.64.0-alpha.23").is_ok()); + assert!(check_version("v0.64.0-beta.0").is_ok()); - // Ensure we deny versions < 0.57.0 + // Ensure we deny versions < 0.63.0 assert!(check_version("v0.48.0").is_err()); assert!(check_version("v0.56.0").is_err()); + assert!(check_version("v0.58.0").is_err()); + assert!(check_version("v0.62.3").is_err()); assert!(check_version("v0.12.0").is_err()); assert!(check_version("v0.56.999").is_err()); if let Err(e) = check_version("v0.56.0") { - assert_eq!(e.to_string(), "wasmCloud version v0.56.0 is earlier than the minimum supported version of v0.57.0"); + assert_eq!(e.to_string(), "wasmCloud version v0.56.0 is earlier than the minimum supported version of v0.63.0"); } else { panic!("v0.56.0 should be before the minimum version") } diff --git a/crates/wash-lib/tests/parser/files/withcargotoml/Cargo.toml b/crates/wash-lib/tests/parser/files/withcargotoml/Cargo.toml index 05e1356f..6125f865 100644 --- a/crates/wash-lib/tests/parser/files/withcargotoml/Cargo.toml +++ b/crates/wash-lib/tests/parser/files/withcargotoml/Cargo.toml @@ -5,4 +5,7 @@ edition = "2021" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html +[lib] +crate-type = ["cdylib", "rlib"] + [dependencies] diff --git a/src/down/mod.rs b/src/down/mod.rs index a82dc8e0..fcbc4fc9 100644 --- a/src/down/mod.rs +++ b/src/down/mod.rs @@ -78,10 +78,11 @@ pub(crate) async fn handle_down( let mut out_text = String::from(""); if let Ok(client) = nats_client_from_opts( - &cmd.ctl_host.unwrap_or(DEFAULT_NATS_HOST.to_string()), + &cmd.ctl_host + .unwrap_or_else(|| DEFAULT_NATS_HOST.to_string()), &cmd.ctl_port .map(|port| port.to_string()) - .unwrap_or(DEFAULT_NATS_PORT.to_string()), + .unwrap_or_else(|| DEFAULT_NATS_PORT.to_string()), cmd.ctl_jwt, cmd.ctl_seed, cmd.ctl_credsfile, @@ -165,51 +166,36 @@ async fn stop_hosts( })?; Ok((vec![host_id_string], hosts.len() > 1)) + } else if hosts.is_empty() { + Ok((vec![], false)) + } else if hosts.len() == 1 { + let host_id = &hosts[0].id; + client + .stop_host(host_id, None) + .await + .map_err(|e| anyhow!(e))?; + Ok((vec![host_id.to_string()], false)) + } else if all { + let host_stops = hosts + .iter() + .map(|host| async { + let host_id = &host.id; + let _ = client + .stop_host(host_id, None) + .await + // TODO: deal with this error + .map_err(|e| anyhow!(e)); + host_id.to_owned() + }) + .collect::>(); + Ok((futures::future::join_all(host_stops).await, false)) } else { - if hosts.is_empty() { - Ok((vec![], false)) - } else if hosts.len() == 1 { - let host_id = &hosts[0].id; - client - .stop_host(host_id, None) - .await - .map_err(|e| anyhow!(e))?; - Ok((vec![host_id.to_string()], false)) - } else if all { - let host_stops = hosts - .iter() - .map(|host| async { - let host_id = &host.id; - let _ = client - .stop_host(&host_id, None) - .await - // TODO: deal with this error - .map_err(|e| anyhow!(e)); - host_id.to_owned() - }) - .collect::>(); - Ok((futures::future::join_all(host_stops).await, false)) - } else { - Err(anyhow!( + Err(anyhow!( "More than one host is running, please specify a host ID or use --all\nRunning hosts: {:?}", hosts.into_iter().map(|h| h.id).collect::>() )) - } } } -/// Helper function to send wasmCloud the `stop` command and wait for it to clean up -pub(crate) async fn stop_wasmcloud

(bin_path: P) -> Result -where - P: AsRef, -{ - Command::new(bin_path.as_ref()) - .stdout(Stdio::piped()) - .arg("stop") - .output() - .await - .map_err(anyhow::Error::from) -} - /// Helper function to send the nats-server the stop command pub(crate) async fn stop_nats

(install_dir: P) -> Result where diff --git a/src/up/mod.rs b/src/up/mod.rs index 165992bd..f970f329 100644 --- a/src/up/mod.rs +++ b/src/up/mod.rs @@ -31,7 +31,6 @@ use wash_lib::start::{ use crate::appearance::spinner::Spinner; use crate::cfg::cfg_dir; use crate::down::stop_nats; -use crate::down::stop_wasmcloud; use crate::util::nats_client_from_opts; mod config; @@ -311,11 +310,7 @@ pub(crate) async fn handle_up(cmd: UpCommand, output_kind: OutputKind) -> Result // Based on the options provided for wasmCloud, form a client connection to NATS. // If this fails, we should return early since wasmCloud wouldn't be able to connect either - let nats_client = if let Ok(client) = nats_client { - client - } else { - nats_client_from_wasmcloud_opts(&cmd.wasmcloud_opts).await? - }; + nats_client_from_wasmcloud_opts(&cmd.wasmcloud_opts).await?; let wadm_process = if !cmd.wadm_opts.disable_wadm && !is_wadm_running(&nats_opts, &cmd.wasmcloud_opts.lattice_prefix) @@ -599,11 +594,11 @@ async fn nats_client_from_wasmcloud_opts(wasmcloud_opts: &WasmcloudOpts) -> Resu &wasmcloud_opts .ctl_host .clone() - .unwrap_or(DEFAULT_NATS_HOST.to_string()), + .unwrap_or_else(|| DEFAULT_NATS_HOST.to_string()), &wasmcloud_opts .ctl_port .map(|port| port.to_string()) - .unwrap_or(DEFAULT_NATS_PORT.to_string()), + .unwrap_or_else(|| DEFAULT_NATS_PORT.to_string()), wasmcloud_opts.ctl_jwt.clone(), wasmcloud_opts.ctl_seed.clone(), wasmcloud_opts.ctl_credsfile.clone(), From a5bfb294d34c0f1e2630e599e60e19250e1a220d Mon Sep 17 00:00:00 2001 From: Brooks Townsend Date: Thu, 18 May 2023 09:55:11 -0400 Subject: [PATCH 5/9] make sure hosts are running on separate things Signed-off-by: Brooks Townsend v2 Signed-off-by: Brooks Townsend same port fail Signed-off-by: Brooks Townsend --- crates/wash-lib/src/start/wasmcloud.rs | 2 ++ tests/integration_up.rs | 13 ++++++++++++- 2 files changed, 14 insertions(+), 1 deletion(-) diff --git a/crates/wash-lib/src/start/wasmcloud.rs b/crates/wash-lib/src/start/wasmcloud.rs index 6d19df5f..71aeaac2 100644 --- a/crates/wash-lib/src/start/wasmcloud.rs +++ b/crates/wash-lib/src/start/wasmcloud.rs @@ -456,6 +456,7 @@ mod test { .await; let mut host_env = HashMap::new(); + host_env.insert("WASMCLOUD_DASHBOARD_PORT".to_string(), "5003".to_string()); host_env.insert("WASMCLOUD_RPC_PORT".to_string(), nats_port.to_string()); host_env.insert("WASMCLOUD_CTL_PORT".to_string(), nats_port.to_string()); host_env.insert("WASMCLOUD_PROV_RPC_PORT".to_string(), nats_port.to_string()); @@ -488,6 +489,7 @@ mod test { // Should fail because the port is already in use by another host let mut host_env = HashMap::new(); + host_env.insert("WASMCLOUD_DASHBOARD_PORT".to_string(), "5003".to_string()); host_env.insert("WASMCLOUD_RPC_PORT".to_string(), nats_port.to_string()); host_env.insert("WASMCLOUD_CTL_PORT".to_string(), nats_port.to_string()); host_env.insert("WASMCLOUD_PROV_RPC_PORT".to_string(), nats_port.to_string()); diff --git a/tests/integration_up.rs b/tests/integration_up.rs index acb4e190..a9b67e7e 100644 --- a/tests/integration_up.rs +++ b/tests/integration_up.rs @@ -29,6 +29,8 @@ async fn integration_up_can_start_wasmcloud_and_actor_serial() -> Result<()> { "up", "--nats-port", "5893", + "--dashboard-port", + "5002", "-o", "json", "--detached", @@ -119,7 +121,16 @@ async fn integration_up_can_stop_detached_host_serial() -> Result<()> { let stdout = std::fs::File::create(&path).expect("could not create log file for wash up test"); let mut up_cmd = Command::new(env!("CARGO_BIN_EXE_wash")) - .args(["up", "--nats-port", "5894", "-o", "json", "--detached"]) + .args([ + "up", + "--nats-port", + "5894", + "--dashboard-port", + "5001", + "-o", + "json", + "--detached", + ]) .kill_on_drop(true) .stdout(stdout) .spawn() From 1a8b741f0d07d943b63f5c8fbdf7bce18c3b3c03 Mon Sep 17 00:00:00 2001 From: Brooks Townsend Date: Tue, 6 Jun 2023 10:57:24 -0400 Subject: [PATCH 6/9] addressed PR concerns Signed-off-by: Brooks Townsend --- crates/wash-lib/src/start/wasmcloud.rs | 11 +++-------- src/down/mod.rs | 25 +++++++++++++++++-------- src/up/config.rs | 1 + src/up/mod.rs | 13 ++++++++----- 4 files changed, 29 insertions(+), 21 deletions(-) diff --git a/crates/wash-lib/src/start/wasmcloud.rs b/crates/wash-lib/src/start/wasmcloud.rs index 71aeaac2..a9be38f4 100644 --- a/crates/wash-lib/src/start/wasmcloud.rs +++ b/crates/wash-lib/src/start/wasmcloud.rs @@ -7,7 +7,6 @@ use std::process::Stdio; use anyhow::{anyhow, Result}; #[cfg(target_family = "unix")] use command_group::AsyncCommandGroup; -use futures::future::join_all; use log::warn; use reqwest::StatusCode; use tokio::fs::{create_dir_all, metadata, File}; @@ -167,7 +166,7 @@ where download_response.status() )); } - let wasmcloud_host_burrito = reqwest::get(url).await?.bytes().await?.to_vec(); + let wasmcloud_host_burrito = download_response.bytes().await?.to_vec(); let version_dir = dir.as_ref().join(version); let file_path = version_dir.join(WASMCLOUD_HOST_BIN); if let Some(parent_folder) = file_path.parent() { @@ -258,12 +257,8 @@ where { let versioned_dir = dir.as_ref().join(version); let bin_file = versioned_dir.join(WASMCLOUD_HOST_BIN); - let file_checks = vec![metadata(bin_file.clone())]; - join_all(file_checks) - .await - .iter() - .all(|i| i.is_ok()) - .then_some(bin_file) + + metadata(&bin_file).await.is_ok().then_some(bin_file) } /// Helper function to determine the wasmCloud host release path given an os/arch and version diff --git a/src/down/mod.rs b/src/down/mod.rs index fcbc4fc9..416d91f1 100644 --- a/src/down/mod.rs +++ b/src/down/mod.rs @@ -5,7 +5,7 @@ use std::process::{Output, Stdio}; use anyhow::{anyhow, Result}; use async_nats::Client; use clap::Parser; -use log::warn; +use log::{error, warn}; use serde_json::json; use tokio::process::Command; use wash_lib::cli::{CommandOutput, OutputKind}; @@ -180,15 +180,24 @@ async fn stop_hosts( .iter() .map(|host| async { let host_id = &host.id; - let _ = client - .stop_host(host_id, None) - .await - // TODO: deal with this error - .map_err(|e| anyhow!(e)); - host_id.to_owned() + match client.stop_host(host_id, None).await { + Ok(_) => Some(host_id.to_owned()), + Err(e) => { + error!("Could not stop host {}: {:?}", host_id, e); + None + } + } }) .collect::>(); - Ok((futures::future::join_all(host_stops).await, false)) + let all_stops = futures::future::join_all(host_stops).await; + let host_ids = all_stops + .iter() + // Remove any host IDs that ran into errors + .filter_map(|host_id| host_id.to_owned()) + .collect::>(); + let hosts_remaining = all_stops.len() > host_ids.len(); + + Ok((host_ids, hosts_remaining)) } else { Err(anyhow!( "More than one host is running, please specify a host ID or use --all\nRunning hosts: {:?}", hosts.into_iter().map(|h| h.id).collect::>() diff --git a/src/up/config.rs b/src/up/config.rs index b273abdf..37c1dc96 100644 --- a/src/up/config.rs +++ b/src/up/config.rs @@ -13,6 +13,7 @@ pub(crate) const WADM_VERSION: &str = "v0.4.0"; // wasmCloud configuration values, https://wasmcloud.dev/reference/host-runtime/host_configure/ pub(crate) const WASMCLOUD_HOST_VERSION: &str = "v0.63.0"; pub(crate) const WASMCLOUD_DASHBOARD_PORT: &str = "WASMCLOUD_DASHBOARD_PORT"; +// NOTE: We scan from this port up to 1000 ports higher, should always be under 64535 pub(crate) const DEFAULT_DASHBOARD_PORT: &str = "4000"; // NATS isolation configuration variables pub(crate) const WASMCLOUD_LATTICE_PREFIX: &str = "WASMCLOUD_LATTICE_PREFIX"; diff --git a/src/up/mod.rs b/src/up/mod.rs index f970f329..aec51b60 100644 --- a/src/up/mod.rs +++ b/src/up/mod.rs @@ -291,11 +291,13 @@ pub(crate) async fn handle_up(cmd: UpCommand, output_kind: OutputKind) -> Result let nats_client = nats_client_from_wasmcloud_opts(&cmd.wasmcloud_opts).await; let nats_opts = cmd.nats_opts.clone(); + // Avoid downloading + starting NATS if the user already runs their own server and we can connect. + let should_run_nats = !cmd.nats_opts.connect_only && nats_client.is_err(); // Ignore connect_only if this server has a remote and credsfile as we have to start a leafnode in that scenario - let nats_bin = if (!cmd.nats_opts.connect_only && nats_client.is_err()) - || cmd.nats_opts.nats_remote_url.is_some() && cmd.nats_opts.nats_credsfile.is_some() - { + let supplied_remote_credentials = + cmd.nats_opts.nats_remote_url.is_some() && cmd.nats_opts.nats_credsfile.is_some(); + let nats_bin = if should_run_nats || supplied_remote_credentials { // Download NATS if not already installed spinner.update_spinner_message(" Downloading NATS ...".to_string()); let nats_binary = ensure_nats_server(&cmd.nats_opts.nats_version, &install_dir).await?; @@ -396,7 +398,6 @@ pub(crate) async fn handle_up(cmd: UpCommand, output_kind: OutputKind) -> Result if let Some(mut child) = wadm_process { child.kill().await?; } - //TODO: only kill NATS if there are no running hosts if !cmd.nats_opts.connect_only { stop_nats(install_dir).await?; } @@ -576,7 +577,9 @@ async fn ensure_open_port(supplied_port: Option) -> Result { .map(|_tcp_stream| port) .map_err(|e| anyhow!(e)) } else { - for i in 4000..=5000 { + let start_port = DEFAULT_DASHBOARD_PORT.parse().unwrap_or(4000); + let end_port = start_port + 1000; + for i in start_port..=end_port { if tokio::net::TcpStream::connect((LOCALHOST, i)) .await .is_err() From 0b2854843b053c6a15ee82d31688acc49858942d Mon Sep 17 00:00:00 2001 From: Brooks Townsend Date: Tue, 6 Jun 2023 11:05:27 -0400 Subject: [PATCH 7/9] fixed #492, bumped versions Signed-off-by: Brooks Townsend --- src/up/mod.rs | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/src/up/mod.rs b/src/up/mod.rs index aec51b60..faeb922f 100644 --- a/src/up/mod.rs +++ b/src/up/mod.rs @@ -20,6 +20,7 @@ use tokio::{ }; use wash_lib::cli::{CommandOutput, OutputKind}; use wash_lib::start::ensure_wadm; +use wash_lib::start::find_wasmcloud_binary; use wash_lib::start::nats_pid_path; use wash_lib::start::start_wadm; use wash_lib::start::WadmConfig; @@ -358,11 +359,17 @@ pub(crate) async fn handle_up(cmd: UpCommand, output_kind: OutputKind) -> Result spinner.update_spinner_message(" Downloading wasmCloud ...".to_string()); ensure_wasmcloud(&cmd.wasmcloud_opts.wasmcloud_version, &install_dir).await? } else { - // Ensure we clean up the NATS server if we can't start wasmCloud - if nats_bin.is_some() { - stop_nats(install_dir).await?; + if let Some(wasmcloud_bin) = + find_wasmcloud_binary(&install_dir, &cmd.wasmcloud_opts.wasmcloud_version).await + { + wasmcloud_bin + } else { + // Ensure we clean up the NATS server if we can't start wasmCloud + if nats_bin.is_some() { + stop_nats(install_dir).await?; + } + return Err(anyhow!("wasmCloud was not installed, exiting without downloading as --wasmcloud-start-only was set")); } - return Err(anyhow!("wasmCloud was not installed, exiting without downloading as --wasmcloud-start-only was set")); }; // Redirect output (which is on stderr) to a log file in detached mode, or use the terminal From 854404d6db248e2c1290cf5b166cf194a620abb6 Mon Sep 17 00:00:00 2001 From: Brooks Townsend Date: Fri, 9 Jun 2023 10:26:38 -0400 Subject: [PATCH 8/9] Adjusted tests with new burrito behavior Signed-off-by: Brooks Townsend un-re-de-comment integration build Signed-off-by: Brooks Townsend removed test debugs Signed-off-by: Brooks Townsend removed moar printlns Signed-off-by: Brooks Townsend cfg idk Signed-off-by: Brooks Townsend sleepy Signed-off-by: Brooks Townsend sleepy Signed-off-by: Brooks Townsend added debug info to failing test Signed-off-by: Brooks Townsend removed kill-on-drop logic Signed-off-by: Brooks Townsend --- Makefile | 2 +- crates/wash-lib/src/registry.rs | 1 + src/up/mod.rs | 80 +++++++++++++++++++++++---------- tests/common.rs | 12 ++++- tests/integration_inspect.rs | 2 +- tests/integration_par.rs | 2 +- tests/integration_up.rs | 46 ++++++++++++++----- 7 files changed, 105 insertions(+), 40 deletions(-) diff --git a/Makefile b/Makefile index f924ff3f..3dfc3eb3 100644 --- a/Makefile +++ b/Makefile @@ -30,7 +30,7 @@ build-watch: ## Continuously build the project test: ## Run unit test suite @$(CARGO) nextest run $(CARGO_TEST_TARGET) --no-fail-fast --bin wash - @$(CARGO) nextest run $(CARGO_TEST_TARGET) --no-fail-fast -p wash-lib + @$(CARGO) nextest run $(CARGO_TEST_TARGET) --no-fail-fast -p wash-lib --features=cli test-wash-ci: @$(CARGO) nextest run --profile ci --workspace --bin wash diff --git a/crates/wash-lib/src/registry.rs b/crates/wash-lib/src/registry.rs index b9babac0..e450cd8f 100644 --- a/crates/wash-lib/src/registry.rs +++ b/crates/wash-lib/src/registry.rs @@ -6,6 +6,7 @@ use std::{ }; use anyhow::{anyhow, bail, Result}; +#[cfg(feature = "cli")] use clap::{Parser, Subcommand}; use oci_distribution::manifest::OciImageManifest; use oci_distribution::{ diff --git a/src/up/mod.rs b/src/up/mod.rs index faeb922f..2e274d78 100644 --- a/src/up/mod.rs +++ b/src/up/mod.rs @@ -287,10 +287,45 @@ pub(crate) async fn handle_up(cmd: UpCommand, output_kind: OutputKind) -> Result // Find an open port for the host, and if the user specified a port, ensure it's open let host_port = ensure_open_port(cmd.wasmcloud_opts.dashboard_port).await?; + // Ensure we use the open dashboard port and the supplied NATS host/port if no overrides were supplied + let wasmcloud_opts = WasmcloudOpts { + dashboard_port: Some(host_port), + ctl_host: Some( + cmd.wasmcloud_opts + .ctl_host + .unwrap_or_else(|| cmd.nats_opts.nats_host.to_owned()), + ), + ctl_port: Some( + cmd.wasmcloud_opts + .ctl_port + .unwrap_or(cmd.nats_opts.nats_port), + ), + rpc_host: Some( + cmd.wasmcloud_opts + .rpc_host + .unwrap_or_else(|| cmd.nats_opts.nats_host.to_owned()), + ), + rpc_port: Some( + cmd.wasmcloud_opts + .rpc_port + .unwrap_or(cmd.nats_opts.nats_port), + ), + prov_rpc_host: Some( + cmd.wasmcloud_opts + .prov_rpc_host + .unwrap_or_else(|| cmd.nats_opts.nats_host.to_owned()), + ), + prov_rpc_port: Some( + cmd.wasmcloud_opts + .prov_rpc_port + .unwrap_or(cmd.nats_opts.nats_port), + ), + ..cmd.wasmcloud_opts + }; // Capture listen address to keep the value after the nats_opts are moved let nats_listen_address = format!("{}:{}", cmd.nats_opts.nats_host, cmd.nats_opts.nats_port); - let nats_client = nats_client_from_wasmcloud_opts(&cmd.wasmcloud_opts).await; + let nats_client = nats_client_from_wasmcloud_opts(&wasmcloud_opts).await; let nats_opts = cmd.nats_opts.clone(); // Avoid downloading + starting NATS if the user already runs their own server and we can connect. @@ -298,6 +333,7 @@ pub(crate) async fn handle_up(cmd: UpCommand, output_kind: OutputKind) -> Result // Ignore connect_only if this server has a remote and credsfile as we have to start a leafnode in that scenario let supplied_remote_credentials = cmd.nats_opts.nats_remote_url.is_some() && cmd.nats_opts.nats_credsfile.is_some(); + let nats_bin = if should_run_nats || supplied_remote_credentials { // Download NATS if not already installed spinner.update_spinner_message(" Downloading NATS ...".to_string()); @@ -313,10 +349,10 @@ pub(crate) async fn handle_up(cmd: UpCommand, output_kind: OutputKind) -> Result // Based on the options provided for wasmCloud, form a client connection to NATS. // If this fails, we should return early since wasmCloud wouldn't be able to connect either - nats_client_from_wasmcloud_opts(&cmd.wasmcloud_opts).await?; + nats_client_from_wasmcloud_opts(&wasmcloud_opts).await?; let wadm_process = if !cmd.wadm_opts.disable_wadm - && !is_wadm_running(&nats_opts, &cmd.wasmcloud_opts.lattice_prefix) + && !is_wadm_running(&nats_opts, &wasmcloud_opts.lattice_prefix) .await .unwrap_or(false) { @@ -357,19 +393,17 @@ pub(crate) async fn handle_up(cmd: UpCommand, output_kind: OutputKind) -> Result // Download wasmCloud if not already installed let wasmcloud_executable = if !cmd.wasmcloud_opts.start_only { spinner.update_spinner_message(" Downloading wasmCloud ...".to_string()); - ensure_wasmcloud(&cmd.wasmcloud_opts.wasmcloud_version, &install_dir).await? + ensure_wasmcloud(&wasmcloud_opts.wasmcloud_version, &install_dir).await? + } else if let Some(wasmcloud_bin) = + find_wasmcloud_binary(&install_dir, &wasmcloud_opts.wasmcloud_version).await + { + wasmcloud_bin } else { - if let Some(wasmcloud_bin) = - find_wasmcloud_binary(&install_dir, &cmd.wasmcloud_opts.wasmcloud_version).await - { - wasmcloud_bin - } else { - // Ensure we clean up the NATS server if we can't start wasmCloud - if nats_bin.is_some() { - stop_nats(install_dir).await?; - } - return Err(anyhow!("wasmCloud was not installed, exiting without downloading as --wasmcloud-start-only was set")); + // Ensure we clean up the NATS server if we can't start wasmCloud + if nats_bin.is_some() { + stop_nats(install_dir).await?; } + return Err(anyhow!("wasmCloud was not installed, exiting without downloading as --wasmcloud-start-only was set")); }; // Redirect output (which is on stderr) to a log file in detached mode, or use the terminal @@ -384,12 +418,8 @@ pub(crate) async fn handle_up(cmd: UpCommand, output_kind: OutputKind) -> Result } else { Stdio::piped() }; - let version = cmd.wasmcloud_opts.wasmcloud_version.clone(); - // Ensure we use the open dashboard port - let wasmcloud_opts = WasmcloudOpts { - dashboard_port: Some(host_port), - ..cmd.wasmcloud_opts - }; + let version = wasmcloud_opts.wasmcloud_version.clone(); + let host_env = configure_host_env(nats_opts, wasmcloud_opts).await; let wasmcloud_child = match start_wasmcloud_host( &wasmcloud_executable, @@ -579,10 +609,12 @@ async fn is_wadm_running(nats_opts: &NatsOpts, lattice_prefix: &str) -> Result) -> Result { if let Some(port) = supplied_port { - tokio::net::TcpStream::connect((LOCALHOST, port)) - .await - .map(|_tcp_stream| port) - .map_err(|e| anyhow!(e)) + match tokio::net::TcpStream::connect((LOCALHOST, port)).await { + Ok(_tcp_stream) => Err(anyhow!( + "Supplied host port {port} already has a process listening" + )), + Err(_e) => Ok(port), + } } else { let start_port = DEFAULT_DASHBOARD_PORT.parse().unwrap_or(4000); let end_port = start_port + 1000; diff --git a/tests/common.rs b/tests/common.rs index bb1e4ef3..9ac571a4 100644 --- a/tests/common.rs +++ b/tests/common.rs @@ -105,7 +105,13 @@ impl Drop for TestWashInstance { let kill_cmd = kill_cmd.to_string(); let (_wash, down) = kill_cmd.trim_matches('"').split_once(' ').unwrap(); wash() - .args(vec![down]) + .args(vec![ + down, + "--host-id", + &self.host_id, + "--ctl-port", + &self.nats_port.to_string(), + ]) .output() .expect("Could not spawn wash down process"); @@ -171,6 +177,7 @@ impl TestWashInstance { .context("up command failed to complete")?; assert!(status.success()); + let out = read_to_string(&log_path).context("could not read output of wash up")?; let (kill_cmd, wasmcloud_log) = match serde_json::from_str::(&out) { @@ -179,7 +186,7 @@ impl TestWashInstance { }; // Wait until the host starts by checking the logs - let mut tries = 30; + let mut tries: i32 = 30; let mut start_message_logs: String = String::new(); loop { start_message_logs = read_to_string(wasmcloud_log.to_string().trim_matches('"')) @@ -191,6 +198,7 @@ impl TestWashInstance { assert!(tries >= 0); tokio::time::sleep(tokio::time::Duration::from_secs(1)).await; } + tokio::time::sleep(tokio::time::Duration::from_secs(2)).await; Ok(TestWashInstance { test_dir, diff --git a/tests/integration_inspect.rs b/tests/integration_inspect.rs index 1e8269af..a6dc7a54 100644 --- a/tests/integration_inspect.rs +++ b/tests/integration_inspect.rs @@ -219,5 +219,5 @@ fn integration_inspect_cached() { assert!(!remote_inspect_no_cache.status.success()); - remove_file(http_client_cache_path).unwrap(); + let _ = remove_file(http_client_cache_path); } diff --git a/tests/integration_par.rs b/tests/integration_par.rs index 8fdbe25d..018a045a 100644 --- a/tests/integration_par.rs +++ b/tests/integration_par.rs @@ -345,5 +345,5 @@ fn integration_par_inspect_cached() { assert!(!remote_inspect_no_cache.status.success()); - remove_file(http_client_cache_path).unwrap(); + let _ = remove_file(http_client_cache_path); } diff --git a/tests/integration_up.rs b/tests/integration_up.rs index a9b67e7e..0647e213 100644 --- a/tests/integration_up.rs +++ b/tests/integration_up.rs @@ -38,7 +38,6 @@ async fn integration_up_can_start_wasmcloud_and_actor_serial() -> Result<()> { &host_seed.seed().expect("Should have a seed for the host"), ]) .stdout(stdout) - .kill_on_drop(true) .spawn() .context("Could not spawn wash up process")?; @@ -61,7 +60,13 @@ async fn integration_up_can_start_wasmcloud_and_actor_serial() -> Result<()> { let mut tries = 30; while tries >= 0 { let output = Command::new(env!("CARGO_BIN_EXE_wash")) - .args(["ctl", "get", "inventory", &host_seed.public_key()]) + .args([ + "get", + "inventory", + &host_seed.public_key(), + "--ctl-port", + "5893", + ]) .kill_on_drop(true) .output() .await @@ -71,13 +76,13 @@ async fn integration_up_can_start_wasmcloud_and_actor_serial() -> Result<()> { assert!(tries >= 0); tokio::time::sleep(tokio::time::Duration::from_secs(1)).await; } else { + tokio::time::sleep(tokio::time::Duration::from_secs(5)).await; break; } } let start_echo = Command::new(env!("CARGO_BIN_EXE_wash")) .args([ - "ctl", "start", "actor", "wasmcloud.azurecr.io/echo:0.3.4", @@ -100,10 +105,17 @@ async fn integration_up_can_start_wasmcloud_and_actor_serial() -> Result<()> { String::from_utf8_lossy(&start_echo.stderr) ); + tokio::time::sleep(tokio::time::Duration::from_secs(5)).await; let kill_cmd = kill_cmd.to_string(); let (_wash, down) = kill_cmd.trim_matches('"').split_once(' ').unwrap(); Command::new(env!("CARGO_BIN_EXE_wash")) - .args(vec![down]) + .args(vec![ + down, + "--ctl-port", + "5893", + "--host-id", + &host_seed.public_key(), + ]) .kill_on_drop(true) .output() .await @@ -120,18 +132,22 @@ async fn integration_up_can_stop_detached_host_serial() -> Result<()> { let path = dir.join("washup.log"); let stdout = std::fs::File::create(&path).expect("could not create log file for wash up test"); + // sleep for 10 seconds + tokio::time::sleep(tokio::time::Duration::from_secs(10)).await; + + let host_seed = nkeys::KeyPair::new_server(); + let mut up_cmd = Command::new(env!("CARGO_BIN_EXE_wash")) .args([ "up", "--nats-port", "5894", - "--dashboard-port", - "5001", "-o", "json", "--detached", + "--host-seed", + &host_seed.seed().expect("Should have a seed for the host"), ]) - .kill_on_drop(true) .stdout(stdout) .spawn() .context("Could not spawn wash up process")?; @@ -160,11 +176,18 @@ async fn integration_up_can_stop_detached_host_serial() -> Result<()> { tokio::time::sleep(tokio::time::Duration::from_secs(1)).await; } + tokio::time::sleep(tokio::time::Duration::from_millis(5000)).await; + let kill_cmd = kill_cmd.to_string(); let (_wash, down) = kill_cmd.trim_matches('"').split_once(' ').unwrap(); Command::new(env!("CARGO_BIN_EXE_wash")) - .args(vec![down]) - .kill_on_drop(true) + .args(vec![ + down, + "--ctl-port", + "5894", + "--host-id", + &host_seed.public_key(), + ]) .output() .await .context("Could not spawn wash down process")?; @@ -210,7 +233,6 @@ async fn integration_up_doesnt_kill_unowned_nats_serial() -> Result<()> { "json", "--detached", ]) - .kill_on_drop(true) .stdout(stdout) .spawn() .context("Could not spawn wash up process")?; @@ -239,11 +261,13 @@ async fn integration_up_doesnt_kill_unowned_nats_serial() -> Result<()> { tokio::time::sleep(tokio::time::Duration::from_secs(1)).await; } + tokio::time::sleep(tokio::time::Duration::from_secs(5)).await; + let kill_cmd = kill_cmd.to_string(); let (_wash, down) = kill_cmd.trim_matches('"').split_once(' ').unwrap(); Command::new(env!("CARGO_BIN_EXE_wash")) .kill_on_drop(true) - .args(vec![down]) + .args(vec![down, "--ctl-port", "5895"]) .output() .await .context("Could not spawn wash down process")?; From fa48cee3181d04a7fa8e433f4ecb43db059e724e Mon Sep 17 00:00:00 2001 From: Brooks Townsend Date: Mon, 12 Jun 2023 16:59:17 -0400 Subject: [PATCH 9/9] more gracefully downloaded, bumped 0.63.1 Signed-off-by: Brooks Townsend --- Cargo.lock | 1 + Cargo.toml | 1 + crates/wash-lib/Cargo.toml | 1 + crates/wash-lib/src/start/wasmcloud.rs | 12 +++++++++--- src/up/config.rs | 2 +- 5 files changed, 13 insertions(+), 4 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 134aa0fc..58260ca6 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4267,6 +4267,7 @@ dependencies = [ "tokio", "tokio-stream", "tokio-tar", + "tokio-util 0.7.8", "toml 0.7.4", "wadm", "walkdir", diff --git a/Cargo.toml b/Cargo.toml index fb8a4786..270ef80d 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -135,6 +135,7 @@ thiserror = "1.0" tokio = { version = "1.28.2", default-features = false, features = ["fs"] } tokio-stream = "0.1" tokio-tar = "0.3" +tokio-util = "0.7.8" toml = "0.7.4" wadm = "0.4.0" walkdir = "2.3" diff --git a/crates/wash-lib/Cargo.toml b/crates/wash-lib/Cargo.toml index 6cbf1625..807a3b36 100644 --- a/crates/wash-lib/Cargo.toml +++ b/crates/wash-lib/Cargo.toml @@ -60,6 +60,7 @@ time = "0.3" tokio = { workspace = true, features = ["process"] } tokio-stream = { workspace = true } tokio-tar = { workspace = true } +tokio-util = { workspace = true } toml = { workspace = true } wadm = { workspace = true, optional = true} walkdir = { workspace = true } diff --git a/crates/wash-lib/src/start/wasmcloud.rs b/crates/wash-lib/src/start/wasmcloud.rs index a9be38f4..d09d06d5 100644 --- a/crates/wash-lib/src/start/wasmcloud.rs +++ b/crates/wash-lib/src/start/wasmcloud.rs @@ -11,6 +11,8 @@ use log::warn; use reqwest::StatusCode; use tokio::fs::{create_dir_all, metadata, File}; use tokio::process::{Child, Command}; +use tokio_stream::StreamExt; +use tokio_util::io::StreamReader; const WASMCLOUD_GITHUB_RELEASE_URL: &str = "https://github.com/wasmCloud/wasmcloud-otp/releases/download"; @@ -166,7 +168,11 @@ where download_response.status() )); } - let wasmcloud_host_burrito = download_response.bytes().await?.to_vec(); + + let burrito_bites_stream = download_response + .bytes_stream() + .map(|result| result.map_err(|err| std::io::Error::new(std::io::ErrorKind::Other, err))); + let mut wasmcloud_host_burrito = StreamReader::new(burrito_bites_stream); let version_dir = dir.as_ref().join(version); let file_path = version_dir.join(WASMCLOUD_HOST_BIN); if let Some(parent_folder) = file_path.parent() { @@ -185,7 +191,7 @@ where wasmcloud_file.set_permissions(perms).await?; } } - tokio::io::copy(&mut wasmcloud_host_burrito.as_slice(), &mut wasmcloud_file).await?; + tokio::io::copy(&mut wasmcloud_host_burrito, &mut wasmcloud_file).await?; } // Return success if wasmCloud components exist, error otherwise @@ -392,7 +398,7 @@ mod test { } const NATS_SERVER_VERSION: &str = "v2.8.4"; - const WASMCLOUD_HOST_VERSION: &str = "v0.63.0"; + const WASMCLOUD_HOST_VERSION: &str = "v0.63.1"; #[tokio::test] async fn can_download_and_start_wasmcloud() -> anyhow::Result<()> { diff --git a/src/up/config.rs b/src/up/config.rs index 37c1dc96..764f1ebb 100644 --- a/src/up/config.rs +++ b/src/up/config.rs @@ -11,7 +11,7 @@ pub(crate) const DEFAULT_NATS_PORT: &str = "4222"; // wadm configuration values pub(crate) const WADM_VERSION: &str = "v0.4.0"; // wasmCloud configuration values, https://wasmcloud.dev/reference/host-runtime/host_configure/ -pub(crate) const WASMCLOUD_HOST_VERSION: &str = "v0.63.0"; +pub(crate) const WASMCLOUD_HOST_VERSION: &str = "v0.63.1"; pub(crate) const WASMCLOUD_DASHBOARD_PORT: &str = "WASMCLOUD_DASHBOARD_PORT"; // NOTE: We scan from this port up to 1000 ports higher, should always be under 64535 pub(crate) const DEFAULT_DASHBOARD_PORT: &str = "4000";