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