1use std::collections::HashSet;23use anyhow::{Result, bail};4use clap::Parser;5use fleet_base::{6 deploy::{DeployAction, deploy_task, upload_task},7 host::{Config, ConfigHost, Generation, GenerationStorage},8 opts::FleetOpts,9};10use tabled::Table;11use tracing::{info, warn};1213#[derive(Parser)]14pub struct RollbackSingle {15 machine: String,16 #[clap(subcommand)]17 action: RollbackAction,18}1920#[derive(Parser, Clone)]21struct DeployOptions {22 23 id: String,24 25 26 #[clap(long)]27 enable_rollback: bool,28 29 #[clap(long)]30 specialization: Option<String>,31}3233#[derive(Parser, Clone)]34enum RollbackAction {35 36 ListTargets,37 38 Test(#[clap(flatten)] DeployOptions),39 40 Switch(#[clap(flatten)] DeployOptions),41 42 Boot(#[clap(flatten)] DeployOptions),43}4445pub async fn list_all_generations(host: &ConfigHost, config: &Config) -> Vec<Generation> {46 let stored_on_machine = host47 .list_generations("system")48 .await49 .inspect_err(|e| {50 warn!("failed to list generations available on the remote machine: {e}");51 })52 .unwrap_or_default();53 let on_machine_store_paths = stored_on_machine54 .iter()55 .map(|g| &g.store_path)56 .collect::<HashSet<_>>();57 let mut stored_locally = config58 .local_host()59 .list_generations(&format!("{}-{}", config.data.gc_root_prefix, host.name))60 .await61 .inspect_err(|e| {62 warn!("failed to list generations available locally: {e}");63 })64 .unwrap_or_default();65 dbg!(&stored_locally);66 stored_locally.retain(|g| !on_machine_store_paths.contains(&g.store_path));67 for ele in stored_locally.iter_mut() {68 ele.current = false;69 ele.location = GenerationStorage::Deployer;70 }71 stored_locally.extend(stored_on_machine);72 stored_locally.sort_by_key(|v| v.datetime);73 stored_locally74}7576impl RollbackSingle {77 pub(crate) async fn run(&self, config: &Config, _opts: &FleetOpts) -> Result<()> {78 let host = config.host(&self.machine)?;79 match &self.action {80 RollbackAction::ListTargets => {81 let generations = list_all_generations(&host, config).await;82 if generations.is_empty() {83 bail!("no available rollback targets found");84 }85 info!("Generation list:\n{}", Table::new(&generations));86 Ok(())87 }88 RollbackAction::Boot(o) | RollbackAction::Test(o) | RollbackAction::Switch(o) => {89 let DeployOptions {90 id,91 enable_rollback,92 specialization,93 } = o;94 let action: DeployAction = match self.action {95 RollbackAction::Test { .. } => DeployAction::Test,96 RollbackAction::Switch { .. } => DeployAction::Switch,97 RollbackAction::Boot { .. } => DeployAction::Boot,98 _ => unreachable!(),99 };100 let generations = list_all_generations(&host, config).await;101 let Some(generation) = generations.iter().find(|g| &g.rollback_id() == id) else {102 bail!(103 "generation by this name is not found, existing generations:\n{}",104 Table::new(&generations)105 );106 };107 let remote_path = upload_task(108 config,109 &host,110 generation.location,111 generation.store_path.clone(),112 )113 .await?;114115 deploy_task(116 action,117 &host,118 remote_path,119 specialization.clone(),120 !*enable_rollback,121 )122 .await?;123 Ok(())124 }125 }126 }127}