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