rc_conf 0.18.0

rc_conf provides rc.conf and rc.d-like data structures for consumption.
Documentation
//! This is a simple utility to format a string from a configuration.
//!
//! It is intended for free-form text of playbooks, runbooks, or other documentation that belongs
//! alongside binaries or services.
//!
//! ```
//! USAGE: rccat run << EOF
//! # Nginx 500 Error Response Playbook ($ENVIRONMENT)
//!
//! * **Initial Response (0-5 minutes)**: Acknowledge error, notify team/stakeholders, and verify
//!   Nginx as error source.
//! * **Diagnostic Phase**: Review Nginx error logs, check configuration files, assess server
//!   resources, and inspect backend application logs (if applicable).
//! * **Resolution Phase**: Address identified cause, apply fix, restart Nginx if needed, and
//!   verify resolution.
//! * **Post-Incident**: Conduct root cause analysis, update documentation, and notify stakeholders
//!   of resolution.
//! * **Review Schedule**: Every 6 months or after significant infrastructure changes.
//! EOF
//! # Nginx 500 Error Response Playbook (production)
//!
//! * **Initial Response (0-5 minutes)**: Acknowledge error, notify team/stakeholders, and verify
//!   Nginx as error source.
//! * **Diagnostic Phase**: Review Nginx error logs, check configuration files, assess server
//!   resources, and inspect backend application logs (if applicable).
//! * **Resolution Phase**: Address identified cause, apply fix, restart Nginx if needed, and
//!   verify resolution.
//! * **Post-Incident**: Conduct root cause analysis, update documentation, and notify stakeholders
//!   of resolution.
//! * **Review Schedule**: Every 6 months or after significant infrastructure changes.
//! ```

use std::collections::HashMap;

use utf8path::Path;

fn main() {
    // Parse arguments.
    let args = std::env::args().collect::<Vec<_>>();
    if args.len() != 3 {
        eprintln!("USAGE: rccat file [describe|rcvar|run]");
        std::process::exit(1);
    }
    // Figure out the name of the service.
    let name = if let Ok(path) = std::env::var("RCVAR_ARGV0") {
        path.to_string()
    } else {
        rc_conf::name_from_path(&Path::new(&args[1]))
    };
    let prefix = rc_conf::var_prefix_from_service(&name);
    // Read stdin.
    let stdin = match std::fs::read_to_string(&args[1]) {
        Ok(stdin) => stdin,
        Err(err) => {
            eprintln!("failed to read rcscript file {}: {err}", args[1]);
            std::process::exit(129);
        }
    };
    if args[2] == "describe" {
        println!("{}", stdin.trim_end());
    } else if args[2] == "rcvar" {
        let mut rcvar = match shvar::rcvar(&stdin) {
            Ok(vars) => vars,
            Err(err) => {
                eprintln!("invalid rcscript input: {err}");
                std::process::exit(130);
            }
        }
        .into_iter()
        .map(|v| format!("{prefix}{v}"))
        .collect::<Vec<_>>();
        rcvar.sort();
        println!("{}", rcvar.join("\n"));
    } else if args[2] == "run" {
        let evp = rc_conf::EnvironmentVariableProvider::new(Some(prefix.clone()));
        let meta = HashMap::from([("NAME".to_string(), name)]);
        for line in stdin.lines() {
            let out = match shvar::expand_recursive(&(&meta, &evp), line) {
                Ok(out) => out,
                Err(err) => {
                    eprintln!("failed to expand rcscript line: {err}");
                    std::process::exit(131);
                }
            };
            println!("{}", out.trim_end());
        }
    } else {
        eprintln!("USAGE: rccat [describe|rcvar|run]");
        std::process::exit(1);
    }
}