Skip to main content

tatara_lisp_script/stdlib/
sops.rs

1//! SOPS-decrypt primitive. Shells out to the system `sops` binary — no
2//! point re-implementing age/PGP in-process when the CLI is a one-liner
3//! and all the surrounding tooling already assumes it.
4//!
5//!   (sops-extract PATH EXPR) → string
6//!
7//! EXPR uses sops' JSON-path syntax: `["cloudflare"]["api-token"]`.
8
9use std::process::Command;
10use std::sync::Arc;
11
12use tatara_lisp_eval::{Arity, EvalError, Interpreter, Value};
13
14use crate::script_ctx::ScriptCtx;
15use crate::stdlib::env::str_arg;
16
17pub fn install(interp: &mut Interpreter<ScriptCtx>) {
18    interp.register_fn(
19        "sops-extract",
20        Arity::Exact(2),
21        |args: &[Value], _ctx: &mut ScriptCtx, sp| {
22            let path = str_arg(&args[0], "sops-extract", sp)?;
23            let expr = str_arg(&args[1], "sops-extract", sp)?;
24
25            // Prefer a sops binary on PATH; error loudly if absent.
26            let sops = which::which("sops").map_err(|e| {
27                EvalError::native_fn("sops-extract", format!("sops not found on PATH: {e}"), sp)
28            })?;
29
30            let out = Command::new(&sops)
31                .args(["-d", "--extract", &expr, &path])
32                .output()
33                .map_err(|e| EvalError::native_fn("sops-extract", e.to_string(), sp))?;
34
35            if !out.status.success() {
36                let stderr = String::from_utf8_lossy(&out.stderr);
37                return Err(EvalError::native_fn(
38                    "sops-extract",
39                    format!("sops failed ({}): {stderr}", out.status),
40                    sp,
41                ));
42            }
43
44            let body = String::from_utf8(out.stdout).map_err(|e| {
45                EvalError::native_fn("sops-extract", format!("non-utf8 output: {e}"), sp)
46            })?;
47            // sops -d --extract sometimes includes a trailing newline; strip it.
48            Ok(Value::Str(Arc::from(body.trim_end_matches('\n'))))
49        },
50    );
51}