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