Skip to content

Commit

Permalink
Merge pull request #164 from mindvalley/feat/display-upgrade-option
Browse files Browse the repository at this point in the history
PXP-628: [CLI]: Display new release info banner
  • Loading branch information
onimsha authored Nov 6, 2023
2 parents fd70d41 + 4848b56 commit 13f76b4
Show file tree
Hide file tree
Showing 15 changed files with 243 additions and 20 deletions.
1 change: 1 addition & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions cli/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,7 @@ miette = { version = "5.10.0", features = ["fancy"] }
ignore = "0.4.20"
rayon = "1.7.0"
similar = { version = "2.2.1", features = ["inline"] }
semver = "1.0.20"

# Telemetry
wukong-telemetry-macro = { path = "../telemetry-macro" }
Expand Down
2 changes: 1 addition & 1 deletion cli/completions/bash/wukong.bash
Original file line number Diff line number Diff line change
Expand Up @@ -2004,4 +2004,4 @@ _wukong() {
esac
}

complete -F _wukong -o bashdefault -o default wukong
complete -F _wukong -o nosort -o bashdefault -o default wukong
20 changes: 10 additions & 10 deletions cli/completions/fish/wukong.fish
Original file line number Diff line number Diff line change
Expand Up @@ -57,8 +57,8 @@ By default, it\'ll only report errors.
complete -c wukong -n "__fish_seen_subcommand_from application; and __fish_seen_subcommand_from info" -s q -l quiet -d 'Do not print log message'
complete -c wukong -n "__fish_seen_subcommand_from application; and __fish_seen_subcommand_from info" -l canary -d 'Use the Canary channel API'
complete -c wukong -n "__fish_seen_subcommand_from application; and __fish_seen_subcommand_from info" -s h -l help -d 'Print help'
complete -c wukong -n "__fish_seen_subcommand_from application; and __fish_seen_subcommand_from logs" -l namespace -d '(optional) The namespace to deploy to' -r -f -a "{prod ,staging }"
complete -c wukong -n "__fish_seen_subcommand_from application; and __fish_seen_subcommand_from logs" -l version -d '(optional) The version that the deployment will perform against' -r -f -a "{blue ,green }"
complete -c wukong -n "__fish_seen_subcommand_from application; and __fish_seen_subcommand_from logs" -l namespace -d '(optional) The namespace to deploy to' -r -f -a "{prod '',staging ''}"
complete -c wukong -n "__fish_seen_subcommand_from application; and __fish_seen_subcommand_from logs" -l version -d '(optional) The version that the deployment will perform against' -r -f -a "{blue '',green ''}"
complete -c wukong -n "__fish_seen_subcommand_from application; and __fish_seen_subcommand_from logs" -s s -l since -d 'Show logs lines newer from relative duration, e.g 5m, 1h, 1d. Also accept datetime in RFC 3339 format' -r
complete -c wukong -n "__fish_seen_subcommand_from application; and __fish_seen_subcommand_from logs" -s u -l until -d 'Show logs lines older than relative duration, e.g 30m, 2h, 2d. Also accept datetime in RFC 3339 format' -r
complete -c wukong -n "__fish_seen_subcommand_from application; and __fish_seen_subcommand_from logs" -l limit -d 'Limiting the number of log entries to return' -r
Expand Down Expand Up @@ -91,8 +91,8 @@ complete -c wukong -n "__fish_seen_subcommand_from application; and __fish_seen_
complete -c wukong -n "__fish_seen_subcommand_from application; and __fish_seen_subcommand_from instances; and not __fish_seen_subcommand_from list; and not __fish_seen_subcommand_from connect; and not __fish_seen_subcommand_from help" -f -a "list" -d 'Listing the currently running Elixir instances, normally under a GKE Pod'
complete -c wukong -n "__fish_seen_subcommand_from application; and __fish_seen_subcommand_from instances; and not __fish_seen_subcommand_from list; and not __fish_seen_subcommand_from connect; and not __fish_seen_subcommand_from help" -f -a "connect" -d 'Start the interactive session to connect to the remote Elixir instance'
complete -c wukong -n "__fish_seen_subcommand_from application; and __fish_seen_subcommand_from instances; and not __fish_seen_subcommand_from list; and not __fish_seen_subcommand_from connect; and not __fish_seen_subcommand_from help" -f -a "help" -d 'Print this message or the help of the given subcommand(s)'
complete -c wukong -n "__fish_seen_subcommand_from application; and __fish_seen_subcommand_from instances; and __fish_seen_subcommand_from list" -l namespace -d '(optional) The namespace to list the running instances' -r -f -a "{prod ,staging }"
complete -c wukong -n "__fish_seen_subcommand_from application; and __fish_seen_subcommand_from instances; and __fish_seen_subcommand_from list" -l version -d '(optional) The version of the application to filter the returning running instances' -r -f -a "{blue ,green }"
complete -c wukong -n "__fish_seen_subcommand_from application; and __fish_seen_subcommand_from instances; and __fish_seen_subcommand_from list" -l namespace -d '(optional) The namespace to list the running instances' -r -f -a "{prod '',staging ''}"
complete -c wukong -n "__fish_seen_subcommand_from application; and __fish_seen_subcommand_from instances; and __fish_seen_subcommand_from list" -l version -d '(optional) The version of the application to filter the returning running instances' -r -f -a "{blue '',green ''}"
complete -c wukong -n "__fish_seen_subcommand_from application; and __fish_seen_subcommand_from instances; and __fish_seen_subcommand_from list" -s a -l application -d 'Override the application name that the CLI will perform the command against. If the flag is not used, then the CLI will use the default application name from the config' -r
complete -c wukong -n "__fish_seen_subcommand_from application; and __fish_seen_subcommand_from instances; and __fish_seen_subcommand_from list" -s v -l verbose -d 'Use verbos output. More output per occurrence.
Expand All @@ -104,8 +104,8 @@ By default, it\'ll only report errors.
complete -c wukong -n "__fish_seen_subcommand_from application; and __fish_seen_subcommand_from instances; and __fish_seen_subcommand_from list" -s q -l quiet -d 'Do not print log message'
complete -c wukong -n "__fish_seen_subcommand_from application; and __fish_seen_subcommand_from instances; and __fish_seen_subcommand_from list" -l canary -d 'Use the Canary channel API'
complete -c wukong -n "__fish_seen_subcommand_from application; and __fish_seen_subcommand_from instances; and __fish_seen_subcommand_from list" -s h -l help -d 'Print help (see more with \'--help\')'
complete -c wukong -n "__fish_seen_subcommand_from application; and __fish_seen_subcommand_from instances; and __fish_seen_subcommand_from connect" -l namespace -d '(optional) The namespace to list the running instances' -r -f -a "{prod ,staging }"
complete -c wukong -n "__fish_seen_subcommand_from application; and __fish_seen_subcommand_from instances; and __fish_seen_subcommand_from connect" -l version -d '(optional) The version of the application to filter the returning running instances' -r -f -a "{blue ,green }"
complete -c wukong -n "__fish_seen_subcommand_from application; and __fish_seen_subcommand_from instances; and __fish_seen_subcommand_from connect" -l namespace -d '(optional) The namespace to list the running instances' -r -f -a "{prod '',staging ''}"
complete -c wukong -n "__fish_seen_subcommand_from application; and __fish_seen_subcommand_from instances; and __fish_seen_subcommand_from connect" -l version -d '(optional) The version of the application to filter the returning running instances' -r -f -a "{blue '',green ''}"
complete -c wukong -n "__fish_seen_subcommand_from application; and __fish_seen_subcommand_from instances; and __fish_seen_subcommand_from connect" -s a -l application -d 'Override the application name that the CLI will perform the command against. If the flag is not used, then the CLI will use the default application name from the config' -r
complete -c wukong -n "__fish_seen_subcommand_from application; and __fish_seen_subcommand_from instances; and __fish_seen_subcommand_from connect" -s v -l verbose -d 'Use verbos output. More output per occurrence.
Expand Down Expand Up @@ -206,8 +206,8 @@ By default, it\'ll only report errors.
complete -c wukong -n "__fish_seen_subcommand_from deployment; and __fish_seen_subcommand_from list" -s q -l quiet -d 'Do not print log message'
complete -c wukong -n "__fish_seen_subcommand_from deployment; and __fish_seen_subcommand_from list" -l canary -d 'Use the Canary channel API'
complete -c wukong -n "__fish_seen_subcommand_from deployment; and __fish_seen_subcommand_from list" -s h -l help -d 'Print help'
complete -c wukong -n "__fish_seen_subcommand_from deployment; and __fish_seen_subcommand_from execute" -l namespace -d 'The namespace to deploy to' -r -f -a "{prod ,staging }"
complete -c wukong -n "__fish_seen_subcommand_from deployment; and __fish_seen_subcommand_from execute" -l version -d 'The version that the deployment will perform against' -r -f -a "{blue ,green }"
complete -c wukong -n "__fish_seen_subcommand_from deployment; and __fish_seen_subcommand_from execute" -l namespace -d 'The namespace to deploy to' -r -f -a "{prod '',staging ''}"
complete -c wukong -n "__fish_seen_subcommand_from deployment; and __fish_seen_subcommand_from execute" -l version -d 'The version that the deployment will perform against' -r -f -a "{blue '',green ''}"
complete -c wukong -n "__fish_seen_subcommand_from deployment; and __fish_seen_subcommand_from execute" -l artifact -d 'The build artifact that the deployment will use' -r
complete -c wukong -n "__fish_seen_subcommand_from deployment; and __fish_seen_subcommand_from execute" -s a -l application -d 'Override the application name that the CLI will perform the command against. If the flag is not used, then the CLI will use the default application name from the config' -r
complete -c wukong -n "__fish_seen_subcommand_from deployment; and __fish_seen_subcommand_from execute" -s v -l verbose -d 'Use verbos output. More output per occurrence.
Expand All @@ -220,8 +220,8 @@ By default, it\'ll only report errors.
complete -c wukong -n "__fish_seen_subcommand_from deployment; and __fish_seen_subcommand_from execute" -s q -l quiet -d 'Do not print log message'
complete -c wukong -n "__fish_seen_subcommand_from deployment; and __fish_seen_subcommand_from execute" -l canary -d 'Use the Canary channel API'
complete -c wukong -n "__fish_seen_subcommand_from deployment; and __fish_seen_subcommand_from execute" -s h -l help -d 'Print help'
complete -c wukong -n "__fish_seen_subcommand_from deployment; and __fish_seen_subcommand_from rollback" -l namespace -d 'The namespace to deploy to' -r -f -a "{prod ,staging }"
complete -c wukong -n "__fish_seen_subcommand_from deployment; and __fish_seen_subcommand_from rollback" -l version -d 'The version that the deployment will perform against' -r -f -a "{blue ,green }"
complete -c wukong -n "__fish_seen_subcommand_from deployment; and __fish_seen_subcommand_from rollback" -l namespace -d 'The namespace to deploy to' -r -f -a "{prod '',staging ''}"
complete -c wukong -n "__fish_seen_subcommand_from deployment; and __fish_seen_subcommand_from rollback" -l version -d 'The version that the deployment will perform against' -r -f -a "{blue '',green ''}"
complete -c wukong -n "__fish_seen_subcommand_from deployment; and __fish_seen_subcommand_from rollback" -s a -l application -d 'Override the application name that the CLI will perform the command against. If the flag is not used, then the CLI will use the default application name from the config' -r
complete -c wukong -n "__fish_seen_subcommand_from deployment; and __fish_seen_subcommand_from rollback" -s v -l verbose -d 'Use verbos output. More output per occurrence.
Expand Down
10 changes: 8 additions & 2 deletions cli/src/commands/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ use crate::{
},
config::{ApiChannel, Config},
error::WKCliError,
update,
};

