diff --git a/CHANGELOG.md b/CHANGELOG.md index dfb0a13..9e9b645 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -16,6 +16,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 `https://.../main.tar.gz`, etc). - `--flake` is now an alias for `--path`. - A terminal bell will be output if a sudo / ssh prompt is ever printed. +- `--handle-unreachable` arg was added. You can use `--handle-unreachable ignore` to + ignore unreachable nodes in the status of the deployment. ### Fixed diff --git a/wire/cli/src/apply.rs b/wire/cli/src/apply.rs index 90477a2..30d06ca 100644 --- a/wire/cli/src/apply.rs +++ b/wire/cli/src/apply.rs @@ -108,6 +108,7 @@ pub async fn apply( modifiers, reboot: args.reboot, should_apply_locally, + handle_unreachable: args.handle_unreachable.clone().into(), }; GoalExecutor::new(context) diff --git a/wire/cli/src/cli.rs b/wire/cli/src/cli.rs index 0b42d39..a46b84d 100644 --- a/wire/cli/src/cli.rs +++ b/wire/cli/src/cli.rs @@ -1,6 +1,7 @@ // SPDX-License-Identifier: AGPL-3.0-or-later // Copyright 2024-2025 wire Contributors +use clap::builder::PossibleValue; use clap::crate_version; use clap::{Args, Parser, Subcommand, ValueEnum}; use clap_complete::Shell; @@ -8,7 +9,7 @@ use clap_num::number_range; use clap_verbosity_flag::InfoLevel; use lib::SubCommandModifiers; use lib::hive::Hive; -use lib::hive::node::{Goal as HiveGoal, Name, SwitchToConfigurationGoal}; +use lib::hive::node::{Goal as HiveGoal, HandleUnreachable, Name, SwitchToConfigurationGoal}; use std::io::IsTerminal; use std::{ @@ -92,6 +93,43 @@ fn more_than_zero(s: &str) -> Result { number_range(s, 1, usize::MAX) } +#[derive(Clone)] +pub enum HandleUnreachableArg { + Ignore, + FailNode, +} + +impl Display for HandleUnreachableArg { + fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { + match self { + Self::Ignore => write!(f, "ignore"), + Self::FailNode => write!(f, "fail-node"), + } + } +} + +impl clap::ValueEnum for HandleUnreachableArg { + fn value_variants<'a>() -> &'a [Self] { + &[Self::Ignore, Self::FailNode] + } + + fn to_possible_value(&self) -> Option { + match self { + Self::Ignore => Some(PossibleValue::new("ignore")), + Self::FailNode => Some(PossibleValue::new("fail-node")), + } + } +} + +impl From for HandleUnreachable { + fn from(value: HandleUnreachableArg) -> Self { + match value { + HandleUnreachableArg::Ignore => Self::Ignore, + HandleUnreachableArg::FailNode => Self::FailNode, + } + } +} + #[derive(Args)] pub struct ApplyArgs { #[arg(value_enum, default_value_t)] @@ -119,6 +157,13 @@ pub struct ApplyArgs { #[arg(short, long, default_value_t = false)] pub reboot: bool, + /// How to handle an unreachable node in the ping step. + /// + /// This only effects the ping step. + /// wire will still fail the node if it becomes unreachable after activation + #[arg(long, default_value_t = HandleUnreachableArg::FailNode)] + pub handle_unreachable: HandleUnreachableArg, + /// Unconditionally accept SSH host keys [!!] /// /// Sets `StrictHostKeyChecking` to `no`. diff --git a/wire/lib/src/hive/node.rs b/wire/lib/src/hive/node.rs index 9f93ed6..45575da 100644 --- a/wire/lib/src/hive/node.rs +++ b/wire/lib/src/hive/node.rs @@ -113,6 +113,7 @@ impl<'a> Context<'a> { goal: Goal::SwitchToConfiguration(SwitchToConfigurationGoal::Switch), reboot: false, should_apply_locally: false, + handle_unreachable: HandleUnreachable::default(), } } } @@ -275,6 +276,15 @@ pub(crate) trait ExecuteStep: Send + Sync + Display + std::fmt::Debug { fn should_execute(&self, context: &Context) -> bool; } +// may include other options such as FailAll in the future +#[non_exhaustive] +#[derive(Clone, Default)] +pub enum HandleUnreachable { + Ignore, + #[default] + FailNode, +} + #[derive(Default)] pub struct StepState { pub evaluation: Option, @@ -293,6 +303,7 @@ pub struct Context<'a> { pub goal: Goal, pub reboot: bool, pub should_apply_locally: bool, + pub handle_unreachable: HandleUnreachable, } #[enum_dispatch(ExecuteStep)] @@ -429,6 +440,12 @@ impl<'a> GoalExecutor<'a> { // discard error from cleanup let _ = CleanUp.execute(&mut self.context).await; + if matches!(step, Step::Ping(..)) + && matches!(self.context.handle_unreachable, HandleUnreachable::Ignore) + { + return Ok(()); + } + return Err(err); } }