diff --git a/sn_node_manager/src/bin/cli/main.rs b/sn_node_manager/src/bin/cli/main.rs index 5b386eb53b..534a595385 100644 --- a/sn_node_manager/src/bin/cli/main.rs +++ b/sn_node_manager/src/bin/cli/main.rs @@ -71,14 +71,16 @@ pub enum SubCmd { #[clap(long)] port: Option, #[clap(long)] - /// Specify an Ipv4Addr for the node's RPC service to run on. This is useful if you want to expose the - /// RPC server outside. The ports are assigned automatically. + /// Specify an Ipv4Addr for the node's RPC server to run on. + /// + /// Useful if you want to expose the RPC server pubilcly. Ports are assigned automatically. /// /// If not set, the RPC server is run locally. rpc_address: Option, /// Provide environment variables for the safenode service. /// - /// This is useful to set the safenode's log levels. Each variable should be comma separated without any space. + /// Useful to set safenode's log levels. Variables should be comma separated without + /// spaces. /// /// Example: --env SN_LOG=all,RUST_LOG=libp2p=debug #[clap(name = "env", long, use_value_delimiter = true, value_parser = parse_environment_variables)] @@ -125,7 +127,7 @@ pub enum SubCmd { Join { /// Set to build the safenode and faucet binaries. /// - /// This assumes the command is being run from the root of the safe_network repository. + /// This option requires the command run from the root of the safe_network repository. #[clap(long)] build: bool, /// The number of nodes to run. @@ -190,7 +192,7 @@ pub enum SubCmd { Run { /// Set to build the safenode and faucet binaries. /// - /// This assumes the command is being run from the root of the safe_network repository. + /// This option requires the command run from the root of the safe_network repository. #[clap(long)] build: bool, /// Set to remove the client data directory and kill any existing local network. @@ -199,7 +201,7 @@ pub enum SubCmd { /// The number of nodes to run. #[clap(long, default_value_t = DEFAULT_NODE_COUNT)] count: u16, - /// Path to a faucet binary + /// Path to a faucet binary. /// /// The path and version arguments are mutually exclusive. #[clap(long, conflicts_with = "faucet_version", conflicts_with = "build")] @@ -287,8 +289,7 @@ pub enum SubCmd { /// Set this flag to force the upgrade command to replace binaries without comparing any /// version numbers. /// - /// This may be required in a case where we want to 'downgrade' in case an upgrade caused a - /// problem, or for testing purposes. + /// Required if we want to downgrade, or for testing purposes. #[clap(long)] force: bool, /// The peer ID of the service to upgrade @@ -297,15 +298,17 @@ pub enum SubCmd { /// The name of the service to upgrade #[clap(long, conflicts_with = "peer_id")] service_name: Option, - /// Provide environment variables for the safenode service. This will override the values set during the Add - /// command. + /// Provide environment variables for the safenode service. /// - /// This is useful to set the safenode's log levels. Each variable should be comma separated without any space. + /// Values set when the service was added will be overridden. + /// + /// Useful to set safenode's log levels. Variables should be comma separated without + /// spaces. /// /// Example: --env SN_LOG=all,RUST_LOG=libp2p=debug #[clap(name = "env", long, use_value_delimiter = true, value_parser = parse_environment_variables)] env_variables: Option>, - /// Provide a binary to upgrade to, using a URL. + /// Provide a binary to upgrade to using a URL. /// /// The binary must be inside a zip or gzipped tar archive. /// @@ -401,6 +404,46 @@ pub enum FaucetSubCmd { /// This command must run as the root/administrative user. #[clap(name = "stop")] Stop {}, + /// Upgrade the faucet. + /// + /// The running faucet will be stopped, its binary will be replaced, then it will be started + /// again. + /// + /// This command must run as the root/administrative user. + #[clap(name = "upgrade")] + Upgrade { + /// Set this flag to upgrade the faucet without starting it. + /// + /// Can be useful for testing scenarios. + #[clap(long)] + do_not_start: bool, + /// Set this flag to force the upgrade command to replace binaries without comparing any + /// version numbers. + /// + /// Required if we want to downgrade, or for testing purposes. + #[clap(long)] + force: bool, + /// Provide environment variables for the faucet service. + /// + /// Values set when the service was added will be overridden. + /// + /// Useful to set safenode's log levels. Variables should be comma separated without + /// spaces. + /// + /// Example: --env SN_LOG=all,RUST_LOG=libp2p=debug + #[clap(name = "env", long, use_value_delimiter = true, value_parser = parse_environment_variables)] + env_variables: Option>, + /// Provide a binary to upgrade to using a URL. + /// + /// The binary must be inside a zip or gzipped tar archive. + /// + /// This can be useful for testing scenarios. + #[clap(long, conflicts_with = "version")] + url: Option, + /// Upgrade to a specific version rather than the latest version. + #[clap(long)] + version: Option, + }, } #[tokio::main(flavor = "current_thread")] @@ -458,6 +501,23 @@ async fn main() -> Result<()> { } FaucetSubCmd::Start {} => cmd::faucet::start(verbosity).await, FaucetSubCmd::Stop {} => cmd::faucet::stop(verbosity).await, + FaucetSubCmd::Upgrade { + do_not_start, + force, + env_variables: provided_env_variable, + url, + version, + } => { + cmd::faucet::upgrade( + do_not_start, + force, + provided_env_variable, + url, + version, + verbosity, + ) + .await + } }, SubCmd::Join { build, diff --git a/sn_node_manager/src/cmd/faucet.rs b/sn_node_manager/src/cmd/faucet.rs index 25e8872fa6..b700f1402a 100644 --- a/sn_node_manager/src/cmd/faucet.rs +++ b/sn_node_manager/src/cmd/faucet.rs @@ -6,7 +6,7 @@ // KIND, either express or implied. Please review the Licences for the specific language governing // permissions and limitations relating to use of the SAFE Network Software. -use super::is_running_as_root; +use super::{download_and_get_upgrade_bin_path, is_running_as_root, print_upgrade_summary}; use crate::{ add_services::{add_faucet, config::AddFaucetServiceOptions}, config, @@ -14,11 +14,13 @@ use crate::{ ServiceManager, VerbosityLevel, }; use color_eyre::{eyre::eyre, Result}; +use colored::Colorize; +use semver::Version; use sn_peers_acquisition::{get_peers_from_args, PeersArgs}; use sn_releases::{ReleaseType, SafeReleaseRepositoryInterface}; use sn_service_management::{ control::{ServiceControl, ServiceController}, - FaucetService, NodeRegistry, + FaucetService, NodeRegistry, UpgradeOptions, }; use sn_transfers::get_faucet_data_dir; use std::path::PathBuf; @@ -131,3 +133,70 @@ pub async fn stop(verbosity: VerbosityLevel) -> Result<()> { Err(eyre!("The faucet service has not been added yet")) } + +pub async fn upgrade( + do_not_start: bool, + force: bool, + provided_env_variables: Option>, + url: Option, + version: Option, + verbosity: VerbosityLevel, +) -> Result<()> { + if !is_running_as_root() { + return Err(eyre!("The upgrade command must run as the root user")); + } + + let mut node_registry = NodeRegistry::load(&config::get_node_registry_path()?)?; + if node_registry.faucet.is_none() { + println!("No faucet service has been created yet. No upgrade required."); + return Ok(()); + } + + if verbosity != VerbosityLevel::Minimal { + println!("================================================="); + println!(" Upgrade Faucet Service "); + println!("================================================="); + } + + let (upgrade_bin_path, target_version) = + download_and_get_upgrade_bin_path(ReleaseType::Faucet, url, version).await?; + let faucet = node_registry.faucet.clone().unwrap(); + + if !force { + let current_version = Version::parse(&faucet.version)?; + if target_version <= current_version { + println!( + "{} The faucet is already at the latest version", + "✓".green() + ); + return Ok(()); + } + } + + let env_variables = if provided_env_variables.is_some() { + &provided_env_variables + } else { + &node_registry.environment_variables + }; + let options = UpgradeOptions { + bootstrap_peers: node_registry.bootstrap_peers.clone(), + env_variables: env_variables.clone(), + force, + start_service: !do_not_start, + target_bin_path: upgrade_bin_path.clone(), + target_version: target_version.clone(), + }; + let service = FaucetService::new(faucet.clone(), Box::new(ServiceController {})); + let mut service_manager = + ServiceManager::new(service, Box::new(ServiceController {}), verbosity); + + match service_manager.upgrade(options).await { + Ok(upgrade_result) => { + node_registry.faucet = Some(service_manager.service.service_data); + print_upgrade_summary(vec![("faucet".to_string(), upgrade_result)]); + node_registry.save()?; + Ok(()) + } + Err(e) => Err(eyre!("Upgrade failed: {e}")), + } +} diff --git a/sn_node_manager/src/cmd/mod.rs b/sn_node_manager/src/cmd/mod.rs index 9935477a4e..3eaa0b570e 100644 --- a/sn_node_manager/src/cmd/mod.rs +++ b/sn_node_manager/src/cmd/mod.rs @@ -13,7 +13,10 @@ pub mod node; use crate::helpers::download_and_extract_release; use color_eyre::{eyre::eyre, Result}; +use colored::Colorize; +use semver::Version; use sn_releases::{ReleaseType, SafeReleaseRepositoryInterface}; +use sn_service_management::UpgradeResult; use std::{ path::PathBuf, process::{Command, Stdio}, @@ -29,6 +32,66 @@ pub fn is_running_as_root() -> bool { true } +pub async fn download_and_get_upgrade_bin_path( + release_type: ReleaseType, + url: Option, + version: Option, +) -> Result<(PathBuf, Version)> { + let release_repo = ::default_config(); + if let Some(version) = version { + let (upgrade_bin_path, version) = + download_and_extract_release(release_type, None, Some(version), &*release_repo).await?; + Ok((upgrade_bin_path, Version::parse(&version)?)) + } else if let Some(url) = url { + let (upgrade_bin_path, version) = + download_and_extract_release(release_type, Some(url), None, &*release_repo).await?; + Ok((upgrade_bin_path, Version::parse(&version)?)) + } else { + println!("Retrieving latest version of safenode..."); + let latest_version = release_repo + .get_latest_version(&ReleaseType::Safenode) + .await?; + let latest_version = Version::parse(&latest_version)?; + println!("Latest version is {latest_version}"); + let (upgrade_bin_path, _) = download_and_extract_release( + ReleaseType::Safenode, + None, + Some(latest_version.to_string()), + &*release_repo, + ) + .await?; + Ok((upgrade_bin_path, latest_version)) + } +} + +pub fn print_upgrade_summary(upgrade_summary: Vec<(String, UpgradeResult)>) { + println!("Upgrade summary:"); + for (service_name, upgrade_result) in upgrade_summary { + match upgrade_result { + UpgradeResult::NotRequired => { + println!("- {} did not require an upgrade", service_name); + } + UpgradeResult::Upgraded(previous_version, new_version) => { + println!( + "{} {} upgraded from {previous_version} to {new_version}", + "✓".green(), + service_name + ); + } + UpgradeResult::Forced(previous_version, target_version) => { + println!( + "{} Forced {} version change from {previous_version} to {target_version}.", + "✓".green(), + service_name + ); + } + UpgradeResult::Error(msg) => { + println!("{} {} was not upgraded: {}", "✕".red(), service_name, msg); + } + } + } +} + pub async fn get_bin_path( build: bool, path: Option, diff --git a/sn_node_manager/src/cmd/node.rs b/sn_node_manager/src/cmd/node.rs index cfacebdd28..67d1dac6a0 100644 --- a/sn_node_manager/src/cmd/node.rs +++ b/sn_node_manager/src/cmd/node.rs @@ -8,7 +8,7 @@ #![allow(clippy::too_many_arguments)] -use super::is_running_as_root; +use super::{download_and_get_upgrade_bin_path, is_running_as_root, print_upgrade_summary}; use crate::{ add_services::{add_node, config::AddNodeServiceOptions}, config, @@ -361,37 +361,8 @@ pub async fn upgrade( println!("================================================="); } - let release_repo = ::default_config(); - let (upgrade_bin_path, target_version) = if let Some(version) = version { - let (upgrade_bin_path, version) = download_and_extract_release( - ReleaseType::Safenode, - None, - Some(version), - &*release_repo, - ) - .await?; - (upgrade_bin_path, Version::parse(&version)?) - } else if let Some(url) = url { - let (upgrade_bin_path, version) = - download_and_extract_release(ReleaseType::Safenode, Some(url), None, &*release_repo) - .await?; - (upgrade_bin_path, Version::parse(&version)?) - } else { - println!("Retrieving latest version of safenode..."); - let latest_version = release_repo - .get_latest_version(&ReleaseType::Safenode) - .await?; - let latest_version = Version::parse(&latest_version)?; - println!("Latest version is {latest_version}"); - let (upgrade_bin_path, _) = download_and_extract_release( - ReleaseType::Safenode, - None, - Some(latest_version.to_string()), - &*release_repo, - ) - .await?; - (upgrade_bin_path, latest_version) - }; + let (upgrade_bin_path, target_version) = + download_and_get_upgrade_bin_path(ReleaseType::Safenode, url, version).await?; let mut node_registry = NodeRegistry::load(&config::get_node_registry_path()?)?; if !force { @@ -518,36 +489,15 @@ pub async fn upgrade( } } - println!("Upgrade summary:"); - for (node, upgrade_result) in upgrade_summary { + print_upgrade_summary( + upgrade_summary + .iter() + .map(|x| (x.0.service_name.clone(), x.1.clone())) + .collect::>(), + ); + + for (node, _) in upgrade_summary { node_registry.update_node(node.clone())?; - match upgrade_result { - UpgradeResult::NotRequired => { - println!("- {} did not require an upgrade", node.service_name); - } - UpgradeResult::Upgraded(previous_version, new_version) => { - println!( - "{} {} upgraded from {previous_version} to {new_version}", - "✓".green(), - node.service_name - ); - } - UpgradeResult::Forced(previous_version, target_version) => { - println!( - "{} Forced {} version change from {previous_version} to {target_version}.", - "✓".green(), - node.service_name - ); - } - UpgradeResult::Error(msg) => { - println!( - "{} {} was not upgraded: {}", - "✕".red(), - node.service_name, - msg - ); - } - } } node_registry.save()?;