mod application;
Expand Down Expand Up @@ -113,7 +114,7 @@ impl ClapApp {
};
debug!("API channel: {:?}", channel);

match &self.command_group {
let command = match &self.command_group {
CommandGroup::Init => handle_init(channel).await,
CommandGroup::Completion { shell } => handle_completion(*shell),
CommandGroup::Login => handle_login().await,
Expand All @@ -127,7 +128,12 @@ impl ClapApp {
CommandGroup::Config(config) => config.handle_command(),
CommandGroup::Dev(dev) => dev.handle_command(self).await,
CommandGroup::Tui => handle_tui(channel).await,
}
};

// Check for CLI updates:
update::check_for_update().await;

command
}
}

Expand Down
8 changes: 8 additions & 0 deletions cli/src/config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,7 @@ pub struct Config {
pub core: CoreConfig,
pub auth: Option<AuthConfig>,
pub vault: Option<VaultConfig>,
pub update_check: Option<UpdateCheck>,
}

#[derive(Serialize, Deserialize, Debug, PartialEq, Eq, Clone, Default)]
Expand Down Expand Up @@ -94,6 +95,12 @@ pub struct AuthConfig {
pub refresh_token: String,
}

// ReleaseInfo stores information about a release
#[derive(Serialize, Deserialize, Debug, PartialEq, Eq, Clone)]
pub struct UpdateCheck {
pub last_update_checked_at: String,
}

