diff --git a/.github/workflows/pr.yml b/.github/workflows/pr.yml index 59fe78a..8a921d1 100644 --- a/.github/workflows/pr.yml +++ b/.github/workflows/pr.yml @@ -12,6 +12,7 @@ env: RUSTFLAGS: "-D warnings" CLIENT_VERSION: 0.77.27 NODE_VERSION: 0.83.25 + NODE_MANAGER_VERSION: 0.1.8 TESTNET_VERSION: 0.1.29 jobs: @@ -140,6 +141,7 @@ jobs: run: | cargo run -- client --version $env:CLIENT_VERSION cargo run -- node --version $env:NODE_VERSION + cargo run -- node-manager --version $env:NODE_MANAGER_VERSION cargo run -- testnet --version $env:TESTNET_VERSION - name: Check if binaries are available in new shell session shell: pwsh @@ -152,6 +154,10 @@ jobs: Write-Host "safenode.exe does not exist" exit 1 } + if (!(Test-Path "$env:USERPROFILE\safe\safenode-manager.exe")) { + Write-Host "safenode-manager.exe does not exist" + exit 1 + } if (!(Test-Path "$env:USERPROFILE\safe\testnet.exe")) { Write-Host "testnet.exe does not exist" exit 1 @@ -185,6 +191,18 @@ jobs: exit 1 } + $output = & "${env:USERPROFILE}\safe\safenode-manager.exe" --version + $version = $output | Select-String -Pattern "sn-node-manager (\d+\.\d+\.\d+)" + $versionNumber = $version.Matches.Groups[1].Value + if ($versionNumber -eq "$env:NODE_MANAGER_VERSION") { + Write-Host "The correct version of safenode-manager has been installed" + } else { + Write-Host "The correct version of safenode-manager has not been installed" + Write-Host "We expected version $env:NODE_MANAGER_VERSION" + Write-Host "The downloaded binary has $versionNumber" + exit 1 + } + $output = & "${env:USERPROFILE}\safe\testnet.exe" --version $version = $output | Select-String -Pattern "testnet (\d+\.\d+\.\d+)" $versionNumber = $version.Matches.Groups[1].Value @@ -213,6 +231,7 @@ jobs: run: | cargo run -- client --version $CLIENT_VERSION cargo run -- node --version $NODE_VERSION + cargo run -- node-manager --version $NODE_MANAGER_VERSION cargo run -- testnet --version $TESTNET_VERSION - name: Check if binaries are available in new shell session shell: bash @@ -228,6 +247,7 @@ jobs: [[ -f "$HOME/.local/bin/safe" ]] || { echo "safe not in expected location"; exit 1; } [[ -f "$HOME/.local/bin/safenode" ]] || { echo "safenode not in expected location"; exit 1; } + [[ -f "$HOME/.local/bin/safenode-manager" ]] || { echo "safenode-manager not in expected location"; exit 1; } [[ -f "$HOME/.local/bin/testnet" ]] || { echo "testnet not in expected location"; exit 1; } version=$(safe --version | awk '{ print $2 }') @@ -250,6 +270,16 @@ jobs: exit 1 fi + version=$(safenode-manager --version | awk '{ print $3 }') + if [[ "$version" == "$NODE_MANAGER_VERSION" ]]; then + echo "The correct version of safenode-manager has been installed" + else + echo "The correct version of safenode-manager has not been installed" + echo "We expected $NODE_MANAGER_VERSION" + echo "The downloaded binary has $version" + exit 1 + fi + version=$(testnet --version | awk '{ print $2 }') if [[ "$version" == "$TESTNET_VERSION" ]]; then echo "The correct version of testnet has been installed" @@ -275,6 +305,7 @@ jobs: run: | cargo run -- client --version $CLIENT_VERSION cargo run -- node --version $NODE_VERSION + cargo run -- node-manager --version $NODE_MANAGER_VERSION cargo run -- testnet --version $TESTNET_VERSION - name: Check if binaries are available in new shell session run: | @@ -283,6 +314,7 @@ jobs: [[ -f "$HOME/.local/bin/safe" ]] || { echo "safe not in expected location"; exit 1; } [[ -f "$HOME/.local/bin/safenode" ]] || { echo "safenode not in expected location"; exit 1; } + [[ -f "$HOME/.local/bin/safenode-manager" ]] || { echo "safenode-manager not in expected location"; exit 1; } [[ -f "$HOME/.local/bin/testnet" ]] || { echo "testnet not in expected location"; exit 1; } version=$(safe --version | awk '{ print $2 }') @@ -305,6 +337,16 @@ jobs: exit 1 fi + version=$(safenode-manager --version | awk '{ print $3 }') + if [[ "$version" == "$NODE_MANAGER_VERSION" ]]; then + echo "The correct version of safenode-manager has been installed" + else + echo "The correct version of safenode-manager has not been installed" + echo "We expected $NODE_MANAGER_VERSION" + echo "The downloaded binary has $version" + exit 1 + fi + version=$(testnet --version | awk '{ print $2 }') if [[ "$version" == "$TESTNET_VERSION" ]]; then echo "The correct version of testnet has been installed" diff --git a/Cargo.toml b/Cargo.toml index 21034fc..5b051fb 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -25,7 +25,7 @@ semver = "1.0.4" serde = "1.0" serde_derive = "1.0" serde_json = "1.0" -sn-releases = { git = "https://github.com/jacderida/sn-releases", branch = "custom-url" } +sn-releases = "0.1.5" tempfile = "3.8.1" textwrap = "0.16.0" tokio = { version = "1.26", features = ["full"] } diff --git a/src/cmd.rs b/src/cmd.rs index bece2d5..77e150f 100644 --- a/src/cmd.rs +++ b/src/cmd.rs @@ -172,6 +172,10 @@ async fn do_install_binary( settings.safenode_path = bin_path; settings.safenode_version = installed_version; } + AssetType::NodeManager => { + settings.safenode_manager_path = bin_path; + settings.safenode_manager_version = installed_version; + } AssetType::Testnet => { settings.testnet_path = bin_path; settings.testnet_version = installed_version; diff --git a/src/install.rs b/src/install.rs index 8e44b63..50661f6 100644 --- a/src/install.rs +++ b/src/install.rs @@ -49,18 +49,25 @@ const SET_PATH_FILE_CONTENT: &str = indoc! {r#" pub enum AssetType { Client, Node, + NodeManager, Testnet, } impl AssetType { pub fn variants() -> Vec { - vec![AssetType::Client, AssetType::Node, AssetType::Testnet] + vec![ + AssetType::Client, + AssetType::Node, + AssetType::NodeManager, + AssetType::Testnet, + ] } pub fn get_release_type(&self) -> ReleaseType { match self { AssetType::Client => ReleaseType::Safe, AssetType::Node => ReleaseType::Safenode, + AssetType::NodeManager => ReleaseType::SafenodeManager, AssetType::Testnet => ReleaseType::Testnet, } } @@ -71,6 +78,7 @@ impl std::fmt::Display for AssetType { match *self { AssetType::Client => write!(f, "safe"), AssetType::Node => write!(f, "safenode"), + AssetType::NodeManager => write!(f, "safenode-manager"), AssetType::Testnet => write!(f, "testnet"), } } @@ -82,6 +90,8 @@ pub struct Settings { pub safe_version: String, pub safenode_path: PathBuf, pub safenode_version: String, + pub safenode_manager_path: PathBuf, + pub safenode_manager_version: String, pub testnet_path: PathBuf, pub testnet_version: String, } @@ -96,6 +106,8 @@ impl Settings { safe_version: String::new(), safenode_path: PathBuf::new(), safenode_version: String::new(), + safenode_manager_path: PathBuf::new(), + safenode_manager_version: String::new(), testnet_path: PathBuf::new(), testnet_version: String::new(), }) @@ -105,6 +117,8 @@ impl Settings { safe_version: String::new(), safenode_path: PathBuf::new(), safenode_version: String::new(), + safenode_manager_path: PathBuf::new(), + safenode_manager_version: String::new(), testnet_path: PathBuf::new(), testnet_version: String::new(), } @@ -116,6 +130,7 @@ impl Settings { match asset_type { AssetType::Client => self.safe_version.clone(), AssetType::Node => self.safenode_version.clone(), + AssetType::NodeManager => self.safenode_manager_version.clone(), AssetType::Testnet => self.testnet_version.clone(), } } @@ -124,6 +139,7 @@ impl Settings { match asset_type { AssetType::Client => !self.safe_version.is_empty(), AssetType::Node => !self.safenode_version.is_empty(), + AssetType::NodeManager => !self.safenode_manager_version.is_empty(), AssetType::Testnet => !self.testnet_version.is_empty(), } } @@ -132,6 +148,7 @@ impl Settings { match asset_type { AssetType::Client => self.safe_path.clone(), AssetType::Node => self.safenode_path.clone(), + AssetType::NodeManager => self.safenode_manager_path.clone(), AssetType::Testnet => self.testnet_path.clone(), } } @@ -341,6 +358,7 @@ fn get_bin_name(asset_type: &AssetType) -> String { let mut bin_name = match asset_type { AssetType::Client => "safe".to_string(), AssetType::Node => "safenode".to_string(), + AssetType::NodeManager => "safenode-manager".to_string(), AssetType::Testnet => "testnet".to_string(), }; if OS == "windows" { @@ -374,9 +392,6 @@ mod test { }; #[cfg(unix)] use std::os::unix::fs::PermissionsExt; - #[cfg(windows)] - use std::path::PathBuf; - #[cfg(unix)] use std::path::{Path, PathBuf}; /// These may seem pointless, but they are useful for when the tests run on different @@ -440,7 +455,7 @@ mod test { mock_release_repo .expect_download_release_from_s3() .with( - eq(&ReleaseType::Safenode), + eq(&ReleaseType::Safe), eq(latest_version), always(), // Varies per platform eq(&ArchiveType::TarGz), @@ -518,7 +533,7 @@ mod test { mock_release_repo .expect_download_release_from_s3() .with( - eq(&ReleaseType::Safenode), + eq(&ReleaseType::Safe), eq(latest_version), always(), // Varies per platform eq(&ArchiveType::TarGz), @@ -580,7 +595,7 @@ mod test { mock_release_repo .expect_download_release_from_s3() .with( - eq(&ReleaseType::Safenode), + eq(&ReleaseType::Safe), eq(specific_version), always(), // Varies per platform eq(&ArchiveType::TarGz), @@ -706,6 +721,8 @@ mod test { safe_bin_file.write_binary(b"fake safe code")?; let safenode_bin_file = tmp_data_path.child("safenode"); safenode_bin_file.write_binary(b"fake safenode code")?; + let safenode_manager_bin_file = tmp_data_path.child("safenode-manager"); + safenode_manager_bin_file.write_binary(b"fake safenode-manager code")?; let testnet_bin_file = tmp_data_path.child("testnet"); testnet_bin_file.write_binary(b"fake testnet code")?; @@ -714,6 +731,8 @@ mod test { safe_version: "v0.75.1".to_string(), safenode_path: safenode_bin_file.to_path_buf(), safenode_version: "v0.75.2".to_string(), + safenode_manager_path: safenode_manager_bin_file.to_path_buf(), + safenode_manager_version: "v0.1.8".to_string(), testnet_path: testnet_bin_file.to_path_buf(), testnet_version: "v0.75.3".to_string(), }; @@ -726,6 +745,11 @@ mod test { assert_eq!(settings.safe_version, "v0.75.1"); assert_eq!(settings.safenode_path, safenode_bin_file.to_path_buf()); assert_eq!(settings.safenode_version, "v0.75.2"); + assert_eq!( + settings.safenode_manager_path, + safenode_manager_bin_file.to_path_buf() + ); + assert_eq!(settings.safenode_manager_version, "v0.1.8"); assert_eq!(settings.testnet_path, testnet_bin_file.to_path_buf()); assert_eq!(settings.testnet_version, "v0.75.3"); Ok(()) @@ -744,6 +768,8 @@ mod test { safe_bin_file.write_binary(b"fake safe code")?; let safenode_bin_file = tmp_data_path.child("safenode"); safenode_bin_file.write_binary(b"fake safenode code")?; + let safenode_manager_bin_file = tmp_data_path.child("safenode-manager"); + safenode_manager_bin_file.write_binary(b"fake safenode-manager code")?; let testnet_bin_file = tmp_data_path.child("testnet"); testnet_bin_file.write_binary(b"fake testnet code")?; @@ -752,6 +778,8 @@ mod test { safe_version: "v0.75.1".to_string(), safenode_path: safenode_bin_file.to_path_buf(), safenode_version: "v0.75.2".to_string(), + safenode_manager_path: safenode_manager_bin_file.to_path_buf(), + safenode_manager_version: "v0.1.8".to_string(), testnet_path: testnet_bin_file.to_path_buf(), testnet_version: "v0.75.3".to_string(), }; @@ -764,6 +792,11 @@ mod test { assert_eq!(settings.safe_version, "v0.75.1"); assert_eq!(settings.safenode_path, safenode_bin_file.to_path_buf()); assert_eq!(settings.safenode_version, "v0.75.2"); + assert_eq!( + settings.safenode_manager_path, + safenode_manager_bin_file.to_path_buf() + ); + assert_eq!(settings.safenode_manager_version, "v0.1.8"); assert_eq!(settings.testnet_path, testnet_bin_file.to_path_buf()); assert_eq!(settings.testnet_version, "v0.75.3"); Ok(()) @@ -778,13 +811,12 @@ mod test { { "safe_path": "/home/chris/.local/safe", "safe_version": "v0.75.1", - "safe_is_elevated_install": false, "safenode_path": "/home/chris/.local/bin/safenode", "safenode_version": "v0.75.2", - "safenode_is_elevated_install": false, + "safenode_manager_path": "/home/chris/.local/bin/safenode-manager", + "safenode_manager_version": "v0.1.8", "testnet_path": "/home/chris/.local/bin/testnet", - "testnet_version": "v0.75.3", - "testnet_is_elevated_install": false + "testnet_version": "v0.75.3" } "#, )?; @@ -803,6 +835,11 @@ mod test { assert_eq!(settings.safe_version, "v0.75.1"); assert_eq!(settings.safenode_path, safenode_bin_file.to_path_buf()); assert_eq!(settings.safenode_version, "v0.75.2"); + assert_eq!( + settings.safenode_manager_path, + PathBuf::from("/home/chris/.local/bin/safenode-manager") + ); + assert_eq!(settings.safenode_manager_version, "v0.1.8"); assert_eq!( settings.testnet_path, PathBuf::from("/home/chris/.local/bin/testnet") diff --git a/src/main.rs b/src/main.rs index 3571c57..e5a27dd 100644 --- a/src/main.rs +++ b/src/main.rs @@ -77,6 +77,30 @@ enum Commands { #[arg(short = 'v', long)] version: Option, }, + /// Install the safenode-manager binary. + /// + /// The location is platform specific: + /// - Linux/macOS: $HOME/.local/bin + /// - Windows: C:\Users\\safe + /// + /// On Linux/macOS, the Bash shell profile will be modified to add $HOME/.local/bin to the PATH + /// variable. On Windows, the user Path variable will be modified to add C:\Users\\safe. + #[clap(verbatim_doc_comment)] + NodeManager { + /// Override the default installation path. + /// + /// Any directories that don't exist will be created. + #[arg(short = 'p', long, value_name = "DIRECTORY")] + path: Option, + + /// Disable modification of the shell profile. + #[arg(short = 'n', long)] + no_modify_shell_profile: bool, + + /// Install a specific version rather than the latest. + #[arg(short = 'v', long)] + version: Option, + }, /// Install the testnet binary. /// /// The location is platform specific: @@ -139,6 +163,25 @@ async fn main() -> Result<()> { install::check_prerequisites()?; process_install_cmd(AssetType::Node, path, version, no_modify_shell_profile).await } + Some(Commands::NodeManager { + path, + no_modify_shell_profile, + version, + }) => { + println!("**************************************"); + println!("* *"); + println!("* Installing safenode-manager *"); + println!("* *"); + println!("**************************************"); + install::check_prerequisites()?; + process_install_cmd( + AssetType::NodeManager, + path, + version, + no_modify_shell_profile, + ) + .await + } Some(Commands::Testnet { path, no_modify_shell_profile, diff --git a/src/update.rs b/src/update.rs index 1e9f11e..52d5714 100644 --- a/src/update.rs +++ b/src/update.rs @@ -56,6 +56,8 @@ mod test { safe_version: String::new(), safenode_path: PathBuf::from("/home/chris/.local/bin/safenode"), safenode_version: "v0.83.13".to_string(), + safenode_manager_path: PathBuf::from("/home/chris/.local/bin/safenode-manager"), + safenode_manager_version: "v0.1.8".to_string(), testnet_path: PathBuf::from("/home/chris/.local/bin/testnet"), testnet_version: "v0.3.4".to_string(), }; @@ -67,6 +69,8 @@ mod test { safe_version: "v0.78.26".to_string(), safenode_path: PathBuf::new(), safenode_version: String::new(), + safenode_manager_path: PathBuf::from("/home/chris/.local/bin/safenode-manager"), + safenode_manager_version: "v0.1.8".to_string(), testnet_path: PathBuf::from("/home/chris/.local/bin/testnet"), testnet_version: "v0.3.4".to_string(), }; @@ -78,6 +82,21 @@ mod test { safe_version: "v0.78.26".to_string(), safenode_path: PathBuf::from("/home/chris/.local/bin/safenode"), safenode_version: "v0.83.13".to_string(), + safenode_manager_path: PathBuf::new(), + safenode_manager_version: String::new(), + testnet_path: PathBuf::from("/home/chris/.local/bin/testnet"), + testnet_version: "v0.3.4".to_string(), + }; + let decision = perform_update_assessment(&AssetType::NodeManager, "v0.1.8", &settings)?; + assert_matches!(decision, UpdateAssessmentResult::NoPreviousInstallation); + + let settings = Settings { + safe_path: PathBuf::from("/home/chris/.local/safe"), + safe_version: "v0.78.26".to_string(), + safenode_path: PathBuf::from("/home/chris/.local/bin/safenode"), + safenode_version: "v0.83.13".to_string(), + safenode_manager_path: PathBuf::from("/home/chris/.local/bin/safenode-manager"), + safenode_manager_version: "v0.1.8".to_string(), testnet_path: PathBuf::new(), testnet_version: String::new(), }; @@ -94,6 +113,8 @@ mod test { safe_version: "v0.78.26".to_string(), safenode_path: PathBuf::from("/home/chris/.local/bin/safenode"), safenode_version: "v0.83.13".to_string(), + safenode_manager_path: PathBuf::from("/home/chris/.local/bin/safenode-manager"), + safenode_manager_version: "v0.1.8".to_string(), testnet_path: PathBuf::from("/home/chris/.local/bin/testnet"), testnet_version: "v0.3.4".to_string(), }; @@ -104,6 +125,9 @@ mod test { let decision = perform_update_assessment(&AssetType::Node, "v0.83.13", &settings)?; assert_matches!(decision, UpdateAssessmentResult::AtLatestVersion); + let decision = perform_update_assessment(&AssetType::NodeManager, "v0.1.8", &settings)?; + assert_matches!(decision, UpdateAssessmentResult::AtLatestVersion); + let decision = perform_update_assessment(&AssetType::Testnet, "v0.3.4", &settings)?; assert_matches!(decision, UpdateAssessmentResult::AtLatestVersion); @@ -118,6 +142,8 @@ mod test { safe_version: "v0.78.26".to_string(), safenode_path: PathBuf::from("/home/chris/.local/bin/safenode"), safenode_version: "v0.83.13".to_string(), + safenode_manager_path: PathBuf::from("/home/chris/.local/bin/safenode-manager"), + safenode_manager_version: "v0.1.8".to_string(), testnet_path: PathBuf::from("/home/chris/.local/bin/testnet"), testnet_version: "v0.3.4".to_string(), }; @@ -140,6 +166,15 @@ mod test { ), } + let result = perform_update_assessment(&AssetType::NodeManager, "v0.1.7", &settings); + match result { + Ok(_) => return Err(eyre!("this test should return an error")), + Err(e) => assert_eq!( + "The latest version is less than the current version of your binary.", + e.to_string() + ), + } + let result = perform_update_assessment(&AssetType::Node, "v0.2.0", &settings); match result { Ok(_) => return Err(eyre!("this test should return an error")), @@ -159,6 +194,8 @@ mod test { safe_version: "v0.78.26".to_string(), safenode_path: PathBuf::from("/home/chris/.local/bin/safenode"), safenode_version: "v0.83.13".to_string(), + safenode_manager_path: PathBuf::from("/home/chris/.local/bin/safenode-manager"), + safenode_manager_version: "v0.1.7".to_string(), testnet_path: PathBuf::from("/home/chris/.local/bin/testnet"), testnet_version: "v0.3.4".to_string(), }; @@ -169,6 +206,9 @@ mod test { let decision = perform_update_assessment(&AssetType::Node, "v0.83.14", &settings)?; assert_matches!(decision, UpdateAssessmentResult::PerformUpdate); + let decision = perform_update_assessment(&AssetType::NodeManager, "v0.1.8", &settings)?; + assert_matches!(decision, UpdateAssessmentResult::PerformUpdate); + let decision = perform_update_assessment(&AssetType::Testnet, "v0.3.5", &settings)?; assert_matches!(decision, UpdateAssessmentResult::PerformUpdate); @@ -183,6 +223,8 @@ mod test { safe_version: "v0.78.26".to_string(), safenode_path: PathBuf::from("/home/chris/.local/bin/safenode"), safenode_version: "v0.83.13".to_string(), + safenode_manager_path: PathBuf::from("/home/chris/.local/bin/safenode-manager"), + safenode_manager_version: "v0.1.7".to_string(), testnet_path: PathBuf::from("/home/chris/.local/bin/testnet"), testnet_version: "v0.3.4".to_string(), }; @@ -193,6 +235,9 @@ mod test { let decision = perform_update_assessment(&AssetType::Node, "v0.84.0", &settings)?; assert_matches!(decision, UpdateAssessmentResult::PerformUpdate); + let decision = perform_update_assessment(&AssetType::NodeManager, "v0.2.0", &settings)?; + assert_matches!(decision, UpdateAssessmentResult::PerformUpdate); + let decision = perform_update_assessment(&AssetType::Testnet, "v0.4.0", &settings)?; assert_matches!(decision, UpdateAssessmentResult::PerformUpdate); @@ -207,6 +252,8 @@ mod test { safe_version: "v0.78.26".to_string(), safenode_path: PathBuf::from("/home/chris/.local/bin/safenode"), safenode_version: "v0.83.13".to_string(), + safenode_manager_path: PathBuf::from("/home/chris/.local/bin/safenode-manager"), + safenode_manager_version: "v0.1.7".to_string(), testnet_path: PathBuf::from("/home/chris/.local/bin/testnet"), testnet_version: "v0.3.4".to_string(), }; @@ -217,6 +264,9 @@ mod test { let decision = perform_update_assessment(&AssetType::Node, "v1.0.0", &settings)?; assert_matches!(decision, UpdateAssessmentResult::PerformUpdate); + let decision = perform_update_assessment(&AssetType::NodeManager, "v1.0.0", &settings)?; + assert_matches!(decision, UpdateAssessmentResult::PerformUpdate); + let decision = perform_update_assessment(&AssetType::Testnet, "v1.0.0", &settings)?; assert_matches!(decision, UpdateAssessmentResult::PerformUpdate); @@ -230,6 +280,8 @@ mod test { safe_version: "0.78.26".to_string(), safenode_path: PathBuf::from("/home/chris/.local/bin/safenode"), safenode_version: "0.83.13".to_string(), + safenode_manager_path: PathBuf::from("/home/chris/.local/bin/safenode-manager"), + safenode_manager_version: "v0.1.7".to_string(), testnet_path: PathBuf::from("/home/chris/.local/bin/testnet"), testnet_version: "0.3.4".to_string(), };