Skip to main content

vpg/
main.rs

1//! Update Postgres credentials from Vault, rewrite pgpass & nvim-dbee, optionally launch pgcli.
2//!
3//! # Errors
4//! - External command fails or file I/O fails.
5//! - JSON serialization/deserialization fails.
6//! - Environment variable missing or user interaction fails.
7#![feature(exit_status_error)]
8
9use std::process::Command;
10use std::process::Stdio;
11
12use owo_colors::OwoColorize as _;
13use rootcause::prelude::ResultExt;
14use ytil_sys::cli::Args;
15
16use crate::pgpass::PgpassEntry;
17use crate::pgpass::PgpassFile;
18use crate::vault::VaultReadOutput;
19
20mod nvim_dbee;
21mod pgpass;
22mod vault;
23
24/// Executes the `vault` CLI to read a secret as JSON.
25///
26/// # Errors
27/// - Vault command fails or JSON deserialization fails.
28fn exec_vault_read_cmd(vault_path: &str) -> rootcause::Result<VaultReadOutput> {
29    let mut cmd = Command::new("vault");
30    cmd.args(["read", vault_path, "--format=json"]);
31
32    let cmd_stdout = &cmd.output()?.stdout;
33
34    Ok(serde_json::from_slice(cmd_stdout)
35        .context("error deserializing vault command output")
36        .attach_with(|| {
37            str::from_utf8(cmd_stdout).map_or_else(
38                |error| format!("cmd={cmd:#?} error={error:?}"),
39                |str_stdout| format!("cmd={cmd:#?} stdout={str_stdout:?}"),
40            )
41        })?)
42}
43
44/// Update Postgres credentials from Vault, rewrite pgpass & nvim-dbee, optionally launch pgcli.
45#[ytil_sys::main]
46fn main() -> rootcause::Result<()> {
47    let args = ytil_sys::cli::get();
48    if args.has_help() {
49        println!("{}", include_str!("../help.txt"));
50        return Ok(());
51    }
52
53    let pgpass_path = ytil_sys::dir::build_home_path(&[".pgpass"])?;
54    let pgpass_content = std::fs::read_to_string(&pgpass_path)?;
55    let pgpass_file = PgpassFile::parse(pgpass_content.as_str())?;
56
57    let args = ytil_sys::cli::get();
58    let Some(mut pgpass_entry) = ytil_tui::get_item_from_cli_args_or_select(
59        &args,
60        |(idx, _)| *idx == 0,
61        pgpass_file.entries,
62        |alias: &str| Box::new(move |entry: &PgpassEntry| entry.metadata.alias == alias),
63    )?
64    else {
65        return Ok(());
66    };
67
68    println!(
69        "\nLogging into Vault @ {}\n{}\n",
70        std::env::var("VAULT_ADDR")?.bold(),
71        "(be sure to have the VPN on!)".bold()
72    );
73    vault::log_into_vault_if_required()?;
74    let vault_read_output = exec_vault_read_cmd(&pgpass_entry.metadata.vault_path)?;
75
76    pgpass_entry.connection_params.update(&vault_read_output.data);
77    pgpass::save_new_pgpass_file(pgpass_file.idx_lines, &pgpass_entry.connection_params, &pgpass_path)?;
78
79    let nvim_dbee_conns_path = ytil_sys::dir::build_home_path(&[".local", "state", "nvim", "dbee", "conns.json"])?;
80    nvim_dbee::save_new_nvim_dbee_conns_file(&pgpass_entry, &nvim_dbee_conns_path)?;
81
82    println!(
83        "{} credentials updated in {}",
84        pgpass_entry.metadata.alias.green().bold(),
85        pgpass_path.display()
86    );
87    println!(
88        "{} credentials updated in {}",
89        pgpass_entry.metadata.alias.green().bold(),
90        nvim_dbee_conns_path.display()
91    );
92
93    println!(); // Cosmetic spacing.
94
95    if Some(true) == ytil_tui::yes_no_select(&format!("Connect to {}? ", pgpass_entry.metadata.alias))? {
96        let db_url = pgpass_entry.connection_params.db_url();
97        println!(
98            "\nConnecting to {} @\n\n{}\n",
99            pgpass_entry.metadata.alias.bold(),
100            db_url.bold()
101        );
102
103        if let Some(psql_exit_code) = Command::new("pgcli")
104            .arg(&db_url)
105            .stdin(Stdio::inherit())
106            .stdout(Stdio::inherit())
107            .stderr(Stdio::inherit())
108            .spawn()?
109            .wait()?
110            .code()
111        {
112            std::process::exit(psql_exit_code);
113        }
114
115        eprintln!("{}", format!("pgcli {db_url} terminated by signal.").red().bold());
116        std::process::exit(1);
117    }
118
119    Ok(())
120}