impl Default for Config {
fn default() -> Self {
let mut home_dir = dirs::home_dir().unwrap();
Expand All @@ -107,6 +114,7 @@ impl Default for Config {
},
auth: None,
vault: None,
update_check: None,
}
}
}
Expand Down
1 change: 1 addition & 0 deletions cli/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ mod error;
mod loader;
mod logger;
pub mod output;
mod update;
mod utils;
mod wukong_client;

Expand Down
196 changes: 196 additions & 0 deletions cli/src/update.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,196 @@
use crate::{
config::{Config, UpdateCheck},
utils::compare_with_current_time,
};
use aion::*;
use chrono::Utc;
use clap::crate_version;
use log::debug;
use owo_colors::OwoColorize;
use reqwest::Client;
use semver::Version;
use serde::{Deserialize, Serialize};

const WUKONG_GITHUB_REPO: &str = "mindvalley/wukong-cli";
const GITHUB_API_URL: &str = "https://api.github.com";

#[derive(Debug, Serialize, Deserialize)]
pub struct GithubLatestReleaseInfo {
pub tag_name: String,
pub url: String,
pub published_at: String,
pub html_url: String,
}

// check_for_update checks whether this wukong has had a newer release on Github
pub async fn check_for_update() {
let last_updated_at = get_last_update_checked_at();

if let Some(last_updated_at) = last_updated_at {
let last_update_checked_since = compare_with_current_time(&last_updated_at);

if last_update_checked_since >= -(72.hours()) {
debug!("No need to check for update");
return;
}
}

debug!("Checking for update");

if let Some(latest_release_info) = get_latest_release_info(GITHUB_API_URL).await {
update_last_update_checked_at();
print_update_message(latest_release_info);
}
}

