diff --git a/src/cli/src/cli.rs b/src/cli/src/cli.rs index 44b6458b6e2987..538be382820cd2 100644 --- a/src/cli/src/cli.rs +++ b/src/cli/src/cli.rs @@ -75,7 +75,7 @@ pub struct ServeArgs { pub struct InstallArgs { /// Install specified commit ID of VS Code #[clap(long)] - pub commit_id: String, + pub commit_id: Option, /// VS Code quality #[clap(arg_enum, default_value = "stable")] @@ -94,6 +94,15 @@ pub enum Quality { Insiders, } +impl Quality { + pub fn download_quality(&self) -> &'static str { + match self { + Quality::Insiders => "insider", + Quality::Stable => "stable", + } + } +} + impl fmt::Display for Quality { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { match self { diff --git a/src/cli/src/code_server.rs b/src/cli/src/code_server.rs index 9fddec06a945de..e8fff708ab0b1e 100644 --- a/src/cli/src/code_server.rs +++ b/src/cli/src/code_server.rs @@ -3,7 +3,7 @@ *--------------------------------------------------------------------------------------------*/ use crate::cli::Quality; -use crate::launcher::ServerParams; +use crate::launcher::ResolvedServerParams; use crate::log; use crate::state::{LauncherPaths, ServerPaths}; use crate::util::command::capture_command; @@ -11,6 +11,7 @@ use crate::util::errors::{ wrap, AnyError, ExtensionInstallFailed, InvalidServerExtensionError, MissingEntrypointError, StatusError, WrappedError, }; +use crate::util::version::PLATFORM_DOWNLOAD_PATH; use crate::util::{http, sync}; use crate::util::{tar, zipper}; use lazy_static::lazy_static; @@ -109,30 +110,11 @@ async fn download_server( quality: Quality, log: &log::Logger, ) -> Result { - let platform_download_path = if cfg!(all(target_os = "linux", target_arch = "x86_64")) { - "server-linux-x64" - } else if cfg!(all(target_os = "linux", target_arch = "armhf")) { - "server-linux-armhf" - } else if cfg!(all(target_os = "linux", target_arch = "arm64")) { - "server-linux-arm64" - } else if cfg!(target_os = "macos") { - "server-darwin" - } else if cfg!(all(target_os = "windows", target_arch = "x64")) { - "server-win32-x64" - } else if cfg!(target_os = "windows") { - "server-win32" - } else { - panic!("Your platform or architecture isn't supported!"); - }; - - let quality_path = match quality { - Quality::Insiders => "insider", - Quality::Stable => "stable", - }; - let download_url = format!( "https://update.code.visualstudio.com/commit:{}/{}/{}", - commit_id, platform_download_path, quality_path + commit_id, + PLATFORM_DOWNLOAD_PATH, + quality.download_quality() ); let response = reqwest::get(download_url).await?; @@ -192,7 +174,7 @@ fn install_server(compressed_file: &Path, paths: &ServerPaths) -> Result<(), Any } pub async fn find_running_server( - server_params: &ServerParams, + server_params: &ResolvedServerParams, launcher_paths: &LauncherPaths, log: &log::Logger, ) -> Result, AnyError> { @@ -261,7 +243,7 @@ async fn do_extension_install_on_running_server( pub struct StartServerParams<'a> { pub logger: &'a log::Logger, - pub server_params: &'a ServerParams, + pub server_params: &'a ResolvedServerParams, pub launcher_paths: &'a LauncherPaths, } diff --git a/src/cli/src/launcher.rs b/src/cli/src/launcher.rs index 5c27cee313b8a8..8ca400123a9b2a 100644 --- a/src/cli/src/launcher.rs +++ b/src/cli/src/launcher.rs @@ -2,23 +2,33 @@ * Copyright (c) Microsoft Corporation. All rights reserved. *--------------------------------------------------------------------------------------------*/ -use std::time::Duration; - use crate::basis::{Basis, BasisTunnel, BasisTunnelDetails}; use crate::cli; use crate::cli::Quality; use crate::code_server::CodeServer; use crate::log; use crate::state::LauncherPaths; -use crate::util::errors::{AnyError, MismatchConnectionToken}; +use crate::util::errors::{AnyError, MismatchConnectionToken, StatusError}; use crate::util::machine::process_exists; +use crate::util::version::PLATFORM_DOWNLOAD_PATH; use crate::{code_server, code_server::StartServerParams}; use opentelemetry::KeyValue; use serde::{Deserialize, Serialize}; +use std::time::Duration; use tokio::time; #[derive(Serialize, Deserialize, Debug)] -pub struct ServerParams { +pub struct ServerParamsRaw { + pub commit_id: Option, + pub quality: Quality, + pub connection_token: String, + pub port: String, + pub telemetry_level: String, + pub extensions: Vec, + pub server_listen_flag: String, +} + +pub struct ResolvedServerParams { pub commit_id: String, pub quality: Quality, pub connection_token: String, @@ -28,6 +38,46 @@ pub struct ServerParams { pub server_listen_flag: String, } +impl ServerParamsRaw { + pub async fn resolve(self, log: &log::Logger) -> Result { + Ok(ResolvedServerParams { + commit_id: self.get_or_fetch_commit_id(log).await?, + quality: self.quality, + connection_token: self.connection_token, + port: self.port, + telemetry_level: self.telemetry_level, + extensions: self.extensions, + server_listen_flag: self.server_listen_flag, + }) + } + + async fn get_or_fetch_commit_id(&self, log: &log::Logger) -> Result { + if let Some(c) = &self.commit_id { + return Ok(c.to_string()); + } + + let download_url = format!( + "https://update.code.visualstudio.com/api/latest/{}/{}", + PLATFORM_DOWNLOAD_PATH, + self.quality.download_quality() + ); + + let response = log::spanf!( + log, + log.span("server.version.resolve"), + reqwest::get(download_url) + )?; + + if !response.status().is_success() { + return Err(StatusError::from_res(response).await?.into()); + } + + let version = response.json::().await?.version; + log::verbose!(log, "Resolved quality {} to {}", self.quality, version); + Ok(version) + } +} + #[derive(Serialize, Deserialize, Debug)] pub struct SuccessParams { pub listening_port: u32, @@ -35,11 +85,21 @@ pub struct SuccessParams { pub tunnel: Option, } +#[derive(Deserialize)] +#[serde(rename_all = "camelCase")] +#[allow(dead_code)] +struct UpdateServerVersion { + pub name: String, + pub version: String, + pub product_version: String, + pub timestamp: i64, +} + pub async fn install_and_start_server( logger: &log::Logger, basis: &mut Basis, launcher_paths: &LauncherPaths, - server_params: &ServerParams, + server_params: &ResolvedServerParams, tunnel_args: &cli::BasisDetails, ) -> Result { code_server::setup_server( @@ -108,7 +168,7 @@ async fn forward_server( server: CodeServer, basis: &mut Basis, launcher_paths: &LauncherPaths, - server_params: &ServerParams, + server_params: &ResolvedServerParams, tunnel_args: &cli::BasisDetails, ) -> Result { let paths = launcher_paths.server_paths(server_params.quality, &server_params.commit_id); diff --git a/src/cli/src/main.rs b/src/cli/src/main.rs index 2fb4dfc3a29840..5dcadd0d1fa2d9 100644 --- a/src/cli/src/main.rs +++ b/src/cli/src/main.rs @@ -149,7 +149,7 @@ async fn run_command( log::spanf!(log, log.span("prereq"), PreReqChecker::new().verify())?; let mut basis = Basis::new(log, launcher_paths); - let params = launcher::ServerParams { + let params = launcher::ServerParamsRaw { commit_id: install_args.commit_id.to_owned(), quality: install_args.quality, port: String::from("0"), @@ -157,7 +157,9 @@ async fn run_command( extensions: [].to_vec(), server_listen_flag: String::from(""), connection_token: format!("{}", Uuid::new_v4()), - }; + } + .resolve(log) + .await?; let success = launcher::install_and_start_server( log, diff --git a/src/cli/src/server.rs b/src/cli/src/server.rs index 6b9a3041862eed..4f7801c9b4dc44 100644 --- a/src/cli/src/server.rs +++ b/src/cli/src/server.rs @@ -3,7 +3,7 @@ *--------------------------------------------------------------------------------------------*/ use crate::basis::Basis; -use crate::launcher::{ServerParams, SuccessParams}; +use crate::launcher::{ServerParamsRaw, SuccessParams}; use crate::state::{prune_stopped_servers, LauncherPaths}; use crate::update::Update; use crate::util::errors::{wrap, AnyError}; @@ -20,7 +20,7 @@ use tokio::sync::mpsc; #[serde(tag = "method", content = "params")] #[allow(non_camel_case_types)] enum RequestMethod { - serve(launcher::ServerParams), + serve(launcher::ServerParamsRaw), prune, update(UpdateParams), } @@ -195,7 +195,7 @@ async fn process_socket(ctx: &mut HandlerContext, socket: TcpStream) -> Result<( } let response = match req.params { - RequestMethod::serve(p) => tj!("serve", handle_serve(ctx, &log, &p)), + RequestMethod::serve(p) => tj!("serve", handle_serve(ctx, &log, p)), RequestMethod::prune => tj!("prune", handle_prune(ctx)), RequestMethod::update(p) => tj!("update", handle_update(ctx, &p)), }; @@ -218,13 +218,14 @@ async fn process_socket(ctx: &mut HandlerContext, socket: TcpStream) -> Result<( async fn handle_serve( ctx: &mut HandlerContext, log: &log::Logger, - params: &ServerParams, + params: ServerParamsRaw, ) -> Result { + let resolved = params.resolve(log).await?; let success = launcher::install_and_start_server( log, &mut ctx.basis, &ctx.launcher_paths, - params, + &resolved, &ctx.serve_args.tunnel, ) .await?; diff --git a/src/cli/src/util/version.rs b/src/cli/src/util/version.rs index 6551aee791cbad..b634f6859339b7 100644 --- a/src/cli/src/util/version.rs +++ b/src/cli/src/util/version.rs @@ -6,3 +6,19 @@ pub const LAUNCHER_VERSION: Option<&'static str> = option_env!("LAUNCHER_VERSION pub const LAUNCHER_ASSET_NAME: Option<&'static str> = option_env!("LAUNCHER_ASSET_NAME"); pub const LAUNCHER_AI_KEY: Option<&'static str> = option_env!("LAUNCHER_AI_KEY"); pub const LAUNCHER_AI_ENDPOINT: Option<&'static str> = option_env!("LAUNCHER_AI_ENDPOINT"); + +pub const PLATFORM_DOWNLOAD_PATH: &str = if cfg!(all(target_os = "linux", target_arch = "x86_64")) { + "server-linux-x64" +} else if cfg!(all(target_os = "linux", target_arch = "armhf")) { + "server-linux-armhf" +} else if cfg!(all(target_os = "linux", target_arch = "arm64")) { + "server-linux-arm64" +} else if cfg!(target_os = "macos") { + "server-darwin" +} else if cfg!(all(target_os = "windows", target_arch = "x64")) { + "server-win32-x64" +} else if cfg!(target_os = "windows") { + "server-win32" +} else { + panic!("Your platform or architecture isn't supported!"); +};