Radish alpha
r
rad:zwTxygwuz5LDGBq255RA2CbNGrz8
Radicle CI broker
Radicle
Git
feat: when writing HTML report pages, write temp file and rename
Lars Wirzenius committed 7 months ago
commit 62f1ded7e6f5e5f5c36eca1ad4dfd8eb75e14fbb
parent 4610dcf
2 files changed +32 -4
modified src/pages.rs
@@ -9,7 +9,6 @@

use std::{
    collections::{HashMap, HashSet},
-
    fs::write,
    path::{Path, PathBuf},
    sync::mpsc::RecvTimeoutError,
};
@@ -33,7 +32,7 @@ use crate::{
    msg::{RunId, RunResult},
    notif::NotificationReceiver,
    run::{Run, RunState, Whence},
-
    util::{parse_timestamp, rfc822_timestamp},
+
    util::{parse_timestamp, rfc822_timestamp, safely_overwrite},
    worker::Worker,
};

@@ -53,7 +52,7 @@ pub enum PageError {
    Timeformat(#[from] time::error::Format),

    #[error("failed to write status page to {0}")]
-
    Write(PathBuf, #[source] std::io::Error),
+
    Write(PathBuf, #[source] crate::util::UtilError),

    #[error("no node alias has been set for builder")]
    NoAlias,
@@ -972,7 +971,8 @@ impl StatusPage {
    }

    fn write_file(filename: &Path, text: &str) -> Result<(), PageError> {
-
        write(filename, text).map_err(|e| PageError::Write(filename.into(), e))?;
+
        safely_overwrite(filename, text.as_bytes())
+
            .map_err(|err| PageError::Write(filename.into(), err))?;
        Ok(())
    }
}
modified src/util.rs
@@ -1,8 +1,10 @@
use std::{
+
    io::Write,
    path::{Path, PathBuf},
    str::FromStr,
};

+
use tempfile::NamedTempFile;
use time::{
    format_description::{well_known::Rfc2822, FormatItem},
    macros::format_description,
@@ -138,6 +140,20 @@ pub fn read_file_as_objectid(filename: &Path) -> Result<ObjectId, UtilError> {
    ObjectId::from_str(s.trim()).map_err(|err| UtilError::ReadObjectId(filename.into(), err))
}

+
pub fn safely_overwrite<P: AsRef<Path>>(filename: P, data: &[u8]) -> Result<(), UtilError> {
+
    let filename = filename.as_ref();
+
    let dirname = filename
+
        .parent()
+
        .ok_or(UtilError::NoParent(filename.to_path_buf()))?;
+
    let mut tmp = NamedTempFile::new_in(dirname)
+
        .map_err(|err| UtilError::CreateTemp(dirname.to_path_buf(), err))?;
+
    tmp.write_all(data)
+
        .map_err(|err| UtilError::WriteTemp(dirname.to_path_buf(), err))?;
+
    tmp.persist(filename)
+
        .map_err(|err| UtilError::RenameTemp(filename.to_path_buf(), err))?;
+
    Ok(())
+
}
+

#[derive(Debug, thiserror::Error)]
pub enum UtilError {
    #[error("failed to look up node profile")]
@@ -178,4 +194,16 @@ pub enum UtilError {

    #[error("failed to read object id from {0}")]
    ReadObjectId(PathBuf, #[source] radicle::cob::object::ParseObjectId),
+

+
    #[error("file name to write to doesn't have a parent directory: {0}")]
+
    NoParent(PathBuf),
+

+
    #[error("failed to create temporary file in directory {0}")]
+
    CreateTemp(PathBuf, #[source] std::io::Error),
+

+
    #[error("failed to write to temporary file in {0}")]
+
    WriteTemp(PathBuf, #[source] std::io::Error),
+

+
    #[error("failed to rename temporary file to {0}")]
+
    RenameTemp(PathBuf, #[source] tempfile::PersistError),
}