fn print_update_message(latest_release_info: GithubLatestReleaseInfo) {
let current_version = crate_version!().to_string();

let has_update = version_greater_than(&latest_release_info.tag_name, &current_version);

if has_update {
debug!("New release found");
eprintln!(
"{} {} {} {}",
"A new release of wukong is available:".yellow(),
current_version.cyan(),
"→".cyan(),
latest_release_info.tag_name.cyan(),
);

eprintln!("To upgrade, run: brew update && brew upgrade wukong");
eprintln!("{}", latest_release_info.url.yellow());
} else {
debug!("No new release found");
}
}

fn get_last_update_checked_at() -> Option<String> {
let config = Config::load_from_default_path().map_err(|e| {
debug!("Error: {:?}", e);
});

if let Ok(config) = config {
if let Some(config) = config.update_check {
return Some(config.last_update_checked_at);
}
}

None
}

fn update_last_update_checked_at() {
let config = Config::load_from_default_path();

match config {
Ok(mut config) => {
config.update_check = Some(UpdateCheck {
last_update_checked_at: Utc::now().to_rfc3339(),
});

let _ = config.save_to_default_path().map_err(|e| {
debug!("Error: {:?}", e);
});
}
Err(e) => {
debug!("Error: {:?}", e);
}
};
}

fn version_greater_than(new_version: &str, current_version: &str) -> bool {
if let (Ok(new_version), Ok(current_version)) =
(Version::parse(new_version), Version::parse(current_version))
{
new_version > current_version
} else {
false
}
}

