dbctl_cli/
lib.rs

1use clap::{Parser, Subcommand};
2use dbctl_core::db::{Database, MariaDB, Postgres, Redis};
3use dbctl_core::docker::DockerEngine;
4use std::path::PathBuf;
5
6#[derive(Parser)]
7#[command(author, version, about, long_about = None)]
8pub struct Cli {
9    #[command(subcommand)]
10    command: Option<Commands>,
11}
12
13#[derive(Subcommand)]
14pub enum Commands {
15    /// Create a new database container
16    Create {
17        #[arg(value_enum)]
18        db_type: String,
19        #[arg(short, long)]
20        name: Option<String>,
21        #[arg(short, long)]
22        user: Option<String>,
23        #[arg(short = 'P', long)]
24        password: Option<String>,
25        #[arg(short = 'p', long)]
26        port: Option<u16>,
27        #[arg(short, long)]
28        db_name: Option<String>,
29        #[arg(short, long)]
30        from_file: Option<PathBuf>,
31    },
32    /// List running database containers
33    List {},
34    /// View logs for a container
35    Logs { container_id: String },
36    /// Remove a database container
37    Remove { container_id: String },
38}
39
40async fn create_postgres(
41    name: Option<String>,
42    user: Option<String>,
43    password: Option<String>,
44    port: Option<u16>,
45    db_name: Option<String>,
46) -> anyhow::Result<()> {
47    let mut pg = Postgres::default();
48    if let Some(name) = name {
49        pg.name = name;
50    }
51    if let Some(user) = user {
52        pg.user = user;
53    }
54    if let Some(password) = password {
55        pg.password = password;
56    }
57    if let Some(port) = port {
58        pg.port = port;
59    }
60    if let Some(db_name) = db_name {
61        pg.db_name = db_name;
62    }
63
64    let engine = DockerEngine::new().await;
65    let container_id = engine.start_container(pg.clone()).await?;
66
67    println!("✅ Database '{}' started in Docker", pg.name);
68    println!("🔗 URL: {}", pg.connection_url());
69    println!("🆔 Container ID: {}", &container_id[..12]);
70
71    Ok(())
72}
73
74async fn create_redis(
75    name: Option<String>,
76    password: Option<String>,
77    port: Option<u16>,
78) -> anyhow::Result<()> {
79    let mut redis = Redis::default();
80    if let Some(name) = name {
81        redis.name = name;
82    }
83    if let Some(password) = password {
84        redis.password = Some(password);
85    }
86    if let Some(port) = port {
87        redis.port = port;
88    }
89
90    let engine = DockerEngine::new().await;
91    let container_id = engine.start_container(redis.clone()).await?;
92
93    println!("✅ Redis '{}' started in Docker", redis.name);
94    println!("🔗 URL: {}", redis.connection_url());
95    println!("🆔 Container ID: {}", &container_id[..12]);
96
97    Ok(())
98}
99
100async fn create_mariadb(
101    name: Option<String>,
102    user: Option<String>,
103    password: Option<String>,
104    port: Option<u16>,
105    db_name: Option<String>,
106) -> anyhow::Result<()> {
107    let mut mariadb = MariaDB::default();
108    if let Some(name) = name {
109        mariadb.name = name;
110    }
111    if let Some(user) = user {
112        mariadb.user = user;
113    }
114    if let Some(password) = password {
115        mariadb.password = password;
116    }
117    if let Some(port) = port {
118        mariadb.port = port;
119    }
120    if let Some(db_name) = db_name {
121        mariadb.db_name = db_name;
122    }
123
124    let engine = DockerEngine::new().await;
125    let container_id = engine.start_container(mariadb.clone()).await?;
126
127    println!("✅ MariaDB '{}' started in Docker", mariadb.name);
128    println!("🔗 URL: {}", mariadb.connection_url());
129    println!("🆔 Container ID: {}", &container_id[..12]);
130
131    Ok(())
132}
133
134async fn list_containers() -> anyhow::Result<()> {
135    let engine = DockerEngine::new().await;
136    let containers = engine.docker.list_containers::<String>(None).await?;
137
138    println!("🐳 Running Database Containers:");
139    println!(
140        "{:<15} {:<20} {:<15} {:<10}",
141        "CONTAINER ID", "NAME", "IMAGE", "STATUS"
142    );
143
144    for container in containers {
145        if let (Some(id), Some(names), Some(image), Some(status)) = (
146            container.id,
147            container.names,
148            container.image,
149            container.status,
150        ) {
151            if names.iter().any(|name| name.contains("dbctl")) {
152                let name = names.first().unwrap_or(&String::new()).replace("/", "");
153                println!(
154                    "{:<15} {:<20} {:<15} {:<10}",
155                    &id[..12],
156                    name,
157                    image,
158                    status
159                );
160            }
161        }
162    }
163
164    Ok(())
165}
166
167async fn view_logs(container_id: &str) -> anyhow::Result<()> {
168    let engine = DockerEngine::new().await;
169    let logs = engine.container_logs(container_id).await?;
170
171    println!("📋 Logs for container {}:", container_id);
172    for line in logs {
173        println!("{}", line);
174    }
175
176    Ok(())
177}
178
179async fn remove_container(container_id: &str) -> anyhow::Result<()> {
180    let engine = DockerEngine::new().await;
181    engine.stop_container(container_id).await?;
182
183    println!("✅ Container {} stopped and removed", container_id);
184
185    Ok(())
186}
187
188pub async fn run_cli_async() -> anyhow::Result<()> {
189    let cli = Cli::parse();
190
191    match &cli.command {
192        Some(Commands::Create {
193            db_type,
194            name,
195            user,
196            password,
197            port,
198            db_name,
199            from_file,
200        }) => {
201            if let Some(_path) = from_file {
202                println!("Loading from file not yet implemented");
203                return Ok(());
204            }
205
206            match db_type.to_lowercase().as_str() {
207                "postgres" => {
208                    create_postgres(
209                        name.clone(),
210                        user.clone(),
211                        password.clone(),
212                        *port,
213                        db_name.clone(),
214                    )
215                    .await?
216                }
217                "redis" => create_redis(name.clone(), password.clone(), *port).await?,
218                "mariadb" => {
219                    create_mariadb(
220                        name.clone(),
221                        user.clone(),
222                        password.clone(),
223                        *port,
224                        db_name.clone(),
225                    )
226                    .await?
227                }
228                _ => {
229                    println!("Unsupported database type: {}", db_type);
230                }
231            }
232        }
233        Some(Commands::List {}) => list_containers().await?,
234        Some(Commands::Logs { container_id }) => view_logs(&container_id).await?,
235        Some(Commands::Remove { container_id }) => remove_container(&container_id).await?,
236        None => {
237            println!("No command specified. Use --help for available commands.");
238        }
239    }
240
241    Ok(())
242}