lmrc_postgres/
manager.rs

1//! PostgreSQL Manager with builder pattern
2//!
3//! This module provides the main `PostgresManager` type for managing PostgreSQL
4//! installations on remote servers.
5
6use crate::config::PostgresConfig;
7use crate::diff::ConfigDiff;
8use crate::error::{Error, Result};
9use crate::operations;
10use lmrc_ssh::{AuthMethod, SshClient};
11use tracing::{info, warn};
12
13/// PostgreSQL Manager
14///
15/// Main entry point for managing PostgreSQL installations on remote servers.
16///
17/// Use [`PostgresManagerBuilder`] to construct instances.
18///
19/// # Example
20///
21/// ```rust,no_run
22/// use lmrc_postgres::{PostgresConfig, PostgresManager};
23///
24/// # async fn example() -> Result<(), Box<dyn std::error::Error>> {
25/// let config = PostgresConfig::builder()
26///     .version("15")
27///     .database_name("myapp")
28///     .username("myuser")
29///     .password("secure_password")
30///     .build()?;
31///
32/// let manager = PostgresManager::builder()
33///     .config(config)
34///     .server_ip("192.168.1.100")
35///     .ssh_user("root")
36///     .build()?;
37///
38/// manager.install().await?;
39/// # Ok(())
40/// # }
41/// ```
42pub struct PostgresManager {
43    config: PostgresConfig,
44    server_ip: String,
45    ssh_user: String,
46    ssh_password: Option<String>,
47    ssh_key_path: Option<String>,
48    ssh_port: u16,
49    private_ip: Option<String>,
50}
51
52impl PostgresManager {
53    /// Create a new builder
54    pub fn builder() -> PostgresManagerBuilder {
55        PostgresManagerBuilder::default()
56    }
57
58    /// Get a mutable SSH client connection to the server
59    fn connect(&self) -> Result<SshClient> {
60        let auth = if let Some(ref password) = self.ssh_password {
61            AuthMethod::Password {
62                username: self.ssh_user.clone(),
63                password: password.clone(),
64            }
65        } else if let Some(ref key_path) = self.ssh_key_path {
66            AuthMethod::PublicKey {
67                username: self.ssh_user.clone(),
68                private_key_path: key_path.clone(),
69                passphrase: None,
70            }
71        } else {
72            // Default to SSH agent or default key
73            AuthMethod::PublicKey {
74                username: self.ssh_user.clone(),
75                private_key_path: format!("/home/{}/.ssh/id_rsa", self.ssh_user),
76                passphrase: None,
77            }
78        };
79
80        SshClient::new(&self.server_ip, self.ssh_port)?
81            .with_auth(auth)
82            .connect()
83            .map_err(Error::Ssh)
84    }
85
86    /// Check if PostgreSQL is installed
87    ///
88    /// # Example
89    ///
90    /// ```rust,no_run
91    /// # use lmrc_postgres::PostgresManager;
92    /// # async fn example(manager: PostgresManager) -> Result<(), Box<dyn std::error::Error>> {
93    /// if !manager.is_installed().await? {
94    ///     println!("PostgreSQL is not installed");
95    /// }
96    /// # Ok(())
97    /// # }
98    /// ```
99    pub async fn is_installed(&self) -> Result<bool> {
100        let mut ssh = self.connect()?;
101        operations::is_installed(&mut ssh).await
102    }
103
104    /// Get installed PostgreSQL version
105    ///
106    /// Returns `None` if PostgreSQL is not installed.
107    pub async fn get_installed_version(&self) -> Result<Option<String>> {
108        let mut ssh = self.connect()?;
109        operations::get_installed_version(&mut ssh).await
110    }
111
112    /// Install PostgreSQL (idempotent)
113    ///
114    /// This operation is idempotent - it can be safely run multiple times.
115    /// If PostgreSQL is already installed with the correct version, it will skip installation.
116    ///
117    /// # Example
118    ///
119    /// ```rust,no_run
120    /// # use lmrc_postgres::PostgresManager;
121    /// # async fn example(manager: PostgresManager) -> Result<(), Box<dyn std::error::Error>> {
122    /// manager.install().await?;
123    /// println!("PostgreSQL installed successfully");
124    /// # Ok(())
125    /// # }
126    /// ```
127    pub async fn install(&self) -> Result<()> {
128        let mut ssh = self.connect()?;
129        operations::install(&mut ssh, &self.config).await
130    }
131
132    /// Uninstall PostgreSQL
133    ///
134    /// # Arguments
135    ///
136    /// * `purge` - If true, remove all data and configuration files
137    ///
138    /// # Example
139    ///
140    /// ```rust,no_run
141    /// # use lmrc_postgres::PostgresManager;
142    /// # async fn example(manager: PostgresManager) -> Result<(), Box<dyn std::error::Error>> {
143    /// // Remove PostgreSQL but keep data
144    /// manager.uninstall(false).await?;
145    ///
146    /// // Remove PostgreSQL and all data
147    /// manager.uninstall(true).await?;
148    /// # Ok(())
149    /// # }
150    /// ```
151    pub async fn uninstall(&self, purge: bool) -> Result<()> {
152        let mut ssh = self.connect()?;
153        operations::uninstall(&mut ssh, &self.config, purge).await
154    }
155
156    /// Configure database and user (idempotent)
157    ///
158    /// Creates the database and user if they don't exist, grants permissions.
159    /// This operation is idempotent.
160    ///
161    /// # Example
162    ///
163    /// ```rust,no_run
164    /// # use lmrc_postgres::PostgresManager;
165    /// # async fn example(manager: PostgresManager) -> Result<(), Box<dyn std::error::Error>> {
166    /// manager.configure_database().await?;
167    /// # Ok(())
168    /// # }
169    /// ```
170    pub async fn configure_database(&self) -> Result<()> {
171        let mut ssh = self.connect()?;
172        operations::configure_database(&mut ssh, &self.config).await
173    }
174
175    /// Configure PostgreSQL server settings
176    ///
177    /// Updates server configuration (listen addresses, port, memory settings, etc.)
178    /// and restarts the service.
179    ///
180    /// # Example
181    ///
182    /// ```rust,no_run
183    /// # use lmrc_postgres::PostgresManager;
184    /// # async fn example(manager: PostgresManager) -> Result<(), Box<dyn std::error::Error>> {
185    /// manager.configure_server().await?;
186    /// # Ok(())
187    /// # }
188    /// ```
189    pub async fn configure_server(&self) -> Result<()> {
190        let mut ssh = self.connect()?;
191        operations::configure_server(&mut ssh, &self.config).await
192    }
193
194    /// Configure both database and server
195    ///
196    /// Convenience method that calls both `configure_database()` and `configure_server()`.
197    ///
198    /// # Example
199    ///
200    /// ```rust,no_run
201    /// # use lmrc_postgres::PostgresManager;
202    /// # async fn example(manager: PostgresManager) -> Result<(), Box<dyn std::error::Error>> {
203    /// manager.configure().await?;
204    /// # Ok(())
205    /// # }
206    /// ```
207    pub async fn configure(&self) -> Result<()> {
208        self.configure_database().await?;
209        self.configure_server().await?;
210        Ok(())
211    }
212
213    /// Complete setup (install and configure)
214    ///
215    /// Installs PostgreSQL (if not already installed) and configures both database and server.
216    /// All operations are idempotent.
217    ///
218    /// # Example
219    ///
220    /// ```rust,no_run
221    /// # use lmrc_postgres::PostgresManager;
222    /// # async fn example(manager: PostgresManager) -> Result<(), Box<dyn std::error::Error>> {
223    /// manager.setup().await?;
224    /// println!("PostgreSQL is ready!");
225    /// # Ok(())
226    /// # }
227    /// ```
228    pub async fn setup(&self) -> Result<()> {
229        if !self.is_installed().await? {
230            self.install().await?;
231        } else {
232            info!("PostgreSQL is already installed");
233        }
234
235        self.configure().await?;
236
237        // Display connection info
238        if let Some(ref priv_ip) = self.private_ip {
239            info!(
240                "Database is accessible at: postgresql://{}:***@{}:{}/{}",
241                self.config.username, priv_ip, self.config.port, self.config.database_name
242            );
243        }
244
245        Ok(())
246    }
247
248    /// Detect configuration differences
249    ///
250    /// Compares the desired configuration with the current server configuration
251    /// and returns a diff of changes.
252    ///
253    /// # Example
254    ///
255    /// ```rust,no_run
256    /// # use lmrc_postgres::PostgresManager;
257    /// # async fn example(manager: PostgresManager) -> Result<(), Box<dyn std::error::Error>> {
258    /// let diff = manager.diff().await?;
259    ///
260    /// if diff.has_changes() {
261    ///     println!("Configuration changes:");
262    ///     for change in diff.changes() {
263    ///         println!("  {}", change);
264    ///     }
265    /// }
266    /// # Ok(())
267    /// # }
268    /// ```
269    pub async fn diff(&self) -> Result<ConfigDiff> {
270        let mut ssh = self.connect()?;
271        operations::detect_diff(&mut ssh, &self.config).await
272    }
273
274    /// Apply configuration diff
275    ///
276    /// Applies the changes detected by `diff()` to the server.
277    ///
278    /// # Arguments
279    ///
280    /// * `diff` - Configuration diff to apply
281    ///
282    /// # Example
283    ///
284    /// ```rust,no_run
285    /// # use lmrc_postgres::PostgresManager;
286    /// # async fn example(manager: PostgresManager) -> Result<(), Box<dyn std::error::Error>> {
287    /// let diff = manager.diff().await?;
288    ///
289    /// if diff.has_changes() {
290    ///     manager.apply_diff(&diff).await?;
291    ///     println!("Configuration updated");
292    /// }
293    /// # Ok(())
294    /// # }
295    /// ```
296    pub async fn apply_diff(&self, diff: &ConfigDiff) -> Result<()> {
297        if !diff.has_changes() {
298            info!("No changes to apply");
299            return Ok(());
300        }
301
302        info!("Applying configuration changes: {}", diff.summary());
303
304        // For now, we reconfigure the server to apply all changes
305        // In a more sophisticated implementation, we could apply individual changes
306        self.configure_server().await?;
307
308        info!("Configuration changes applied successfully");
309        Ok(())
310    }
311
312    /// Test database connection
313    ///
314    /// Attempts to connect to the database and verify credentials.
315    ///
316    /// # Example
317    ///
318    /// ```rust,no_run
319    /// # use lmrc_postgres::PostgresManager;
320    /// # async fn example(manager: PostgresManager) -> Result<(), Box<dyn std::error::Error>> {
321    /// manager.test_connection().await?;
322    /// println!("Connection successful!");
323    /// # Ok(())
324    /// # }
325    /// ```
326    pub async fn test_connection(&self) -> Result<()> {
327        let mut ssh = self.connect()?;
328        operations::test_connection(&mut ssh, &self.config).await
329    }
330
331    /// Get the PostgreSQL configuration
332    pub fn config(&self) -> &PostgresConfig {
333        &self.config
334    }
335
336    /// Get the server IP address
337    pub fn server_ip(&self) -> &str {
338        &self.server_ip
339    }
340
341    /// Get the SSH username
342    pub fn ssh_user(&self) -> &str {
343        &self.ssh_user
344    }
345
346    /// Get the private IP (if set)
347    pub fn private_ip(&self) -> Option<&str> {
348        self.private_ip.as_deref()
349    }
350
351    // === Advanced Installation Features ===
352
353    /// Detect the server platform
354    ///
355    /// # Example
356    ///
357    /// ```rust,no_run
358    /// # use lmrc_postgres::PostgresManager;
359    /// # async fn example(manager: PostgresManager) -> Result<(), Box<dyn std::error::Error>> {
360    /// let platform = manager.detect_platform().await?;
361    /// println!("Platform: {:?}", platform);
362    /// # Ok(())
363    /// # }
364    /// ```
365    pub async fn detect_platform(&self) -> Result<crate::install::Platform> {
366        let mut ssh = self.connect()?;
367        crate::install::detect_platform(&mut ssh).await
368    }
369
370    /// Get system information
371    ///
372    /// # Example
373    ///
374    /// ```rust,no_run
375    /// # use lmrc_postgres::PostgresManager;
376    /// # async fn example(manager: PostgresManager) -> Result<(), Box<dyn std::error::Error>> {
377    /// let info = manager.get_system_info().await?;
378    /// println!("RAM: {}MB, Disk: {}MB, CPUs: {}",
379    ///          info.total_ram_mb, info.free_disk_mb, info.cpu_cores);
380    /// # Ok(())
381    /// # }
382    /// ```
383    pub async fn get_system_info(&self) -> Result<crate::install::SystemInfo> {
384        let mut ssh = self.connect()?;
385        crate::install::get_system_info(&mut ssh).await
386    }
387
388    /// Check system requirements before installation
389    ///
390    /// # Arguments
391    ///
392    /// * `requirements` - Optional custom requirements (uses defaults if None)
393    ///
394    /// # Example
395    ///
396    /// ```rust,no_run
397    /// # use lmrc_postgres::{PostgresManager, SystemRequirements};
398    /// # async fn example(manager: PostgresManager) -> Result<(), Box<dyn std::error::Error>> {
399    /// // Use default requirements
400    /// manager.check_requirements(None).await?;
401    ///
402    /// // Or use custom requirements
403    /// let requirements = SystemRequirements {
404    ///     min_ram_mb: 2048,
405    ///     min_disk_mb: 10240,
406    ///     min_cpu_cores: 2,
407    /// };
408    /// manager.check_requirements(Some(requirements)).await?;
409    /// # Ok(())
410    /// # }
411    /// ```
412    pub async fn check_requirements(
413        &self,
414        requirements: Option<crate::install::SystemRequirements>,
415    ) -> Result<crate::install::SystemInfo> {
416        let mut ssh = self.connect()?;
417        let req = requirements.unwrap_or_default();
418        crate::install::check_requirements(&mut ssh, &req).await
419    }
420
421    /// Check if a PostgreSQL version is available for installation
422    ///
423    /// # Example
424    ///
425    /// ```rust,no_run
426    /// # use lmrc_postgres::PostgresManager;
427    /// # async fn example(manager: PostgresManager) -> Result<(), Box<dyn std::error::Error>> {
428    /// if manager.check_version_available("16").await? {
429    ///     println!("PostgreSQL 16 is available");
430    /// }
431    /// # Ok(())
432    /// # }
433    /// ```
434    pub async fn check_version_available(&self, version: &str) -> Result<bool> {
435        let mut ssh = self.connect()?;
436        crate::install::check_version_available(&mut ssh, version).await
437    }
438
439    /// Verify installation comprehensively
440    ///
441    /// Performs extensive verification including:
442    /// - Binary existence
443    /// - Version check
444    /// - Service status
445    /// - Configuration files
446    /// - Network listening
447    ///
448    /// # Example
449    ///
450    /// ```rust,no_run
451    /// # use lmrc_postgres::PostgresManager;
452    /// # async fn example(manager: PostgresManager) -> Result<(), Box<dyn std::error::Error>> {
453    /// manager.verify_installation().await?;
454    /// println!("Installation verified!");
455    /// # Ok(())
456    /// # }
457    /// ```
458    pub async fn verify_installation(&self) -> Result<()> {
459        let mut ssh = self.connect()?;
460        crate::install::verify_installation(&mut ssh, &self.config).await
461    }
462
463    /// Upgrade PostgreSQL to a new version
464    ///
465    /// # Arguments
466    ///
467    /// * `new_version` - Target PostgreSQL version
468    ///
469    /// # Example
470    ///
471    /// ```rust,no_run
472    /// # use lmrc_postgres::PostgresManager;
473    /// # async fn example(manager: PostgresManager) -> Result<(), Box<dyn std::error::Error>> {
474    /// // Upgrade from current version to version 16
475    /// manager.upgrade("16").await?;
476    /// # Ok(())
477    /// # }
478    /// ```
479    ///
480    /// # Warning
481    ///
482    /// This is a simplified upgrade. For production systems, you should:
483    /// - Backup your data first
484    /// - Review migration notes for the new version
485    /// - Test in a staging environment
486    /// - Plan for data migration with pg_upgrade
487    pub async fn upgrade(&self, new_version: &str) -> Result<()> {
488        let mut ssh = self.connect()?;
489
490        // Get current version
491        let current_version = operations::get_installed_version(&mut ssh)
492            .await?
493            .ok_or(Error::NotInstalled)?;
494
495        crate::install::upgrade(&mut ssh, &current_version, new_version, &self.config).await
496    }
497
498    /// Create a backup of the current PostgreSQL configuration
499    ///
500    /// Backs up:
501    /// - postgresql.conf
502    /// - pg_hba.conf
503    /// - pg_ident.conf (if exists)
504    ///
505    /// Returns backup metadata including backup directory and timestamp.
506    ///
507    /// # Example
508    ///
509    /// ```rust,no_run
510    /// # use lmrc_postgres::PostgresManager;
511    /// # async fn example(manager: PostgresManager) -> Result<(), Box<dyn std::error::Error>> {
512    /// let backup = manager.backup_config().await?;
513    /// println!("Backup created: {}", backup.backup_dir);
514    /// # Ok(())
515    /// # }
516    /// ```
517    pub async fn backup_config(&self) -> Result<crate::backup::ConfigBackup> {
518        let mut ssh = self.connect()?;
519        crate::backup::backup_config(&mut ssh, &self.config).await
520    }
521
522    /// List available configuration backups
523    ///
524    /// # Example
525    ///
526    /// ```rust,no_run
527    /// # use lmrc_postgres::PostgresManager;
528    /// # async fn example(manager: PostgresManager) -> Result<(), Box<dyn std::error::Error>> {
529    /// let backups = manager.list_backups().await?;
530    /// for backup in backups {
531    ///     println!("Backup: {} ({} files)", backup.timestamp, backup.files.len());
532    /// }
533    /// # Ok(())
534    /// # }
535    /// ```
536    pub async fn list_backups(&self) -> Result<Vec<crate::backup::ConfigBackup>> {
537        let mut ssh = self.connect()?;
538        crate::backup::list_backups(&mut ssh).await
539    }
540
541    /// Restore configuration from a specific backup
542    ///
543    /// # Arguments
544    ///
545    /// * `backup` - The backup to restore
546    ///
547    /// # Example
548    ///
549    /// ```rust,no_run
550    /// # use lmrc_postgres::PostgresManager;
551    /// # async fn example(manager: PostgresManager) -> Result<(), Box<dyn std::error::Error>> {
552    /// let backups = manager.list_backups().await?;
553    /// if let Some(backup) = backups.first() {
554    ///     manager.restore_backup(backup).await?;
555    ///     println!("Configuration restored from {}", backup.timestamp);
556    /// }
557    /// # Ok(())
558    /// # }
559    /// ```
560    pub async fn restore_backup(&self, backup: &crate::backup::ConfigBackup) -> Result<()> {
561        let mut ssh = self.connect()?;
562        crate::backup::restore_backup(&mut ssh, &self.config, backup).await
563    }
564
565    /// Rollback to the most recent configuration backup
566    ///
567    /// # Example
568    ///
569    /// ```rust,no_run
570    /// # use lmrc_postgres::PostgresManager;
571    /// # async fn example(manager: PostgresManager) -> Result<(), Box<dyn std::error::Error>> {
572    /// // Make some changes that you want to undo
573    /// // ...
574    ///
575    /// // Rollback to previous configuration
576    /// manager.rollback_config().await?;
577    /// println!("Configuration rolled back successfully");
578    /// # Ok(())
579    /// # }
580    /// ```
581    pub async fn rollback_config(&self) -> Result<()> {
582        let mut ssh = self.connect()?;
583        crate::backup::rollback_config(&mut ssh, &self.config).await
584    }
585
586    /// Clean up old configuration backups
587    ///
588    /// Keeps only the most recent N backups and deletes the rest.
589    ///
590    /// # Arguments
591    ///
592    /// * `keep_count` - Number of backups to keep
593    ///
594    /// # Example
595    ///
596    /// ```rust,no_run
597    /// # use lmrc_postgres::PostgresManager;
598    /// # async fn example(manager: PostgresManager) -> Result<(), Box<dyn std::error::Error>> {
599    /// // Keep only the 5 most recent backups
600    /// let deleted = manager.cleanup_old_backups(5).await?;
601    /// println!("Deleted {} old backup(s)", deleted);
602    /// # Ok(())
603    /// # }
604    /// ```
605    pub async fn cleanup_old_backups(&self, keep_count: usize) -> Result<usize> {
606        let mut ssh = self.connect()?;
607        crate::backup::cleanup_old_backups(&mut ssh, keep_count).await
608    }
609
610    /// Detect pg_hba.conf configuration differences
611    ///
612    /// Compares the current pg_hba.conf rules with a desired state.
613    ///
614    /// # Example
615    ///
616    /// ```rust,no_run
617    /// # use lmrc_postgres::PostgresManager;
618    /// # async fn example(manager: PostgresManager) -> Result<(), Box<dyn std::error::Error>> {
619    /// let current_content = manager.read_pg_hba().await?;
620    /// println!("Current pg_hba.conf:\n{}", current_content);
621    /// # Ok(())
622    /// # }
623    /// ```
624    pub async fn read_pg_hba(&self) -> Result<String> {
625        let mut ssh = self.connect()?;
626        crate::backup::read_pg_hba(&mut ssh, &self.config).await
627    }
628
629    /// Preview configuration changes without applying them (dry-run)
630    ///
631    /// This method shows what changes would be made without actually applying them.
632    /// Useful for validating changes before execution.
633    ///
634    /// # Example
635    ///
636    /// ```rust,no_run
637    /// # use lmrc_postgres::PostgresManager;
638    /// # async fn example(manager: PostgresManager) -> Result<(), Box<dyn std::error::Error>> {
639    /// // Preview changes
640    /// let diff = manager.dry_run_configure().await?;
641    ///
642    /// if diff.has_changes() {
643    ///     println!("Would make the following changes:");
644    ///     println!("{}", diff);
645    ///
646    ///     // If changes look good, apply them
647    ///     manager.apply_diff(&diff).await?;
648    /// }
649    /// # Ok(())
650    /// # }
651    /// ```
652    pub async fn dry_run_configure(&self) -> Result<ConfigDiff> {
653        info!("Running dry-run configuration check (no changes will be applied)");
654        self.diff().await
655    }
656
657    /// Apply configuration changes with automatic backup
658    ///
659    /// This method:
660    /// 1. Creates a backup of the current configuration
661    /// 2. Applies the changes
662    /// 3. Verifies the changes
663    /// 4. Rolls back if verification fails
664    ///
665    /// # Arguments
666    ///
667    /// * `diff` - Configuration diff to apply
668    ///
669    /// # Example
670    ///
671    /// ```rust,no_run
672    /// # use lmrc_postgres::PostgresManager;
673    /// # async fn example(manager: PostgresManager) -> Result<(), Box<dyn std::error::Error>> {
674    /// let diff = manager.diff().await?;
675    ///
676    /// if diff.has_changes() {
677    ///     // Apply with automatic backup and rollback on failure
678    ///     manager.apply_diff_safe(&diff).await?;
679    /// }
680    /// # Ok(())
681    /// # }
682    /// ```
683    pub async fn apply_diff_safe(&self, diff: &ConfigDiff) -> Result<()> {
684        if !diff.has_changes() {
685            info!("No changes to apply");
686            return Ok(());
687        }
688
689        info!("Creating backup before applying changes...");
690        let backup = self.backup_config().await?;
691        info!("✓ Backup created: {}", backup.backup_dir);
692
693        info!("Applying configuration changes...");
694        match self.apply_diff(diff).await {
695            Ok(_) => {
696                info!("✓ Configuration changes applied successfully");
697                Ok(())
698            }
699            Err(e) => {
700                warn!("Configuration apply failed, rolling back to backup...");
701                self.restore_backup(&backup).await?;
702                info!("✓ Configuration rolled back successfully");
703                Err(e)
704            }
705        }
706    }
707
708    // ==================== User & Database Management ====================
709
710    /// List all PostgreSQL users
711    ///
712    /// # Example
713    ///
714    /// ```rust,no_run
715    /// # use lmrc_postgres::PostgresManager;
716    /// # async fn example(manager: PostgresManager) -> Result<(), Box<dyn std::error::Error>> {
717    /// let users = manager.list_users().await?;
718    /// for user in users {
719    ///     println!("User: {} (superuser: {})", user.name, user.is_superuser);
720    /// }
721    /// # Ok(())
722    /// # }
723    /// ```
724    pub async fn list_users(&self) -> Result<Vec<crate::user_db_management::UserInfo>> {
725        let mut ssh = self.connect()?;
726        crate::user_db_management::list_users(&mut ssh).await
727    }
728
729    /// List all PostgreSQL databases
730    ///
731    /// # Example
732    ///
733    /// ```rust,no_run
734    /// # use lmrc_postgres::PostgresManager;
735    /// # async fn example(manager: PostgresManager) -> Result<(), Box<dyn std::error::Error>> {
736    /// let databases = manager.list_databases().await?;
737    /// for db in databases {
738    ///     println!("Database: {} (owner: {}, encoding: {})", db.name, db.owner, db.encoding);
739    /// }
740    /// # Ok(())
741    /// # }
742    /// ```
743    pub async fn list_databases(&self) -> Result<Vec<crate::user_db_management::DatabaseInfo>> {
744        let mut ssh = self.connect()?;
745        crate::user_db_management::list_databases(&mut ssh).await
746    }
747
748    /// Drop a database
749    ///
750    /// # Arguments
751    ///
752    /// * `database_name` - Name of the database to drop
753    ///
754    /// # Example
755    ///
756    /// ```rust,no_run
757    /// # use lmrc_postgres::PostgresManager;
758    /// # async fn example(manager: PostgresManager) -> Result<(), Box<dyn std::error::Error>> {
759    /// manager.drop_database("old_database").await?;
760    /// # Ok(())
761    /// # }
762    /// ```
763    pub async fn drop_database(&self, database_name: &str) -> Result<()> {
764        let mut ssh = self.connect()?;
765        crate::user_db_management::drop_database(&mut ssh, database_name).await
766    }
767
768    /// Drop a user
769    ///
770    /// # Arguments
771    ///
772    /// * `username` - Name of the user to drop
773    ///
774    /// # Example
775    ///
776    /// ```rust,no_run
777    /// # use lmrc_postgres::PostgresManager;
778    /// # async fn example(manager: PostgresManager) -> Result<(), Box<dyn std::error::Error>> {
779    /// manager.drop_user("old_user").await?;
780    /// # Ok(())
781    /// # }
782    /// ```
783    pub async fn drop_user(&self, username: &str) -> Result<()> {
784        let mut ssh = self.connect()?;
785        crate::user_db_management::drop_user(&mut ssh, username).await
786    }
787
788    /// Update a user's password
789    ///
790    /// # Arguments
791    ///
792    /// * `username` - Name of the user
793    /// * `new_password` - New password for the user
794    ///
795    /// # Example
796    ///
797    /// ```rust,no_run
798    /// # use lmrc_postgres::PostgresManager;
799    /// # async fn example(manager: PostgresManager) -> Result<(), Box<dyn std::error::Error>> {
800    /// manager.update_user_password("myuser", "new_secure_password").await?;
801    /// # Ok(())
802    /// # }
803    /// ```
804    pub async fn update_user_password(&self, username: &str, new_password: &str) -> Result<()> {
805        let mut ssh = self.connect()?;
806        crate::user_db_management::update_user_password(&mut ssh, username, new_password).await
807    }
808
809    /// Grant privileges to a user on a database
810    ///
811    /// # Arguments
812    ///
813    /// * `database` - Database name
814    /// * `username` - User to grant privileges to
815    /// * `privileges` - List of privileges to grant
816    ///
817    /// # Example
818    ///
819    /// ```rust,no_run
820    /// # use lmrc_postgres::{PostgresManager, Privilege};
821    /// # async fn example(manager: PostgresManager) -> Result<(), Box<dyn std::error::Error>> {
822    /// manager.grant_privileges(
823    ///     "myapp_db",
824    ///     "app_user",
825    ///     &[Privilege::Select, Privilege::Insert, Privilege::Update]
826    /// ).await?;
827    /// # Ok(())
828    /// # }
829    /// ```
830    pub async fn grant_privileges(
831        &self,
832        database: &str,
833        username: &str,
834        privileges: &[crate::user_db_management::Privilege],
835    ) -> Result<()> {
836        let mut ssh = self.connect()?;
837        crate::user_db_management::grant_privileges(&mut ssh, database, username, privileges).await
838    }
839
840    /// Revoke privileges from a user on a database
841    ///
842    /// # Arguments
843    ///
844    /// * `database` - Database name
845    /// * `username` - User to revoke privileges from
846    /// * `privileges` - List of privileges to revoke
847    ///
848    /// # Example
849    ///
850    /// ```rust,no_run
851    /// # use lmrc_postgres::{PostgresManager, Privilege};
852    /// # async fn example(manager: PostgresManager) -> Result<(), Box<dyn std::error::Error>> {
853    /// manager.revoke_privileges(
854    ///     "myapp_db",
855    ///     "app_user",
856    ///     &[Privilege::Delete, Privilege::Truncate]
857    /// ).await?;
858    /// # Ok(())
859    /// # }
860    /// ```
861    pub async fn revoke_privileges(
862        &self,
863        database: &str,
864        username: &str,
865        privileges: &[crate::user_db_management::Privilege],
866    ) -> Result<()> {
867        let mut ssh = self.connect()?;
868        crate::user_db_management::revoke_privileges(&mut ssh, database, username, privileges).await
869    }
870
871    /// Create a role
872    ///
873    /// # Arguments
874    ///
875    /// * `role_name` - Name of the role to create
876    /// * `can_login` - Whether the role can login
877    /// * `is_superuser` - Whether the role is a superuser
878    ///
879    /// # Example
880    ///
881    /// ```rust,no_run
882    /// # use lmrc_postgres::PostgresManager;
883    /// # async fn example(manager: PostgresManager) -> Result<(), Box<dyn std::error::Error>> {
884    /// // Create a non-login role for grouping privileges
885    /// manager.create_role("app_readonly", false, false).await?;
886    /// # Ok(())
887    /// # }
888    /// ```
889    pub async fn create_role(
890        &self,
891        role_name: &str,
892        can_login: bool,
893        is_superuser: bool,
894    ) -> Result<()> {
895        let mut ssh = self.connect()?;
896        crate::user_db_management::create_role(&mut ssh, role_name, can_login, is_superuser).await
897    }
898
899    /// Grant a role to a user
900    ///
901    /// # Arguments
902    ///
903    /// * `role_name` - Name of the role to grant
904    /// * `username` - User to grant the role to
905    ///
906    /// # Example
907    ///
908    /// ```rust,no_run
909    /// # use lmrc_postgres::PostgresManager;
910    /// # async fn example(manager: PostgresManager) -> Result<(), Box<dyn std::error::Error>> {
911    /// manager.grant_role("app_readonly", "new_user").await?;
912    /// # Ok(())
913    /// # }
914    /// ```
915    pub async fn grant_role(&self, role_name: &str, username: &str) -> Result<()> {
916        let mut ssh = self.connect()?;
917        crate::user_db_management::grant_role(&mut ssh, role_name, username).await
918    }
919
920    /// Revoke a role from a user
921    ///
922    /// # Arguments
923    ///
924    /// * `role_name` - Name of the role to revoke
925    /// * `username` - User to revoke the role from
926    ///
927    /// # Example
928    ///
929    /// ```rust,no_run
930    /// # use lmrc_postgres::PostgresManager;
931    /// # async fn example(manager: PostgresManager) -> Result<(), Box<dyn std::error::Error>> {
932    /// manager.revoke_role("app_readonly", "old_user").await?;
933    /// # Ok(())
934    /// # }
935    /// ```
936    pub async fn revoke_role(&self, role_name: &str, username: &str) -> Result<()> {
937        let mut ssh = self.connect()?;
938        crate::user_db_management::revoke_role(&mut ssh, role_name, username).await
939    }
940
941    /// Check if a user exists
942    ///
943    /// # Arguments
944    ///
945    /// * `username` - Name of the user to check
946    ///
947    /// # Example
948    ///
949    /// ```rust,no_run
950    /// # use lmrc_postgres::PostgresManager;
951    /// # async fn example(manager: PostgresManager) -> Result<(), Box<dyn std::error::Error>> {
952    /// if manager.user_exists("myuser").await? {
953    ///     println!("User exists");
954    /// }
955    /// # Ok(())
956    /// # }
957    /// ```
958    pub async fn user_exists(&self, username: &str) -> Result<bool> {
959        let mut ssh = self.connect()?;
960        crate::user_db_management::user_exists(&mut ssh, username).await
961    }
962
963    /// Check if a database exists
964    ///
965    /// # Arguments
966    ///
967    /// * `database` - Name of the database to check
968    ///
969    /// # Example
970    ///
971    /// ```rust,no_run
972    /// # use lmrc_postgres::PostgresManager;
973    /// # async fn example(manager: PostgresManager) -> Result<(), Box<dyn std::error::Error>> {
974    /// if manager.database_exists("mydb").await? {
975    ///     println!("Database exists");
976    /// }
977    /// # Ok(())
978    /// # }
979    /// ```
980    pub async fn database_exists(&self, database: &str) -> Result<bool> {
981        let mut ssh = self.connect()?;
982        crate::user_db_management::database_exists(&mut ssh, database).await
983    }
984
985    /// Create a database with advanced options
986    ///
987    /// # Arguments
988    ///
989    /// * `database_name` - Name of the database to create
990    /// * `owner` - Optional owner of the database
991    /// * `encoding` - Optional character encoding (e.g., "UTF8")
992    /// * `template` - Optional template database
993    ///
994    /// # Example
995    ///
996    /// ```rust,no_run
997    /// # use lmrc_postgres::PostgresManager;
998    /// # async fn example(manager: PostgresManager) -> Result<(), Box<dyn std::error::Error>> {
999    /// manager.create_database_with_options(
1000    ///     "analytics_db",
1001    ///     Some("analytics_user"),
1002    ///     Some("UTF8"),
1003    ///     Some("template0")
1004    /// ).await?;
1005    /// # Ok(())
1006    /// # }
1007    /// ```
1008    pub async fn create_database_with_options(
1009        &self,
1010        database_name: &str,
1011        owner: Option<&str>,
1012        encoding: Option<&str>,
1013        template: Option<&str>,
1014    ) -> Result<()> {
1015        let mut ssh = self.connect()?;
1016        crate::user_db_management::create_database_with_options(
1017            &mut ssh,
1018            database_name,
1019            owner,
1020            encoding,
1021            template,
1022        )
1023        .await
1024    }
1025
1026    /// Create a user with advanced options
1027    ///
1028    /// # Arguments
1029    ///
1030    /// * `username` - Name of the user to create
1031    /// * `password` - Password for the user
1032    /// * `is_superuser` - Whether the user should be a superuser
1033    /// * `can_create_db` - Whether the user can create databases
1034    /// * `can_create_role` - Whether the user can create roles
1035    /// * `connection_limit` - Optional connection limit (-1 for unlimited)
1036    ///
1037    /// # Example
1038    ///
1039    /// ```rust,no_run
1040    /// # use lmrc_postgres::PostgresManager;
1041    /// # async fn example(manager: PostgresManager) -> Result<(), Box<dyn std::error::Error>> {
1042    /// manager.create_user_with_options(
1043    ///     "limited_user",
1044    ///     "secure_password",
1045    ///     false,
1046    ///     false,
1047    ///     false,
1048    ///     Some(10)
1049    /// ).await?;
1050    /// # Ok(())
1051    /// # }
1052    /// ```
1053    pub async fn create_user_with_options(
1054        &self,
1055        username: &str,
1056        password: &str,
1057        is_superuser: bool,
1058        can_create_db: bool,
1059        can_create_role: bool,
1060        connection_limit: Option<i32>,
1061    ) -> Result<()> {
1062        let mut ssh = self.connect()?;
1063        crate::user_db_management::create_user_with_options(
1064            &mut ssh,
1065            username,
1066            password,
1067            is_superuser,
1068            can_create_db,
1069            can_create_role,
1070            connection_limit,
1071        )
1072        .await
1073    }
1074}
1075
1076/// Builder for [`PostgresManager`]
1077///
1078/// # Example
1079///
1080/// ```rust,no_run
1081/// use lmrc_postgres::{PostgresConfig, PostgresManager};
1082///
1083/// # fn example() -> Result<(), Box<dyn std::error::Error>> {
1084/// let config = PostgresConfig::builder()
1085///     .version("15")
1086///     .database_name("myapp")
1087///     .username("myuser")
1088///     .password("secure_password")
1089///     .build()?;
1090///
1091/// let manager = PostgresManager::builder()
1092///     .config(config)
1093///     .server_ip("192.168.1.100")
1094///     .ssh_user("root")
1095///     .ssh_port(22)
1096///     .private_ip("10.0.1.100")
1097///     .build()?;
1098/// # Ok(())
1099/// # }
1100/// ```
1101#[derive(Debug, Default)]
1102pub struct PostgresManagerBuilder {
1103    config: Option<PostgresConfig>,
1104    server_ip: Option<String>,
1105    ssh_user: Option<String>,
1106    ssh_password: Option<String>,
1107    ssh_key_path: Option<String>,
1108    ssh_port: Option<u16>,
1109    private_ip: Option<String>,
1110}
1111
1112impl PostgresManagerBuilder {
1113    /// Set PostgreSQL configuration
1114    pub fn config(mut self, config: PostgresConfig) -> Self {
1115        self.config = Some(config);
1116        self
1117    }
1118
1119    /// Set server IP address
1120    pub fn server_ip(mut self, ip: impl Into<String>) -> Self {
1121        self.server_ip = Some(ip.into());
1122        self
1123    }
1124
1125    /// Set SSH username (default: "root")
1126    pub fn ssh_user(mut self, user: impl Into<String>) -> Self {
1127        self.ssh_user = Some(user.into());
1128        self
1129    }
1130
1131    /// Set SSH password for password authentication
1132    pub fn ssh_password(mut self, password: impl Into<String>) -> Self {
1133        self.ssh_password = Some(password.into());
1134        self
1135    }
1136
1137    /// Set SSH private key path for key-based authentication
1138    pub fn ssh_key_path(mut self, path: impl Into<String>) -> Self {
1139        self.ssh_key_path = Some(path.into());
1140        self
1141    }
1142
1143    /// Set SSH port (default: 22)
1144    pub fn ssh_port(mut self, port: u16) -> Self {
1145        self.ssh_port = Some(port);
1146        self
1147    }
1148
1149    /// Set private IP address (for connection string display)
1150    pub fn private_ip(mut self, ip: impl Into<String>) -> Self {
1151        self.private_ip = Some(ip.into());
1152        self
1153    }
1154
1155    /// Build the PostgresManager
1156    pub fn build(self) -> Result<PostgresManager> {
1157        Ok(PostgresManager {
1158            config: self
1159                .config
1160                .ok_or_else(|| Error::MissingConfig("config".to_string()))?,
1161            server_ip: self
1162                .server_ip
1163                .ok_or_else(|| Error::MissingConfig("server_ip".to_string()))?,
1164            ssh_user: self.ssh_user.unwrap_or_else(|| "root".to_string()),
1165            ssh_password: self.ssh_password,
1166            ssh_key_path: self.ssh_key_path,
1167            ssh_port: self.ssh_port.unwrap_or(22),
1168            private_ip: self.private_ip,
1169        })
1170    }
1171}
1172
1173#[cfg(test)]
1174mod tests {
1175    use super::*;
1176
1177    fn create_test_config() -> PostgresConfig {
1178        PostgresConfig::builder()
1179            .version("15")
1180            .database_name("test_db")
1181            .username("test_user")
1182            .password("test_pass")
1183            .build()
1184            .unwrap()
1185    }
1186
1187    #[test]
1188    fn test_builder_minimal() {
1189        let config = create_test_config();
1190        let manager = PostgresManager::builder()
1191            .config(config.clone())
1192            .server_ip("192.168.1.100")
1193            .build()
1194            .unwrap();
1195
1196        assert_eq!(manager.server_ip(), "192.168.1.100");
1197        assert_eq!(manager.ssh_user(), "root");
1198        assert_eq!(manager.ssh_port, 22);
1199        assert_eq!(manager.private_ip(), None);
1200    }
1201
1202    #[test]
1203    fn test_builder_full() {
1204        let config = create_test_config();
1205        let manager = PostgresManager::builder()
1206            .config(config.clone())
1207            .server_ip("192.168.1.100")
1208            .ssh_user("admin")
1209            .ssh_port(2222)
1210            .private_ip("10.0.1.100")
1211            .build()
1212            .unwrap();
1213
1214        assert_eq!(manager.server_ip(), "192.168.1.100");
1215        assert_eq!(manager.ssh_user(), "admin");
1216        assert_eq!(manager.ssh_port, 2222);
1217        assert_eq!(manager.private_ip(), Some("10.0.1.100"));
1218    }
1219
1220    #[test]
1221    fn test_builder_missing_config() {
1222        let result = PostgresManager::builder()
1223            .server_ip("192.168.1.100")
1224            .build();
1225
1226        assert!(matches!(result, Err(Error::MissingConfig(_))));
1227    }
1228
1229    #[test]
1230    fn test_builder_missing_server_ip() {
1231        let config = create_test_config();
1232        let result = PostgresManager::builder().config(config).build();
1233
1234        assert!(matches!(result, Err(Error::MissingConfig(_))));
1235    }
1236}