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, ¤t_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}