diff --git a/crates/trident/src/cli.rs b/crates/trident/src/cli.rs index 1bbbe4c88..bb71eb5bb 100644 --- a/crates/trident/src/cli.rs +++ b/crates/trident/src/cli.rs @@ -74,6 +74,12 @@ pub enum Commands { #[clap(long, value_delimiter = ',', num_args = 0.., default_value = "stage,finalize")] allowed_operations: Vec, + /// Boolean indicating that a runtime update is expected. If Trident + /// determines that an A/B update is required, it will issue a fatal + /// error to avoid an unexpected reboot + #[clap(long)] + runtime: bool, + /// Path to save the resulting Host Status #[clap(short, long)] status: Option, diff --git a/crates/trident/src/engine/update.rs b/crates/trident/src/engine/update.rs index 679b3f7c7..eecc2cb0c 100644 --- a/crates/trident/src/engine/update.rs +++ b/crates/trident/src/engine/update.rs @@ -24,6 +24,7 @@ pub(crate) fn update( host_config: &HostConfiguration, state: &mut DataStore, allowed_operations: &Operations, + expect_runtime: bool, image: OsImage, #[cfg(feature = "grpc-dangerous")] sender: &mut Option, ) -> Result { @@ -80,6 +81,11 @@ pub(crate) fn update( } ServicingType::RuntimeUpdate => {} ServicingType::AbUpdate => { + if expect_runtime { + return Err(TridentError::new( + InvalidInputError::AbUpdateRuntimeFlagMismatch, + )); + } // Execute pre-servicing scripts HooksSubsystem::new_for_local_scripts().execute_pre_servicing_scripts(&ctx)?; } diff --git a/crates/trident/src/lib.rs b/crates/trident/src/lib.rs index 5c9f2e0aa..53df6483f 100644 --- a/crates/trident/src/lib.rs +++ b/crates/trident/src/lib.rs @@ -262,7 +262,7 @@ impl Trident { if let Some((host_config, allowed_operations, sender)) = receiver.blocking_recv() { self.host_config = Some(host_config); if let ExitKind::NeedsReboot = - self.update(datastore, allowed_operations, &mut Some(sender))? + self.update(datastore, allowed_operations, false, &mut Some(sender))? { reboot().message("Failed to reboot after grpc update")?; } @@ -549,6 +549,7 @@ impl Trident { &mut self, datastore: &mut DataStore, allowed_operations: Operations, + runtime: bool, #[cfg(feature = "grpc-dangerous")] sender: &mut Option, ) -> Result { let mut host_config = self @@ -585,7 +586,7 @@ impl Trident { debug!("Host Configuration has been updated"); // If allowed operations include 'stage', start update if allowed_operations.has_stage() { - engine::update(&host_config, datastore, &allowed_operations, image, #[cfg(feature = "grpc-dangerous")] sender).message("Failed to execute an update") + engine::update(&host_config, datastore, &allowed_operations, runtime, image, #[cfg(feature = "grpc-dangerous")] sender).message("Failed to execute an update") } else { warn!("Host Configuration has been updated but allowed operations do not include 'stage'. Add 'stage' and re-run to stage the update"); Ok(ExitKind::Done) @@ -631,7 +632,7 @@ impl Trident { ServicingState::AbUpdateFinalized | ServicingState::Provisioned => { // Need to either re-execute the failed update OR inform the user that no update // is needed. - engine::update(&host_config, datastore, &allowed_operations, image, #[cfg(feature = "grpc-dangerous")] sender).message("Failed to update host") + engine::update(&host_config, datastore, &allowed_operations, runtime, image, #[cfg(feature = "grpc-dangerous")] sender).message("Failed to execute an update") } servicing_state => { Err(TridentError::new(InternalError::UnexpectedServicingState { diff --git a/crates/trident/src/main.rs b/crates/trident/src/main.rs index 5477f350f..977ec4ce7 100644 --- a/crates/trident/src/main.rs +++ b/crates/trident/src/main.rs @@ -196,10 +196,12 @@ fn run_trident( ), Commands::Update { ref allowed_operations, + runtime, .. } => trident.update( &mut datastore, cli::to_operations(allowed_operations), + runtime, #[cfg(feature = "grpc-dangerous")] &mut None, ), diff --git a/crates/trident_api/src/error.rs b/crates/trident_api/src/error.rs index 551d4ccb0..6f29097fb 100644 --- a/crates/trident_api/src/error.rs +++ b/crates/trident_api/src/error.rs @@ -141,6 +141,13 @@ pub enum InternalError { #[derive(Debug, Eq, thiserror::Error, Serialize, Deserialize, PartialEq)] #[serde(rename_all = "kebab-case")] pub enum InvalidInputError { + #[error( + "Determined that an A/B update is required, but Trident was run with --runtime flag \ + indicating runtime update was expected. Returning fatal error to avoid unexpected reboot. \ + Edit Host Configuration or re-run without the --runtime flag." + )] + AbUpdateRuntimeFlagMismatch, + #[error("Allowed operations must be passed via command line, not in Host Configuration")] AllowedOperationsInHostConfiguration, diff --git a/docs/Reference/Trident-CLI.md b/docs/Reference/Trident-CLI.md index 0d130eda4..5c0a6347e 100644 --- a/docs/Reference/Trident-CLI.md +++ b/docs/Reference/Trident-CLI.md @@ -155,6 +155,10 @@ Options: -v, --verbosity Logging verbosity [OFF, ERROR, WARN, INFO, DEBUG, TRACE] [default: DEBUG] + --runtime + Boolean indicating that a runtime update is expected. If + Trident determines that an A/B update is required, it will + issue a fatal error to avoid an unexpected reboot -s, --status Path to save the resulting Host Status -e, --error @@ -183,6 +187,11 @@ Possible values: Default: `stage,finalize` +#### --runtime <RUNTIME> + +Boolean indicating that a runtime update is expected. If Trident determines that an A/B update is required, it will issue a fatal error to avoid an unexpected reboot + + #### --status <STATUS> Path to save the resulting Host Status