diff --git a/Cargo.lock b/Cargo.lock index 79b82a0..7556ee2 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -249,6 +249,20 @@ version = "0.7.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a1d728cc89cf3aee9ff92b05e62b19ee65a02b5702cff7d5a377e32c6ae29d8d" +[[package]] +name = "cliclack" +version = "0.3.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2381872509dfa50d8b92b92a5da8367ba68458ab9494be4134b57ad6ca26295f" +dependencies = [ + "console 0.15.11", + "indicatif", + "once_cell", + "strsim", + "textwrap", + "zeroize", +] + [[package]] name = "colorchoice" version = "1.0.4" @@ -275,6 +289,19 @@ dependencies = [ "unicode-width 0.2.2", ] +[[package]] +name = "console" +version = "0.15.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "054ccb5b10f9f2cbf51eb355ca1d05c2d279ce1804688d0db74b4733a5aeafd8" +dependencies = [ + "encode_unicode", + "libc", + "once_cell", + "unicode-width 0.2.2", + "windows-sys 0.59.0", +] + [[package]] name = "console" version = "0.16.1" @@ -382,6 +409,15 @@ dependencies = [ "litrs", ] +[[package]] +name = "dsn" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a68ec86c8ab056c40c4d3f6a543ad0ad6a251d7d01dac251feed242aa44e754a" +dependencies = [ + "percent-encoding", +] + [[package]] name = "dyn-clone" version = "1.0.20" @@ -841,7 +877,7 @@ version = "0.18.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9375e112e4b463ec1b1c6c011953545c65a30164fbab5b581df32b3abf0dcb88" dependencies = [ - "console", + "console 0.16.1", "portable-atomic", "unicode-segmentation", "unicode-width 0.2.2", @@ -1064,8 +1100,10 @@ version = "0.0.1" dependencies = [ "bollard", "clap", + "cliclack", "colored", "comfy-table", + "dsn", "futures", "indicatif", "miette", @@ -1408,6 +1446,12 @@ version = "1.15.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "67b1b7a3b5fe4f1376887184045fcf45c69e92af734b7aaddc05fb777b6fbd03" +[[package]] +name = "smawk" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b7c388c1b5e93756d0c740965c41e8822f866621d41acbdf6336a6a168f8840c" + [[package]] name = "socket2" version = "0.6.1" @@ -1489,6 +1533,7 @@ version = "0.16.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c13547615a44dc9c452a8a534638acdf07120d4b6847c8178705da06306a3057" dependencies = [ + "smawk", "unicode-linebreak", "unicode-width 0.2.2", ] @@ -2176,6 +2221,26 @@ dependencies = [ "synstructure", ] +[[package]] +name = "zeroize" +version = "1.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b97154e67e32c85465826e8bcc1c59429aaaf107c1e4a9e53c8d8ccd5eff88d0" +dependencies = [ + "zeroize_derive", +] + +[[package]] +name = "zeroize_derive" +version = "1.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ce36e65b0d2999d2aafac989fb249189a141aee1f53c612c1f37d72631959f69" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "zerotrie" version = "0.2.3" diff --git a/Cargo.toml b/Cargo.toml index 5d9578c..afa65ff 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -9,8 +9,10 @@ license = "MIT" [dependencies] bollard = "0.19.4" clap = { version = "4.5.53", features = ["derive"] } +cliclack = "0.3.7" colored = "3.0.0" comfy-table = "7.2.1" +dsn = "1.2.1" futures = "0.3.31" indicatif = { version = "0.18.3", features = ["improved_unicode"] } miette = { version = "7.6.0", features = ["fancy"] } diff --git a/src/cli.rs b/src/cli.rs index 7fde3e7..87e54ff 100644 --- a/src/cli.rs +++ b/src/cli.rs @@ -36,13 +36,19 @@ pub enum ControlCommands { /// Restart postgres instance Restart, /// (WARNING!) Destroy postgres instance - Destroy, + Destroy { accept: bool }, + /// (WARNING!) Destruct database + Wipe { accept: bool }, + /// Status of instance Status, /// View logs produced by postgres Logs { follow: bool }, /// (Sensitive) get connection details - Connection { format: ConnectionFormat }, + Connection { + #[arg(short, long, default_value = "dsn")] + format: ConnectionFormat, + }, } #[derive(Subcommand)] diff --git a/src/controller.rs b/src/controller.rs index b4d7ea8..4a365f5 100644 --- a/src/controller.rs +++ b/src/controller.rs @@ -1,14 +1,14 @@ -use std::time::Duration; - -use miette::{Diagnostic, bail, miette}; +use dsn::DSN; +use miette::miette; use colored::Colorize; use comfy_table::{Attribute, Cell, Color, ContentArrangement, Table, presets::UTF8_FULL}; use miette::Result; -use thiserror::Error; use crate::{ - config::{PGDConfig, PostgresVersion, Project}, + cli::ConnectionFormat, + config::{PGDConfig, Project}, + consts::{DATABASE, USERNAME}, controller::{docker::DockerController, reconciler::Reconciler}, state::{InstanceState, StateManager}, }; @@ -26,6 +26,16 @@ pub struct Context { } impl Context { + pub fn require_instance(&self) -> Result<&InstanceState> { + self.instance.as_ref().ok_or(miette!("This command requires instance. Either initiliaze a project, or pass -I with instance name")) + } + + pub fn require_project(&self) -> Result<&Project> { + self.project.as_ref().ok_or(miette!( + "This command requires project. Please, initiliaze a project." + )) + } + pub async fn new(instance_override: Option) -> Result { let project = Project::load()?; let state = StateManager::new()?; @@ -55,6 +65,59 @@ impl Controller { Self { ctx } } + pub async fn show_connection(&self, format: ConnectionFormat) -> Result<()> { + let project = self.ctx.require_project()?; + let reconciler = Reconciler { ctx: &self.ctx }; + + reconciler.reconcile(project).await?; + + match format { + ConnectionFormat::DSN => { + let dsn = DSN::builder() + .driver("postgres") + .username(USERNAME) + .password(project.config.password.clone()) + .host("127.0.0.1") + .port(project.config.port) + .database(DATABASE) + .build(); + println!("{}", dsn.to_string()); + } + ConnectionFormat::Human => { + let mut table = create_ui_table("Instance"); + table.add_row(vec![ + Cell::new("Project").fg(Color::White), + Cell::new(&project.name).add_attribute(Attribute::Bold), + ]); + table.add_row(vec![ + Cell::new("PostgreSQL Version").fg(Color::White), + Cell::new(project.config.version.to_string()).add_attribute(Attribute::Bold), + ]); + table.add_row(vec![ + Cell::new("Host").fg(Color::White), + Cell::new("127.0.0.1").add_attribute(Attribute::Bold), + ]); + + table.add_row(vec![ + Cell::new("Port").fg(Color::White), + Cell::new(project.config.port.to_string()).add_attribute(Attribute::Bold), + ]); + table.add_row(vec![ + Cell::new("Username").fg(Color::White), + Cell::new(USERNAME).add_attribute(Attribute::Bold), + ]); + + table.add_row(vec![ + Cell::new("Password").fg(Color::White), + Cell::new(project.config.password.clone()).fg(Color::DarkGrey), + ]); + println!("{}", table); + } + } + + Ok(()) + } + pub async fn init_project(&self) -> Result<()> { let reconciler = Reconciler { ctx: &self.ctx }; @@ -83,20 +146,7 @@ impl Controller { project.path.display().to_string().bright_white().bold() ); - let mut table = Table::new(); - table - .load_preset(UTF8_FULL) - .set_content_arrangement(ContentArrangement::Dynamic) - .set_style(comfy_table::TableComponent::MiddleIntersections, ' ') - .set_header(vec![ - Cell::new("Instance Configuration").add_attribute(Attribute::Bold), - ]); - - use comfy_table::TableComponent::*; - table.set_style(TopLeftCorner, '╭'); - table.set_style(TopRightCorner, '╮'); - table.set_style(BottomLeftCorner, '╰'); - table.set_style(BottomRightCorner, '╯'); + let mut table = create_ui_table("Project Configuration"); table.add_row(vec![ Cell::new("Project").fg(Color::White), Cell::new(&project.name).add_attribute(Attribute::Bold), @@ -123,3 +173,19 @@ impl Controller { Ok(()) } } + +fn create_ui_table(header: &'static str) -> Table { + let mut table = Table::new(); + table + .load_preset(UTF8_FULL) + .set_content_arrangement(ContentArrangement::Dynamic) + .set_style(comfy_table::TableComponent::MiddleIntersections, ' ') + .set_header(vec![Cell::new(header).add_attribute(Attribute::Bold)]); + + use comfy_table::TableComponent::*; + table.set_style(TopLeftCorner, '╭'); + table.set_style(TopRightCorner, '╮'); + table.set_style(BottomLeftCorner, '╰'); + table.set_style(BottomRightCorner, '╯'); + table +} diff --git a/src/controller/reconciler.rs b/src/controller/reconciler.rs index 05a5a95..c67499f 100644 --- a/src/controller/reconciler.rs +++ b/src/controller/reconciler.rs @@ -1,19 +1,18 @@ use std::time::Duration; -use miette::{Diagnostic, bail, miette}; +use miette::{Diagnostic, bail}; use colored::Colorize; -use comfy_table::{Attribute, Cell, Color, ContentArrangement, Table, presets::UTF8_FULL}; use miette::Result; use thiserror::Error; use crate::{ - config::{PGDConfig, PostgresVersion, Project}, + config::{PostgresVersion, Project}, controller::{ Context, - docker::{self, DockerController}, + docker::{self}, }, - state::{InstanceState, StateManager}, + state::InstanceState, }; const MAX_RETRIES: usize = 10; diff --git a/src/main.rs b/src/main.rs index 76f5adb..0ebcfb6 100644 --- a/src/main.rs +++ b/src/main.rs @@ -34,10 +34,15 @@ async fn main() -> Result<()> { ControlCommands::Start => {} ControlCommands::Stop => {} ControlCommands::Restart => {} - ControlCommands::Destroy => {} + ControlCommands::Destroy { accept } => {} ControlCommands::Logs { follow } => todo!(), ControlCommands::Status => {} - ControlCommands::Connection { format: _ } => {} + // can't override an instance for this command, because password is in config + ControlCommands::Connection { format } => { + let ctx = Context::new(None).await?; + Controller::new(ctx).show_connection(format).await?; + } + ControlCommands::Wipe { accept } => {} }, } diff --git a/src/state.rs b/src/state.rs index 1f095b4..3fcf0ff 100644 --- a/src/state.rs +++ b/src/state.rs @@ -1,6 +1,6 @@ use miette::{Context, IntoDiagnostic, Result}; use serde::{Deserialize, Serialize}; -use std::cell::{Ref, RefCell}; +use std::cell::RefCell; use std::collections::HashMap; use std::path::PathBuf;