adamflott.com

Collect System Command(s) Output In Parallel

Description: A Rust program to collect command outputs across remote hosts in parallel
Authored: 2021-10-20;
Permalink: https://adamflott.com/programming/rust/collect-system-command-output-in-parallel/
tags : parallel; rust;
categories : programming;


In order to generate /systems/, I use a small Rust program to collect the info and format into Markdown for the site.

Only 2 crates are needed, chrono and rayon .

A silly-useful-only-to-me example, but does demonstrate how powerful Rust can be. The meat lies in the par_iter().for_each(). This assumes no sharing of data is required across computations.

use std::{error::Error, io::Write, process::Command};

use rayon::prelude::*;

fn main() {
    match run() {
        Ok(_) => {}
        Err(err) => {
            eprintln!("{}", err);
        }
    }
}

fn sys_and_commands() -> Vec<(&'static str, Vec<Vec<&'static str>>)> {
    vec![
        (
            "host1",
            vec![
                vec!["cmd1", "arg1"],
            ],
        ),
        (
            "host2",
            vec![
                vec!["cmd2", "arg2"],
            ],
        ),
    ]
}

fn run() -> Result<(), Box<dyn Error>> {
    let cwd = std::env::current_dir()?;

    let scs = sys_and_commands();
    scs.par_iter().for_each(|sc| {
        let mut sys_cwd = cwd.clone();
        let ts = chrono::offset::Utc::now();
        let ts_str = ts.format("%Y-%m-%d").to_string();

        sys_cwd.extend(["..", "..", "content", "systems", sc.0]);

        let _ = std::fs::create_dir(sys_cwd.clone());

        sys_cwd.extend(["index.md"]);
        let mut f = std::fs::File::create(sys_cwd).unwrap();

        let hdr = format!(
            r#"+++
title = "{}"
description = "{} System Information"
date = {}

[taxonomies]
categories = ["systems"]
+++"#,
            sc.0, sc.0, ts_str
        );

        f.write_all(&hdr.into_bytes()).unwrap();

        let now_str = chrono::offset::Utc::now()
            .format("\n\ngenerated %Y-%m-%d %H:%M")
            .to_string();
        f.write_all(&now_str.into_bytes()).unwrap();

        for c in &sc.1 {
            let mut command = Command::new(c[0]);
            for arg in &c[1..] {
                command.arg(arg);
            }

            match command.output() {
                Ok(out) => {
                    let command_str = c.join(" ");
                    let cmd_hdr = format!("\n## Command '{}'", command_str);
                    f.write_all(&cmd_hdr.into_bytes()).unwrap();

                    f.write_all("\n```\n".as_bytes()).unwrap();
                    f.write_all(&out.stdout).unwrap();
                    f.write_all("```\n".as_bytes()).unwrap();

                    f.flush().unwrap();
                }
                Err(err) => eprintln!("{}", err),
            }
        }
    });

    Ok(())
}