From 0f8e018ad7be07127b8b45e9f6894e36762e8b14 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Boni=20Garc=C3=ADa?= Date: Sat, 7 Oct 2023 16:43:07 +0200 Subject: [PATCH] [rust] Automated Edge management (#11681 and #11683) (#12835) * [rust] Automated Edge management (macOS) (#11681) * [rust] Check also unstable versions for Edge management * [rust] Include logic to check fixed versions of Edge * [rust] Get browser url again if empty * [rust] Include logic for stable label * [rust] Automated Edge management (Linux) (#11681 and #11683) * [rust] Fix paths used to extract edge * [rust] Clean extract label and fix searched version * [rust] Refactor logic for downloading browsers in a common function * [rust] Install Edge in Windows through the MSI installer * [rust] Check admin permissions in Windows before downloading MSI installer * [rust] Use browser version in functions for requesting online repos * [rust] Refactor common logic when unavailable download or discovery * [rust] Fix condition checking Firefox nightly in mac * [rust] Update cargo bazel lock file * [rust] Separate function to ensure empty parent path before moving files --------- Co-authored-by: titusfortner Co-authored-by: Titus Fortner --- rust/Cargo.Bazel.lock | 306 +++++++++++++++++++++++- rust/Cargo.lock | 62 ++++- rust/Cargo.toml | 1 + rust/src/chrome.rs | 184 +++++--------- rust/src/edge.rs | 252 +++++++++++++++++++- rust/src/files.rs | 67 +++++- rust/src/firefox.rs | 342 ++++++++++----------------- rust/src/grid.rs | 39 ++- rust/src/iexplorer.rs | 39 ++- rust/src/lib.rs | 282 +++++++++++++++++++--- rust/src/safari.rs | 39 ++- rust/src/safaritp.rs | 39 ++- rust/tests/browser_download_tests.rs | 11 +- 13 files changed, 1247 insertions(+), 416 deletions(-) diff --git a/rust/Cargo.Bazel.lock b/rust/Cargo.Bazel.lock index 58046d9a2e4db..8188b66f1681c 100644 --- a/rust/Cargo.Bazel.lock +++ b/rust/Cargo.Bazel.lock @@ -1,5 +1,5 @@ { - "checksum": "495be0038fd3bd2826baeb0433f22c042c1b03eeeb117631135bc6339799addf", + "checksum": "7bb62c0cb24820374fb08c7eb1d2c1661ceb1a296f8cf2cad91579a7d2687eaf", "crates": { "addr2line 0.19.0": { "name": "addr2line", @@ -478,6 +478,73 @@ }, "license": "MIT OR Apache-2.0" }, + "ar 0.9.0": { + "name": "ar", + "version": "0.9.0", + "repository": { + "Http": { + "url": "https://crates.io/api/v1/crates/ar/0.9.0/download", + "sha256": "d67af77d68a931ecd5cbd8a3b5987d63a1d1d1278f7f6a60ae33db485cdebb69" + } + }, + "targets": [ + { + "Library": { + "crate_name": "ar", + "crate_root": "src/lib.rs", + "srcs": [ + "**/*.rs" + ] + } + } + ], + "library_target_name": "ar", + "common_attrs": { + "compile_data_glob": [ + "**" + ], + "edition": "2015", + "version": "0.9.0" + }, + "license": "MIT" + }, + "arrayvec 0.7.4": { + "name": "arrayvec", + "version": "0.7.4", + "repository": { + "Http": { + "url": "https://crates.io/api/v1/crates/arrayvec/0.7.4/download", + "sha256": "96d30a06541fbafbc7f82ed10c06164cfbd2c401138f6addd8404629c4b16711" + } + }, + "targets": [ + { + "Library": { + "crate_name": "arrayvec", + "crate_root": "src/lib.rs", + "srcs": [ + "**/*.rs" + ] + } + } + ], + "library_target_name": "arrayvec", + "common_attrs": { + "compile_data_glob": [ + "**" + ], + "crate_features": { + "common": [ + "default", + "std" + ], + "selects": {} + }, + "edition": "2018", + "version": "0.7.4" + }, + "license": "MIT OR Apache-2.0" + }, "assert_cmd 2.0.12": { "name": "assert_cmd", "version": "2.0.12", @@ -2021,6 +2088,81 @@ }, "license": "MIT OR Apache-2.0" }, + "debpkg 0.6.0": { + "name": "debpkg", + "version": "0.6.0", + "repository": { + "Http": { + "url": "https://crates.io/api/v1/crates/debpkg/0.6.0/download", + "sha256": "7ffffa9a03449467cfac11c9a4260556f477de22a935bb5e9475153de323c337" + } + }, + "targets": [ + { + "Library": { + "crate_name": "debpkg", + "crate_root": "src/lib.rs", + "srcs": [ + "**/*.rs" + ] + } + } + ], + "library_target_name": "debpkg", + "common_attrs": { + "compile_data_glob": [ + "**" + ], + "deps": { + "common": [ + { + "id": "ar 0.9.0", + "target": "ar" + }, + { + "id": "arrayvec 0.7.4", + "target": "arrayvec" + }, + { + "id": "bzip2 0.4.4", + "target": "bzip2" + }, + { + "id": "flate2 1.0.27", + "target": "flate2" + }, + { + "id": "indexmap 1.9.2", + "target": "indexmap" + }, + { + "id": "infer 0.8.1", + "target": "infer" + }, + { + "id": "log 0.4.20", + "target": "log" + }, + { + "id": "tar 0.4.40", + "target": "tar" + }, + { + "id": "xz2 0.1.7", + "target": "xz2" + }, + { + "id": "zstd 0.11.2+zstd.1.5.2", + "target": "zstd" + } + ], + "selects": {} + }, + "edition": "2018", + "version": "0.6.0" + }, + "license": "MIT" + }, "difflib 0.4.0": { "name": "difflib", "version": "0.4.0", @@ -4647,6 +4789,54 @@ }, "license": "MIT" }, + "infer 0.8.1": { + "name": "infer", + "version": "0.8.1", + "repository": { + "Http": { + "url": "https://crates.io/api/v1/crates/infer/0.8.1/download", + "sha256": "e035cede526e0b21d5adffc9fa0eb4ef5d6026fe9c5b0bfe8084b9472b587a55" + } + }, + "targets": [ + { + "Library": { + "crate_name": "infer", + "crate_root": "src/lib.rs", + "srcs": [ + "**/*.rs" + ] + } + } + ], + "library_target_name": "infer", + "common_attrs": { + "compile_data_glob": [ + "**" + ], + "crate_features": { + "common": [ + "alloc", + "cfb", + "default", + "std" + ], + "selects": {} + }, + "deps": { + "common": [ + { + "id": "cfb 0.7.3", + "target": "cfb" + } + ], + "selects": {} + }, + "edition": "2018", + "version": "0.8.1" + }, + "license": "MIT" + }, "inout 0.1.3": { "name": "inout", "version": "0.1.3", @@ -5280,6 +5470,77 @@ }, "license": "Apache-2.0" }, + "lzma-sys 0.1.20": { + "name": "lzma-sys", + "version": "0.1.20", + "repository": { + "Http": { + "url": "https://crates.io/api/v1/crates/lzma-sys/0.1.20/download", + "sha256": "5fda04ab3764e6cde78b9974eec4f779acaba7c4e84b36eca3cf77c581b85d27" + } + }, + "targets": [ + { + "Library": { + "crate_name": "lzma_sys", + "crate_root": "src/lib.rs", + "srcs": [ + "**/*.rs" + ] + } + }, + { + "BuildScript": { + "crate_name": "build_script_build", + "crate_root": "build.rs", + "srcs": [ + "**/*.rs" + ] + } + } + ], + "library_target_name": "lzma_sys", + "common_attrs": { + "compile_data_glob": [ + "**" + ], + "deps": { + "common": [ + { + "id": "libc 0.2.147", + "target": "libc" + }, + { + "id": "lzma-sys 0.1.20", + "target": "build_script_build" + } + ], + "selects": {} + }, + "edition": "2018", + "version": "0.1.20" + }, + "build_script_attrs": { + "data_glob": [ + "**" + ], + "deps": { + "common": [ + { + "id": "cc 1.0.79", + "target": "cc" + }, + { + "id": "pkg-config 0.3.26", + "target": "pkg_config" + } + ], + "selects": {} + }, + "links": "lzma" + }, + "license": "MIT/Apache-2.0" + }, "memchr 2.5.0": { "name": "memchr", "version": "2.5.0", @@ -7750,6 +8011,10 @@ "id": "clap 4.3.23", "target": "clap" }, + { + "id": "debpkg 0.6.0", + "target": "debpkg" + }, { "id": "directories 5.0.1", "target": "directories" @@ -12349,6 +12614,45 @@ }, "license": "MIT/Apache-2.0" }, + "xz2 0.1.7": { + "name": "xz2", + "version": "0.1.7", + "repository": { + "Http": { + "url": "https://crates.io/api/v1/crates/xz2/0.1.7/download", + "sha256": "388c44dc09d76f1536602ead6d325eb532f5c122f17782bd57fb47baeeb767e2" + } + }, + "targets": [ + { + "Library": { + "crate_name": "xz2", + "crate_root": "src/lib.rs", + "srcs": [ + "**/*.rs" + ] + } + } + ], + "library_target_name": "xz2", + "common_attrs": { + "compile_data_glob": [ + "**" + ], + "deps": { + "common": [ + { + "id": "lzma-sys 0.1.20", + "target": "lzma_sys" + } + ], + "selects": {} + }, + "edition": "2018", + "version": "0.1.7" + }, + "license": "MIT/Apache-2.0" + }, "zip 0.6.6": { "name": "zip", "version": "0.6.6", diff --git a/rust/Cargo.lock b/rust/Cargo.lock index 27bbb73d5360b..f1cf6b94a6b64 100644 --- a/rust/Cargo.lock +++ b/rust/Cargo.lock @@ -98,6 +98,18 @@ dependencies = [ "windows-sys 0.48.0", ] +[[package]] +name = "ar" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d67af77d68a931ecd5cbd8a3b5987d63a1d1d1278f7f6a60ae33db485cdebb69" + +[[package]] +name = "arrayvec" +version = "0.7.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "96d30a06541fbafbc7f82ed10c06164cfbd2c401138f6addd8404629c4b16711" + [[package]] name = "assert_cmd" version = "2.0.12" @@ -393,6 +405,24 @@ dependencies = [ "typenum", ] +[[package]] +name = "debpkg" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7ffffa9a03449467cfac11c9a4260556f477de22a935bb5e9475153de323c337" +dependencies = [ + "ar", + "arrayvec", + "bzip2", + "flate2", + "indexmap 1.9.2", + "infer 0.8.1", + "log", + "tar", + "xz2", + "zstd", +] + [[package]] name = "difflib" version = "0.4.0" @@ -868,6 +898,15 @@ dependencies = [ "hashbrown 0.14.0", ] +[[package]] +name = "infer" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e035cede526e0b21d5adffc9fa0eb4ef5d6026fe9c5b0bfe8084b9472b587a55" +dependencies = [ + "cfb", +] + [[package]] name = "infer" version = "0.15.0" @@ -990,6 +1029,17 @@ dependencies = [ "byteorder", ] +[[package]] +name = "lzma-sys" +version = "0.1.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5fda04ab3764e6cde78b9974eec4f779acaba7c4e84b36eca3cf77c581b85d27" +dependencies = [ + "cc", + "libc", + "pkg-config", +] + [[package]] name = "memchr" version = "2.5.0" @@ -1445,11 +1495,12 @@ dependencies = [ "assert_cmd", "bzip2", "clap", + "debpkg", "directories", "env_logger", "exitcode", "flate2", - "infer", + "infer 0.15.0", "is_executable", "log", "regex", @@ -2233,6 +2284,15 @@ dependencies = [ "libc", ] +[[package]] +name = "xz2" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "388c44dc09d76f1536602ead6d325eb532f5c122f17782bd57fb47baeeb767e2" +dependencies = [ + "lzma-sys", +] + [[package]] name = "zip" version = "0.6.6" diff --git a/rust/Cargo.toml b/rust/Cargo.toml index b6dcf3d853af7..1fe680ee5a0a4 100644 --- a/rust/Cargo.toml +++ b/rust/Cargo.toml @@ -32,6 +32,7 @@ toml = "0.7.6" bzip2 = "0.4.4" sevenz-rust = "0.5.2" walkdir = "2.4.0" +debpkg = "0.6.0" [dev-dependencies] assert_cmd = "2.0.12" diff --git a/rust/src/chrome.rs b/rust/src/chrome.rs index b488aaadb34b9..54692ee4f5c65 100644 --- a/rust/src/chrome.rs +++ b/rust/src/chrome.rs @@ -26,15 +26,15 @@ use std::path::PathBuf; use crate::config::ARCH::{ARM64, X32}; use crate::config::OS::{LINUX, MACOS, WINDOWS}; use crate::downloads::{parse_json_from_url, read_version_from_link}; -use crate::files::{compose_driver_path_in_cache, path_to_string, BrowserPath}; +use crate::files::{compose_driver_path_in_cache, BrowserPath}; use crate::logger::Logger; use crate::metadata::{ create_driver_metadata, get_driver_version_from_metadata, get_metadata, write_metadata, }; use crate::{ - create_browser_metadata, create_http_client, download_to_tmp_folder, format_three_args, - get_browser_version_from_metadata, uncompress, SeleniumManager, BETA, DASH_DASH_VERSION, DEV, - NIGHTLY, OFFLINE_REQUEST_ERR_MSG, REG_VERSION_ARG, STABLE, + create_http_client, format_three_args, SeleniumManager, BETA, DASH_DASH_VERSION, DEV, NIGHTLY, + OFFLINE_REQUEST_ERR_MSG, REG_VERSION_ARG, STABLE, + UNAVAILABLE_DOWNLOAD_WITH_MIN_VERSION_ERR_MSG, }; pub const CHROME_NAME: &str = "chrome"; @@ -48,8 +48,6 @@ const CFT_MACOS_APP_NAME: &str = "Google Chrome for Testing.app/Contents/MacOS/Google Chrome for Testing"; const MIN_CHROME_VERSION_CFT: i32 = 113; const MIN_CHROMEDRIVER_VERSION_CFT: i32 = 115; -const UNAVAILABLE_CFT_ERROR_MESSAGE: &str = - "{} {} not available for download in Chrome for Testing (minimum version: {})"; pub struct ChromeManager { pub browser_name: &'static str, @@ -173,7 +171,7 @@ impl ChromeManager { .collect(); if filtered_versions.is_empty() { return Err(format_three_args( - UNAVAILABLE_CFT_ERROR_MESSAGE, + UNAVAILABLE_DOWNLOAD_WITH_MIN_VERSION_ERR_MSG, self.get_driver_name(), &version_for_filtering, &MIN_CHROMEDRIVER_VERSION_CFT.to_string(), @@ -195,15 +193,6 @@ impl ChromeManager { Ok(driver_version.version.to_string()) } - - fn get_browser_binary_path_in_cache(&self) -> Result> { - let browser_in_cache = self.get_browser_path_in_cache()?; - if MACOS.is(self.get_os()) { - Ok(browser_in_cache.join(CFT_MACOS_APP_NAME)) - } else { - Ok(browser_in_cache.join(self.get_browser_name_with_extension())) - } - } } impl SeleniumManager for ChromeManager { @@ -414,118 +403,6 @@ impl SeleniumManager for ChromeManager { self.log = log; } - fn download_browser(&mut self) -> Result, Box> { - let browser_version; - let browser_name = self.browser_name; - let cache_path = self.get_cache_path()?; - let mut metadata = get_metadata(self.get_logger(), &cache_path); - let major_browser_version = self.get_major_browser_version(); - let major_browser_version_int = major_browser_version.parse::().unwrap_or_default(); - - // Browser version should be available in the CfT endpoints (>= 113) - if !self.is_browser_version_unstable() - && !self.is_browser_version_stable() - && !self.is_browser_version_empty() - && major_browser_version_int < MIN_CHROME_VERSION_CFT - { - return Err(format_three_args( - UNAVAILABLE_CFT_ERROR_MESSAGE, - browser_name, - &major_browser_version, - &MIN_CHROME_VERSION_CFT.to_string(), - ) - .into()); - } - - // Browser version is checked in the local metadata - match get_browser_version_from_metadata( - &metadata.browsers, - browser_name, - &major_browser_version, - ) { - Some(version) => { - self.get_logger().trace(format!( - "Browser with valid TTL. Getting {} version from metadata", - browser_name - )); - browser_version = version; - self.set_browser_version(browser_version.clone()); - } - _ => { - // If not in metadata, discover version using Chrome for Testing (CfT) endpoints - if self.is_browser_version_stable() || self.is_browser_version_empty() { - browser_version = self.request_latest_browser_version_from_online()?; - } else { - browser_version = self.request_fixed_browser_version_from_online()?; - } - self.set_browser_version(browser_version.clone()); - - let browser_ttl = self.get_ttl(); - if browser_ttl > 0 - && !self.is_browser_version_empty() - && !self.is_browser_version_stable() - { - metadata.browsers.push(create_browser_metadata( - browser_name, - &major_browser_version, - &browser_version, - browser_ttl, - )); - write_metadata(&metadata, self.get_logger(), cache_path); - } - } - } - self.get_logger().debug(format!( - "Required browser: {} {}", - browser_name, browser_version - )); - - // Checking if browser version is in the cache - let browser_binary_path = self.get_browser_binary_path_in_cache()?; - if browser_binary_path.exists() { - self.get_logger().debug(format!( - "{} {} already in the cache", - browser_name, browser_version - )); - } else { - // If browser is not in the cache, download it - let browser_url = if let Some(url) = self.browser_url.clone() { - url - } else { - if self.is_browser_version_stable() || self.is_browser_version_empty() { - self.request_latest_browser_version_from_online()?; - } else { - self.request_fixed_browser_version_from_online()?; - } - self.browser_url.clone().unwrap() - }; - self.get_logger().debug(format!( - "Downloading {} {} from {}", - self.get_browser_name(), - self.get_browser_version(), - browser_url - )); - let (_tmp_folder, driver_zip_file) = - download_to_tmp_folder(self.get_http_client(), browser_url, self.get_logger())?; - - uncompress( - &driver_zip_file, - &self.get_browser_path_in_cache()?, - self.get_logger(), - self.get_os(), - None, - None, - None, - )?; - } - if browser_binary_path.exists() { - self.set_browser_path(path_to_string(&browser_binary_path)); - Ok(Some(browser_binary_path)) - } else { - Ok(None) - } - } - fn get_platform_label(&self) -> &str { let os = self.get_os(); let arch = self.get_arch(); @@ -546,7 +423,10 @@ impl SeleniumManager for ChromeManager { } } - fn request_latest_browser_version_from_online(&mut self) -> Result> { + fn request_latest_browser_version_from_online( + &mut self, + _browser_version: &str, + ) -> Result> { let browser_name = self.browser_name; self.get_logger().trace(format!( "Using Chrome for Testing (CfT) endpoints to find out latest stable {} version", @@ -574,7 +454,10 @@ impl SeleniumManager for ChromeManager { Ok(browser_version) } - fn request_fixed_browser_version_from_online(&mut self) -> Result> { + fn request_fixed_browser_version_from_online( + &mut self, + _browser_version: &str, + ) -> Result> { let browser_name = self.browser_name; let mut browser_version = self.get_browser_version().to_string(); let major_browser_version = self.get_major_browser_version(); @@ -615,7 +498,7 @@ impl SeleniumManager for ChromeManager { .collect(); if filtered_versions.is_empty() { return Err(format_three_args( - UNAVAILABLE_CFT_ERROR_MESSAGE, + UNAVAILABLE_DOWNLOAD_WITH_MIN_VERSION_ERR_MSG, browser_name, &major_browser_version, &MIN_CHROME_VERSION_CFT.to_string(), @@ -634,6 +517,45 @@ impl SeleniumManager for ChromeManager { Ok(last_browser.version.to_string()) } } + + fn get_min_browser_version_for_download(&self) -> Result> { + Ok(MIN_CHROME_VERSION_CFT) + } + + fn get_browser_binary_path( + &mut self, + _browser_version: &str, + ) -> Result> { + let browser_in_cache = self.get_browser_path_in_cache()?; + if MACOS.is(self.get_os()) { + Ok(browser_in_cache.join(CFT_MACOS_APP_NAME)) + } else { + Ok(browser_in_cache.join(self.get_browser_name_with_extension())) + } + } + + fn get_browser_url_for_download( + &mut self, + browser_version: &str, + ) -> Result> { + if let Some(browser_url) = self.browser_url.clone() { + Ok(browser_url) + } else { + if self.is_browser_version_stable() || self.is_browser_version_empty() { + self.request_latest_browser_version_from_online(browser_version)?; + } else { + self.request_fixed_browser_version_from_online(browser_version)?; + } + Ok(self.browser_url.clone().unwrap()) + } + } + + fn get_browser_label_for_download( + &self, + _browser_version: &str, + ) -> Result, Box> { + Ok(None) + } } #[derive(Serialize, Deserialize)] diff --git a/rust/src/edge.rs b/rust/src/edge.rs index 18e69bc41b207..2913f20ec1227 100644 --- a/rust/src/edge.rs +++ b/rust/src/edge.rs @@ -17,20 +17,23 @@ use crate::config::ManagerConfig; use reqwest::Client; +use serde::{Deserialize, Serialize}; use std::collections::HashMap; +use std::env; use std::error::Error; -use std::path::PathBuf; +use std::path::{Path, PathBuf}; use crate::config::ARCH::{ARM64, X32}; use crate::config::OS::{LINUX, MACOS, WINDOWS}; -use crate::downloads::read_version_from_link; +use crate::downloads::{parse_json_from_url, read_version_from_link}; use crate::files::{compose_driver_path_in_cache, BrowserPath}; use crate::metadata::{ create_driver_metadata, get_driver_version_from_metadata, get_metadata, write_metadata, }; use crate::{ - create_http_client, Logger, SeleniumManager, BETA, DASH_DASH_VERSION, DEV, NIGHTLY, - OFFLINE_REQUEST_ERR_MSG, REG_VERSION_ARG, STABLE, + create_http_client, get_binary_extension, path_to_string, Logger, SeleniumManager, BETA, + DASH_DASH_VERSION, DEV, ENV_PROGRAM_FILES_X86, NIGHTLY, OFFLINE_REQUEST_ERR_MSG, + REG_VERSION_ARG, STABLE, }; pub const EDGE_NAMES: &[&str] = &["edge", "msedge", "microsoftedge"]; @@ -38,6 +41,14 @@ pub const EDGEDRIVER_NAME: &str = "msedgedriver"; const DRIVER_URL: &str = "https://msedgedriver.azureedge.net/"; const LATEST_STABLE: &str = "LATEST_STABLE"; const LATEST_RELEASE: &str = "LATEST_RELEASE"; +const BROWSER_URL: &str = "https://edgeupdates.microsoft.com/api/products"; +const MIN_EDGE_VERSION_DOWNLOAD: i32 = 113; +const EDGE_WINDOWS_AND_LINUX_APP_NAME: &str = "msedge"; +const EDGE_MACOS_APP_NAME: &str = "Microsoft Edge.app/Contents/MacOS/Microsoft Edge"; +const EDGE_BETA_MACOS_APP_NAME: &str = "Microsoft Edge Beta.app/Contents/MacOS/Microsoft Edge Beta"; +const EDGE_DEV_MACOS_APP_NAME: &str = "Microsoft Edge Dev.app/Contents/MacOS/Microsoft Edge Dev"; +const EDGE_CANARY_MACOS_APP_NAME: &str = + "Microsoft Edge Canary.app/Contents/MacOS/Microsoft Edge Canary"; pub struct EdgeManager { pub browser_name: &'static str, @@ -45,6 +56,7 @@ pub struct EdgeManager { pub config: ManagerConfig, pub http_client: Client, pub log: Logger, + pub browser_url: Option, } impl EdgeManager { @@ -60,6 +72,7 @@ impl EdgeManager { http_client: create_http_client(default_timeout, default_proxy)?, config, log: Logger::new(), + browser_url: None, })) } } @@ -268,10 +281,6 @@ impl SeleniumManager for EdgeManager { self.log = log; } - fn download_browser(&mut self) -> Result, Box> { - Ok(None) - } - fn get_platform_label(&self) -> &str { let os = self.get_os(); let arch = self.get_arch(); @@ -294,11 +303,230 @@ impl SeleniumManager for EdgeManager { } } - fn request_latest_browser_version_from_online(&mut self) -> Result> { - self.unavailable_download() + fn request_latest_browser_version_from_online( + &mut self, + browser_version: &str, + ) -> Result> { + let browser_name = self.browser_name; + let is_fixed_browser_version = !self.is_empty(browser_version) + && !self.is_stable(browser_version) + && !self.is_unstable(browser_version); + let edge_updates_url = if is_fixed_browser_version { + format!("{}?view=enterprise", BROWSER_URL) + } else { + BROWSER_URL.to_string() + }; + self.get_logger().debug(format!( + "Checking {} releases on {}", + browser_name, edge_updates_url + )); + + let edge_products = + parse_json_from_url::>(self.get_http_client(), edge_updates_url)?; + + let edge_channel = if self.is_beta(browser_version) { + "Beta" + } else if self.is_dev(browser_version) { + "Dev" + } else if self.is_nightly(browser_version) { + "Canary" + } else { + "Stable" + }; + let products: Vec<&EdgeProduct> = edge_products + .iter() + .filter(|p| p.product.eq_ignore_ascii_case(edge_channel)) + .collect(); + self.get_logger().trace(format!("Products: {:?}", products)); + + let os = self.get_os(); + let arch = self.get_arch(); + let os_label; + let arch_label = if WINDOWS.is(os) { + os_label = "Windows"; + if ARM64.is(arch) { + "arm64" + } else if X32.is(arch) { + "x86" + } else { + "x64" + } + } else if MACOS.is(os) { + os_label = "MacOS"; + "universal" + } else { + os_label = "Linux"; + "x64" + }; + if products.is_empty() { + return self.unavailable_discovery(); + } + + let releases: Vec<&Release> = products + .first() + .unwrap() + .releases + .iter() + .filter(|r| { + let os_arch = r.platform.eq_ignore_ascii_case(os_label) + && r.architecture.eq_ignore_ascii_case(arch_label); + if is_fixed_browser_version { + os_arch && r.product_version.starts_with(browser_version) + } else { + os_arch + } + }) + .collect(); + self.get_logger().trace(format!("Releases: {:?}", releases)); + + let package_label = if WINDOWS.is(os) { + "msi" + } else if MACOS.is(os) { + "pkg" + } else { + "deb" + }; + if releases.is_empty() { + return self.unavailable_discovery(); + } + + let release = releases.first().unwrap(); + let artifacts: Vec<&Artifact> = release + .artifacts + .iter() + .filter(|a| a.artifact_name.eq_ignore_ascii_case(package_label)) + .collect(); + self.get_logger() + .trace(format!("Artifacts: {:?}", artifacts)); + + if artifacts.is_empty() { + return self.unavailable_discovery(); + } + let artifact = artifacts.first().unwrap(); + let browser_version = release.product_version.clone(); + self.browser_url = Some(artifact.location.clone()); + + Ok(browser_version) + } + + fn request_fixed_browser_version_from_online( + &mut self, + browser_version: &str, + ) -> Result> { + self.request_latest_browser_version_from_online(browser_version) } - fn request_fixed_browser_version_from_online(&mut self) -> Result> { - self.unavailable_download() + fn get_min_browser_version_for_download(&self) -> Result> { + Ok(MIN_EDGE_VERSION_DOWNLOAD) + } + + fn get_browser_binary_path( + &mut self, + browser_version: &str, + ) -> Result> { + let browser_in_cache = self.get_browser_path_in_cache()?; + if MACOS.is(self.get_os()) { + let macos_app_name = if self.is_beta(browser_version) { + EDGE_BETA_MACOS_APP_NAME + } else if self.is_dev(browser_version) { + EDGE_DEV_MACOS_APP_NAME + } else if self.is_nightly(browser_version) { + EDGE_CANARY_MACOS_APP_NAME + } else { + EDGE_MACOS_APP_NAME + }; + Ok(browser_in_cache.join(macos_app_name)) + } else if WINDOWS.is(self.get_os()) { + let browser_path = if self.is_unstable(browser_version) { + self.get_browser_path_from_version(browser_version) + .to_string() + } else { + format!( + r#"Microsoft\Edge\Application\{}\msedge.exe"#, + self.get_browser_version() + ) + }; + let mut full_browser_path = Path::new(&browser_path).to_path_buf(); + if WINDOWS.is(self.get_os()) { + let env_value = env::var(ENV_PROGRAM_FILES_X86).unwrap_or_default(); + let parent_path = Path::new(&env_value); + full_browser_path = parent_path.join(&browser_path); + } + Ok((&path_to_string(&full_browser_path)).into()) + } else { + Ok(browser_in_cache.join(format!( + "{}{}", + EDGE_WINDOWS_AND_LINUX_APP_NAME, + get_binary_extension(self.get_os()) + ))) + } } + + fn get_browser_url_for_download( + &mut self, + browser_version: &str, + ) -> Result> { + if self.browser_url.is_none() { + self.request_latest_browser_version_from_online(browser_version)?; + } + Ok(self.browser_url.clone().unwrap()) + } + + fn get_browser_label_for_download( + &self, + browser_version: &str, + ) -> Result, Box> { + let browser_label = if self.is_beta(browser_version) { + "msedge-beta" + } else if self.is_dev(browser_version) { + "msedge-dev" + } else if self.is_nightly(browser_version) { + "msedge-canary" + } else { + "msedge" + }; + Ok(Some(browser_label)) + } +} + +#[derive(Serialize, Deserialize, Debug)] +pub struct EdgeProduct { + #[serde(rename = "Product")] + pub product: String, + #[serde(rename = "Releases")] + pub releases: Vec, +} + +#[derive(Serialize, Deserialize, Debug)] +pub struct Release { + #[serde(rename = "ReleaseId")] + pub release_id: u32, + #[serde(rename = "Platform")] + pub platform: String, + #[serde(rename = "Architecture")] + pub architecture: String, + #[serde(rename = "CVEs")] + pub cves: Vec, + #[serde(rename = "ProductVersion")] + pub product_version: String, + #[serde(rename = "Artifacts")] + pub artifacts: Vec, + #[serde(rename = "PublishedTime")] + pub published_time: String, + #[serde(rename = "ExpectedExpiryDate")] + pub expected_expiry_date: String, +} + +#[derive(Serialize, Deserialize, Debug)] +pub struct Artifact { + #[serde(rename = "ArtifactName")] + pub artifact_name: String, + #[serde(rename = "Location")] + pub location: String, + #[serde(rename = "Hash")] + pub hash: String, + #[serde(rename = "HashAlgorithm")] + pub hash_algorithm: String, + #[serde(rename = "SizeInBytes")] + pub size_in_bytes: u32, } diff --git a/rust/src/files.rs b/rust/src/files.rs index 06fb9b4bd290a..2eff09e160ad6 100644 --- a/rust/src/files.rs +++ b/rust/src/files.rs @@ -35,8 +35,8 @@ use zip::ZipArchive; use crate::config::OS::WINDOWS; use crate::{ format_one_arg, format_three_args, format_two_args, run_shell_command_by_os, Command, Logger, - CP_VOLUME_COMMAND, HDIUTIL_ATTACH_COMMAND, HDIUTIL_DETACH_COMMAND, MACOS, MV_PAYLOAD_COMMAND, - MV_PAYLOAD_OLD_VERSIONS_COMMAND, PKGUTIL_COMMAND, + CP_VOLUME_COMMAND, HDIUTIL_ATTACH_COMMAND, HDIUTIL_DETACH_COMMAND, MACOS, + MSIEXEC_INSTALL_COMMAND, MV_PAYLOAD_COMMAND, MV_PAYLOAD_OLD_VERSIONS_COMMAND, PKGUTIL_COMMAND, }; pub const PARSE_ERROR: &str = "Wrong browser/driver version"; @@ -49,6 +49,8 @@ const BZ2: &str = "bz2"; const PKG: &str = "pkg"; const DMG: &str = "dmg"; const EXE: &str = "exe"; +const DEB: &str = "deb"; +const MSI: &str = "msi"; const SEVEN_ZIP_HEADER: &[u8; 6] = b"7z\xBC\xAF\x27\x1C"; const UNCOMPRESS_MACOS_ERR_MSG: &str = "{} files are only supported in macOS"; @@ -67,6 +69,14 @@ impl BrowserPath { } } +pub fn create_empty_parent_path_if_not_exists(path: &Path) -> Result<(), Box> { + if let Some(p) = path.parent() { + create_path_if_not_exists(p)?; + fs::remove_dir_all(p).and_then(|_| fs::create_dir(p))?; + } + Ok(()) +} + pub fn create_parent_path_if_not_exists(path: &Path) -> Result<(), Box> { if let Some(p) = path.parent() { create_path_if_not_exists(p)?; @@ -136,6 +146,10 @@ pub fn uncompress( uncompress_dmg(compressed_file, target, log, os, volume.unwrap_or_default())? } else if extension.eq_ignore_ascii_case(EXE) { uncompress_sfx(compressed_file, target, log)? + } else if extension.eq_ignore_ascii_case(DEB) { + uncompress_deb(compressed_file, target, log, volume.unwrap_or_default())? + } else if extension.eq_ignore_ascii_case(MSI) { + install_msi(compressed_file, log, os)? } else if extension.eq_ignore_ascii_case(XML) || extension.eq_ignore_ascii_case(HTML) { log.debug(format!( "Wrong downloaded driver: {}", @@ -177,7 +191,7 @@ pub fn uncompress_sfx( "Moving extracted files and folders from {} to {}", core_str, target_str )); - create_parent_path_if_not_exists(target)?; + create_empty_parent_path_if_not_exists(target)?; fs::rename(&core_str, &target_str)?; Ok(()) @@ -259,6 +273,53 @@ pub fn uncompress_dmg( Ok(()) } +pub fn uncompress_deb( + compressed_file: &str, + target: &Path, + log: &Logger, + label: &str, +) -> Result<(), Box> { + let zip_parent = Path::new(compressed_file).parent().unwrap(); + log.trace(format!( + "Extracting from {} to {}", + compressed_file, + zip_parent.display() + )); + + let deb_file = File::open(compressed_file)?; + let mut deb_pkg = debpkg::DebPkg::parse(deb_file)?; + deb_pkg.data()?.unpack(zip_parent)?; + + let zip_parent_str = path_to_string(zip_parent); + let target_str = path_to_string(target); + let opt_edge_str = format!("{}/opt/microsoft/{}", zip_parent_str, label); + log.trace(format!( + "Moving extracted files and folders from {} to {}", + opt_edge_str, target_str + )); + create_empty_parent_path_if_not_exists(target)?; + fs::rename(&opt_edge_str, &target_str)?; + + Ok(()) +} + +pub fn install_msi(msi_file: &str, log: &Logger, os: &str) -> Result<(), Box> { + let msi_file_name = Path::new(msi_file) + .file_name() + .unwrap_or_default() + .to_os_string(); + log.debug(format!( + "Installing {}", + msi_file_name.to_str().unwrap_or_default() + )); + + let command = Command::new_single(format_one_arg(MSIEXEC_INSTALL_COMMAND, msi_file)); + log.trace(format!("Running command: {}", command.display())); + run_shell_command_by_os(os, command)?; + + Ok(()) +} + pub fn untargz(compressed_file: &str, target: &Path, log: &Logger) -> Result<(), Box> { log.trace(format!( "Untargz {} to {}", diff --git a/rust/src/firefox.rs b/rust/src/firefox.rs index 297f2248449d3..bd701c098d0aa 100644 --- a/rust/src/firefox.rs +++ b/rust/src/firefox.rs @@ -32,10 +32,8 @@ use crate::metadata::{ create_driver_metadata, get_driver_version_from_metadata, get_metadata, write_metadata, }; use crate::{ - create_browser_metadata, create_http_client, download_to_tmp_folder, format_three_args, - format_two_args, get_browser_version_from_metadata, path_to_string, uncompress, Logger, - SeleniumManager, BETA, CANARY, DASH_VERSION, DEV, NIGHTLY, OFFLINE_REQUEST_ERR_MSG, - REG_CURRENT_VERSION_ARG, STABLE, + create_http_client, format_three_args, format_two_args, Logger, SeleniumManager, BETA, + DASH_VERSION, DEV, NIGHTLY, OFFLINE_REQUEST_ERR_MSG, REG_CURRENT_VERSION_ARG, STABLE, }; pub const FIREFOX_NAME: &str = "firefox"; @@ -61,7 +59,6 @@ const FIREFOX_NIGHTLY_VOLUME: &str = r#"Firefox\ Nightly"#; const MIN_DOWNLOADABLE_FIREFOX_VERSION_WIN: i32 = 13; const MIN_DOWNLOADABLE_FIREFOX_VERSION_MAC: i32 = 4; const MIN_DOWNLOADABLE_FIREFOX_VERSION_LINUX: i32 = 4; -const ONLINE_DISCOVERY_ERROR_MESSAGE: &str = "Unable to discover {} {} in online repository"; const UNAVAILABLE_DOWNLOAD_ERROR_MESSAGE: &str = "{} {} not available for downloading (minimum version: {})"; @@ -114,94 +111,6 @@ impl FirefoxManager { .map(|v| v.to_string()) .collect()) } - - fn get_browser_url( - &mut self, - is_browser_version_nightly: bool, - ) -> Result> { - let arch = self.get_arch(); - let os = self.get_os(); - let platform_label; - let artifact_name; - let artifact_extension; - let major_browser_version = self - .get_major_browser_version() - .parse::() - .unwrap_or_default(); - - if WINDOWS.is(os) { - artifact_name = "Firefox%20Setup%20"; - artifact_extension = "exe"; - // Before Firefox 42, only Windows 32 was supported - if X32.is(arch) || major_browser_version < 42 { - platform_label = "win32"; - } else if ARM64.is(arch) { - platform_label = "win-aarch64"; - } else { - platform_label = "win64"; - } - } else if MACOS.is(os) { - artifact_name = "Firefox%20"; - // Before Firefox 68, only DMG was released - if major_browser_version < 68 { - artifact_extension = "dmg"; - } else { - artifact_extension = "pkg"; - } - if is_browser_version_nightly { - platform_label = "osx"; - } else { - platform_label = "mac"; - } - } else { - // Linux - artifact_name = "firefox-"; - artifact_extension = "tar.bz2"; - if X32.is(arch) { - platform_label = "linux-i686"; - } else if is_browser_version_nightly { - platform_label = "linux64"; - } else { - platform_label = "linux-x86_64"; - } - } - - // A possible future improvement is to allow downloading language-specific releases - let language = FIREFOX_DEFAULT_LANG; - if is_browser_version_nightly { - Ok(format_two_args( - FIREFOX_NIGHTLY_URL, - platform_label, - language, - )) - } else { - let browser_version = self.get_browser_version(); - Ok(format!( - "{}{}/{}/{}/{}{}.{}", - BROWSER_URL, - browser_version, - platform_label, - language, - artifact_name, - browser_version, - artifact_extension - )) - } - } - - fn get_browser_binary_path_in_cache(&self) -> Result> { - let browser_in_cache = self.get_browser_path_in_cache()?; - if MACOS.is(self.get_os()) { - let macos_app_name = if self.is_browser_version_nightly() { - FIREFOX_NIGHTLY_MACOS_APP_NAME - } else { - FIREFOX_MACOS_APP_NAME - }; - Ok(browser_in_cache.join(macos_app_name)) - } else { - Ok(browser_in_cache.join(self.get_browser_name_with_extension())) - } - } } impl SeleniumManager for FirefoxManager { @@ -388,105 +297,6 @@ impl SeleniumManager for FirefoxManager { self.log = log; } - fn download_browser(&mut self) -> Result, Box> { - let browser_version; - let browser_name = self.browser_name; - let original_browser_version = self.get_config().browser_version.clone(); - let cache_path = self.get_cache_path()?; - let mut metadata = get_metadata(self.get_logger(), &cache_path); - let major_browser_version = self.get_major_browser_version(); - let is_browser_version_nightly = original_browser_version.eq_ignore_ascii_case(NIGHTLY) - || original_browser_version.eq_ignore_ascii_case(CANARY); - - // Browser version is checked in the local metadata - match get_browser_version_from_metadata( - &metadata.browsers, - browser_name, - &major_browser_version, - ) { - Some(version) => { - self.get_logger().trace(format!( - "Browser with valid TTL. Getting {} version from metadata", - browser_name - )); - browser_version = version; - self.set_browser_version(browser_version.clone()); - } - _ => { - // If not in metadata, discover version using Mozilla online metadata - if self.is_browser_version_stable() || self.is_browser_version_empty() { - browser_version = self.request_latest_browser_version_from_online()?; - } else { - browser_version = self.request_fixed_browser_version_from_online()?; - } - self.set_browser_version(browser_version.clone()); - - let browser_ttl = self.get_ttl(); - if browser_ttl > 0 - && !self.is_browser_version_empty() - && !self.is_browser_version_stable() - { - metadata.browsers.push(create_browser_metadata( - browser_name, - &major_browser_version, - &browser_version, - browser_ttl, - )); - write_metadata(&metadata, self.get_logger(), cache_path); - } - } - } - self.get_logger().debug(format!( - "Required browser: {} {}", - browser_name, browser_version - )); - - // Checking if browser version is in the cache - let browser_binary_path = self.get_browser_binary_path_in_cache()?; - if browser_binary_path.exists() { - self.get_logger().debug(format!( - "{} {} already in the cache", - browser_name, browser_version - )); - } else { - // If browser is not in the cache, download it - let browser_url = self.get_browser_url(is_browser_version_nightly)?; - self.get_logger().debug(format!( - "Downloading {} {} from {}", - self.get_browser_name(), - self.get_browser_version(), - browser_url - )); - let (_tmp_folder, driver_zip_file) = - download_to_tmp_folder(self.get_http_client(), browser_url, self.get_logger())?; - - let major_browser_version_int = self - .get_major_browser_version() - .parse::() - .unwrap_or_default(); - let volume = if is_browser_version_nightly { - FIREFOX_NIGHTLY_VOLUME - } else { - FIREFOX_VOLUME - }; - uncompress( - &driver_zip_file, - &self.get_browser_path_in_cache()?, - self.get_logger(), - self.get_os(), - None, - Some(volume), - Some(major_browser_version_int), - )?; - } - if browser_binary_path.exists() { - self.set_browser_path(path_to_string(&browser_binary_path)); - Ok(Some(browser_binary_path)) - } else { - Ok(None) - } - } - fn get_platform_label(&self) -> &str { let driver_version = self.get_driver_version(); let os = self.get_os(); @@ -519,7 +329,10 @@ impl SeleniumManager for FirefoxManager { } } - fn request_latest_browser_version_from_online(&mut self) -> Result> { + fn request_latest_browser_version_from_online( + &mut self, + _browser_version: &str, + ) -> Result> { let browser_name = self.browser_name; self.get_logger().trace(format!( "Using Firefox endpoints to find out latest stable {} version", @@ -538,7 +351,10 @@ impl SeleniumManager for FirefoxManager { Ok(browser_version.to_string()) } - fn request_fixed_browser_version_from_online(&mut self) -> Result> { + fn request_fixed_browser_version_from_online( + &mut self, + _browser_version: &str, + ) -> Result> { let browser_name = self.browser_name; let browser_version = self.get_browser_version().to_string(); self.get_logger().trace(format!( @@ -564,19 +380,12 @@ impl SeleniumManager for FirefoxManager { .unwrap(); Ok(browser_version.to_string()) } else { - let os = self.get_os(); let major_browser_version = self .get_major_browser_version() .parse::() .unwrap_or_default(); - let min_downloadable_version = if WINDOWS.is(os) { - MIN_DOWNLOADABLE_FIREFOX_VERSION_WIN - } else if MACOS.is(os) { - MIN_DOWNLOADABLE_FIREFOX_VERSION_MAC - } else { - MIN_DOWNLOADABLE_FIREFOX_VERSION_LINUX - }; + let min_downloadable_version = self.get_min_browser_version_for_download()?; if major_browser_version < min_downloadable_version { return Err(format_three_args( UNAVAILABLE_DOWNLOAD_ERROR_MESSAGE, @@ -593,12 +402,7 @@ impl SeleniumManager for FirefoxManager { firefox_versions = self.request_versions_from_online(FIREFOX_HISTORY_DEV_ENDPOINT)?; if firefox_versions.is_empty() { - return Err(format_two_args( - ONLINE_DISCOVERY_ERROR_MESSAGE, - browser_name, - self.get_browser_version(), - ) - .into()); + return self.unavailable_discovery(); } } @@ -611,14 +415,124 @@ impl SeleniumManager for FirefoxManager { return Ok(version.to_string()); } } - Err(format_two_args( - ONLINE_DISCOVERY_ERROR_MESSAGE, - browser_name, - self.get_browser_version(), - ) - .into()) + self.unavailable_discovery() + } + } + + fn get_min_browser_version_for_download(&self) -> Result> { + let os = self.get_os(); + let min_browser_version_for_download = if WINDOWS.is(os) { + MIN_DOWNLOADABLE_FIREFOX_VERSION_WIN + } else if MACOS.is(os) { + MIN_DOWNLOADABLE_FIREFOX_VERSION_MAC + } else { + MIN_DOWNLOADABLE_FIREFOX_VERSION_LINUX + }; + Ok(min_browser_version_for_download) + } + + fn get_browser_binary_path( + &mut self, + browser_version: &str, + ) -> Result> { + let browser_in_cache = self.get_browser_path_in_cache()?; + if MACOS.is(self.get_os()) { + let macos_app_name = if self.is_nightly(browser_version) { + FIREFOX_NIGHTLY_MACOS_APP_NAME + } else { + FIREFOX_MACOS_APP_NAME + }; + Ok(browser_in_cache.join(macos_app_name)) + } else { + Ok(browser_in_cache.join(self.get_browser_name_with_extension())) + } + } + + fn get_browser_url_for_download( + &mut self, + browser_version: &str, + ) -> Result> { + let arch = self.get_arch(); + let os = self.get_os(); + let platform_label; + let artifact_name; + let artifact_extension; + let major_browser_version = self + .get_major_browser_version() + .parse::() + .unwrap_or_default(); + + if WINDOWS.is(os) { + artifact_name = "Firefox%20Setup%20"; + artifact_extension = "exe"; + // Before Firefox 42, only Windows 32 was supported + if X32.is(arch) || major_browser_version < 42 { + platform_label = "win32"; + } else if ARM64.is(arch) { + platform_label = "win-aarch64"; + } else { + platform_label = "win64"; + } + } else if MACOS.is(os) { + artifact_name = "Firefox%20"; + // Before Firefox 68, only DMG was released + if major_browser_version < 68 { + artifact_extension = "dmg"; + } else { + artifact_extension = "pkg"; + } + if self.is_nightly(browser_version) { + platform_label = "osx"; + } else { + platform_label = "mac"; + } + } else { + // Linux + artifact_name = "firefox-"; + artifact_extension = "tar.bz2"; + if X32.is(arch) { + platform_label = "linux-i686"; + } else if self.is_nightly(browser_version) { + platform_label = "linux64"; + } else { + platform_label = "linux-x86_64"; + } + } + + // A possible future improvement is to allow downloading language-specific releases + let language = FIREFOX_DEFAULT_LANG; + if self.is_nightly(browser_version) { + Ok(format_two_args( + FIREFOX_NIGHTLY_URL, + platform_label, + language, + )) + } else { + let browser_version = self.get_browser_version(); + Ok(format!( + "{}{}/{}/{}/{}{}.{}", + BROWSER_URL, + browser_version, + platform_label, + language, + artifact_name, + browser_version, + artifact_extension + )) } } + + fn get_browser_label_for_download( + &self, + browser_version: &str, + ) -> Result, Box> { + let browser_label = if self.is_nightly(browser_version) { + FIREFOX_NIGHTLY_VOLUME + } else { + FIREFOX_VOLUME + }; + Ok(Some(browser_label)) + } } #[cfg(test)] diff --git a/rust/src/grid.rs b/rust/src/grid.rs index 39d3437adeb36..e3855893cacb3 100644 --- a/rust/src/grid.rs +++ b/rust/src/grid.rs @@ -224,19 +224,46 @@ impl SeleniumManager for GridManager { self.log = log; } - fn download_browser(&mut self) -> Result, Box> { - Ok(None) - } - fn get_platform_label(&self) -> &str { "" } - fn request_latest_browser_version_from_online(&mut self) -> Result> { + fn request_latest_browser_version_from_online( + &mut self, + _browser_version: &str, + ) -> Result> { + self.unavailable_download() + } + + fn request_fixed_browser_version_from_online( + &mut self, + _browser_version: &str, + ) -> Result> { + self.unavailable_download() + } + + fn get_min_browser_version_for_download(&self) -> Result> { + self.unavailable_download() + } + + fn get_browser_binary_path( + &mut self, + _browser_version: &str, + ) -> Result> { + self.unavailable_download() + } + + fn get_browser_url_for_download( + &mut self, + _browser_version: &str, + ) -> Result> { self.unavailable_download() } - fn request_fixed_browser_version_from_online(&mut self) -> Result> { + fn get_browser_label_for_download( + &self, + _browser_version: &str, + ) -> Result, Box> { self.unavailable_download() } } diff --git a/rust/src/iexplorer.rs b/rust/src/iexplorer.rs index 1a60216dfcb0a..a045cae3b1232 100644 --- a/rust/src/iexplorer.rs +++ b/rust/src/iexplorer.rs @@ -229,19 +229,46 @@ impl SeleniumManager for IExplorerManager { self.log = log; } - fn download_browser(&mut self) -> Result, Box> { - Ok(None) - } - fn get_platform_label(&self) -> &str { "win32" } - fn request_latest_browser_version_from_online(&mut self) -> Result> { + fn request_latest_browser_version_from_online( + &mut self, + _browser_version: &str, + ) -> Result> { + self.unavailable_download() + } + + fn request_fixed_browser_version_from_online( + &mut self, + _browser_version: &str, + ) -> Result> { + self.unavailable_download() + } + + fn get_min_browser_version_for_download(&self) -> Result> { + self.unavailable_download() + } + + fn get_browser_binary_path( + &mut self, + _browser_version: &str, + ) -> Result> { + self.unavailable_download() + } + + fn get_browser_url_for_download( + &mut self, + _browser_version: &str, + ) -> Result> { self.unavailable_download() } - fn request_fixed_browser_version_from_online(&mut self) -> Result> { + fn get_browser_label_for_download( + &self, + _browser_version: &str, + ) -> Result, Box> { self.unavailable_download() } } diff --git a/rust/src/lib.rs b/rust/src/lib.rs index 28ddece6ae5a6..bbdb34d737e87 100644 --- a/rust/src/lib.rs +++ b/rust/src/lib.rs @@ -81,6 +81,8 @@ pub const HDIUTIL_DETACH_COMMAND: &str = "hdiutil detach /Volumes/{}"; pub const CP_VOLUME_COMMAND: &str = "cp -R /Volumes/{}/{}.app {}"; pub const MV_PAYLOAD_COMMAND: &str = "mv {}/*{}/Payload/*.app {}"; pub const MV_PAYLOAD_OLD_VERSIONS_COMMAND: &str = "mv {}/Payload/*.app {}"; +pub const MSIEXEC_INSTALL_COMMAND: &str = "start /wait msiexec /i {} /qn ALLOWDOWNGRADE=1"; +pub const WINDOWS_CHECK_ADMIN_COMMAND: &str = "net session"; pub const DASH_VERSION: &str = "{}{}{} -v"; pub const DASH_DASH_VERSION: &str = "{}{}{} --version"; pub const DOUBLE_QUOTE: &str = "\""; @@ -101,7 +103,12 @@ pub const ESCAPE_COMMAND: &str = "printf %q \"{}\""; pub const SNAPSHOT: &str = "SNAPSHOT"; pub const OFFLINE_REQUEST_ERR_MSG: &str = "Unable to discover proper {} version in offline mode"; pub const OFFLINE_DOWNLOAD_ERR_MSG: &str = "Unable to download {} in offline mode"; -pub const UNAVAILABLE_DOWNLOAD_ERR_MSG: &str = "{} not available for downloading"; +pub const UNAVAILABLE_DOWNLOAD_ERR_MSG: &str = "{}{} not available for download"; +pub const UNAVAILABLE_DOWNLOAD_WITH_MIN_VERSION_ERR_MSG: &str = + "{} {} not available for download (minimum version: {})"; +pub const NOT_ADMIN_FOR_EDGE_INSTALLER_ERR_MSG: &str = + "{} can only be installed in Windows with administrator permissions"; +pub const ONLINE_DISCOVERY_ERROR_MESSAGE: &str = "Unable to discover {}{} in online repository"; pub const UNC_PREFIX: &str = r#"\\?\"#; pub trait SeleniumManager { @@ -141,13 +148,32 @@ pub trait SeleniumManager { fn set_logger(&mut self, log: Logger); - fn download_browser(&mut self) -> Result, Box>; - fn get_platform_label(&self) -> &str; - fn request_latest_browser_version_from_online(&mut self) -> Result>; + fn request_latest_browser_version_from_online( + &mut self, + browser_version: &str, + ) -> Result>; + + fn request_fixed_browser_version_from_online( + &mut self, + browser_version: &str, + ) -> Result>; + + fn get_min_browser_version_for_download(&self) -> Result>; + + fn get_browser_binary_path(&mut self, browser_version: &str) + -> Result>; + + fn get_browser_url_for_download( + &mut self, + browser_version: &str, + ) -> Result>; - fn request_fixed_browser_version_from_online(&mut self) -> Result>; + fn get_browser_label_for_download( + &self, + _browser_version: &str, + ) -> Result, Box>; // ---------------------------------------------------------- // Shared functions @@ -183,22 +209,151 @@ pub trait SeleniumManager { } } - fn detect_browser_path(&mut self) -> Option { - let mut browser_version = self.get_browser_version(); + fn download_browser(&mut self) -> Result, Box> { + if WINDOWS.is(self.get_os()) && self.is_edge() && !self.is_windows_admin() { + return Err(format_one_arg( + NOT_ADMIN_FOR_EDGE_INSTALLER_ERR_MSG, + self.get_browser_name(), + ) + .into()); + } + + let browser_version; + let original_browser_version = self.get_config().browser_version.clone(); + let cache_path = self.get_cache_path()?; + let mut metadata = get_metadata(self.get_logger(), &cache_path); + let major_browser_version = self.get_major_browser_version(); + let major_browser_version_int = major_browser_version.parse::().unwrap_or_default(); + + // Browser version should be available for download + let min_browser_version_for_download = self.get_min_browser_version_for_download()?; + if !self.is_browser_version_unstable() + && !self.is_browser_version_stable() + && !self.is_browser_version_empty() + && major_browser_version_int < min_browser_version_for_download + { + return Err(format_three_args( + UNAVAILABLE_DOWNLOAD_WITH_MIN_VERSION_ERR_MSG, + self.get_browser_name(), + &major_browser_version, + &min_browser_version_for_download.to_string(), + ) + .into()); + } + + // Browser version is checked in the local metadata + match get_browser_version_from_metadata( + &metadata.browsers, + self.get_browser_name(), + &major_browser_version, + ) { + Some(version) => { + self.get_logger().trace(format!( + "Browser with valid TTL. Getting {} version from metadata", + self.get_browser_name() + )); + browser_version = version; + self.set_browser_version(browser_version.clone()); + } + _ => { + // If not in metadata, discover version using online metadata + if self.is_browser_version_stable() || self.is_browser_version_empty() { + browser_version = + self.request_latest_browser_version_from_online(&original_browser_version)?; + } else { + browser_version = + self.request_fixed_browser_version_from_online(&original_browser_version)?; + } + self.set_browser_version(browser_version.clone()); + + let browser_ttl = self.get_ttl(); + if browser_ttl > 0 + && !self.is_browser_version_empty() + && !self.is_browser_version_stable() + { + metadata.browsers.push(create_browser_metadata( + self.get_browser_name(), + &major_browser_version, + &browser_version, + browser_ttl, + )); + write_metadata(&metadata, self.get_logger(), cache_path); + } + } + } + self.get_logger().debug(format!( + "Required browser: {} {}", + self.get_browser_name(), + browser_version + )); + + // Checking if browser version is in the cache + let browser_binary_path = self.get_browser_binary_path(&original_browser_version)?; + if browser_binary_path.exists() && !self.is_edge() { + self.get_logger().debug(format!( + "{} {} already exists", + self.get_browser_name(), + browser_version + )); + } else { + // If browser is not available, download it + let browser_url = self.get_browser_url_for_download(&original_browser_version)?; + self.get_logger().debug(format!( + "Downloading {} {} from {}", + self.get_browser_name(), + self.get_browser_version(), + browser_url + )); + let (_tmp_folder, driver_zip_file) = + download_to_tmp_folder(self.get_http_client(), browser_url, self.get_logger())?; + + let major_browser_version_int = self + .get_major_browser_version() + .parse::() + .unwrap_or_default(); + let browser_label_for_download = + self.get_browser_label_for_download(&original_browser_version)?; + uncompress( + &driver_zip_file, + &self.get_browser_path_in_cache()?, + self.get_logger(), + self.get_os(), + None, + browser_label_for_download, + Some(major_browser_version_int), + )?; + } + if browser_binary_path.exists() { + self.set_browser_path(path_to_string(&browser_binary_path)); + Ok(Some(browser_binary_path)) + } else { + self.get_logger().warn(format!( + "Expected {} path does not exists: {}", + self.get_browser_name(), + browser_binary_path.display() + )); + Ok(None) + } + } + + fn get_browser_path_from_version(&self, mut browser_version: &str) -> &str { if browser_version.eq_ignore_ascii_case(CANARY) { browser_version = NIGHTLY; - } else if !self.is_browser_version_unstable() { + } else if !self.is_unstable(browser_version) { browser_version = STABLE; } - - let browser_path = self - .get_browser_path_map() + self.get_browser_path_map() .get(&BrowserPath::new( str_to_os(self.get_os()).unwrap(), browser_version, )) .cloned() - .unwrap_or_default(); + .unwrap_or_default() + } + + fn detect_browser_path(&mut self) -> Option { + let browser_version = self.get_browser_version(); + let browser_path = self.get_browser_path_from_version(browser_version); let mut full_browser_path = Path::new(browser_path).to_path_buf(); if WINDOWS.is(self.get_os()) { @@ -324,7 +479,7 @@ pub trait SeleniumManager { && !major_browser_version.eq(&discovered_major_browser_version) { self.get_logger().debug(format!( - "Discovered online {} version ({}) different to specified browser version ({})", + "Discovered {} version ({}) different to specified browser version ({})", self.get_browser_name(), discovered_major_browser_version, major_browser_version, @@ -345,16 +500,21 @@ pub trait SeleniumManager { } } - if download_browser && !self.is_avoid_browser_download() { + if download_browser + && !self.is_avoid_browser_download() + && !self.is_iexplorer() + && !self.is_grid() + && !self.is_safari() + { let browser_path = self.download_browser()?; if browser_path.is_some() { self.get_logger().debug(format!( - "{} {} has been downloaded at {}", + "{} {} is available at {}", self.get_browser_name(), self.get_browser_version(), browser_path.unwrap().display() )); - } else if !self.is_iexplorer() && !self.is_grid() { + } else if !self.is_iexplorer() && !self.is_grid() && !self.is_safari() { return Err(format!( "{}{} cannot be downloaded", self.get_browser_name(), @@ -459,6 +619,17 @@ pub trait SeleniumManager { } } + fn is_windows_admin(&self) -> bool { + let os = self.get_os(); + if WINDOWS.is(os) { + let command = Command::new_single(WINDOWS_CHECK_ADMIN_COMMAND.to_string()); + let output = run_shell_command_by_os(os, command).unwrap_or_default(); + !output.is_empty() && !output.contains("error") + } else { + false + } + } + fn is_safari(&self) -> bool { self.get_browser_name().contains(SAFARI_NAME) } @@ -475,34 +646,60 @@ pub trait SeleniumManager { self.get_browser_name().contains(FIREFOX_NAME) } + fn is_edge(&self) -> bool { + self.get_browser_name().eq(EDGE_NAMES[0]) + } + fn is_browser_version_beta(&self) -> bool { - self.get_browser_version().eq_ignore_ascii_case(BETA) + self.is_beta(self.get_browser_version()) + } + + fn is_beta(&self, browser_version: &str) -> bool { + browser_version.eq_ignore_ascii_case(BETA) } fn is_browser_version_dev(&self) -> bool { - self.get_browser_version().eq_ignore_ascii_case(DEV) + self.is_dev(self.get_browser_version()) + } + + fn is_dev(&self, browser_version: &str) -> bool { + browser_version.eq_ignore_ascii_case(DEV) } fn is_browser_version_nightly(&self) -> bool { - let browser_version = self.get_browser_version(); + self.is_nightly(self.get_browser_version()) + } + + fn is_nightly(&self, browser_version: &str) -> bool { browser_version.eq_ignore_ascii_case(NIGHTLY) || browser_version.eq_ignore_ascii_case(CANARY) } - fn is_browser_version_unstable(&self) -> bool { - let browser_version = self.get_browser_version(); + fn is_unstable(&self, browser_version: &str) -> bool { browser_version.eq_ignore_ascii_case(BETA) || browser_version.eq_ignore_ascii_case(DEV) || browser_version.eq_ignore_ascii_case(NIGHTLY) || browser_version.eq_ignore_ascii_case(CANARY) } + fn is_browser_version_unstable(&self) -> bool { + self.is_unstable(self.get_browser_version()) + } + + fn is_empty(&self, browser_version: &str) -> bool { + browser_version.is_empty() + } + fn is_browser_version_empty(&self) -> bool { - self.get_browser_version().is_empty() + self.is_empty(self.get_browser_version()) + } + + fn is_stable(&self, browser_version: &str) -> bool { + browser_version.eq_ignore_ascii_case(STABLE) } fn is_browser_version_stable(&self) -> bool { - self.get_browser_version().eq_ignore_ascii_case(STABLE) + self.is_stable(self.get_browser_version()) } fn setup(&mut self) -> Result> { @@ -718,6 +915,7 @@ pub trait SeleniumManager { browser_name: &str, ) -> Result, Box> { let browser_version; + let original_browser_version = self.get_config().browser_version.clone(); let major_browser_version = self.get_major_browser_version(); let cache_path = self.get_cache_path()?; let mut metadata = get_metadata(self.get_logger(), &cache_path); @@ -737,12 +935,12 @@ pub trait SeleniumManager { self.set_browser_version(browser_version.clone()); } _ => { - // If not in metadata, discover version using Chrome for Testing (CfT) endpoints + // If not in metadata, discover version using online endpoints browser_version = if major_browser_version.is_empty() || self.is_browser_version_stable() { - self.request_latest_browser_version_from_online()? + self.request_latest_browser_version_from_online(&original_browser_version)? } else { - self.request_fixed_browser_version_from_online()? + self.request_fixed_browser_version_from_online(&original_browser_version)? }; let browser_ttl = self.get_ttl(); @@ -861,8 +1059,36 @@ pub trait SeleniumManager { } } - fn unavailable_download(&mut self) -> Result> { - Err(format_one_arg(UNAVAILABLE_DOWNLOAD_ERR_MSG, self.get_browser_name()).into()) + fn unavailable_download(&self) -> Result> + where + Self: Sized, + { + self.throw_error_message(UNAVAILABLE_DOWNLOAD_ERR_MSG) + } + + fn unavailable_discovery(&self) -> Result> + where + Self: Sized, + { + self.throw_error_message(ONLINE_DISCOVERY_ERROR_MESSAGE) + } + + fn throw_error_message(&self, error_message: &str) -> Result> + where + Self: Sized, + { + let browser_version = self.get_browser_version(); + let browser_version_label = if browser_version.is_empty() { + "".to_string() + } else { + format!(" {}", browser_version) + }; + Err(format_two_args( + error_message, + self.get_browser_name(), + &browser_version_label, + ) + .into()) } // ---------------------------------------------------------- diff --git a/rust/src/safari.rs b/rust/src/safari.rs index 4489269e278e5..1a6ae66c4d4df 100644 --- a/rust/src/safari.rs +++ b/rust/src/safari.rs @@ -122,19 +122,46 @@ impl SeleniumManager for SafariManager { self.log = log; } - fn download_browser(&mut self) -> Result, Box> { - Ok(None) - } - fn get_platform_label(&self) -> &str { "" } - fn request_latest_browser_version_from_online(&mut self) -> Result> { + fn request_latest_browser_version_from_online( + &mut self, + _browser_version: &str, + ) -> Result> { + self.unavailable_download() + } + + fn request_fixed_browser_version_from_online( + &mut self, + _browser_version: &str, + ) -> Result> { + self.unavailable_download() + } + + fn get_min_browser_version_for_download(&self) -> Result> { + self.unavailable_download() + } + + fn get_browser_binary_path( + &mut self, + _browser_version: &str, + ) -> Result> { + self.unavailable_download() + } + + fn get_browser_url_for_download( + &mut self, + _browser_version: &str, + ) -> Result> { self.unavailable_download() } - fn request_fixed_browser_version_from_online(&mut self) -> Result> { + fn get_browser_label_for_download( + &self, + _browser_version: &str, + ) -> Result, Box> { self.unavailable_download() } } diff --git a/rust/src/safaritp.rs b/rust/src/safaritp.rs index 70d1720cc64e8..4ce0ccc617b68 100644 --- a/rust/src/safaritp.rs +++ b/rust/src/safaritp.rs @@ -130,19 +130,46 @@ impl SeleniumManager for SafariTPManager { self.log = log; } - fn download_browser(&mut self) -> Result, Box> { - Ok(None) - } - fn get_platform_label(&self) -> &str { "" } - fn request_latest_browser_version_from_online(&mut self) -> Result> { + fn request_latest_browser_version_from_online( + &mut self, + _browser_version: &str, + ) -> Result> { + self.unavailable_download() + } + + fn request_fixed_browser_version_from_online( + &mut self, + _browser_version: &str, + ) -> Result> { + self.unavailable_download() + } + + fn get_min_browser_version_for_download(&self) -> Result> { + self.unavailable_download() + } + + fn get_browser_binary_path( + &mut self, + _browser_version: &str, + ) -> Result> { + self.unavailable_download() + } + + fn get_browser_url_for_download( + &mut self, + _browser_version: &str, + ) -> Result> { self.unavailable_download() } - fn request_fixed_browser_version_from_online(&mut self) -> Result> { + fn get_browser_label_for_download( + &self, + _browser_version: &str, + ) -> Result, Box> { self.unavailable_download() } } diff --git a/rust/tests/browser_download_tests.rs b/rust/tests/browser_download_tests.rs index be8daad88d951..404d6b29cb8ab 100644 --- a/rust/tests/browser_download_tests.rs +++ b/rust/tests/browser_download_tests.rs @@ -16,6 +16,7 @@ // under the License. use assert_cmd::Command; +use std::env::consts::OS; use crate::common::{assert_browser, assert_driver}; use rstest::rstest; @@ -25,6 +26,7 @@ mod common; #[rstest] #[case("chrome")] #[case("firefox")] +#[case("edge")] fn browser_latest_download_test(#[case] browser: String) { let mut cmd = Command::new(env!("CARGO_BIN_EXE_selenium-manager")); cmd.args([ @@ -40,7 +42,9 @@ fn browser_latest_download_test(#[case] browser: String) { .code(0); assert_driver(&mut cmd); - assert_browser(&mut cmd); + if !OS.eq("windows") { + assert_browser(&mut cmd); + } } #[rstest] @@ -48,6 +52,7 @@ fn browser_latest_download_test(#[case] browser: String) { #[case("chrome", "beta")] #[case("firefox", "116")] #[case("firefox", "beta")] +#[case("edge", "beta")] fn browser_version_download_test(#[case] browser: String, #[case] browser_version: String) { let mut cmd = Command::new(env!("CARGO_BIN_EXE_selenium-manager")); cmd.args([ @@ -64,5 +69,7 @@ fn browser_version_download_test(#[case] browser: String, #[case] browser_versio .code(0); assert_driver(&mut cmd); - assert_browser(&mut cmd); + if !OS.eq("windows") { + assert_browser(&mut cmd); + } }