Radish alpha
r
rad:z3qg5TKmN83afz2fj9z3fQjU8vaYE
Radicle CI adapter for native CI
Radicle
Git
refactor how log is written
Lars Wirzenius committed 2 years ago
commit 6571432e94a2a5674818cc4a4c9a2d51cad8cbca
parent 9805136
3 files changed +59 -63
modified Cargo.lock
@@ -1097,7 +1097,6 @@ dependencies = [
 "radicle-git-ext",
 "serde",
 "serde_yaml",
-
 "subprocess",
 "tempfile",
 "thiserror",
 "time",
@@ -1494,16 +1493,6 @@ dependencies = [
]

[[package]]
-
name = "subprocess"
-
version = "0.2.9"
-
source = "registry+https://github.com/rust-lang/crates.io-index"
-
checksum = "0c2e86926081dda636c546d8c5e641661049d7562a68f5488be4a1f7f66f6086"
-
dependencies = [
-
 "libc",
-
 "winapi",
-
]
-

-
[[package]]
name = "subtle"
version = "2.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
modified Cargo.toml
@@ -10,7 +10,6 @@ pretty_env_logger = "0.5.0"
radicle-git-ext = "0.7.0"
serde = { version = "1.0.193", features = ["derive"] }
serde_yaml = "0.9.27"
-
subprocess = "0.2.9"
tempfile = "3.8.1"
thiserror = "1.0.50"
time = { version = "0.3.31", features = ["formatting", "macros"] }
modified src/main.rs
@@ -14,15 +14,14 @@

use std::{
    error::Error,
-
    fs::File,
+
    fs::{File, OpenOptions},
    io::Write,
    path::{Path, PathBuf},
-
    rc::Rc,
+
    process::Command,
};

-
use log::{debug, info};
+
use log::{debug, error, info};
use serde::{Deserialize, Serialize};
-
use subprocess::{Popen, PopenConfig, Redirection};
use time::{macros::format_description, OffsetDateTime};
use uuid::Uuid;

@@ -54,7 +53,7 @@ fn fallible_main() -> Result<(), NativeError> {

    let config = Config::load_via_env()?;
    let mut logfile = config.open_log()?;
-
    logfile.write("radicle-native-ci starts\n".into())?;
+
    logfile.write_str("radicle-native-ci starts\n")?;

    let (run_id, run_dir) = mkdir_run(&config)?;
    let run_id = RunId::from(format!("{}", run_id).as_str());
@@ -66,12 +65,14 @@ fn fallible_main() -> Result<(), NativeError> {

    let profile = Profile::load().map_err(NativeError::LoadProfile)?;
    let storage = profile.storage.path();
+
    logfile.write(format!("profile: {:#?}\n", profile))?;

    let req = read_request()?;
+
    logfile.write(format!("request: {:#?}\n", req))?;

    let run_info = if let Request::Trigger { repo, commit } = req {
        info!("Request to trigger CI on {}, {}", repo, commit);
-
        run(
+
        let result = run(
            run_id,
            storage,
            repo,
@@ -80,7 +81,14 @@ fn fallible_main() -> Result<(), NativeError> {
            &mut logfile,
            &run_log,
            &config.state,
-
        )?
+
        );
+
        logfile.write(format!("CI result: {:?}", result))?;
+
        if let Err(e) = result {
+
            error!("CI run failed: {}", e);
+
            return Err(e);
+
        }
+
        logfile.write_str("CI run exited zero")?;
+
        result.unwrap()
    } else {
        write_response(&Response::error("first request was not Trigger\n"))?;
        RunInfo::builder()
@@ -94,7 +102,7 @@ fn fallible_main() -> Result<(), NativeError> {

    report::build_report(&config.state)?;

-
    logfile.write("radicle-native-ci ends successfully".into())?;
+
    logfile.write_str("radicle-native-ci ends successfully")?;
    info!("radicle-native-ci ends");
    Ok(())
}
@@ -144,7 +152,7 @@ fn run(

    log.write(format!("CI run on {}, {}\n", repo, commit))?;

-
    run_log.write("# Log from Radicle native CI\n\n".into())?;
+
    run_log.write_str("# Log from Radicle native CI\n\n")?;
    run_log.write(format!("* Repository id: `{}`\n", repo))?;
    run_log.write(format!("* Commit: `{}`\n\n", commit))?;

@@ -181,7 +189,7 @@ fn run(

    write_response(&Response::finished(result.clone()))?;

-
    run_log.write("CI run finished successfully\n".into())?;
+
    run_log.write_str("CI run finished successfully\n")?;

    std::fs::remove_dir_all(src).map_err(|e| NativeError::RemoveDir(src.into(), e))?;

@@ -198,29 +206,24 @@ fn run(

/// Run a command in a directory.
fn runcmd(log: &mut LogFile, argv: &[&str], cwd: &Path) -> Result<(), NativeError> {
-
    let mut p = Popen::create(
-
        argv,
-
        PopenConfig {
-
            cwd: Some(cwd.as_os_str().into()),
-
            stdout: Redirection::RcFile(log.file()?),
-
            stderr: Redirection::Merge,
-
            ..Default::default()
-
        },
-
    )
-
    .map_err(|e| NativeError::PopenCreate(format!("{:?}", argv), e))?;
-

-
    const FENCD_BLOCK: &str = "~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~";
-
    log.write("# Run command\n\n".into())?;
+
    log.write_str("## Run command\n\n")?;
    log.write(format!("~~~\n{:?}\n~~~\n\n", argv))?;
-
    log.write(format!("Output:\n\n{}\n", FENCD_BLOCK))?;

-
    p.communicate(None)
-
        .map_err(|e| NativeError::ChildComms(format!("{:?}", argv), e))?;
-
    let exit = p
-
        .wait()
-
        .map_err(|e| NativeError::PopenFailed(format!("{:?}", argv), e))?;
+
    assert!(!argv.is_empty());
+
    let argv0 = argv[0];
+
    let output = Command::new(argv0)
+
        .args(&argv[1..])
+
        .current_dir(cwd)
+
        .output()
+
        .map_err(|e| NativeError::Command(argv.iter().map(|s| s.to_string()).collect(), e))?;
+
    debug!("{:#?}", output);
+

+
    fenced(log, "Standard output:", &output.stdout)?;
+
    fenced(log, "Standard error:", &output.stderr)?;
+

+
    let exit = output.status;
    debug!("exit: {:?}", exit);
-
    log.write(format!("\n{}\n\n\n", FENCD_BLOCK))?;
+
    log.write(format!("Exit: {}\n\n\n", exit.code().unwrap()))?;

    if !exit.success() {
        let error = Response::error(&format!("command failed: {:?}", argv));
@@ -232,6 +235,23 @@ fn runcmd(log: &mut LogFile, argv: &[&str], cwd: &Path) -> Result<(), NativeErro
    Ok(())
}

+
fn fenced(log: &mut LogFile, msg: &str, data: &[u8]) -> Result<(), NativeError> {
+
    const FENCED_BLOCK: &str = "~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n";
+
    log.write_str(msg)?;
+
    log.write_str("\n")?;
+
    log.write_str(FENCED_BLOCK)?;
+
    if !data.is_empty() {
+
        let text = String::from_utf8_lossy(data);
+
        log.write_str(&text)?;
+
        if !text.ends_with('\n') {
+
            log.write_str("\n")?;
+
        }
+
    }
+
    log.write_str(FENCED_BLOCK)?;
+
    log.write_str("\n")?;
+
    Ok(())
+
}
+

/// Configuration file for `radicle-native-ci`.
#[derive(Debug, Deserialize)]
#[serde(deny_unknown_fields)]
@@ -270,7 +290,7 @@ impl Config {

struct LogFile {
    filename: PathBuf,
-
    file: Rc<std::fs::File>,
+
    file: File,
}

impl LogFile {
@@ -279,29 +299,23 @@ impl LogFile {
    }

    fn open(filename: &Path) -> Result<Self, NativeError> {
-
        let file = std::fs::OpenOptions::new()
+
        let file = OpenOptions::new()
            .append(true)
            .create(true)
            .open(filename)
            .map_err(|e| NativeError::OpenLogFile(filename.into(), e))?;
        Ok(Self {
            filename: filename.into(),
-
            file: Rc::new(file),
+
            file,
        })
    }

-
    fn file(&self) -> Result<Rc<File>, NativeError> {
-
        let file = std::fs::OpenOptions::new()
-
            .append(true)
-
            .create(true)
-
            .open(&self.filename)
-
            .map_err(|e| NativeError::OpenLogFile(self.filename.clone(), e))?;
-
        Ok(Rc::new(file))
+
    fn write(&mut self, msg: String) -> Result<(), NativeError> {
+
        self.write_str(&msg)
    }

-
    fn write(&mut self, msg: String) -> Result<(), NativeError> {
+
    fn write_str(&mut self, msg: &str) -> Result<(), NativeError> {
        self.file
-
            .as_ref()
            .write_all(msg.as_bytes())
            .map_err(|e| NativeError::WriteLogFile(self.filename.clone(), e))
    }
@@ -431,15 +445,6 @@ enum NativeError {
    #[error("failed to read run specification file {0}")]
    ReadRunSpec(PathBuf, #[source] std::io::Error),

-
    #[error("failed to communicate with child process {0}")]
-
    ChildComms(String, #[source] std::io::Error),
-

-
    #[error("failed to run program {0}")]
-
    PopenCreate(String, #[source] subprocess::PopenError),
-

-
    #[error("program failed: {0}")]
-
    PopenFailed(String, #[source] subprocess::PopenError),
-

    #[error("failed to parse configuration file as YAML: {0}")]
    ParseConfig(PathBuf, #[source] serde_yaml::Error),

@@ -487,4 +492,7 @@ enum NativeError {

    #[error("failed to format current time as a string")]
    TimeFormat(#[source] time::error::Format),
+

+
    #[error("failed to run command {0:?}")]
+
    Command(Vec<String>, #[source] std::io::Error),
}