From 1df3426f4a665b3a06d24467e91b160937be4605 Mon Sep 17 00:00:00 2001 From: Vilmos Feher Date: Fri, 29 Mar 2024 19:36:54 +0100 Subject: [PATCH] Check cli vs server versions --- Cargo.lock | 7 ++ golem-cli/Cargo.toml | 1 + golem-cli/src/clients.rs | 1 + golem-cli/src/clients/errors.rs | 8 +- golem-cli/src/clients/health_check.rs | 40 +++++++ golem-cli/src/lib.rs | 2 +- golem-cli/src/main.rs | 30 ++++- golem-cli/src/version.rs | 153 ++++++++++++++++++++++++++ 8 files changed, 239 insertions(+), 3 deletions(-) create mode 100644 golem-cli/src/clients/health_check.rs create mode 100644 golem-cli/src/version.rs diff --git a/Cargo.lock b/Cargo.lock index 191d1c998..a149a53c0 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2802,6 +2802,7 @@ dependencies = [ "tungstenite 0.20.1", "url", "uuid", + "version-compare", ] [[package]] @@ -7874,6 +7875,12 @@ version = "0.2.15" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426" +[[package]] +name = "version-compare" +version = "0.0.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1c18c859eead79d8b95d09e4678566e8d70105c4e7b251f707a03df32442661b" + [[package]] name = "version_check" version = "0.9.4" diff --git a/golem-cli/Cargo.toml b/golem-cli/Cargo.toml index af00ab925..c5dc110c0 100644 --- a/golem-cli/Cargo.toml +++ b/golem-cli/Cargo.toml @@ -53,6 +53,7 @@ tracing-subscriber = "0.3.18" tungstenite = "0.20.1" url = { workspace = true } uuid = { workspace = true } +version-compare = "=0.0.11" [dev-dependencies] async-recursion = "1.0.5" diff --git a/golem-cli/src/clients.rs b/golem-cli/src/clients.rs index acf29e1f1..dec09c411 100644 --- a/golem-cli/src/clients.rs +++ b/golem-cli/src/clients.rs @@ -13,5 +13,6 @@ // limitations under the License. pub mod errors; +pub mod health_check; pub mod template; pub mod worker; diff --git a/golem-cli/src/clients/errors.rs b/golem-cli/src/clients/errors.rs index 6767a8c65..4455fad93 100644 --- a/golem-cli/src/clients/errors.rs +++ b/golem-cli/src/clients/errors.rs @@ -12,7 +12,7 @@ // See the License for the specific language governing permissions and // limitations under the License. -use golem_client::api::{TemplateError, WorkerError}; +use golem_client::api::{HealthCheckError, TemplateError, WorkerError}; use golem_client::model::{ GolemError, GolemErrorFailedToResumeWorker, GolemErrorGetLatestVersionOfTemplateFailed, GolemErrorInterrupted, GolemErrorInvalidRequest, GolemErrorInvalidShardId, @@ -54,6 +54,12 @@ impl ResponseContentErrorMapper for WorkerError { } } +impl ResponseContentErrorMapper for HealthCheckError { + fn map(self) -> String { + "Invalid request".to_string() + } +} + fn display_golem_error(error: golem_client::model::GolemError) -> String { match error { GolemError::InvalidRequest(GolemErrorInvalidRequest { details }) => { diff --git a/golem-cli/src/clients/health_check.rs b/golem-cli/src/clients/health_check.rs new file mode 100644 index 000000000..9a4430568 --- /dev/null +++ b/golem-cli/src/clients/health_check.rs @@ -0,0 +1,40 @@ +// Copyright 2024 Golem Cloud +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +use async_trait::async_trait; +use golem_client::model::VersionInfo; +use tracing::debug; + +use crate::model::GolemError; + +#[async_trait] +pub trait HealthCheckClient { + async fn version(&self) -> Result; +} + +#[derive(Clone)] +pub struct HealthCheckClientLive { + pub client: C, +} + +#[async_trait] +impl HealthCheckClient + for HealthCheckClientLive +{ + async fn version(&self) -> Result { + debug!("Getting server version"); + + Ok(self.client.version().await?) + } +} diff --git a/golem-cli/src/lib.rs b/golem-cli/src/lib.rs index bbfb4f9d6..f164984b5 100644 --- a/golem-cli/src/lib.rs +++ b/golem-cli/src/lib.rs @@ -16,8 +16,8 @@ pub mod clients; pub mod examples; pub mod model; pub mod template; +pub mod version; pub mod worker; - pub fn parse_key_val( s: &str, ) -> Result<(String, String), Box> { diff --git a/golem-cli/src/main.rs b/golem-cli/src/main.rs index 74e60c42d..26c5d69e6 100644 --- a/golem-cli/src/main.rs +++ b/golem-cli/src/main.rs @@ -24,10 +24,12 @@ use golem_examples::model::{ExampleName, GuestLanguage, GuestLanguageTier, Packa use reqwest::Url; use tracing_subscriber::FmtSubscriber; +use golem_cli::clients::health_check::HealthCheckClientLive; use golem_cli::clients::template::TemplateClientLive; use golem_cli::clients::worker::WorkerClientLive; use golem_cli::examples; use golem_cli::template::{TemplateHandler, TemplateHandlerLive, TemplateSubcommand}; +use golem_cli::version::{VersionHandler, VersionHandlerLive}; use golem_cli::worker::{WorkerHandler, WorkerHandlerLive, WorkerSubcommand}; #[derive(Subcommand, Debug)] @@ -169,7 +171,7 @@ async fn async_main(cmd: GolemCommand) -> Result<(), Box> let template_client = TemplateClientLive { client: golem_client::api::TemplateClientLive { - context: template_context, + context: template_context.clone(), }, }; let template_srv = TemplateHandlerLive { @@ -187,6 +189,32 @@ async fn async_main(cmd: GolemCommand) -> Result<(), Box> templates: &template_srv, }; + let health_check_client_for_template = HealthCheckClientLive { + client: golem_client::api::HealthCheckClientLive { + context: template_context.clone(), + }, + }; + + let health_check_client_for_worker = HealthCheckClientLive { + client: golem_client::api::HealthCheckClientLive { + context: worker_context.clone(), + }, + }; + + let update_srv = VersionHandlerLive { + template_client: health_check_client_for_template, + worker_client: health_check_client_for_worker, + }; + + let yellow = "\x1b[33m"; + let reset_color = "\x1b[0m"; + + let version_check = update_srv.check().await; + + if let Err(err) = version_check { + eprintln!("{}{}{}", yellow, err.0, reset_color) + } + let res = match cmd.command { Command::Template { subcommand } => template_srv.handle(subcommand).await, Command::Worker { subcommand } => worker_srv.handle(subcommand).await, diff --git a/golem-cli/src/version.rs b/golem-cli/src/version.rs new file mode 100644 index 000000000..07c926d4a --- /dev/null +++ b/golem-cli/src/version.rs @@ -0,0 +1,153 @@ +// Copyright 2024 Golem Cloud +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +use async_trait::async_trait; +use version_compare::Version; + +use crate::clients::health_check::HealthCheckClient; +use crate::model::{GolemError, GolemResult}; + +const VERSION: &str = env!("CARGO_PKG_VERSION"); + +#[async_trait] +pub trait VersionHandler { + async fn check(&self) -> Result; +} + +pub struct VersionHandlerLive< + T: HealthCheckClient + Send + Sync + Send + Sync, + W: HealthCheckClient + Send + Sync + Send + Sync, +> { + pub template_client: T, + pub worker_client: W, +} + +#[async_trait] +impl VersionHandler + for VersionHandlerLive +{ + async fn check(&self) -> Result { + let template_version_info = self.template_client.version().await?; + let worker_version_info = self.worker_client.version().await?; + + let cli_version = Version::from(VERSION).unwrap(); + let template_version = Version::from(template_version_info.version.as_str()).unwrap(); + let worker_version = Version::from(worker_version_info.version.as_str()).unwrap(); + + let warning = |cli_version: Version, server_version: Version| -> String { + format!("Warning: golem-cli {} is older than the targeted Golem servers ({})\nInstall the matching version with:\ncargo install golem-cli@{}\n", cli_version.as_str(), server_version.as_str(), server_version.as_str()).to_string() + }; + + if cli_version < template_version && cli_version < worker_version { + if template_version > worker_version { + Err(GolemError(warning(cli_version, template_version))) + } else { + Err(GolemError(warning(cli_version, worker_version))) + } + } else if cli_version < template_version { + Err(GolemError(warning(cli_version, template_version))) + } else if cli_version < worker_version { + Err(GolemError(warning(cli_version, worker_version))) + } else { + Ok(GolemResult::Str("No updates found".to_string())) + } + } +} + +#[cfg(test)] +mod tests { + + use crate::{clients::health_check::HealthCheckClient, model::GolemError, version::{VersionHandlerLive, VersionHandler}}; + use async_trait::async_trait; + use golem_client::model::VersionInfo; + use crate::model::GolemResult; + + pub struct HealthCheckClientTest { + version: &'static str + } + + #[async_trait] + impl HealthCheckClient for HealthCheckClientTest + { + async fn version(&self) -> Result { + Ok(VersionInfo {version:self.version.to_string()}) + } + } + + fn client(v: &'static str) -> HealthCheckClientTest { + HealthCheckClientTest {version: v} + } + + + + fn warning(server_version: &str) -> String { + format!("Warning: golem-cli 0.0.0 is older than the targeted Golem servers ({})\nInstall the matching version with:\ncargo install golem-cli@{}\n", server_version, server_version).to_string() + } + + async fn check_version(template_version: &'static str, worker_version: &'static str) -> String { + let update_srv = VersionHandlerLive { + template_client: client(template_version), + worker_client: client(worker_version), + }; + + let checked = update_srv.check().await; + match checked { + Ok(GolemResult::Str(s)) => s, + Err(e) => e.to_string(), + _ => "error".to_string(), + } + } + + #[tokio::test] + pub async fn same_version() { + let result = check_version("0.0.0", "0.0.0").await; + let expected = "No updates found".to_string(); + assert_eq!(result, expected) + } + + #[tokio::test] + pub async fn both_older() { + let result = check_version("0.0.0-snapshot", "0.0.0-snapshot").await; + let expected = "No updates found".to_string(); + assert_eq!(result, expected) + } + + #[tokio::test] + pub async fn both_newer_template_newest() { + let result = check_version("0.1.0", "0.0.3").await; + let expected = warning("0.1.0"); + assert_eq!(result, expected) + } + + #[tokio::test] + pub async fn both_newer_worker_newest() { + let result = check_version("0.1.1", "0.2.0").await; + let expected = warning("0.2.0"); + assert_eq!(result, expected) + } + + #[tokio::test] + pub async fn newer_worker() { + let result = check_version("0.0.0", "0.0.1").await; + let expected = warning("0.0.1"); + assert_eq!(result, expected) + } + + #[tokio::test] + pub async fn newer_template() { + let result = check_version("0.0.1", "0.0.0").await; + let expected = warning("0.0.1"); + assert_eq!(result, expected) + } +} \ No newline at end of file