summaryrefslogtreecommitdiffstats
path: root/src/commands/db.rs
diff options
context:
space:
mode:
Diffstat (limited to 'src/commands/db.rs')
-rw-r--r--src/commands/db.rs245
1 files changed, 245 insertions, 0 deletions
diff --git a/src/commands/db.rs b/src/commands/db.rs
new file mode 100644
index 0000000..ba85446
--- /dev/null
+++ b/src/commands/db.rs
@@ -0,0 +1,245 @@
+use std::fmt::Display;
+use std::path::PathBuf;
+use std::process::Command;
+
+use clap_v3 as clap;
+use anyhow::Context;
+use anyhow::Error;
+use anyhow::Result;
+use anyhow::anyhow;
+use clap::ArgMatches;
+use diesel::RunQueryDsl;
+use itertools::Itertools;
+
+use crate::db::DbConnectionConfig;
+use crate::db::models;
+
+pub fn db(db_connection_config: DbConnectionConfig, matches: &ArgMatches) -> Result<()> {
+ match matches.subcommand() {
+ ("cli", Some(matches)) => cli(db_connection_config, matches),
+ ("artifacts", Some(matches)) => artifacts(db_connection_config, matches),
+ ("envvars", Some(matches)) => envvars(db_connection_config, matches),
+ ("images", Some(matches)) => images(db_connection_config, matches),
+ (other, _) => return Err(anyhow!("Unknown subcommand: {}", other)),
+ }
+}
+
+fn cli(db_connection_config: DbConnectionConfig, matches: &ArgMatches) -> Result<()> {
+ trait PgCliCommand {
+ fn run_for_uri(&self, dbcc: DbConnectionConfig) -> Result<()>;
+ }
+
+ struct Psql(PathBuf);
+ impl PgCliCommand for Psql {
+ fn run_for_uri(&self, dbcc: DbConnectionConfig) -> Result<()> {
+ Command::new(&self.0)
+ .arg(format!("--dbname={}", dbcc.database_name()))
+ .arg(format!("--host={}", dbcc.database_host()))
+ .arg(format!("--port={}", dbcc.database_port()))
+ .arg(format!("--username={}", dbcc.database_user()))
+ .stdin(std::process::Stdio::inherit())
+ .stdout(std::process::Stdio::inherit())
+ .stderr(std::process::Stdio::inherit())
+ .output()
+ .map_err(Error::from)
+ .and_then(|out| {
+ if out.status.success() {
+ info!("pgcli exited successfully");
+ Ok(())
+ } else {
+ Err(anyhow!("gpcli did not exit successfully"))
+ .with_context(|| {
+ match String::from_utf8(out.stderr) {
+ Ok(log) => anyhow!("{}", log),
+ Err(e) => anyhow!("Cannot parse log into valid UTF-8: {}", e),
+ }
+ })
+ .map_err(Error::from)
+ }
+ })
+ }
+ }
+
+ struct PgCli(PathBuf);
+ impl PgCliCommand for PgCli {
+ fn run_for_uri(&self, dbcc: DbConnectionConfig) -> Result<()> {
+ Command::new(&self.0)
+ .arg("--host")
+ .arg(dbcc.database_host())
+ .arg("--port")
+ .arg(dbcc.database_port())
+ .arg("--username")
+ .arg(dbcc.database_user())
+ .arg(dbcc.database_name())
+ .stdin(std::process::Stdio::inherit())
+ .stdout(std::process::Stdio::inherit())
+ .stderr(std::process::Stdio::inherit())
+ .output()
+ .map_err(Error::from)
+ .and_then(|out| {
+ if out.status.success() {
+ info!("pgcli exited successfully");
+ Ok(())
+ } else {
+ Err(anyhow!("gpcli did not exit successfully"))
+ .with_context(|| {
+ match String::from_utf8(out.stderr) {
+ Ok(log) => anyhow!("{}", log),
+ Err(e) => anyhow!("Cannot parse log into valid UTF-8: {}", e),
+ }
+ })
+ .map_err(Error::from)
+ }
+ })
+
+ }
+ }
+
+
+ matches.value_of("tool")
+ .map(|s| vec![s])
+ .unwrap_or_else(|| vec!["psql", "pgcli"])
+ .into_iter()
+ .filter_map(|s| which::which(&s).ok().map(|path| (path, s)))
+ .map(|(path, s)| {
+ match s {
+ "psql" => Ok(Box::new(Psql(path)) as Box<dyn PgCliCommand>),
+ "pgcli" => Ok(Box::new(PgCli(path)) as Box<dyn PgCliCommand>),
+ prog => Err(anyhow!("Unsupported pg CLI program: {}", prog)),
+ }
+ })
+ .next()
+ .transpose()?
+ .ok_or_else(|| anyhow!("No Program found"))?
+ .run_for_uri(db_connection_config)
+}
+
+fn artifacts(conn_cfg: DbConnectionConfig, matches: &ArgMatches) -> Result<()> {
+ use crate::schema::artifacts::dsl;
+
+ let csv = matches.is_present("csv");
+ let hdrs = mk_header(vec!["id", "path"]);
+ let conn = crate::db::establish_connection(conn_cfg)?;
+ let data = dsl::artifacts
+ .load::<models::Artifact>(&conn)?
+ .into_iter()
+ .map(|artifact| vec![format!("{}", artifact.id), artifact.path])
+ .collect::<Vec<_>>();
+
+ if data.is_empty() {
+ info!("No artifacts in database");
+ } else {
+ display_data(hdrs, data, csv)?;
+ }
+
+ Ok(())
+}
+
+fn envvars(conn_cfg: DbConnectionConfig, matches: &ArgMatches) -> Result<()> {
+ use crate::schema::envvars::dsl;
+
+ let csv = matches.is_present("csv");
+ let hdrs = mk_header(vec!["id", "name", "value"]);
+ let conn = crate::db::establish_connection(conn_cfg)?;
+ let data = dsl::envvars
+ .load::<models::EnvVar>(&conn)?
+ .into_iter()
+ .map(|evar| {
+ vec![format!("{}", evar.id), evar.name, evar.value]
+ })
+ .collect::<Vec<_>>();
+
+ if data.is_empty() {
+ info!("No environment variables in database");
+ } else {
+ display_data(hdrs, data, csv)?;
+ }
+
+ Ok(())
+}
+
+fn images(conn_cfg: DbConnectionConfig, matches: &ArgMatches) -> Result<()> {
+ use crate::schema::images::dsl;
+
+ let csv = matches.is_present("csv");
+ let hdrs = mk_header(vec!["id", "name"]);
+ let conn = crate::db::establish_connection(conn_cfg)?;
+ let data = dsl::images
+ .load::<models::Image>(&conn)?
+ .into_iter()
+ .map(|image| {
+ vec![format!("{}", image.id), image.name]
+ })
+ .collect::<Vec<_>>();
+
+ if data.is_empty() {
+ info!("No images in database");
+ } else {
+ display_data(hdrs, data, csv)?;
+ }
+
+ Ok(())
+}
+
+fn mk_header(vec: Vec<&str>) -> Vec<ascii_table::Column> {
+ vec.into_iter()
+ .map(|name| {
+ let mut column = ascii_table::Column::default();
+ column.header = name.into();
+ column.align = ascii_table::Align::Left;
+ column
+ })
+ .collect()
+}
+
+/// Display the passed data as nice ascii table,
+/// or, if stdout is a pipe, print it nicely parseable
+fn display_data<D: Display>(headers: Vec<ascii_table::Column>, data: Vec<Vec<D>>, csv: bool) -> Result<()> {
+ use std::io::Write;
+
+ if csv {
+ use csv::WriterBuilder;
+ let mut wtr = WriterBuilder::new().from_writer(vec![]);
+ for record in data.into_iter() {
+ let r: Vec<String> = record.into_iter()
+ .map(|e| e.to_string())
+ .collect();
+
+ wtr.write_record(&r)?;
+ }
+
+ let out = std::io::stdout();
+ let mut lock = out.lock();
+
+ wtr.into_inner()
+ .map_err(Error::from)
+ .and_then(|t| String::from_utf8(t).map_err(Error::from))
+ .and_then(|text| writeln!(lock, "{}", text).map_err(Error::from))
+
+ } else {
+ if atty::is(atty::Stream::Stdout) {
+ let mut ascii_table = ascii_table::AsciiTable::default();
+
+ ascii_table.max_width = terminal_size::terminal_size()
+ .map(|tpl| tpl.0.0 as usize) // an ugly interface indeed!
+ .unwrap_or(80);
+
+ headers.into_iter()
+ .enumerate()
+ .for_each(|(i, c)| {
+ ascii_table.columns.insert(i, c);
+ });
+
+ ascii_table.print(data);
+ Ok(())
+ } else {
+ let out = std::io::stdout();
+ let mut lock = out.lock();
+ for list in data {
+ writeln!(lock, "{}", list.iter().map(|d| d.to_string()).join(" "))?;
+ }
+ Ok(())
+ }
+ }
+}
+