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