diff --git a/dist/systemd/system/zincati.service b/dist/systemd/system/zincati.service index 1837a092..d2e94cb9 100644 --- a/dist/systemd/system/zincati.service +++ b/dist/systemd/system/zincati.service @@ -21,6 +21,8 @@ Environment=ZINCATI_VERBOSITY="-v" Type=notify ExecStart=/usr/libexec/zincati agent ${ZINCATI_VERBOSITY} Restart=on-failure +# This status signals a non-restartable condition +RestartPreventExitStatus=7 RestartSec=10s [Install] diff --git a/src/main.rs b/src/main.rs index e8c4f03c..2c73832f 100644 --- a/src/main.rs +++ b/src/main.rs @@ -56,20 +56,27 @@ fn run() -> i32 { match cli_opts.run() { Ok(_) => libc::EXIT_SUCCESS, Err(e) => { - log_error_chain(e); - libc::EXIT_FAILURE + log_error_chain(&e); + if e.root_cause() + .downcast_ref::() + .is_some() + { + 7 + } else { + libc::EXIT_FAILURE + } } } } /// Pretty-print a chain of errors, as a series of error-priority log messages. -fn log_error_chain(err_chain: anyhow::Error) { +fn log_error_chain(err_chain: &anyhow::Error) { let mut chain_iter = err_chain.chain(); let top_err = match chain_iter.next() { Some(e) => e.to_string(), None => "(unspecified failure)".to_string(), }; - log::error!("critical error: {}", top_err); + log::error!("error: {}", top_err); for err in chain_iter { log::error!(" -> {}", err); } diff --git a/src/rpm_ostree/cli_status.rs b/src/rpm_ostree/cli_status.rs index fcc08abe..dda17a44 100644 --- a/src/rpm_ostree/cli_status.rs +++ b/src/rpm_ostree/cli_status.rs @@ -37,6 +37,18 @@ lazy_static::lazy_static! { )).unwrap(); } +/// An error which should not result in a retry/restart. +#[derive(Debug, Clone)] +pub struct FatalError(String); + +impl std::fmt::Display for FatalError { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + self.0.fmt(f) + } +} + +impl std::error::Error for FatalError {} + /// JSON output from `rpm-ostree status --json` #[derive(Clone, Debug, Deserialize)] pub struct StatusJson { @@ -48,6 +60,7 @@ pub struct StatusJson { #[serde(rename_all = "kebab-case")] pub struct DeploymentJson { booted: bool, + container_image_reference: Option, base_checksum: Option, #[serde(rename = "base-commit-meta")] base_metadata: BaseCommitMetaJson, @@ -62,7 +75,7 @@ pub struct DeploymentJson { #[derive(Clone, Debug, Deserialize)] struct BaseCommitMetaJson { #[serde(rename = "fedora-coreos.stream")] - stream: String, + stream: Option, } impl DeploymentJson { @@ -85,12 +98,21 @@ impl DeploymentJson { /// Parse the booted deployment from status object. pub fn parse_booted(status: &StatusJson) -> Result { - let json = booted_json(status)?; - Ok(json.into_release()) + let status = booted_json(status)?; + if let Some(img) = status.container_image_reference.as_ref() { + let msg = format!("Automatic updates disabled; booted into container image {img}"); + crate::utils::update_unit_status(&msg); + return Err(anyhow::Error::new(FatalError(msg))); + } + Ok(status.into_release()) } fn fedora_coreos_stream_from_deployment(deploy: &DeploymentJson) -> Result { - let stream = deploy.base_metadata.stream.as_str(); + let stream = deploy + .base_metadata + .stream + .as_ref() + .ok_or_else(|| anyhow!("Missing `fedora-coreos.stream` in commit metadata"))?; ensure!(!stream.is_empty(), "empty stream value"); Ok(stream.to_string()) } diff --git a/src/rpm_ostree/mod.rs b/src/rpm_ostree/mod.rs index 621882de..6ff91f55 100644 --- a/src/rpm_ostree/mod.rs +++ b/src/rpm_ostree/mod.rs @@ -1,7 +1,7 @@ mod cli_deploy; mod cli_finalize; mod cli_status; -pub use cli_status::{invoke_cli_status, parse_booted, parse_booted_updates_stream}; +pub use cli_status::{invoke_cli_status, parse_booted, parse_booted_updates_stream, FatalError}; mod actor; pub use actor::{