Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion .github/spellcheck-ignore
Original file line number Diff line number Diff line change
Expand Up @@ -8,4 +8,5 @@ ro
rw
RO
RW
grubenv
grubenv
msdos
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ Place shell scripts representing *health checks* that **MAY FAIL** in the `/etc/
Place shell scripts you want to run *after* a boot has been declared **successful** (green) in `/etc/greenboot/green.d`.
Place shell scripts you want to run *after* a boot has been declared **failed** (red) in `/etc/greenboot/red.d`.

Unless greenboot is enabled by default in your distribution, enable it by running `systemctl enable greenboot-healthcheck.service greenboot-success.target`.
Unless greenboot is enabled by default in your distribution, enable it by running `systemctl enable greenboot-healthcheck.service`.
It will automatically start during the next boot process and run its checks.

When you `ssh` into the machine after that, a boot status message will be shown:
Expand Down
17 changes: 8 additions & 9 deletions src/lib/greenboot.rs
Original file line number Diff line number Diff line change
Expand Up @@ -21,9 +21,9 @@ pub fn run_diagnostics(skipped: Vec<String>) -> Result<Vec<String>> {

// Run required checks
for path in GREENBOOT_INSTALL_PATHS {
let greenboot_required_path = format!("{}/check/required.d/", path);
let greenboot_required_path = format!("{path}/check/required.d/");
Comment thread
djach7 marked this conversation as resolved.
if !Path::new(&greenboot_required_path).is_dir() {
log::warn!("skipping test as {} is not a dir", greenboot_required_path);
log::warn!("skipping test as {greenboot_required_path} is not a dir");
continue;
}
path_exists = true;
Expand All @@ -43,7 +43,7 @@ pub fn run_diagnostics(skipped: Vec<String>) -> Result<Vec<String>> {

// Run wanted checks
for path in GREENBOOT_INSTALL_PATHS {
let greenboot_wanted_path = format!("{}/check/wanted.d/", path);
let greenboot_wanted_path = format!("{path}/check/wanted.d/");
let result = run_scripts("wanted", &greenboot_wanted_path, Some(&skipped));
all_skipped.extend(result.skipped);

Expand All @@ -61,8 +61,7 @@ pub fn run_diagnostics(skipped: Vec<String>) -> Result<Vec<String>> {

if !missing_disabled.is_empty() {
log::warn!(
"The following disabled scripts were not found in any directory: {:?}",
missing_disabled
"The following disabled scripts were not found in any directory: {missing_disabled:?}"
);
}

Expand All @@ -74,7 +73,7 @@ pub fn run_red() -> Vec<Box<dyn Error>> {
let mut errors = Vec::new();

for path in GREENBOOT_INSTALL_PATHS {
let red_path = format!("{}/red.d/", path);
let red_path = format!("{path}/red.d/");
let result = run_scripts("red", &red_path, None); // Pass None for disabled scripts
errors.extend(result.errors);
}
Expand All @@ -87,7 +86,7 @@ pub fn run_green() -> Vec<Box<dyn Error>> {
let mut errors = Vec::new();

for path in GREENBOOT_INSTALL_PATHS {
let green_path = format!("{}/green.d/", path);
let green_path = format!("{path}/green.d/");
let result = run_scripts("green", &green_path, None); // Pass None for disabled scripts
errors.extend(result.errors);
}
Expand All @@ -106,7 +105,7 @@ fn run_scripts(name: &str, path: &str, disabled_scripts: Option<&[String]>) -> S
skipped: Vec::new(),
};

let entries = match glob(&format!("{}*", path)) {
let entries = match glob(&format!("{path}*")) {
Ok(e) => {
let valid: Vec<_> = e
.filter_map(Result::ok)
Expand Down Expand Up @@ -139,7 +138,7 @@ fn run_scripts(name: &str, path: &str, disabled_scripts: Option<&[String]>) -> S
// Check if script/binary should be skipped
if let Some(disabled) = disabled_scripts {
if disabled.contains(&file_name.to_string()) {
log::info!("Skipping disabled script: {}", file_name);
log::info!("Skipping disabled script: {file_name}");
result.skipped.push(file_name.to_string());
continue;
}
Expand Down
7 changes: 2 additions & 5 deletions src/lib/handler.rs
Original file line number Diff line number Diff line change
Expand Up @@ -60,14 +60,11 @@ pub fn handle_rollback() -> Result<()> {
Some(counter) if counter <= 0 => {
log::info!("Greenboot will now attempt to rollback to a previous deployment.");
if let Some(deployment_cmd) = detect_os_deployment() {
log::info!(
"Deployment manager '{}' detected, attempting rollback.",
deployment_cmd
);
log::info!("Deployment manager '{deployment_cmd}' detected, attempting rollback.");
let status = Command::new(deployment_cmd)
.arg("rollback")
.status()
.context(format!("Failed to execute '{} rollback'", deployment_cmd))?;
.context(format!("Failed to execute '{deployment_cmd} rollback'"))?;

if !status.success() {
bail!(
Expand Down
20 changes: 6 additions & 14 deletions src/lib/mount.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
use log::{info, warn};

Check warning on line 1 in src/lib/mount.rs

View workflow job for this annotation

GitHub Actions / build_and_test

unused imports: `info` and `warn`
use std::fs;
use std::path::Path;
use std::process::{Command, Stdio};

Check warning on line 4 in src/lib/mount.rs

View workflow job for this annotation

GitHub Actions / build_and_test

unused imports: `Command` and `Stdio`
Expand All @@ -12,7 +12,7 @@
MountInfoError,
}

fn is_boot_rw(mounts_path: &Path) -> Result<bool, MountError> {

Check warning on line 15 in src/lib/mount.rs

View workflow job for this annotation

GitHub Actions / build_and_test

function `is_boot_rw` is never used
let mounts = fs::read_to_string(mounts_path).map_err(|_| MountError::MountInfoError)?;
for line in mounts.lines() {
let parts: Vec<&str> = line.split_whitespace().collect();
Expand Down Expand Up @@ -41,18 +41,14 @@
Ok(())
} else {
let error_message = String::from_utf8_lossy(&output.stderr);
warn!(
"Failed to remount /boot as RO using shell: {}",
error_message
);
warn!("Failed to remount /boot as RO using shell: {error_message}");
Err(MountError::RemountFailed(error_message.to_string()))
}
}
Err(e) => {
warn!("Failed to execute mount command: {}", e);
warn!("Failed to execute mount command: {e}");
Err(MountError::RemountFailed(format!(
"Failed to execute mount: {}",
e
"Failed to execute mount: {e}"
)))
}
}
Expand Down Expand Up @@ -81,18 +77,14 @@
Ok(())
} else {
let error_message = String::from_utf8_lossy(&output.stderr);
warn!(
"Failed to remount /boot as RW using shell: {}",
error_message
);
warn!("Failed to remount /boot as RW using shell: {error_message}");
Err(MountError::RemountFailed(error_message.to_string()))
}
}
Err(e) => {
warn!("Failed to execute mount command: {}", e);
warn!("Failed to execute mount command: {e}");
Err(MountError::RemountFailed(format!(
"Failed to execute mount: {}",
e
"Failed to execute mount: {e}"
)))
}
}
Expand Down
24 changes: 10 additions & 14 deletions src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -135,7 +135,7 @@ fn check_previous_rollback() -> Result<bool> {
// Check for specific success indicators
let success = journal_output.contains("Rollback successful");

log::debug!("Rollback detection result: {}", success);
log::debug!("Rollback detection result: {success}");
Ok(success)
}

Expand Down Expand Up @@ -167,10 +167,7 @@ fn health_check() -> Result<()> {
status
}
Err(e) => {
log::warn!(
"Failed to check previous rollback status: {}. Defaulting to false.",
e
);
log::warn!("Failed to check previous rollback status: {e}. Defaulting to false.");
false
}
};
Expand All @@ -194,7 +191,7 @@ fn health_check() -> Result<()> {
"Greenboot healthcheck passed - status is GREEN",
previous_rollback,
)?)
.unwrap_or_else(|e| log::error!("cannot set motd: {}", e));
.unwrap_or_else(|e| log::error!("cannot set motd: {e}"));
set_boot_status(true, GRUB_PATH, MOUNT_INFO_PATH)?;
Ok(())
}
Expand All @@ -205,18 +202,18 @@ fn health_check() -> Result<()> {
"Greenboot healthcheck failed - status is RED",
previous_rollback,
)?)
.unwrap_or_else(|e| log::error!("cannot set motd: {}", e));
.unwrap_or_else(|e| log::error!("cannot set motd: {e}"));
let errors = run_red();
if !errors.is_empty() {
log::error!("There is a problem with red script runner");
errors.iter().for_each(|e| log::error!("{e}"));
}

set_boot_status(false, GRUB_PATH, MOUNT_INFO_PATH)
.unwrap_or_else(|e| log::error!("cannot set boot_status: {}", e));
.unwrap_or_else(|e| log::error!("cannot set boot_status: {e}"));
set_boot_counter(config.max_reboot, GRUB_PATH, MOUNT_INFO_PATH)
.unwrap_or_else(|e| log::error!("cannot set boot_counter: {}", e));
handle_reboot(false).unwrap_or_else(|e| log::error!("cannot reboot: {}", e));
.unwrap_or_else(|e| log::error!("cannot set boot_counter: {e}"));
handle_reboot(false).unwrap_or_else(|e| log::error!("cannot reboot: {e}"));
bail!("greenboot healthcheck failed")
}
}
Expand All @@ -239,7 +236,7 @@ fn trigger_rollback() -> Result<()> {
// This function parses a string expected in bash-array format like
// `( "item1" "item2" ... )` into a Vec<String>.
fn parse_bash_array_string(raw_str: &str) -> Vec<String> {
log::debug!("Attempting to parse raw bash-array string: '{}'", raw_str);
log::debug!("Attempting to parse raw bash-array string: '{raw_str}'");

if raw_str.starts_with('(') && raw_str.ends_with(')') {
// Remove the outer parentheses
Expand All @@ -252,14 +249,13 @@ fn parse_bash_array_string(raw_str: &str) -> Vec<String> {
.filter(|s| !s.is_empty())
.collect();

log::debug!("Parsed list from bash-array string: {:?}", parsed_list);
log::debug!("Parsed list from bash-array string: {parsed_list:?}");
parsed_list
} else if !raw_str.trim().is_empty() {
// If the string is not empty but doesn't match the expected format,
// log a warning and return an empty list.
log::warn!(
"String ('{}') is not in the expected bash-array format '( \"item1\" ... )'. Treating as empty list.",
raw_str
"String ('{raw_str}') is not in the expected bash-array format '( \"item1\" ... )'. Treating as empty list."
);
vec![]
} else {
Expand Down
2 changes: 1 addition & 1 deletion tests/greenboot-bootc-anaconda-iso.sh
Original file line number Diff line number Diff line change
Expand Up @@ -168,7 +168,7 @@ FROM ${BASE_IMAGE_URL}
COPY greenboot-*.rpm /tmp/
RUN dnf install -y \
/tmp/greenboot-*.rpm && \
systemctl enable greenboot-healthcheck.service greenboot-rollback.service greenboot-success.target
systemctl enable greenboot-healthcheck.service
RUN sed -i "s/GREENBOOT_MAX_BOOT_ATTEMPTS=3/GREENBOOT_MAX_BOOT_ATTEMPTS=5/g" /etc/greenboot/greenboot.conf
RUN sed -i 's#DISABLED_HEALTHCHECKS=()#DISABLED_HEALTHCHECKS=("01_repository_dns_check.sh" "not_exit.sh")#g' /etc/greenboot/greenboot.conf
COPY passing_binary /etc/greenboot/check/required.d/
Expand Down
2 changes: 1 addition & 1 deletion tests/greenboot-bootc-qcow2.sh
Original file line number Diff line number Diff line change
Expand Up @@ -168,7 +168,7 @@ FROM ${BASE_IMAGE_URL}
COPY greenboot-*.rpm /tmp/
RUN dnf install -y \
/tmp/greenboot-*.rpm && \
systemctl enable greenboot-healthcheck.service greenboot-rollback.service greenboot-success.target
systemctl enable greenboot-healthcheck.service
RUN sed -i "s/GREENBOOT_MAX_BOOT_ATTEMPTS=3/GREENBOOT_MAX_BOOT_ATTEMPTS=5/g" /etc/greenboot/greenboot.conf
RUN sed -i 's#DISABLED_HEALTHCHECKS=()#DISABLED_HEALTHCHECKS=("01_repository_dns_check.sh" "not_exit.sh")#g' /etc/greenboot/greenboot.conf
COPY passing_binary /etc/greenboot/check/required.d/
Expand Down
4 changes: 1 addition & 3 deletions tests/greenboot-ostree.sh
Original file line number Diff line number Diff line change
Expand Up @@ -129,7 +129,7 @@ sudo systemctl start libvirtd
sudo virsh list --all > /dev/null

# Set a customized dnsmasq configuration for libvirt so we always get the
# same address on bootup.
# same address on boot up.
greenprint "💡 Setup libvirt network"
sudo tee /tmp/integration.xml > /dev/null << EOF
<network xmlns:dnsmasq='http://libvirt.org/schemas/network/dnsmasq/1.0'>
Expand Down Expand Up @@ -320,8 +320,6 @@ version = "*"
name = "sssd"
version = "*"

[customizations.services]
enabled = ["greenboot-success.target"]

[[customizations.user]]
name = "${SSH_USER}"
Expand Down
1 change: 1 addition & 0 deletions usr/lib/systemd/system/greenboot-healthcheck.service
Original file line number Diff line number Diff line change
Expand Up @@ -17,3 +17,4 @@ PrivateMounts=yes
[Install]
RequiredBy=boot-complete.target
WantedBy=multi-user.target
Also=greenboot-success.target
Comment thread
djach7 marked this conversation as resolved.
Loading