Your browser sucks, JavaScript disabled! Gunnar's console needs JavaScript and WebAssembly. Git over SSH on port 2222 does not.

nordisk/nornir

examples/release_demo_znippy_holger.rs

//! End-to-end nornir release demo against znippy + holger.
//!
//!     cargo run --release --example release_demo_znippy_holger
//!
//! Pipeline (per repo, in dep-graph order):
//!   1. pre-flight dirty check (refuses if any repo dirty)
//!   2. `cargo test --workspace --no-fail-fast --quiet`
//!   3. `cargo run -p xtask --release --example nornir-bench --quiet`
//!      → parse last stdout line as a BenchRun
//!      → persist into Urðr (`bench_runs` + `bench_results`)
//!   4. record per-repo row into `release_lineage`
//!
//! Override repo locations via env: ZNIPPY_PATH=… HOLGER_PATH=…
//! Wipe the demo warehouse first with: FRESH_WAREHOUSE=1

use std::path::PathBuf;

use anyhow::{Context, Result};

use nornir::release::pipeline::{
    PipelineOptions, ReleaseReport, query_release_history, run_pipeline,
};
use nornir::warehouse::dep_graph::{build as build_dep_graph, record_dep_graph};
use nornir::warehouse::iceberg::IcebergWarehouse;
use nornir::workspace::descriptor::WorkspaceDescriptor;

fn main() -> Result<()> {
    let znippy = std::env::var("ZNIPPY_PATH")
        .map(PathBuf::from)
        .unwrap_or_else(|_| PathBuf::from(format!("/run/media/{}/T9/git/znippy", std::env::var("USER").unwrap_or_default())));
    let holger = std::env::var("HOLGER_PATH")
        .map(PathBuf::from)
        .unwrap_or_else(|_| PathBuf::from(format!("/run/media/{}/T9/git/holger", std::env::var("USER").unwrap_or_default())));

    for (name, p) in [("znippy", &znippy), ("holger", &holger)] {
        anyhow::ensure!(
            p.join("Cargo.toml").exists(),
            "{} workspace not found at {} (set {}_PATH)",
            name,
            p.display(),
            name.to_uppercase()
        );
    }

    if std::env::var_os("NORNIR_MACHINE").is_none() {
        let host = std::process::Command::new("uname")
            .arg("-n")
            .output()
            .ok()
            .and_then(|o| String::from_utf8(o.stdout).ok())
            .map(|s| s.trim().to_string())
            .filter(|s| !s.is_empty())
            .unwrap_or_else(|| "demo-host".to_string());
        // SAFETY: single-threaded section before any pipeline work spawns threads.
        unsafe { std::env::set_var("NORNIR_MACHINE", &host) };
        println!("🖥  NORNIR_MACHINE={host}");
    }

    let tmp = tempfile::tempdir().context("tempdir for descriptor")?;
    let workspace_name =
        std::env::var("NORNIR_WORKSPACE_NAME").unwrap_or_else(|_| "workspace_holger".to_string());
    let toml = format!(
        r#"[workspace]
name = "{workspace_name}"

[repos.znippy]
path = "{}"

[repos.holger]
path = "{}"
"#,
        znippy.display(),
        holger.display()
    );
    let toml_path = tmp.path().join("nornir-workspace.toml");
    std::fs::write(&toml_path, toml).context("write workspace toml")?;
    let desc = WorkspaceDescriptor::load(&toml_path)?;
    println!(
        "📄 workspace `{}`  ({} repos)",
        desc.workspace.name,
        desc.repos.len()
    );

    let wh_root = std::env::var("NORNIR_WAREHOUSE")
        .map(PathBuf::from)
        .unwrap_or_else(|_| {
            PathBuf::from(format!("/run/media/{}/T9/git/workspace_holger/.nornir/warehouse", std::env::var("USER").unwrap_or_default()))
        });
    if std::env::var("FRESH_WAREHOUSE").ok().as_deref() == Some("1") && wh_root.exists() {
        std::fs::remove_dir_all(&wh_root).context("wipe demo warehouse")?;
    }
    std::fs::create_dir_all(&wh_root).context("mkdir warehouse")?;
    let wh = IcebergWarehouse::open(&wh_root).context("open Iceberg warehouse")?;
    println!("📦 warehouse at {}", wh_root.display());

    let graph = build_dep_graph(&desc).context("build dep graph")?;
    let order = graph.build_order()?;
    println!(
        "🔗 build order = {order:?}  ({} cross-repo edges)",
        graph.edges.len()
    );
    for e in &graph.edges {
        println!(
            "     {} → {}  via [{}]",
            e.from,
            e.to,
            e.via.iter().cloned().collect::<Vec<_>>().join(", ")
        );
    }

    let snapshot_id = wh
        .block_on(record_dep_graph(&wh, &desc.workspace.name, &graph))
        .context("record dep graph snapshot")?;
    println!("📌 dep-graph snapshot = {snapshot_id}");

    let opts = PipelineOptions {
        dry_run: true,
        dep_graph_snapshot_id: snapshot_id,
        refuse_if_dirty: true,
        progress: None,
        gate_config: None,
    };
    println!("\n🚀 running pipeline (dry_run, refuse_if_dirty) — fans, here we come …");
    let report: ReleaseReport = wh
        .block_on(run_pipeline(&wh, &desc, &graph, &opts))
        .context("run release pipeline")?;

    println!("\n✅ release_id = {}", report.release_id);
    for r in &report.repos {
        println!(
            "   [{idx}] {repo:8} sha={sha} branch={br:8} dirty={dty} status={st:18} tests={p}/{f}",
            idx = r.build_order_idx,
            repo = r.repo,
            sha = &r.git.sha[..r.git.sha.len().min(12)],
            br = r.git.branch,
            dty = r.git.dirty,
            st = r.gate_status,
            p = r.tests_passed,
            f = r.tests_failed,
        );
    }

    println!(
        "\n🔎 release_lineage rows in Urðr for `{}`:",
        desc.workspace.name
    );
    let history = wh
        .block_on(query_release_history(&wh, &desc.workspace.name, None))
        .context("query release history")?;
    for rep in &history {
        println!("   release {}", rep.release_id);
        for r in &rep.repos {
            println!(
                "      [{}] {} sha={} status={}",
                r.build_order_idx,
                r.repo,
                &r.git.sha[..r.git.sha.len().min(12)],
                r.gate_status
            );
        }
    }
    println!(
        "\n   ({} release(s) persisted in {})",
        history.len(),
        wh_root.display()
    );
    Ok(())
}