From 509a5619c94f3e925607f56cf6f00935c24b33c1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Felix=20Sch=C3=BCtt?= Date: Thu, 10 Nov 2022 15:46:09 +0100 Subject: [PATCH 01/18] Implement wasmer login --- Cargo.lock | 50 ++- lib/cli/Cargo.toml | 1 + lib/cli/src/cli.rs | 11 +- lib/cli/src/commands.rs | 3 +- lib/cli/src/commands/login.rs | 35 ++ lib/registry/graphql/queries/whoami.graphql | 5 + lib/registry/src/config.rs | 332 ++++++++++++++++ lib/registry/src/graphql.rs | 251 ++++++++++++ lib/registry/src/lib.rs | 408 +------------------- lib/registry/src/login.rs | 24 ++ lib/registry/src/utils.rs | 24 ++ 11 files changed, 738 insertions(+), 406 deletions(-) create mode 100644 lib/cli/src/commands/login.rs create mode 100644 lib/registry/graphql/queries/whoami.graphql create mode 100644 lib/registry/src/config.rs create mode 100644 lib/registry/src/graphql.rs create mode 100644 lib/registry/src/login.rs create mode 100644 lib/registry/src/utils.rs diff --git a/Cargo.lock b/Cargo.lock index 7f7d22b5e6e..b6b0dbb2f68 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -467,6 +467,20 @@ dependencies = [ "winapi", ] +[[package]] +name = "console" +version = "0.15.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c050367d967ced717c04b65d8c619d863ef9292ce0c5760028655a2fb298718c" +dependencies = [ + "encode_unicode 0.3.6", + "lazy_static", + "libc", + "terminal_size", + "unicode-width", + "winapi", +] + [[package]] name = "console_error_panic_hook" version = "0.1.7" @@ -830,6 +844,17 @@ dependencies = [ "syn", ] +[[package]] +name = "dialoguer" +version = "0.10.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a92e7e37ecef6857fdc0c0c5d42fd5b0938e46590c2183cc92dd310a6d078eb1" +dependencies = [ + "console", + "tempfile", + "zeroize", +] + [[package]] name = "diff" version = "0.1.13" @@ -969,6 +994,12 @@ version = "1.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "90e5c1c8368803113bf0c9584fc495a58b86dc8a29edbf8fe877d21d9507e797" +[[package]] +name = "encode_unicode" +version = "0.3.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a357d28ed41a50f9c765dbfe56cbc04a64e53e5fc58ba79fbc34c10ef3df831f" + [[package]] name = "encode_unicode" version = "1.0.0" @@ -2250,7 +2281,7 @@ checksum = "5f375cb74c23b51d23937ffdeb48b1fbf5b6409d4b9979c1418c1de58bc8f801" dependencies = [ "atty", "csv", - "encode_unicode", + "encode_unicode 1.0.0", "lazy_static", "term 0.7.0", "unicode-width", @@ -3214,6 +3245,16 @@ dependencies = [ "winapi-util", ] +[[package]] +name = "terminal_size" +version = "0.1.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "633c1a546cee861a1a6d0dc69ebeca693bf4296661ba7852b9d21d159e0506df" +dependencies = [ + "libc", + "winapi", +] + [[package]] name = "termtree" version = "0.2.4" @@ -3937,6 +3978,7 @@ dependencies = [ "chrono", "clap 3.2.23", "colored 2.0.0", + "dialoguer", "dirs 4.0.0", "distance", "fern", @@ -4856,3 +4898,9 @@ name = "yansi" version = "0.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "09041cd90cf85f7f8b2df60c646f853b7f535ce68f85244eb6731cf89fa498ec" + +[[package]] +name = "zeroize" +version = "1.5.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c394b5bd0c6f669e7275d9c20aa90ae064cb22e75a1cad54e1b34088034b149f" diff --git a/lib/cli/Cargo.toml b/lib/cli/Cargo.toml index 4f18cf35864..04114e920b3 100644 --- a/lib/cli/Cargo.toml +++ b/lib/cli/Cargo.toml @@ -71,6 +71,7 @@ libc = { version = "^0.2", default-features = false } nuke-dir = { version = "0.1.0", optional = true } webc = { version = "3.0.1", optional = true } isatty = "0.1.9" +dialoguer = "0.10.2" [build-dependencies] chrono = { version = "^0.4", default-features = false, features = [ "std", "clock" ] } diff --git a/lib/cli/src/cli.rs b/lib/cli/src/cli.rs index 6e1603f4855..969d541a95f 100644 --- a/lib/cli/src/cli.rs +++ b/lib/cli/src/cli.rs @@ -10,7 +10,7 @@ use crate::commands::CreateExe; use crate::commands::CreateObj; #[cfg(feature = "wast")] use crate::commands::Wast; -use crate::commands::{Cache, Config, Inspect, List, Run, SelfUpdate, Validate}; +use crate::commands::{Cache, Config, Inspect, List, Login, Run, SelfUpdate, Validate}; use crate::error::PrettyError; use clap::{CommandFactory, ErrorKind, Parser}; use std::fmt; @@ -44,6 +44,10 @@ enum WasmerCLIOptions { #[clap(name = "run")] Run(Run), + /// Login into a wapm.io-like registry + #[clap(name = "login")] + Login(Login), + /// Wasmer cache #[clap(subcommand, name = "cache")] Cache(Cache), @@ -164,6 +168,7 @@ impl WasmerCLIOptions { Self::Config(config) => config.execute(), Self::Inspect(inspect) => inspect.execute(), Self::List(list) => list.execute(), + Self::Login(login) => login.execute(), #[cfg(feature = "wast")] Self::Wast(wast) => wast.execute(), #[cfg(target_os = "linux")] @@ -220,7 +225,9 @@ fn wasmer_main_inner() -> Result<(), anyhow::Error> { } else { match command.unwrap_or(&"".to_string()).as_ref() { "cache" | "compile" | "config" | "create-exe" | "help" | "inspect" | "run" - | "self-update" | "validate" | "wast" | "binfmt" | "list" => WasmerCLIOptions::parse(), + | "self-update" | "validate" | "wast" | "binfmt" | "list" | "login" => { + WasmerCLIOptions::parse() + } _ => { WasmerCLIOptions::try_parse_from(args.iter()).unwrap_or_else(|e| { match e.kind() { diff --git a/lib/cli/src/commands.rs b/lib/cli/src/commands.rs index ad5dc013572..cff06ea16ba 100644 --- a/lib/cli/src/commands.rs +++ b/lib/cli/src/commands.rs @@ -11,6 +11,7 @@ mod create_exe; mod create_obj; mod inspect; mod list; +mod login; mod run; mod self_update; mod validate; @@ -27,7 +28,7 @@ pub use create_exe::*; pub use create_obj::*; #[cfg(feature = "wast")] pub use wast::*; -pub use {cache::*, config::*, inspect::*, list::*, run::*, self_update::*, validate::*}; +pub use {cache::*, config::*, inspect::*, list::*, login::*, run::*, self_update::*, validate::*}; /// The kind of object format to emit. #[derive(Debug, Copy, Clone, clap::Parser)] diff --git a/lib/cli/src/commands/login.rs b/lib/cli/src/commands/login.rs new file mode 100644 index 00000000000..3d5c3999c47 --- /dev/null +++ b/lib/cli/src/commands/login.rs @@ -0,0 +1,35 @@ +use clap::Parser; +use dialoguer::Input; + +/// Subcommand for listing packages +#[derive(Debug, Clone, Parser)] +pub struct Login { + /// Registry to log into (default: wapm.io) + #[clap(name = "REGISTRY")] + pub registry: Option, + /// Login token + #[clap(name = "TOKEN")] + pub token: Option, +} + +impl Login { + fn get_token_or_ask_user(&self) -> Result { + match self.token.as_ref() { + Some(s) => Ok(s.clone()), + None => Input::new() + .with_prompt("Please paste the login token from https://wapm.io/me:\"") + .interact_text(), + } + } + + /// execute [List] + pub fn execute(&self) -> Result<(), anyhow::Error> { + let token = self.get_token_or_ask_user()?; + let registry = self + .registry + .as_deref() + .unwrap_or("https://registry.wapm.io"); + wasmer_registry::login::login_and_save_token(registry, &token) + .map_err(|e| anyhow::anyhow!("{e}")) + } +} diff --git a/lib/registry/graphql/queries/whoami.graphql b/lib/registry/graphql/queries/whoami.graphql new file mode 100644 index 00000000000..0c42eec2683 --- /dev/null +++ b/lib/registry/graphql/queries/whoami.graphql @@ -0,0 +1,5 @@ +query WhoAmIQuery { + viewer { + username + } +} \ No newline at end of file diff --git a/lib/registry/src/config.rs b/lib/registry/src/config.rs new file mode 100644 index 00000000000..c72fcb485a8 --- /dev/null +++ b/lib/registry/src/config.rs @@ -0,0 +1,332 @@ +use graphql_client::GraphQLQuery; +use serde::Deserialize; +use serde::Serialize; +use std::collections::BTreeMap; +use std::env; +use std::path::PathBuf; + +pub static GLOBAL_CONFIG_FILE_NAME: &str = if cfg!(target_os = "wasi") { + "/.private/wapm.toml" +} else { + "wapm.toml" +}; + +#[derive(Deserialize, Default, Serialize, Debug, PartialEq)] +pub struct PartialWapmConfig { + /// The number of seconds to wait before checking the registry for a new + /// version of the package. + #[serde(default = "wax_default_cooldown")] + pub wax_cooldown: i32, + + /// The registry that wapm will connect to. + pub registry: Registries, + + /// Whether or not telemetry is enabled. + #[cfg(feature = "telemetry")] + #[serde(default)] + pub telemetry: Telemetry, + + /// Whether or not updated notifications are enabled. + #[cfg(feature = "update-notifications")] + #[serde(default)] + pub update_notifications: UpdateNotifications, + + /// The proxy to use when connecting to the Internet. + #[serde(default)] + pub proxy: Proxy, +} + +pub const fn wax_default_cooldown() -> i32 { + 5 * 60 +} + +#[derive(Deserialize, Serialize, Debug, PartialEq, Default)] +pub struct Proxy { + pub url: Option, +} + +#[derive(Deserialize, Serialize, Debug, PartialEq, Default)] +pub struct UpdateNotifications { + pub enabled: String, +} + +#[cfg(feature = "telemetry")] +#[derive(Deserialize, Serialize, Debug, PartialEq)] +pub struct Telemetry { + pub enabled: String, +} + +#[derive(Deserialize, Serialize, Debug, PartialEq, Clone)] +#[serde(untagged)] +pub enum Registries { + Single(Registry), + Multi(MultiRegistry), +} + +#[derive(Deserialize, Serialize, Debug, PartialEq, Clone)] +pub struct MultiRegistry { + /// Currently active registry + pub current: String, + /// Map from "RegistryUrl" to "LoginToken", in order to + /// be able to be able to easily switch between registries + pub tokens: BTreeMap, +} + +impl Default for Registries { + fn default() -> Self { + Registries::Single(Registry { + url: format_graphql("https://registry.wapm.io"), + token: None, + }) + } +} + +#[derive(Deserialize, Serialize, Debug, PartialEq, Clone)] +pub struct Registry { + pub url: String, + pub token: Option, +} + +pub fn format_graphql(registry: &str) -> String { + let mut registry = registry.to_string(); + if !registry.starts_with("https://") { + registry = format!("https://{registry}"); + } + if registry.ends_with("/graphql") { + registry.to_string() + } else if registry.ends_with('/') { + format!("{}graphql", registry) + } else { + format!("{}/graphql", registry) + } +} + +fn test_if_registry_present(registry: &str) -> Result<(), String> { + let q = TestIfRegistryPresent::build_query(test_if_registry_present::Variables {}); + let _: test_if_registry_present::ResponseData = + crate::graphql::execute_query(registry, "", &q).map_err(|e| format!("{e}"))?; + Ok(()) +} + +#[derive(PartialEq, Eq, Copy, Clone)] +pub enum UpdateRegistry { + Update, + LeaveAsIs, +} + +#[test] +fn test_registries_switch_token() { + let mut registries = Registries::default(); + + registries.set_current_registry("https://registry.wapm.dev"); + assert_eq!( + registries.get_current_registry(), + "https://registry.wapm.dev/graphql".to_string() + ); + registries.set_login_token_for_registry( + "https://registry.wapm.io", + "token1", + UpdateRegistry::LeaveAsIs, + ); + assert_eq!( + registries.get_current_registry(), + "https://registry.wapm.dev/graphql".to_string() + ); + assert_eq!( + registries.get_login_token_for_registry(®istries.get_current_registry()), + None + ); + registries.set_current_registry("https://registry.wapm.io"); + assert_eq!( + registries.get_login_token_for_registry(®istries.get_current_registry()), + Some("token1".to_string()) + ); + registries.clear_current_registry_token(); + assert_eq!( + registries.get_login_token_for_registry(®istries.get_current_registry()), + None + ); +} + +impl Registries { + /// Gets the current (active) registry URL + pub fn clear_current_registry_token(&mut self) { + match self { + Registries::Single(s) => { + s.token = None; + } + Registries::Multi(m) => { + m.tokens.remove(&m.current); + m.tokens.remove(&format_graphql(&m.current)); + } + } + } + + pub fn get_graphql_url(&self) -> String { + let registry = self.get_current_registry(); + format_graphql(®istry) + } + + /// Gets the current (active) registry URL + pub fn get_current_registry(&self) -> String { + match self { + Registries::Single(s) => format_graphql(&s.url), + Registries::Multi(m) => format_graphql(&m.current), + } + } + + /// Sets the current (active) registry URL + pub fn set_current_registry(&mut self, registry: &str) { + let registry = format_graphql(registry); + if let Err(e) = test_if_registry_present(®istry) { + println!("Error when trying to ping registry {registry:?}: {e}"); + if registry.contains("wapm.dev") { + println!("Note: The correct URL for wapm.dev is https://registry.wapm.dev, not {registry}"); + } else if registry.contains("wapm.io") { + println!( + "Note: The correct URL for wapm.io is https://registry.wapm.io, not {registry}" + ); + } + println!("WARNING: Registry {registry:?} will be used, but commands may not succeed."); + } + match self { + Registries::Single(s) => s.url = registry, + Registries::Multi(m) => m.current = registry, + } + } + + /// Returns the login token for the registry + pub fn get_login_token_for_registry(&self, registry: &str) -> Option { + match self { + Registries::Single(s) if s.url == registry || format_graphql(registry) == s.url => { + s.token.clone() + } + Registries::Multi(m) => m + .tokens + .get(registry) + .or_else(|| m.tokens.get(&format_graphql(registry))) + .cloned(), + _ => None, + } + } + + /// Sets the login token for the registry URL + pub fn set_login_token_for_registry( + &mut self, + registry: &str, + token: &str, + update_current_registry: UpdateRegistry, + ) { + let new_map = match self { + Registries::Single(s) => { + if s.url == registry { + Registries::Single(Registry { + url: format_graphql(registry), + token: Some(token.to_string()), + }) + } else { + let mut map = BTreeMap::new(); + if let Some(token) = s.token.clone() { + map.insert(format_graphql(&s.url), token); + } + map.insert(format_graphql(registry), token.to_string()); + Registries::Multi(MultiRegistry { + current: format_graphql(&s.url), + tokens: map, + }) + } + } + Registries::Multi(m) => { + m.tokens.insert(format_graphql(registry), token.to_string()); + if update_current_registry == UpdateRegistry::Update { + m.current = format_graphql(registry); + } + Registries::Multi(m.clone()) + } + }; + *self = new_map; + } +} + +impl PartialWapmConfig { + /// Save the config to a file + #[cfg(not(feature = "integration_tests"))] + pub fn save(&self) -> anyhow::Result<()> { + use std::{fs::File, io::Write}; + let path = Self::get_file_location().map_err(|e| anyhow::anyhow!("{e}"))?; + let config_serialized = toml::to_string(&self)?; + let mut file = File::create(path)?; + file.write_all(config_serialized.as_bytes())?; + Ok(()) + } + + /// A mocked version of the standard function for integration tests + #[cfg(feature = "integration_tests")] + pub fn save(&self) -> anyhow::Result<()> { + let config_serialized = toml::to_string(&self)?; + crate::integration_tests::data::RAW_CONFIG_DATA.with(|rcd| { + *rcd.borrow_mut() = Some(config_serialized); + }); + + Ok(()) + } + + pub fn from_file() -> Result { + let path = Self::get_file_location()?; + + match std::fs::read_to_string(&path) { + Ok(config_toml) => { + toml::from_str(&config_toml).map_err(|e| format!("could not parse {path:?}: {e}")) + } + Err(_e) => Ok(Self::default()), + } + } + + pub fn get_current_dir() -> std::io::Result { + #[cfg(target_os = "wasi")] + if let Some(pwd) = std::env::var("PWD").ok() { + return Ok(PathBuf::from(pwd)); + } + std::env::current_dir() + } + + pub fn get_folder() -> Result { + Ok( + if let Some(folder_str) = env::var("WASMER_DIR").ok().filter(|s| !s.is_empty()) { + let folder = PathBuf::from(folder_str); + std::fs::create_dir_all(folder.clone()) + .map_err(|e| format!("cannot create config directory: {e}"))?; + folder + } else { + #[allow(unused_variables)] + let default_dir = Self::get_current_dir() + .ok() + .unwrap_or_else(|| PathBuf::from("/".to_string())); + #[cfg(feature = "dirs")] + let home_dir = + dirs::home_dir().ok_or(GlobalConfigError::CannotFindHomeDirectory)?; + #[cfg(not(feature = "dirs"))] + let home_dir = std::env::var("HOME") + .ok() + .unwrap_or_else(|| default_dir.to_string_lossy().to_string()); + let mut folder = PathBuf::from(home_dir); + folder.push(".wasmer"); + std::fs::create_dir_all(folder.clone()) + .map_err(|e| format!("cannot create config directory: {e}"))?; + folder + }, + ) + } + + fn get_file_location() -> Result { + Ok(Self::get_folder()?.join(GLOBAL_CONFIG_FILE_NAME)) + } +} + +#[derive(GraphQLQuery)] +#[graphql( + schema_path = "graphql/schema.graphql", + query_path = "graphql/queries/test_if_registry_present.graphql", + response_derives = "Debug" +)] +struct TestIfRegistryPresent; diff --git a/lib/registry/src/graphql.rs b/lib/registry/src/graphql.rs new file mode 100644 index 00000000000..ea579597d39 --- /dev/null +++ b/lib/registry/src/graphql.rs @@ -0,0 +1,251 @@ +use graphql_client::*; +#[cfg(not(target_os = "wasi"))] +use reqwest::{ + blocking::{multipart::Form, Client}, + header::USER_AGENT, +}; +use std::env; +use std::time::Duration; +#[cfg(target_os = "wasi")] +use {wasm_bus_reqwest::prelude::header::*, wasm_bus_reqwest::prelude::*}; + +mod proxy { + //! Code for dealing with setting things up to proxy network requests + use thiserror::Error; + + #[derive(Debug, Error)] + pub enum ProxyError { + #[error("Failed to parse URL from {}: {}", url_location, error_message)] + UrlParseError { + url_location: String, + error_message: String, + }, + + #[error("Could not connect to proxy: {0}")] + ConnectionError(String), + } + + /// Tries to set up a proxy + /// + /// This function reads from wapm config's `proxy.url` first, then checks + /// `ALL_PROXY`, `HTTPS_PROXY`, and `HTTP_PROXY` environment variables, in both + /// upper case and lower case, in that order. + /// + /// If a proxy is specified in wapm config's `proxy.url`, it is assumed + /// to be a general proxy + /// + /// A return value of `Ok(None)` means that there was no attempt to set up a proxy, + /// `Ok(Some(proxy))` means that the proxy was set up successfully, and `Err(e)` that + /// there was a failure while attempting to set up the proxy. + pub fn maybe_set_up_proxy() -> anyhow::Result> { + use std::env; + let proxy = if let Ok(proxy_url) = env::var("ALL_PROXY").or_else(|_| env::var("all_proxy")) + { + reqwest::Proxy::all(&proxy_url).map(|proxy| (proxy_url, proxy, "ALL_PROXY")) + } else if let Ok(https_proxy_url) = + env::var("HTTPS_PROXY").or_else(|_| env::var("https_proxy")) + { + reqwest::Proxy::https(&https_proxy_url) + .map(|proxy| (https_proxy_url, proxy, "HTTPS_PROXY")) + } else if let Ok(http_proxy_url) = + env::var("HTTP_PROXY").or_else(|_| env::var("http_proxy")) + { + reqwest::Proxy::http(&http_proxy_url).map(|proxy| (http_proxy_url, proxy, "http_proxy")) + } else { + return Ok(None); + } + .map_err(|e| ProxyError::ConnectionError(e.to_string())) + .and_then( + |(proxy_url_str, proxy, url_location): (String, _, &'static str)| { + url::Url::parse(&proxy_url_str) + .map_err(|e| ProxyError::UrlParseError { + url_location: url_location.to_string(), + error_message: e.to_string(), + }) + .map(|url| { + if !(url.username().is_empty()) && url.password().is_some() { + proxy.basic_auth(url.username(), url.password().unwrap_or_default()) + } else { + proxy + } + }) + }, + )?; + + Ok(Some(proxy)) + } +} + +#[derive(GraphQLQuery)] +#[graphql( + schema_path = "graphql/schema.graphql", + query_path = "graphql/queries/get_package_version.graphql", + response_derives = "Debug" +)] +pub(crate) struct GetPackageVersionQuery; + +#[derive(GraphQLQuery)] +#[graphql( + schema_path = "graphql/schema.graphql", + query_path = "graphql/queries/get_package_by_command.graphql", + response_derives = "Debug" +)] +pub(crate) struct GetPackageByCommandQuery; + +#[derive(GraphQLQuery)] +#[graphql( + schema_path = "graphql/schema.graphql", + query_path = "graphql/queries/test_if_registry_present.graphql", + response_derives = "Debug" +)] +pub(crate) struct TestIfRegistryPresent; + +#[cfg(target_os = "wasi")] +pub fn whoami_distro() -> String { + whoami::os().to_lowercase() +} + +#[cfg(not(target_os = "wasi"))] +pub fn whoami_distro() -> String { + whoami::distro().to_lowercase() +} + +pub fn execute_query_modifier_inner_check_json( + registry_url: &str, + login_token: &str, + query: &QueryBody, + timeout: Option, + form_modifier: F, +) -> anyhow::Result<()> +where + V: serde::Serialize, + F: FnOnce(Form) -> Form, +{ + let client = { + let builder = Client::builder(); + + #[cfg(not(target_os = "wasi"))] + let builder = if let Some(proxy) = proxy::maybe_set_up_proxy()? { + builder.proxy(proxy) + } else { + builder + }; + builder.build()? + }; + + let vars = serde_json::to_string(&query.variables).unwrap(); + + let form = Form::new() + .text("query", query.query.to_string()) + .text("operationName", query.operation_name.to_string()) + .text("variables", vars); + + let form = form_modifier(form); + + let user_agent = format!( + "wapm/{} {} {}", + env!("CARGO_PKG_VERSION"), + whoami::platform(), + whoami_distro(), + ); + + let mut res = client + .post(registry_url) + .multipart(form) + .bearer_auth(env::var("WAPM_REGISTRY_TOKEN").unwrap_or_else(|_| login_token.to_string())) + .header(USER_AGENT, user_agent); + + if let Some(t) = timeout { + res = res.timeout(t); + } + + let res = res.send()?; + + let _: Response = res.json()?; + + Ok(()) +} + +pub fn execute_query_modifier_inner( + registry_url: &str, + login_token: &str, + query: &QueryBody, + timeout: Option, + form_modifier: F, +) -> anyhow::Result +where + for<'de> R: serde::Deserialize<'de>, + V: serde::Serialize, + F: FnOnce(Form) -> Form, +{ + let client = { + let builder = Client::builder(); + + #[cfg(not(target_os = "wasi"))] + let builder = if let Some(proxy) = proxy::maybe_set_up_proxy()? { + builder.proxy(proxy) + } else { + builder + }; + builder.build()? + }; + + let vars = serde_json::to_string(&query.variables).unwrap(); + + let form = Form::new() + .text("query", query.query.to_string()) + .text("operationName", query.operation_name.to_string()) + .text("variables", vars); + + let form = form_modifier(form); + + let user_agent = format!( + "wapm/{} {} {}", + env!("CARGO_PKG_VERSION"), + whoami::platform(), + whoami_distro(), + ); + + let mut res = client + .post(registry_url) + .multipart(form) + .bearer_auth(env::var("WAPM_REGISTRY_TOKEN").unwrap_or_else(|_| login_token.to_string())) + .header(USER_AGENT, user_agent); + + if let Some(t) = timeout { + res = res.timeout(t); + } + + let res = res.send()?; + let response_body: Response = res.json()?; + if let Some(errors) = response_body.errors { + let error_messages: Vec = errors.into_iter().map(|err| err.message).collect(); + return Err(anyhow::anyhow!("{}", error_messages.join(", "))); + } + Ok(response_body.data.expect("missing response data")) +} + +pub fn execute_query( + registry_url: &str, + login_token: &str, + query: &QueryBody, +) -> anyhow::Result +where + for<'de> R: serde::Deserialize<'de>, + V: serde::Serialize, +{ + execute_query_modifier_inner(registry_url, login_token, query, None, |f| f) +} + +pub fn execute_query_with_timeout( + registry_url: &str, + login_token: &str, + timeout: Duration, + query: &QueryBody, +) -> anyhow::Result +where + for<'de> R: serde::Deserialize<'de>, + V: serde::Serialize, +{ + execute_query_modifier_inner(registry_url, login_token, query, Some(timeout), |f| f) +} diff --git a/lib/registry/src/lib.rs b/lib/registry/src/lib.rs index 4d034d5815d..860f32f3d4b 100644 --- a/lib/registry/src/lib.rs +++ b/lib/registry/src/lib.rs @@ -1,272 +1,15 @@ use std::collections::BTreeMap; -use std::env; use std::fmt; use std::path::{Path, PathBuf}; use std::time::Duration; -use serde::Deserialize; -use serde::Serialize; +pub mod config; +pub mod graphql; +pub mod login; +pub mod utils; -pub mod graphql { - - use graphql_client::*; - #[cfg(not(target_os = "wasi"))] - use reqwest::{ - blocking::{multipart::Form, Client}, - header::USER_AGENT, - }; - use std::env; - use std::time::Duration; - #[cfg(target_os = "wasi")] - use {wasm_bus_reqwest::prelude::header::*, wasm_bus_reqwest::prelude::*}; - - mod proxy { - //! Code for dealing with setting things up to proxy network requests - use thiserror::Error; - - #[derive(Debug, Error)] - pub enum ProxyError { - #[error("Failed to parse URL from {}: {}", url_location, error_message)] - UrlParseError { - url_location: String, - error_message: String, - }, - - #[error("Could not connect to proxy: {0}")] - ConnectionError(String), - } - - /// Tries to set up a proxy - /// - /// This function reads from wapm config's `proxy.url` first, then checks - /// `ALL_PROXY`, `HTTPS_PROXY`, and `HTTP_PROXY` environment variables, in both - /// upper case and lower case, in that order. - /// - /// If a proxy is specified in wapm config's `proxy.url`, it is assumed - /// to be a general proxy - /// - /// A return value of `Ok(None)` means that there was no attempt to set up a proxy, - /// `Ok(Some(proxy))` means that the proxy was set up successfully, and `Err(e)` that - /// there was a failure while attempting to set up the proxy. - pub fn maybe_set_up_proxy() -> anyhow::Result> { - use std::env; - let proxy = if let Ok(proxy_url) = - env::var("ALL_PROXY").or_else(|_| env::var("all_proxy")) - { - reqwest::Proxy::all(&proxy_url).map(|proxy| (proxy_url, proxy, "ALL_PROXY")) - } else if let Ok(https_proxy_url) = - env::var("HTTPS_PROXY").or_else(|_| env::var("https_proxy")) - { - reqwest::Proxy::https(&https_proxy_url) - .map(|proxy| (https_proxy_url, proxy, "HTTPS_PROXY")) - } else if let Ok(http_proxy_url) = - env::var("HTTP_PROXY").or_else(|_| env::var("http_proxy")) - { - reqwest::Proxy::http(&http_proxy_url) - .map(|proxy| (http_proxy_url, proxy, "http_proxy")) - } else { - return Ok(None); - } - .map_err(|e| ProxyError::ConnectionError(e.to_string())) - .and_then( - |(proxy_url_str, proxy, url_location): (String, _, &'static str)| { - url::Url::parse(&proxy_url_str) - .map_err(|e| ProxyError::UrlParseError { - url_location: url_location.to_string(), - error_message: e.to_string(), - }) - .map(|url| { - if !(url.username().is_empty()) && url.password().is_some() { - proxy.basic_auth(url.username(), url.password().unwrap_or_default()) - } else { - proxy - } - }) - }, - )?; - - Ok(Some(proxy)) - } - } - - #[derive(GraphQLQuery)] - #[graphql( - schema_path = "graphql/schema.graphql", - query_path = "graphql/queries/get_package_version.graphql", - response_derives = "Debug" - )] - pub(crate) struct GetPackageVersionQuery; - - #[derive(GraphQLQuery)] - #[graphql( - schema_path = "graphql/schema.graphql", - query_path = "graphql/queries/get_package_by_command.graphql", - response_derives = "Debug" - )] - pub(crate) struct GetPackageByCommandQuery; - - #[derive(GraphQLQuery)] - #[graphql( - schema_path = "graphql/schema.graphql", - query_path = "graphql/queries/test_if_registry_present.graphql", - response_derives = "Debug" - )] - pub(crate) struct TestIfRegistryPresent; - - #[cfg(target_os = "wasi")] - pub fn whoami_distro() -> String { - whoami::os().to_lowercase() - } - - #[cfg(not(target_os = "wasi"))] - pub fn whoami_distro() -> String { - whoami::distro().to_lowercase() - } - - pub fn execute_query_modifier_inner_check_json( - registry_url: &str, - login_token: &str, - query: &QueryBody, - timeout: Option, - form_modifier: F, - ) -> anyhow::Result<()> - where - V: serde::Serialize, - F: FnOnce(Form) -> Form, - { - let client = { - let builder = Client::builder(); - - #[cfg(not(target_os = "wasi"))] - let builder = if let Some(proxy) = proxy::maybe_set_up_proxy()? { - builder.proxy(proxy) - } else { - builder - }; - builder.build()? - }; - - let vars = serde_json::to_string(&query.variables).unwrap(); - - let form = Form::new() - .text("query", query.query.to_string()) - .text("operationName", query.operation_name.to_string()) - .text("variables", vars); - - let form = form_modifier(form); - - let user_agent = format!( - "wapm/{} {} {}", - env!("CARGO_PKG_VERSION"), - whoami::platform(), - whoami_distro(), - ); - - let mut res = client - .post(registry_url) - .multipart(form) - .bearer_auth( - env::var("WAPM_REGISTRY_TOKEN").unwrap_or_else(|_| login_token.to_string()), - ) - .header(USER_AGENT, user_agent); - - if let Some(t) = timeout { - res = res.timeout(t); - } - - let res = res.send()?; - - let _: Response = res.json()?; - - Ok(()) - } - - pub fn execute_query_modifier_inner( - registry_url: &str, - login_token: &str, - query: &QueryBody, - timeout: Option, - form_modifier: F, - ) -> anyhow::Result - where - for<'de> R: serde::Deserialize<'de>, - V: serde::Serialize, - F: FnOnce(Form) -> Form, - { - let client = { - let builder = Client::builder(); - - #[cfg(not(target_os = "wasi"))] - let builder = if let Some(proxy) = proxy::maybe_set_up_proxy()? { - builder.proxy(proxy) - } else { - builder - }; - builder.build()? - }; - - let vars = serde_json::to_string(&query.variables).unwrap(); - - let form = Form::new() - .text("query", query.query.to_string()) - .text("operationName", query.operation_name.to_string()) - .text("variables", vars); - - let form = form_modifier(form); - - let user_agent = format!( - "wapm/{} {} {}", - env!("CARGO_PKG_VERSION"), - whoami::platform(), - whoami_distro(), - ); - - let mut res = client - .post(registry_url) - .multipart(form) - .bearer_auth( - env::var("WAPM_REGISTRY_TOKEN").unwrap_or_else(|_| login_token.to_string()), - ) - .header(USER_AGENT, user_agent); - - if let Some(t) = timeout { - res = res.timeout(t); - } - - let res = res.send()?; - let response_body: Response = res.json()?; - if let Some(errors) = response_body.errors { - let error_messages: Vec = errors.into_iter().map(|err| err.message).collect(); - return Err(anyhow::anyhow!("{}", error_messages.join(", "))); - } - Ok(response_body.data.expect("missing response data")) - } - - pub fn execute_query( - registry_url: &str, - login_token: &str, - query: &QueryBody, - ) -> anyhow::Result - where - for<'de> R: serde::Deserialize<'de>, - V: serde::Serialize, - { - execute_query_modifier_inner(registry_url, login_token, query, None, |f| f) - } - - pub fn execute_query_with_timeout( - registry_url: &str, - login_token: &str, - timeout: Duration, - query: &QueryBody, - ) -> anyhow::Result - where - for<'de> R: serde::Deserialize<'de>, - V: serde::Serialize, - { - execute_query_modifier_inner(registry_url, login_token, query, Some(timeout), |f| f) - } -} +use crate::config::{format_graphql, Registries}; +pub use config::PartialWapmConfig; pub static GLOBAL_CONFIG_FILE_NAME: &str = if cfg!(target_os = "wasi") { "/.private/wapm.toml" @@ -274,145 +17,6 @@ pub static GLOBAL_CONFIG_FILE_NAME: &str = if cfg!(target_os = "wasi") { "wapm.toml" }; -#[derive(Deserialize, Default, Serialize, Debug, PartialEq)] -pub struct PartialWapmConfig { - /// The number of seconds to wait before checking the registry for a new - /// version of the package. - #[serde(default = "wax_default_cooldown")] - pub wax_cooldown: i32, - - /// The registry that wapm will connect to. - pub registry: Registries, - - /// Whether or not telemetry is enabled. - #[cfg(feature = "telemetry")] - #[serde(default)] - pub telemetry: Telemetry, - - /// Whether or not updated notifications are enabled. - #[cfg(feature = "update-notifications")] - #[serde(default)] - pub update_notifications: UpdateNotifications, - - /// The proxy to use when connecting to the Internet. - #[serde(default)] - pub proxy: Proxy, -} - -pub const fn wax_default_cooldown() -> i32 { - 5 * 60 -} - -#[derive(Deserialize, Serialize, Debug, PartialEq, Default)] -pub struct Proxy { - pub url: Option, -} - -#[derive(Deserialize, Serialize, Debug, PartialEq, Default)] -pub struct UpdateNotifications { - pub enabled: String, -} - -#[cfg(feature = "telemetry")] -#[derive(Deserialize, Serialize, Debug, PartialEq)] -pub struct Telemetry { - pub enabled: String, -} - -#[derive(Deserialize, Serialize, Debug, PartialEq, Clone)] -#[serde(untagged)] -pub enum Registries { - Single(Registry), - Multi(MultiRegistry), -} - -#[derive(Deserialize, Serialize, Debug, PartialEq, Clone)] -pub struct MultiRegistry { - /// Currently active registry - pub current: String, - /// Map from "RegistryUrl" to "LoginToken", in order to - /// be able to be able to easily switch between registries - pub tokens: BTreeMap, -} - -impl Default for Registries { - fn default() -> Self { - Registries::Single(Registry { - url: format_graphql("https://registry.wapm.io"), - token: None, - }) - } -} - -#[derive(Deserialize, Serialize, Debug, PartialEq, Clone)] -pub struct Registry { - pub url: String, - pub token: Option, -} - -fn format_graphql(registry: &str) -> String { - if registry.ends_with("/graphql") { - registry.to_string() - } else if registry.ends_with('/') { - format!("{}graphql", registry) - } else { - format!("{}/graphql", registry) - } -} - -impl PartialWapmConfig { - pub fn from_file() -> Result { - let path = Self::get_file_location()?; - - match std::fs::read_to_string(&path) { - Ok(config_toml) => { - toml::from_str(&config_toml).map_err(|e| format!("could not parse {path:?}: {e}")) - } - Err(_e) => Ok(Self::default()), - } - } - - pub fn get_current_dir() -> std::io::Result { - #[cfg(target_os = "wasi")] - if let Some(pwd) = std::env::var("PWD").ok() { - return Ok(PathBuf::from(pwd)); - } - std::env::current_dir() - } - - pub fn get_folder() -> Result { - Ok( - if let Some(folder_str) = env::var("WASMER_DIR").ok().filter(|s| !s.is_empty()) { - let folder = PathBuf::from(folder_str); - std::fs::create_dir_all(folder.clone()) - .map_err(|e| format!("cannot create config directory: {e}"))?; - folder - } else { - #[allow(unused_variables)] - let default_dir = Self::get_current_dir() - .ok() - .unwrap_or_else(|| PathBuf::from("/".to_string())); - #[cfg(feature = "dirs")] - let home_dir = - dirs::home_dir().ok_or(GlobalConfigError::CannotFindHomeDirectory)?; - #[cfg(not(feature = "dirs"))] - let home_dir = std::env::var("HOME") - .ok() - .unwrap_or_else(|| default_dir.to_string_lossy().to_string()); - let mut folder = PathBuf::from(home_dir); - folder.push(".wasmer"); - std::fs::create_dir_all(folder.clone()) - .map_err(|e| format!("cannot create config directory: {e}"))?; - folder - }, - ) - } - - fn get_file_location() -> Result { - Ok(Self::get_folder()?.join(GLOBAL_CONFIG_FILE_NAME)) - } -} - #[derive(Debug, Clone, PartialEq, PartialOrd, Eq, Ord)] pub struct PackageDownloadInfo { pub registry: String, diff --git a/lib/registry/src/login.rs b/lib/registry/src/login.rs new file mode 100644 index 00000000000..4432bd81cac --- /dev/null +++ b/lib/registry/src/login.rs @@ -0,0 +1,24 @@ +use crate::config::{format_graphql, Registries, UpdateRegistry}; +use crate::PartialWapmConfig; + +/// Login to a registry and save the token associated with it. +/// +/// Also sets the registry as the currently active registry to provide a better UX. +pub fn login_and_save_token(registry: &str, token: &str) -> Result<(), anyhow::Error> { + let registry = format_graphql(registry); + let mut config = PartialWapmConfig::from_file().map_err(|e| anyhow::anyhow!("{e}"))?; + config.registry.set_current_registry(®istry); + config.registry.set_login_token_for_registry( + &config.registry.get_current_registry(), + &token, + UpdateRegistry::Update, + ); + config.save()?; + let username = crate::utils::get_username_registry_token(®istry, token); + if let Some(s) = username.ok().and_then(|o| o) { + println!("Login for WAPM user {:?} saved", s); + } else { + println!("Login for WAPM user saved"); + } + Ok(()) +} diff --git a/lib/registry/src/utils.rs b/lib/registry/src/utils.rs new file mode 100644 index 00000000000..54475a9354d --- /dev/null +++ b/lib/registry/src/utils.rs @@ -0,0 +1,24 @@ +use crate::{graphql::execute_query, PartialWapmConfig}; +use graphql_client::GraphQLQuery; + +#[derive(GraphQLQuery)] +#[graphql( + schema_path = "graphql/schema.graphql", + query_path = "graphql/queries/whoami.graphql", + response_derives = "Debug" +)] +struct WhoAmIQuery; + +pub fn get_username() -> anyhow::Result> { + let config = PartialWapmConfig::from_file().map_err(|e| anyhow::anyhow!("{e}"))?; + let registry = &config.registry.get_current_registry(); + let q = WhoAmIQuery::build_query(who_am_i_query::Variables {}); + let response: who_am_i_query::ResponseData = execute_query(registry, "", &q)?; + Ok(response.viewer.map(|viewer| viewer.username)) +} + +pub fn get_username_registry_token(registry: &str, token: &str) -> anyhow::Result> { + let q = WhoAmIQuery::build_query(who_am_i_query::Variables {}); + let response: who_am_i_query::ResponseData = execute_query(registry, token, &q)?; + Ok(response.viewer.map(|viewer| viewer.username)) +} From fd9c354c51688148599728116b21822168a7c20d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Felix=20Sch=C3=BCtt?= Date: Thu, 10 Nov 2022 16:50:33 +0100 Subject: [PATCH 02/18] Add integration test --- tests/integration/cli/tests/login.rs | 30 ++++++++++++++++++++++++++++ 1 file changed, 30 insertions(+) create mode 100644 tests/integration/cli/tests/login.rs diff --git a/tests/integration/cli/tests/login.rs b/tests/integration/cli/tests/login.rs new file mode 100644 index 00000000000..352784a758b --- /dev/null +++ b/tests/integration/cli/tests/login.rs @@ -0,0 +1,30 @@ + +use anyhow::bail; +use std::path::PathBuf; +use std::process::Command; +use wasmer_integration_tests_cli::{get_repo_root_path, get_wasmer_path, ASSET_PATH, C_ASSET_PATH}; + +#[test] +fn login_works() -> anyhow::Result<()> { + let wapm_dev_token = std::env::var("WAPM_DEV_TOKEN").expect("WAPM_DEV_TOKEN env var not set"); + let output = Command::new(get_wasmer_path()) + .arg("login") + .arg("https://registry.wapm.dev/graphql") + .arg(wapm_dev_token) + .output()?; + + if !output.status.success() { + bail!( + "wasmer login failed with: stdout: {}\n\nstderr: {}", + std::str::from_utf8(&output.stdout) + .expect("stdout is not utf8! need to handle arbitrary bytes"), + std::str::from_utf8(&output.stderr) + .expect("stderr is not utf8! need to handle arbitrary bytes") + ); + } + + let stdout_output = std::str::from_utf8(&output.stdout).unwrap(); + assert_eq!(stdout_output, r#"Login for WAPM user "ciuser" saved"#); + + Ok(()) +} From 27e66b281132c21bf1b843333d29cc2ea80174b4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Felix=20Sch=C3=BCtt?= Date: Thu, 10 Nov 2022 17:01:29 +0100 Subject: [PATCH 03/18] Autocorrect wapm.dev / wapm.io URLs, fix integration test --- lib/registry/src/config.rs | 19 ++++++++----------- tests/integration/cli/tests/login.rs | 5 ++--- 2 files changed, 10 insertions(+), 14 deletions(-) diff --git a/lib/registry/src/config.rs b/lib/registry/src/config.rs index c72fcb485a8..3236c46e7c8 100644 --- a/lib/registry/src/config.rs +++ b/lib/registry/src/config.rs @@ -89,6 +89,11 @@ pub struct Registry { pub fn format_graphql(registry: &str) -> String { let mut registry = registry.to_string(); + if registry.contains("wapm.dev") { + registry = "https://registry.wapm.dev/graphql".to_string(); + } else if registry.contains("wapm.io") { + registry = "https://registry.wapm.io/graphql".to_string(); + } if !registry.starts_with("https://") { registry = format!("https://{registry}"); } @@ -102,10 +107,9 @@ pub fn format_graphql(registry: &str) -> String { } fn test_if_registry_present(registry: &str) -> Result<(), String> { - let q = TestIfRegistryPresent::build_query(test_if_registry_present::Variables {}); - let _: test_if_registry_present::ResponseData = - crate::graphql::execute_query(registry, "", &q).map_err(|e| format!("{e}"))?; - Ok(()) + crate::utils::get_username_registry_token(registry, "") + .map(|_| ()) + .map_err(|e| format!("{e}")) } #[derive(PartialEq, Eq, Copy, Clone)] @@ -180,13 +184,6 @@ impl Registries { let registry = format_graphql(registry); if let Err(e) = test_if_registry_present(®istry) { println!("Error when trying to ping registry {registry:?}: {e}"); - if registry.contains("wapm.dev") { - println!("Note: The correct URL for wapm.dev is https://registry.wapm.dev, not {registry}"); - } else if registry.contains("wapm.io") { - println!( - "Note: The correct URL for wapm.io is https://registry.wapm.io, not {registry}" - ); - } println!("WARNING: Registry {registry:?} will be used, but commands may not succeed."); } match self { diff --git a/tests/integration/cli/tests/login.rs b/tests/integration/cli/tests/login.rs index 352784a758b..573b674ba80 100644 --- a/tests/integration/cli/tests/login.rs +++ b/tests/integration/cli/tests/login.rs @@ -1,4 +1,3 @@ - use anyhow::bail; use std::path::PathBuf; use std::process::Command; @@ -9,7 +8,7 @@ fn login_works() -> anyhow::Result<()> { let wapm_dev_token = std::env::var("WAPM_DEV_TOKEN").expect("WAPM_DEV_TOKEN env var not set"); let output = Command::new(get_wasmer_path()) .arg("login") - .arg("https://registry.wapm.dev/graphql") + .arg("wapm.dev") .arg(wapm_dev_token) .output()?; @@ -24,7 +23,7 @@ fn login_works() -> anyhow::Result<()> { } let stdout_output = std::str::from_utf8(&output.stdout).unwrap(); - assert_eq!(stdout_output, r#"Login for WAPM user "ciuser" saved"#); + assert_eq!(stdout_output, "Login for WAPM user \"ciuser\" saved\n"); Ok(()) } From 26c3d4de2ff68fd7de38df92e7170d999c5bf489 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Felix=20Sch=C3=BCtt?= Date: Thu, 10 Nov 2022 17:08:35 +0100 Subject: [PATCH 04/18] Fix make lint --- lib/registry/src/config.rs | 2 +- lib/registry/src/login.rs | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/lib/registry/src/config.rs b/lib/registry/src/config.rs index 3236c46e7c8..114e347e3a9 100644 --- a/lib/registry/src/config.rs +++ b/lib/registry/src/config.rs @@ -98,7 +98,7 @@ pub fn format_graphql(registry: &str) -> String { registry = format!("https://{registry}"); } if registry.ends_with("/graphql") { - registry.to_string() + registry } else if registry.ends_with('/') { format!("{}graphql", registry) } else { diff --git a/lib/registry/src/login.rs b/lib/registry/src/login.rs index 4432bd81cac..19471db7394 100644 --- a/lib/registry/src/login.rs +++ b/lib/registry/src/login.rs @@ -1,4 +1,4 @@ -use crate::config::{format_graphql, Registries, UpdateRegistry}; +use crate::config::{format_graphql, UpdateRegistry}; use crate::PartialWapmConfig; /// Login to a registry and save the token associated with it. @@ -10,7 +10,7 @@ pub fn login_and_save_token(registry: &str, token: &str) -> Result<(), anyhow::E config.registry.set_current_registry(®istry); config.registry.set_login_token_for_registry( &config.registry.get_current_registry(), - &token, + token, UpdateRegistry::Update, ); config.save()?; From 709d4c9ce27b3e8fa73fde2e6a34e5d544e135cf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Felix=20Sch=C3=BCtt?= Date: Thu, 10 Nov 2022 18:16:36 +0100 Subject: [PATCH 05/18] Add WAPM_DEV_TOKEN in CI --- .github/workflows/test-sys.yaml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/test-sys.yaml b/.github/workflows/test-sys.yaml index 46d4a769798..608e3f815e2 100644 --- a/.github/workflows/test-sys.yaml +++ b/.github/workflows/test-sys.yaml @@ -210,6 +210,7 @@ jobs: TARGET: ${{ matrix.target }} TARGET_DIR: target/${{ matrix.target }}/release CARGO_TARGET: --target ${{ matrix.target }} + WAPM_DEV_TOKEN: ${{ secrets.WAPM_DEV_TOKEN }} - name: Test integration CLI if: matrix.run_test && matrix.os == 'windows-2019' shell: bash From b798fe3f876e52f5a0b15ff1bfc2620161c8c175 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Felix=20Sch=C3=BCtt?= Date: Fri, 11 Nov 2022 11:36:17 +0100 Subject: [PATCH 06/18] Adress review comment: wasmer login --registry {value} --- lib/cli/src/commands/login.rs | 10 +++------- tests/integration/cli/tests/login.rs | 1 + 2 files changed, 4 insertions(+), 7 deletions(-) diff --git a/lib/cli/src/commands/login.rs b/lib/cli/src/commands/login.rs index 3d5c3999c47..796b2231cbd 100644 --- a/lib/cli/src/commands/login.rs +++ b/lib/cli/src/commands/login.rs @@ -5,8 +5,8 @@ use dialoguer::Input; #[derive(Debug, Clone, Parser)] pub struct Login { /// Registry to log into (default: wapm.io) - #[clap(name = "REGISTRY")] - pub registry: Option, + #[clap(long, default_value = "wapm.io")] + pub registry: String, /// Login token #[clap(name = "TOKEN")] pub token: Option, @@ -25,11 +25,7 @@ impl Login { /// execute [List] pub fn execute(&self) -> Result<(), anyhow::Error> { let token = self.get_token_or_ask_user()?; - let registry = self - .registry - .as_deref() - .unwrap_or("https://registry.wapm.io"); - wasmer_registry::login::login_and_save_token(registry, &token) + wasmer_registry::login::login_and_save_token(&self.registry, &token) .map_err(|e| anyhow::anyhow!("{e}")) } } diff --git a/tests/integration/cli/tests/login.rs b/tests/integration/cli/tests/login.rs index 573b674ba80..7a3cdb9ad5f 100644 --- a/tests/integration/cli/tests/login.rs +++ b/tests/integration/cli/tests/login.rs @@ -8,6 +8,7 @@ fn login_works() -> anyhow::Result<()> { let wapm_dev_token = std::env::var("WAPM_DEV_TOKEN").expect("WAPM_DEV_TOKEN env var not set"); let output = Command::new(get_wasmer_path()) .arg("login") + .arg("--registry") .arg("wapm.dev") .arg(wapm_dev_token) .output()?; From b8c281b1b544ca77d0075cd3de8ccbcd3ad5fab7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Felix=20Sch=C3=BCtt?= Date: Fri, 11 Nov 2022 11:43:08 +0100 Subject: [PATCH 07/18] Fix https://{registry}/me display --- lib/cli/src/commands/login.rs | 26 +++++++++++++++++++++++--- lib/registry/src/lib.rs | 3 ++- 2 files changed, 25 insertions(+), 4 deletions(-) diff --git a/lib/cli/src/commands/login.rs b/lib/cli/src/commands/login.rs index 796b2231cbd..290c1e73f9f 100644 --- a/lib/cli/src/commands/login.rs +++ b/lib/cli/src/commands/login.rs @@ -16,9 +16,29 @@ impl Login { fn get_token_or_ask_user(&self) -> Result { match self.token.as_ref() { Some(s) => Ok(s.clone()), - None => Input::new() - .with_prompt("Please paste the login token from https://wapm.io/me:\"") - .interact_text(), + None => { + let registry_host = url::Url::parse(&wasmer_registry::format_graphql( + &self.registry, + )) + .map_err(|e| { + std::io::Error::new( + std::io::ErrorKind::Other, + format!("Invalid registry for login {}: {e}", self.registry), + ) + })?; + let registry_host = registry_host.host_str().ok_or_else(|| { + std::io::Error::new( + std::io::ErrorKind::Other, + format!("Invalid registry for login {}: no host", self.registry), + ) + })?; + Input::new() + .with_prompt(&format!( + "Please paste the login token from https://{}/me:\"", + registry_host + )) + .interact_text() + } } } diff --git a/lib/registry/src/lib.rs b/lib/registry/src/lib.rs index 860f32f3d4b..db725ebe492 100644 --- a/lib/registry/src/lib.rs +++ b/lib/registry/src/lib.rs @@ -8,7 +8,8 @@ pub mod graphql; pub mod login; pub mod utils; -use crate::config::{format_graphql, Registries}; +pub use crate::config::format_graphql; +use crate::config::Registries; pub use config::PartialWapmConfig; pub static GLOBAL_CONFIG_FILE_NAME: &str = if cfg!(target_os = "wasi") { From 5084c142accdc5b9dfbb8c28cc4e944eda809105 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Felix=20Sch=C3=BCtt?= Date: Fri, 11 Nov 2022 11:58:59 +0100 Subject: [PATCH 08/18] Remove --feature="integration_tests" --- lib/registry/src/config.rs | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/lib/registry/src/config.rs b/lib/registry/src/config.rs index 114e347e3a9..0e8b75355ac 100644 --- a/lib/registry/src/config.rs +++ b/lib/registry/src/config.rs @@ -1,10 +1,19 @@ use graphql_client::GraphQLQuery; use serde::Deserialize; use serde::Serialize; +#[cfg(test)] +use std::cell::RefCell; use std::collections::BTreeMap; use std::env; use std::path::PathBuf; +#[cfg(test)] +thread_local! { + /// The string is the contents of the manifest, the Option is whether or not the manifest exists. + /// Used to mock reading and writing the manifest to the file system. + pub static RAW_CONFIG_DATA: RefCell> = RefCell::new(None); +} + pub static GLOBAL_CONFIG_FILE_NAME: &str = if cfg!(target_os = "wasi") { "/.private/wapm.toml" } else { @@ -247,7 +256,7 @@ impl Registries { impl PartialWapmConfig { /// Save the config to a file - #[cfg(not(feature = "integration_tests"))] + #[cfg(not(test))] pub fn save(&self) -> anyhow::Result<()> { use std::{fs::File, io::Write}; let path = Self::get_file_location().map_err(|e| anyhow::anyhow!("{e}"))?; @@ -258,10 +267,10 @@ impl PartialWapmConfig { } /// A mocked version of the standard function for integration tests - #[cfg(feature = "integration_tests")] + #[cfg(test)] pub fn save(&self) -> anyhow::Result<()> { let config_serialized = toml::to_string(&self)?; - crate::integration_tests::data::RAW_CONFIG_DATA.with(|rcd| { + RAW_CONFIG_DATA.with(|rcd| { *rcd.borrow_mut() = Some(config_serialized); }); From a9b064736e1e143fb626609ff4e66edfb90f70e5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Felix=20Sch=C3=BCtt?= Date: Fri, 11 Nov 2022 12:08:12 +0100 Subject: [PATCH 09/18] Fix panic in graphql, adress review comments --- lib/registry/src/config.rs | 7 +------ lib/registry/src/graphql.rs | 6 +++--- 2 files changed, 4 insertions(+), 9 deletions(-) diff --git a/lib/registry/src/config.rs b/lib/registry/src/config.rs index 0e8b75355ac..7c7f94e962e 100644 --- a/lib/registry/src/config.rs +++ b/lib/registry/src/config.rs @@ -308,13 +308,8 @@ impl PartialWapmConfig { let default_dir = Self::get_current_dir() .ok() .unwrap_or_else(|| PathBuf::from("/".to_string())); - #[cfg(feature = "dirs")] let home_dir = - dirs::home_dir().ok_or(GlobalConfigError::CannotFindHomeDirectory)?; - #[cfg(not(feature = "dirs"))] - let home_dir = std::env::var("HOME") - .ok() - .unwrap_or_else(|| default_dir.to_string_lossy().to_string()); + dirs::home_dir().ok_or_else(|| "cannot find home directory".to_string())?; let mut folder = PathBuf::from(home_dir); folder.push(".wasmer"); std::fs::create_dir_all(folder.clone()) diff --git a/lib/registry/src/graphql.rs b/lib/registry/src/graphql.rs index ea579597d39..72470b9e464 100644 --- a/lib/registry/src/graphql.rs +++ b/lib/registry/src/graphql.rs @@ -124,7 +124,6 @@ where let client = { let builder = Client::builder(); - #[cfg(not(target_os = "wasi"))] let builder = if let Some(proxy) = proxy::maybe_set_up_proxy()? { builder.proxy(proxy) } else { @@ -181,7 +180,6 @@ where let client = { let builder = Client::builder(); - #[cfg(not(target_os = "wasi"))] let builder = if let Some(proxy) = proxy::maybe_set_up_proxy()? { builder.proxy(proxy) } else { @@ -222,7 +220,9 @@ where let error_messages: Vec = errors.into_iter().map(|err| err.message).collect(); return Err(anyhow::anyhow!("{}", error_messages.join(", "))); } - Ok(response_body.data.expect("missing response data")) + response_body + .data + .ok_or_else(|| anyhow::anyhow!("missing response data")) } pub fn execute_query( From 3cf558a61cb5a70a1b9d6960ae87a624c09d09fc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Felix=20Sch=C3=BCtt?= Date: Fri, 11 Nov 2022 12:23:57 +0100 Subject: [PATCH 10/18] Fix "make lint" --- lib/registry/src/config.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/registry/src/config.rs b/lib/registry/src/config.rs index 7c7f94e962e..e1625e3a4fe 100644 --- a/lib/registry/src/config.rs +++ b/lib/registry/src/config.rs @@ -310,7 +310,7 @@ impl PartialWapmConfig { .unwrap_or_else(|| PathBuf::from("/".to_string())); let home_dir = dirs::home_dir().ok_or_else(|| "cannot find home directory".to_string())?; - let mut folder = PathBuf::from(home_dir); + let mut folder = home_dir; folder.push(".wasmer"); std::fs::create_dir_all(folder.clone()) .map_err(|e| format!("cannot create config directory: {e}"))?; From 7b85da3820a2ad0d5de60e2cbe7fd194900bd6a8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Felix=20Sch=C3=BCtt?= Date: Fri, 11 Nov 2022 13:25:04 +0100 Subject: [PATCH 11/18] Remove duplicate save() function from PartialWapmConfig --- lib/registry/src/config.rs | 79 ++++++++++++++++---------------------- lib/registry/src/login.rs | 3 +- 2 files changed, 35 insertions(+), 47 deletions(-) diff --git a/lib/registry/src/config.rs b/lib/registry/src/config.rs index e1625e3a4fe..cc9dd354b58 100644 --- a/lib/registry/src/config.rs +++ b/lib/registry/src/config.rs @@ -1,18 +1,9 @@ use graphql_client::GraphQLQuery; use serde::Deserialize; use serde::Serialize; -#[cfg(test)] -use std::cell::RefCell; use std::collections::BTreeMap; use std::env; -use std::path::PathBuf; - -#[cfg(test)] -thread_local! { - /// The string is the contents of the manifest, the Option is whether or not the manifest exists. - /// Used to mock reading and writing the manifest to the file system. - pub static RAW_CONFIG_DATA: RefCell> = RefCell::new(None); -} +use std::path::{Path, PathBuf}; pub static GLOBAL_CONFIG_FILE_NAME: &str = if cfg!(target_os = "wasi") { "/.private/wapm.toml" @@ -256,27 +247,14 @@ impl Registries { impl PartialWapmConfig { /// Save the config to a file - #[cfg(not(test))] - pub fn save(&self) -> anyhow::Result<()> { + pub fn save>(&self, to: P) -> anyhow::Result<()> { use std::{fs::File, io::Write}; - let path = Self::get_file_location().map_err(|e| anyhow::anyhow!("{e}"))?; let config_serialized = toml::to_string(&self)?; - let mut file = File::create(path)?; + let mut file = File::create(to)?; file.write_all(config_serialized.as_bytes())?; Ok(()) } - /// A mocked version of the standard function for integration tests - #[cfg(test)] - pub fn save(&self) -> anyhow::Result<()> { - let config_serialized = toml::to_string(&self)?; - RAW_CONFIG_DATA.with(|rcd| { - *rcd.borrow_mut() = Some(config_serialized); - }); - - Ok(()) - } - pub fn from_file() -> Result { let path = Self::get_file_location()?; @@ -297,29 +275,38 @@ impl PartialWapmConfig { } pub fn get_folder() -> Result { - Ok( - if let Some(folder_str) = env::var("WASMER_DIR").ok().filter(|s| !s.is_empty()) { - let folder = PathBuf::from(folder_str); - std::fs::create_dir_all(folder.clone()) - .map_err(|e| format!("cannot create config directory: {e}"))?; - folder - } else { - #[allow(unused_variables)] - let default_dir = Self::get_current_dir() - .ok() - .unwrap_or_else(|| PathBuf::from("/".to_string())); - let home_dir = - dirs::home_dir().ok_or_else(|| "cannot find home directory".to_string())?; - let mut folder = home_dir; - folder.push(".wasmer"); - std::fs::create_dir_all(folder.clone()) - .map_err(|e| format!("cannot create config directory: {e}"))?; - folder - }, - ) + #[cfg(test)] + { + let test_dir = std::env::temp_dir().join("test_wasmer"); + let _ = std::fs::create_dir_all(&test_dir); + Ok(test_dir.to_path_buf()) + } + #[cfg(not(test))] + { + Ok( + if let Some(folder_str) = env::var("WASMER_DIR").ok().filter(|s| !s.is_empty()) { + let folder = PathBuf::from(folder_str); + std::fs::create_dir_all(folder.clone()) + .map_err(|e| format!("cannot create config directory: {e}"))?; + folder + } else { + #[allow(unused_variables)] + let default_dir = Self::get_current_dir() + .ok() + .unwrap_or_else(|| PathBuf::from("/".to_string())); + let home_dir = + dirs::home_dir().ok_or_else(|| "cannot find home directory".to_string())?; + let mut folder = home_dir; + folder.push(".wasmer"); + std::fs::create_dir_all(folder.clone()) + .map_err(|e| format!("cannot create config directory: {e}"))?; + folder + }, + ) + } } - fn get_file_location() -> Result { + pub fn get_file_location() -> Result { Ok(Self::get_folder()?.join(GLOBAL_CONFIG_FILE_NAME)) } } diff --git a/lib/registry/src/login.rs b/lib/registry/src/login.rs index 19471db7394..a63ff03d92d 100644 --- a/lib/registry/src/login.rs +++ b/lib/registry/src/login.rs @@ -13,7 +13,8 @@ pub fn login_and_save_token(registry: &str, token: &str) -> Result<(), anyhow::E token, UpdateRegistry::Update, ); - config.save()?; + let path = PartialWapmConfig::get_file_location().map_err(|e| anyhow::anyhow!("{e}"))?; + config.save(&path)?; let username = crate::utils::get_username_registry_token(®istry, token); if let Some(s) = username.ok().and_then(|o| o) { println!("Login for WAPM user {:?} saved", s); From 8559eaee0335e128c1ad577f05840c27b0b0ca7b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Felix=20Sch=C3=BCtt?= Date: Fri, 11 Nov 2022 13:29:19 +0100 Subject: [PATCH 12/18] Remove wasi-special case for PWD --- lib/registry/src/config.rs | 4 ---- 1 file changed, 4 deletions(-) diff --git a/lib/registry/src/config.rs b/lib/registry/src/config.rs index cc9dd354b58..4b37b4aa466 100644 --- a/lib/registry/src/config.rs +++ b/lib/registry/src/config.rs @@ -267,10 +267,6 @@ impl PartialWapmConfig { } pub fn get_current_dir() -> std::io::Result { - #[cfg(target_os = "wasi")] - if let Some(pwd) = std::env::var("PWD").ok() { - return Ok(PathBuf::from(pwd)); - } std::env::current_dir() } From fcaf2552568eaee0387de8f97edc351063cafb71 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Felix=20Sch=C3=BCtt?= Date: Fri, 11 Nov 2022 15:00:58 +0100 Subject: [PATCH 13/18] Add test_name to WapmConfig::get_folder --- Cargo.lock | 1 + lib/registry/Cargo.toml | 3 ++ lib/registry/src/config.rs | 70 ++++++++++++++++++++++---------------- lib/registry/src/lib.rs | 59 ++++++++++++++++++++++++++------ lib/registry/src/login.rs | 13 ++++++- lib/registry/src/utils.rs | 5 ++- 6 files changed, 108 insertions(+), 43 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index b6b0dbb2f68..09a8a64a3d7 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4223,6 +4223,7 @@ dependencies = [ "flate2", "graphql_client", "lzma-rs", + "rand 0.8.5", "reqwest", "semver 1.0.14", "serde", diff --git a/lib/registry/Cargo.toml b/lib/registry/Cargo.toml index 03a3a97ea9d..25ea4e0dd80 100644 --- a/lib/registry/Cargo.toml +++ b/lib/registry/Cargo.toml @@ -5,6 +5,9 @@ edition = "2021" license = "MIT" description = "Crate to interact with the wasmer registry (wapm.io), download packages, etc." +[dev-dependencies] +rand = "0.8.5" + [dependencies] dirs = "4.0.0" graphql_client = "0.11.0" diff --git a/lib/registry/src/config.rs b/lib/registry/src/config.rs index 4b37b4aa466..63ae4ac3dff 100644 --- a/lib/registry/src/config.rs +++ b/lib/registry/src/config.rs @@ -2,6 +2,7 @@ use graphql_client::GraphQLQuery; use serde::Deserialize; use serde::Serialize; use std::collections::BTreeMap; +#[cfg(not(test))] use std::env; use std::path::{Path, PathBuf}; @@ -255,7 +256,10 @@ impl PartialWapmConfig { Ok(()) } - pub fn from_file() -> Result { + pub fn from_file(#[cfg(test)] test_name: &str) -> Result { + #[cfg(test)] + let path = Self::get_file_location(test_name)?; + #[cfg(not(test))] let path = Self::get_file_location()?; match std::fs::read_to_string(&path) { @@ -270,38 +274,44 @@ impl PartialWapmConfig { std::env::current_dir() } + #[cfg(test)] + pub fn get_folder(test_name: &str) -> Result { + let test_name = std::env::var("WASMER_REGISTRY_TEST_NAME").unwrap(); + let test_dir = std::env::temp_dir().join("test_wasmer").join(test_name); + let _ = std::fs::create_dir_all(&test_dir); + Ok(test_dir.to_path_buf()) + } + + #[cfg(not(test))] pub fn get_folder() -> Result { - #[cfg(test)] - { - let test_dir = std::env::temp_dir().join("test_wasmer"); - let _ = std::fs::create_dir_all(&test_dir); - Ok(test_dir.to_path_buf()) - } - #[cfg(not(test))] - { - Ok( - if let Some(folder_str) = env::var("WASMER_DIR").ok().filter(|s| !s.is_empty()) { - let folder = PathBuf::from(folder_str); - std::fs::create_dir_all(folder.clone()) - .map_err(|e| format!("cannot create config directory: {e}"))?; - folder - } else { - #[allow(unused_variables)] - let default_dir = Self::get_current_dir() - .ok() - .unwrap_or_else(|| PathBuf::from("/".to_string())); - let home_dir = - dirs::home_dir().ok_or_else(|| "cannot find home directory".to_string())?; - let mut folder = home_dir; - folder.push(".wasmer"); - std::fs::create_dir_all(folder.clone()) - .map_err(|e| format!("cannot create config directory: {e}"))?; - folder - }, - ) - } + Ok( + if let Some(folder_str) = env::var("WASMER_DIR").ok().filter(|s| !s.is_empty()) { + let folder = PathBuf::from(folder_str); + std::fs::create_dir_all(folder.clone()) + .map_err(|e| format!("cannot create config directory: {e}"))?; + folder + } else { + #[allow(unused_variables)] + let default_dir = Self::get_current_dir() + .ok() + .unwrap_or_else(|| PathBuf::from("/".to_string())); + let home_dir = + dirs::home_dir().ok_or_else(|| "cannot find home directory".to_string())?; + let mut folder = home_dir; + folder.push(".wasmer"); + std::fs::create_dir_all(folder.clone()) + .map_err(|e| format!("cannot create config directory: {e}"))?; + folder + }, + ) + } + + #[cfg(test)] + pub fn get_file_location(test_name: &str) -> Result { + Ok(Self::get_folder(test_name)?.join(GLOBAL_CONFIG_FILE_NAME)) } + #[cfg(not(test))] pub fn get_file_location() -> Result { Ok(Self::get_folder()?.join(GLOBAL_CONFIG_FILE_NAME)) } diff --git a/lib/registry/src/lib.rs b/lib/registry/src/lib.rs index db725ebe492..ad303472e67 100644 --- a/lib/registry/src/lib.rs +++ b/lib/registry/src/lib.rs @@ -47,8 +47,12 @@ pub fn get_package_local_dir( Ok(install_dir.join(namespace).join(name).join(version)) } -pub fn try_finding_local_command(cmd: &str) -> Option { - for p in get_all_local_packages(None) { +pub fn try_finding_local_command(#[cfg(test)] test_name: &str, cmd: &str) -> Option { + #[cfg(test)] + let local_packages = get_all_local_packages(None, test_name); + #[cfg(not(test))] + let local_packages = get_all_local_packages(None); + for p in local_packages { if p.get_commands() .unwrap_or_default() .iter() @@ -157,7 +161,10 @@ fn get_all_names_in_dir(dir: &PathBuf) -> Vec<(PathBuf, String)> { } /// Returns a list of all locally installed packages -pub fn get_all_local_packages(registry: Option<&str>) -> Vec { +pub fn get_all_local_packages( + registry: Option<&str>, + #[cfg(test)] test_name: &str, +) -> Vec { let mut packages = Vec::new(); let registries = match registry { Some(s) => vec![s.to_string()], @@ -169,7 +176,12 @@ pub fn get_all_local_packages(registry: Option<&str>) -> Vec { .filter_map(|s| url::Url::parse(&s).ok()?.host_str().map(|s| s.to_string())) .collect::>(); - let mut registries_in_root_dir = get_checkouts_dir() + #[cfg(not(test))] + let checkouts_dir = get_checkouts_dir(); + #[cfg(test)] + let checkouts_dir = get_checkouts_dir(test_name); + + let mut registries_in_root_dir = checkouts_dir .as_ref() .map(get_all_names_in_dir) .unwrap_or_default() @@ -208,11 +220,17 @@ pub fn get_all_local_packages(registry: Option<&str>) -> Vec { } pub fn get_local_package( + #[cfg(test)] test_name: &str, registry: Option<&str>, name: &str, version: Option<&str>, ) -> Option { - get_all_local_packages(registry) + #[cfg(not(test))] + let local_packages = get_all_local_packages(registry); + #[cfg(test)] + let local_packages = get_all_local_packages(registry, test_name); + + local_packages .iter() .find(|p| { if p.name != name { @@ -538,16 +556,35 @@ pub fn query_package_from_registry( }) } -pub fn get_wasmer_root_dir() -> Option { - PartialWapmConfig::get_folder().ok() +pub fn get_wasmer_root_dir(#[cfg(test)] test_name: &str) -> Option { + #[cfg(test)] + { + PartialWapmConfig::get_folder(test_name).ok() + } + #[cfg(not(test))] + { + PartialWapmConfig::get_folder().ok() + } } -pub fn get_checkouts_dir() -> Option { - Some(get_wasmer_root_dir()?.join("checkouts")) + +pub fn get_checkouts_dir(#[cfg(test)] test_name: &str) -> Option { + #[cfg(test)] + let root_dir = get_wasmer_root_dir(test_name)?; + #[cfg(not(test))] + let root_dir = get_wasmer_root_dir()?; + Some(root_dir.join("checkouts")) } /// Returs the path to the directory where all packages on this computer are being stored -pub fn get_global_install_dir(registry_host: &str) -> Option { - Some(get_checkouts_dir()?.join(registry_host)) +pub fn get_global_install_dir( + #[cfg(test)] test_name: &str, + registry_host: &str, +) -> Option { + #[cfg(test)] + let root_dir = get_checkouts_dir(test_name)?; + #[cfg(not(test))] + let root_dir = get_checkouts_dir()?; + Some(root_dir.join(registry_host)) } /// Whether the top-level directory should be stripped diff --git a/lib/registry/src/login.rs b/lib/registry/src/login.rs index a63ff03d92d..53bd8628665 100644 --- a/lib/registry/src/login.rs +++ b/lib/registry/src/login.rs @@ -4,8 +4,15 @@ use crate::PartialWapmConfig; /// Login to a registry and save the token associated with it. /// /// Also sets the registry as the currently active registry to provide a better UX. -pub fn login_and_save_token(registry: &str, token: &str) -> Result<(), anyhow::Error> { +pub fn login_and_save_token( + #[cfg(test)] test_name: &str, + registry: &str, + token: &str, +) -> Result<(), anyhow::Error> { let registry = format_graphql(registry); + #[cfg(test)] + let mut config = PartialWapmConfig::from_file(test_name).map_err(|e| anyhow::anyhow!("{e}"))?; + #[cfg(not(test))] let mut config = PartialWapmConfig::from_file().map_err(|e| anyhow::anyhow!("{e}"))?; config.registry.set_current_registry(®istry); config.registry.set_login_token_for_registry( @@ -13,6 +20,10 @@ pub fn login_and_save_token(registry: &str, token: &str) -> Result<(), anyhow::E token, UpdateRegistry::Update, ); + #[cfg(test)] + let path = + PartialWapmConfig::get_file_location(test_name).map_err(|e| anyhow::anyhow!("{e}"))?; + #[cfg(not(test))] let path = PartialWapmConfig::get_file_location().map_err(|e| anyhow::anyhow!("{e}"))?; config.save(&path)?; let username = crate::utils::get_username_registry_token(®istry, token); diff --git a/lib/registry/src/utils.rs b/lib/registry/src/utils.rs index 54475a9354d..e6185a32641 100644 --- a/lib/registry/src/utils.rs +++ b/lib/registry/src/utils.rs @@ -9,7 +9,10 @@ use graphql_client::GraphQLQuery; )] struct WhoAmIQuery; -pub fn get_username() -> anyhow::Result> { +pub fn get_username(#[cfg(test)] test_name: &str) -> anyhow::Result> { + #[cfg(test)] + let config = PartialWapmConfig::from_file(test_name).map_err(|e| anyhow::anyhow!("{e}"))?; + #[cfg(not(test))] let config = PartialWapmConfig::from_file().map_err(|e| anyhow::anyhow!("{e}"))?; let registry = &config.registry.get_current_registry(); let q = WhoAmIQuery::build_query(who_am_i_query::Variables {}); From 3bfcca837d64347a6017a07bc5d232b5d9a6a7d4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Felix=20Sch=C3=BCtt?= Date: Fri, 11 Nov 2022 15:33:57 +0100 Subject: [PATCH 14/18] Try to fix cargo test --- lib/registry/src/lib.rs | 76 +++++++++++++++++++++++++++++++++-------- 1 file changed, 62 insertions(+), 14 deletions(-) diff --git a/lib/registry/src/lib.rs b/lib/registry/src/lib.rs index ad303472e67..281a5f74562 100644 --- a/lib/registry/src/lib.rs +++ b/lib/registry/src/lib.rs @@ -30,6 +30,7 @@ pub struct PackageDownloadInfo { } pub fn get_package_local_dir( + #[cfg(test)] test_name: &str, registry_host: &str, name: &str, version: &str, @@ -42,8 +43,11 @@ pub fn get_package_local_dir( let (namespace, name) = name .split_once('/') .ok_or_else(|| format!("missing namespace / name for {name:?}"))?; - let install_dir = get_global_install_dir(registry_host) - .ok_or_else(|| format!("no install dir for {name:?}"))?; + #[cfg(test)] + let global_install_dir = get_global_install_dir(test_name, registry_host); + #[cfg(not(test))] + let global_install_dir = get_global_install_dir(registry_host); + let install_dir = global_install_dir.ok_or_else(|| format!("no install dir for {name:?}"))?; Ok(install_dir.join(namespace).join(name).join(version)) } @@ -53,11 +57,12 @@ pub fn try_finding_local_command(#[cfg(test)] test_name: &str, cmd: &str) -> Opt #[cfg(not(test))] let local_packages = get_all_local_packages(None); for p in local_packages { - if p.get_commands() - .unwrap_or_default() - .iter() - .any(|c| c == cmd) - { + #[cfg(not(test))] + let commands = p.get_commands(); + #[cfg(test)] + let commands = p.get_commands(test_name); + + if commands.unwrap_or_default().iter().any(|c| c == cmd) { return Some(p); } } @@ -72,16 +77,28 @@ pub struct LocalPackage { } impl LocalPackage { - pub fn get_path(&self) -> Result { + pub fn get_path(&self, #[cfg(test)] test_name: &str) -> Result { let host = url::Url::parse(&self.registry) .ok() .and_then(|o| o.host_str().map(|s| s.to_string())) .unwrap_or_else(|| self.registry.clone()); - get_package_local_dir(&host, &self.name, &self.version) + #[cfg(test)] + { + get_package_local_dir(test_name, &host, &self.name, &self.version) + } + + #[cfg(not(test))] + { + get_package_local_dir(&host, &self.name, &self.version) + } } - pub fn get_commands(&self) -> Result, String> { - let toml_path = self.get_path()?.join("wapm.toml"); + pub fn get_commands(&self, #[cfg(test)] test_name: &str) -> Result, String> { + #[cfg(not(test))] + let path = self.get_path()?; + #[cfg(test)] + let path = self.get_path(test_name)?; + let toml_path = path.join("wapm.toml"); let toml = std::fs::read_to_string(&toml_path) .map_err(|e| format!("error reading {}: {e}", toml_path.display()))?; let toml_parsed = toml::from_str::(&toml) @@ -194,7 +211,11 @@ pub fn get_all_local_packages( registry_hosts.dedup(); for host in registry_hosts { - let root_dir = match get_global_install_dir(&host) { + #[cfg(not(test))] + let global_install_dir = get_global_install_dir(&host); + #[cfg(test)] + let global_install_dir = get_global_install_dir(test_name, &host); + let root_dir = match global_install_dir { Some(o) => o, None => continue, }; @@ -381,6 +402,7 @@ fn test_get_if_package_has_new_version() { /// /// Also returns true if the package is not installed yet. pub fn get_if_package_has_new_version( + #[cfg(test)] test_name: &str, registry_url: &str, name: &str, version: Option, @@ -398,7 +420,12 @@ pub fn get_if_package_has_new_version( .split_once('/') .ok_or_else(|| format!("missing namespace / name for {name:?}"))?; - let package_dir = get_global_install_dir(&host).map(|path| path.join(namespace).join(name)); + #[cfg(not(test))] + let global_install_dir = get_global_install_dir(&host); + #[cfg(test)] + let global_install_dir = get_global_install_dir(test_name, &host); + + let package_dir = global_install_dir.map(|path| path.join(namespace).join(name)); let package_dir = match package_dir { Some(s) => s, @@ -684,6 +711,7 @@ where /// Given a triple of [registry, name, version], downloads and installs the /// .tar.gz if it doesn't yet exist, returns the (package dir, entrypoint .wasm file path) pub fn install_package( + #[cfg(test)] test_name: &str, registry: Option<&str>, name: &str, version: Option<&str>, @@ -714,12 +742,21 @@ pub fn install_package( for r in registries.iter() { if !force_install { + #[cfg(not(test))] let package_has_new_version = get_if_package_has_new_version( r, name, version.map(|s| s.to_string()), Duration::from_secs(60 * 5), )?; + #[cfg(test)] + let package_has_new_version = get_if_package_has_new_version( + test_name, + r, + name, + version.map(|s| s.to_string()), + Duration::from_secs(60 * 5), + )?; if let GetIfPackageHasNewVersionResult::UseLocalAlreadyInstalled { registry_host, namespace, @@ -770,6 +807,14 @@ pub fn install_package( .ok_or_else(|| format!("invalid url: {}", package_info.registry))? .to_string(); + #[cfg(test)] + let dir = get_package_local_dir( + test_name, + &host, + &package_info.package, + &package_info.version, + )?; + #[cfg(not(test))] let dir = get_package_local_dir(&host, &package_info.package, &package_info.version)?; let version = package_info.version; @@ -806,8 +851,11 @@ pub fn test_if_registry_present(registry: &str) -> Result { Ok(true) } -pub fn get_all_available_registries() -> Result, String> { +pub fn get_all_available_registries(#[cfg(test)] test_name: &str) -> Result, String> { + #[cfg(test)] + let config = PartialWapmConfig::from_file(test_name)?; let config = PartialWapmConfig::from_file()?; + let mut registries = Vec::new(); match config.registry { Registries::Single(s) => { From a5ae89b5c22fb51167be9a1ecbe4391533f7bf13 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Felix=20Sch=C3=BCtt?= Date: Thu, 17 Nov 2022 10:30:11 +0100 Subject: [PATCH 15/18] Use WASMER_TOKEN and wasmer/* in request header --- lib/registry/src/graphql.rs | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/lib/registry/src/graphql.rs b/lib/registry/src/graphql.rs index 72470b9e464..77e70fb2add 100644 --- a/lib/registry/src/graphql.rs +++ b/lib/registry/src/graphql.rs @@ -142,7 +142,7 @@ where let form = form_modifier(form); let user_agent = format!( - "wapm/{} {} {}", + "wasmer/{} {} {}", env!("CARGO_PKG_VERSION"), whoami::platform(), whoami_distro(), @@ -198,7 +198,7 @@ where let form = form_modifier(form); let user_agent = format!( - "wapm/{} {} {}", + "wasmer/{} {} {}", env!("CARGO_PKG_VERSION"), whoami::platform(), whoami_distro(), @@ -207,7 +207,12 @@ where let mut res = client .post(registry_url) .multipart(form) - .bearer_auth(env::var("WAPM_REGISTRY_TOKEN").unwrap_or_else(|_| login_token.to_string())) + .bearer_auth( + env::var("WASMER_TOKEN") + .ok() + .or_else(|| env::var("WAPM_REGISTRY_TOKEN").ok()) + .unwrap_or_else(|| login_token.to_string()), + ) .header(USER_AGENT, user_agent); if let Some(t) = timeout { From d27267094f4deffb252554ebcc4bf6e69294a1da Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Felix=20Sch=C3=BCtt?= Date: Thu, 17 Nov 2022 10:35:02 +0100 Subject: [PATCH 16/18] Adress duplicated logic --- lib/registry/src/graphql.rs | 34 ++++++++++++++-------------------- 1 file changed, 14 insertions(+), 20 deletions(-) diff --git a/lib/registry/src/graphql.rs b/lib/registry/src/graphql.rs index 77e70fb2add..5b1146ef69e 100644 --- a/lib/registry/src/graphql.rs +++ b/lib/registry/src/graphql.rs @@ -110,6 +110,18 @@ pub fn whoami_distro() -> String { whoami::distro().to_lowercase() } +fn setup_client() -> Result { + let builder = Client::builder(); + + let builder = if let Some(proxy) = proxy::maybe_set_up_proxy()? { + builder.proxy(proxy) + } else { + builder + }; + + builder.build().map_err(|e| e.into()) +} + pub fn execute_query_modifier_inner_check_json( registry_url: &str, login_token: &str, @@ -121,16 +133,7 @@ where V: serde::Serialize, F: FnOnce(Form) -> Form, { - let client = { - let builder = Client::builder(); - - let builder = if let Some(proxy) = proxy::maybe_set_up_proxy()? { - builder.proxy(proxy) - } else { - builder - }; - builder.build()? - }; + let client = setup_client()?; let vars = serde_json::to_string(&query.variables).unwrap(); @@ -177,16 +180,7 @@ where V: serde::Serialize, F: FnOnce(Form) -> Form, { - let client = { - let builder = Client::builder(); - - let builder = if let Some(proxy) = proxy::maybe_set_up_proxy()? { - builder.proxy(proxy) - } else { - builder - }; - builder.build()? - }; + let client = setup_client()?; let vars = serde_json::to_string(&query.variables).unwrap(); From cd38b39eb3593773be51852505dd8260fd963610 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Felix=20Sch=C3=BCtt?= Date: Thu, 17 Nov 2022 10:37:57 +0100 Subject: [PATCH 17/18] Add comments to GraphQL registry ping code --- lib/registry/src/graphql.rs | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/lib/registry/src/graphql.rs b/lib/registry/src/graphql.rs index 5b1146ef69e..26cf05c5313 100644 --- a/lib/registry/src/graphql.rs +++ b/lib/registry/src/graphql.rs @@ -122,6 +122,10 @@ fn setup_client() -> Result { builder.build().map_err(|e| e.into()) } +/// This function is being used to "ping" the registry +/// (see test_if_registry_present and see whether the response +/// is valid JSON, it doesn't check the response itself, +/// since the response format might change pub fn execute_query_modifier_inner_check_json( registry_url: &str, login_token: &str, @@ -154,7 +158,12 @@ where let mut res = client .post(registry_url) .multipart(form) - .bearer_auth(env::var("WAPM_REGISTRY_TOKEN").unwrap_or_else(|_| login_token.to_string())) + .bearer_auth( + env::var("WASMER_TOKEN") + .ok() + .or_else(|| env::var("WAPM_REGISTRY_TOKEN").ok()) + .unwrap_or_else(|| login_token.to_string()), + ) .header(USER_AGENT, user_agent); if let Some(t) = timeout { From 63fe99a6ff1c8fb00bc4a84cd85bc4675d914534 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Felix=20Sch=C3=BCtt?= Date: Thu, 17 Nov 2022 10:48:26 +0100 Subject: [PATCH 18/18] Fix wasmer-registry cargo test --- lib/registry/src/config.rs | 1 - lib/registry/src/lib.rs | 57 +++++++++++++++++++++++++++++--------- 2 files changed, 44 insertions(+), 14 deletions(-) diff --git a/lib/registry/src/config.rs b/lib/registry/src/config.rs index 63ae4ac3dff..ac2c3e2f051 100644 --- a/lib/registry/src/config.rs +++ b/lib/registry/src/config.rs @@ -276,7 +276,6 @@ impl PartialWapmConfig { #[cfg(test)] pub fn get_folder(test_name: &str) -> Result { - let test_name = std::env::var("WASMER_REGISTRY_TEST_NAME").unwrap(); let test_dir = std::env::temp_dir().join("test_wasmer").join(test_name); let _ = std::fs::create_dir_all(&test_dir); Ok(test_dir.to_path_buf()) diff --git a/lib/registry/src/lib.rs b/lib/registry/src/lib.rs index 281a5f74562..b50c0a03f86 100644 --- a/lib/registry/src/lib.rs +++ b/lib/registry/src/lib.rs @@ -53,7 +53,7 @@ pub fn get_package_local_dir( pub fn try_finding_local_command(#[cfg(test)] test_name: &str, cmd: &str) -> Option { #[cfg(test)] - let local_packages = get_all_local_packages(None, test_name); + let local_packages = get_all_local_packages(test_name, None); #[cfg(not(test))] let local_packages = get_all_local_packages(None); for p in local_packages { @@ -179,13 +179,22 @@ fn get_all_names_in_dir(dir: &PathBuf) -> Vec<(PathBuf, String)> { /// Returns a list of all locally installed packages pub fn get_all_local_packages( - registry: Option<&str>, #[cfg(test)] test_name: &str, + registry: Option<&str>, ) -> Vec { let mut packages = Vec::new(); let registries = match registry { Some(s) => vec![s.to_string()], - None => get_all_available_registries().unwrap_or_default(), + None => { + #[cfg(test)] + { + get_all_available_registries(test_name).unwrap_or_default() + } + #[cfg(not(test))] + { + get_all_available_registries().unwrap_or_default() + } + } }; let mut registry_hosts = registries @@ -249,7 +258,7 @@ pub fn get_local_package( #[cfg(not(test))] let local_packages = get_all_local_packages(registry); #[cfg(test)] - let local_packages = get_all_local_packages(registry, test_name); + let local_packages = get_all_local_packages(test_name, registry); local_packages .iter() @@ -350,15 +359,17 @@ pub enum GetIfPackageHasNewVersionResult { #[test] fn test_get_if_package_has_new_version() { + const TEST_NAME: &str = "test_get_if_package_has_new_version"; let fake_registry = "https://h0.com"; let fake_name = "namespace0/project1"; let fake_version = "1.0.0"; - let package_path = get_package_local_dir("h0.com", fake_name, fake_version).unwrap(); + let package_path = get_package_local_dir(TEST_NAME, "h0.com", fake_name, fake_version).unwrap(); let _ = std::fs::remove_file(&package_path.join("wapm.toml")); let _ = std::fs::remove_file(&package_path.join("wapm.toml")); let r1 = get_if_package_has_new_version( + TEST_NAME, fake_registry, "namespace0/project1", Some(fake_version.to_string()), @@ -375,11 +386,12 @@ fn test_get_if_package_has_new_version() { } ); - let package_path = get_package_local_dir("h0.com", fake_name, fake_version).unwrap(); + let package_path = get_package_local_dir(TEST_NAME, "h0.com", fake_name, fake_version).unwrap(); std::fs::create_dir_all(&package_path).unwrap(); std::fs::write(&package_path.join("wapm.toml"), b"").unwrap(); let r1 = get_if_package_has_new_version( + TEST_NAME, fake_registry, "namespace0/project1", Some(fake_version.to_string()), @@ -723,7 +735,16 @@ pub fn install_package( None => { let registries = match registry { Some(s) => vec![s.to_string()], - None => get_all_available_registries()?, + None => { + #[cfg(test)] + { + get_all_available_registries(test_name)? + } + #[cfg(not(test))] + { + get_all_available_registries()? + } + } }; let mut url_of_package = None; @@ -854,6 +875,7 @@ pub fn test_if_registry_present(registry: &str) -> Result { pub fn get_all_available_registries(#[cfg(test)] test_name: &str) -> Result, String> { #[cfg(test)] let config = PartialWapmConfig::from_file(test_name)?; + #[cfg(not(test))] let config = PartialWapmConfig::from_file()?; let mut registries = Vec::new(); @@ -875,6 +897,8 @@ pub fn get_all_available_registries(#[cfg(test)] test_name: &str) -> Result>()