async fn get_latest_release_info(github_api_url: &str) -> Option<GithubLatestReleaseInfo> {
let client = Client::new();

let url = format!(
"{}/repos/{}/releases/latest",
github_api_url, WUKONG_GITHUB_REPO
);

let response = client
.get(&url)
.header("user-agent", "wukong-cli")
.send()
.await
.map_err(|e| {
debug!("Error: {:?}", e);
});

if let Ok(response) = response {
let github_release_info = response
.json::<GithubLatestReleaseInfo>()
.await
.map_err(|e| {
debug!("Error: {:?}", e);
});

if let Ok(github_release_info) = github_release_info {
return Some(github_release_info);
}
}

None
}

#[cfg(test)]
mod tests {
use super::*;
use httpmock::prelude::*;

#[test]
fn test_version_greater_than() {
// Test cases for version comparison.
assert!(version_greater_than("1.1.0", "1.0.0"));
assert!(!version_greater_than("1.0.0", "1.1.0"));
assert!(!version_greater_than("1.0.0", "1.0.0"));
assert!(!version_greater_than("invalid", "1.0.0"));
assert!(!version_greater_than("1.0.0", "invalid"));
}

#[tokio::test]
async fn test_get_latest_release_info() {
let server = MockServer::start();

let api_resp = r#"{
"url": "https://github.com/mindvalley/wukong-cli/releases/tag/1.2.0",
"html_url": "https://github.com/mindvalley/wukong-cli/releases/tag/1.2.0",
"id": 120063991,
"tag_name": "1.2.0",
"published_at": "2023-09-06T07:08:46Z",
"body": null
}"#;

let url = format!("/repos/{}/releases/latest", WUKONG_GITHUB_REPO);

let mock_server = server.mock(|when, then| {
when.method(GET)
.path(url)
.header("user-agent", "wukong-cli");
then.status(200)
.header("content-type", "application/json; charset=UTF-8")
.body(api_resp);
});
println!("{:?}", &server.base_url());

let release_info = get_latest_release_info(&server.base_url()).await;

mock_server.assert();

let release_info = release_info.unwrap();
assert_eq!(release_info.tag_name, "1.2.0");
assert_eq!(release_info.published_at, "2023-09-06T07:08:46Z");
assert_eq!(
release_info.url,
"https://github.com/mindvalley/wukong-cli/releases/tag/1.2.0"
);
}
}
9 changes: 9 additions & 0 deletions cli/tests/config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,9 @@ id_token = "id_token"
access_token = "access_token"
expiry_time = "2023-02-19T06:55:51.501915+00:00"
refresh_token = "refresh_token"
[update_check]
last_update_checked_at = "2023-11-03T08:51:02.800908+00:00"
"#,
)
.unwrap();
Expand Down Expand Up @@ -200,6 +203,9 @@ id_token = "id_token"
access_token = "access_token"
expiry_time = "2023-02-19T06:55:51.501915+00:00"
refresh_token = "refresh_token"
[update_check]
last_update_checked_at = "2023-11-03T08:51:02.800908+00:00"
"#,
)
.unwrap();
Expand Down Expand Up @@ -230,6 +236,9 @@ id_token = "id_token"
access_token = "access_token"
expiry_time = "2023-02-19T06:55:51.501915+00:00"
refresh_token = "refresh_token"
[update_check]
last_update_checked_at = "2023-11-03T08:51:02.800908+00:00"
"#,
);

Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
---
source: tests/application_instances.rs
source: cli/tests/application_instances.rs
expression: "std::str::from_utf8(&output.stderr).unwrap()"
---
You don't have permission to connect to this instance.
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
---
source: tests/config.rs
source: cli/tests/config.rs
expression: "std::str::from_utf8(&output.stderr).unwrap()"
---
Error - Config file not found at "/path/to/non/exist/config.toml".
Expand Down
5 changes: 3 additions & 2 deletions cli/tests/snapshots/config__wukong_config_list_success.snap
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
---
source: tests/config.rs
source: cli/tests/config.rs
expression: "std::str::from_utf8(&output.stdout).unwrap()"
---
[core]
Expand All @@ -15,4 +15,5 @@ access_token = "access_token"
expiry_time = "2023-02-19T06:55:51.501915+00:00"
refresh_token = "refresh_token"


[update_check]
last_update_checked_at = "2023-11-03T08:51:02.800908+00:00"
Loading

0 comments on commit 13f76b4

Please sign in to comment.