1use async_trait::async_trait;
10use crate::{Error as PostgresError, Privilege};
11use lmrc_ports::{
12 CreatedDatabase, DatabaseCreateRequest, DatabaseProvider, DatabaseUser, DatabaseUserRequest,
13 PortError, PortResult,
14};
15use lmrc_ssh::{AuthMethod, SshClient};
16
17pub struct PostgresAdapter;
21
22impl PostgresAdapter {
23 pub fn new() -> Self {
25 Self
26 }
27
28 async fn create_ssh_client(&self, host: &str, ssh_key_path: &str) -> PortResult<SshClient> {
35 let ssh_auth = AuthMethod::PublicKey {
36 username: "root".to_string(),
37 private_key_path: ssh_key_path.to_string(),
38 passphrase: None,
39 };
40
41 let client = SshClient::new(host, 22)
42 .map_err(|e| PortError::NetworkError(format!("Failed to create SSH client: {}", e)))?
43 .with_auth(ssh_auth)
44 .connect()
45 .map_err(|e| {
46 PortError::NetworkError(format!("Failed to connect via SSH: {}", e))
47 })?;
48
49 Ok(client)
50 }
51}
52
53fn convert_error(err: PostgresError) -> PortError {
55 match err {
56 PostgresError::Ssh(e) => PortError::NetworkError(format!("SSH error: {}", e)),
57 PostgresError::SshExecution { message, command } => PortError::OperationFailed(format!(
58 "SSH command '{}' failed: {}",
59 command, message
60 )),
61 PostgresError::Installation(msg) => PortError::OperationFailed(format!("Installation failed: {}", msg)),
62 PostgresError::Configuration(msg) => PortError::InvalidConfiguration(msg),
63 PostgresError::NotInstalled => PortError::OperationFailed("PostgreSQL is not installed".to_string()),
64 PostgresError::AlreadyInstalled(version) => PortError::AlreadyExists {
65 resource_type: "PostgreSQL installation".to_string(),
66 resource_id: version,
67 },
68 PostgresError::InvalidVersion(version) => {
69 PortError::InvalidConfiguration(format!("Invalid PostgreSQL version: {}", version))
70 }
71 PostgresError::InvalidConfig { parameter, value } => {
72 PortError::InvalidConfiguration(format!("Invalid config {} = {}", parameter, value))
73 }
74 PostgresError::MissingConfig(msg) => PortError::InvalidConfiguration(format!("Missing config: {}", msg)),
75 PostgresError::ServiceError(msg) => PortError::OperationFailed(format!("Service error: {}", msg)),
76 PostgresError::ConnectionTest(msg) => {
77 PortError::OperationFailed(format!("Connection test failed: {}", msg))
78 }
79 PostgresError::Uninstallation(msg) => {
80 PortError::OperationFailed(format!("Uninstallation failed: {}", msg))
81 }
82 PostgresError::Io(e) => PortError::OperationFailed(format!("IO error: {}", e)),
83 PostgresError::Serialization(e) => PortError::OperationFailed(format!("Serialization error: {}", e)),
84 PostgresError::Other(msg) => PortError::OperationFailed(msg),
85 }
86}
87
88#[async_trait]
89impl DatabaseProvider for PostgresAdapter {
90 async fn create_database(&self, request: DatabaseCreateRequest) -> PortResult<CreatedDatabase> {
91 let mut ssh = self.create_ssh_client(&request.host, &request.ssh_key_path).await?;
92
93 crate::create_database_with_options(
96 &mut ssh,
97 &request.name,
98 Some(&request.owner),
99 request.encoding.as_deref(),
100 None, )
102 .await
103 .map_err(convert_error)?;
104
105 let connection_string = format!(
107 "postgresql://{}@{}:{}/{}",
108 request.owner, request.host, request.port, request.name
109 );
110
111 Ok(CreatedDatabase {
112 name: request.name.clone(),
113 owner: request.owner.clone(),
114 connection_string,
115 host: request.host,
116 port: request.port,
117 })
118 }
119
120 async fn drop_database(&self, name: &str, host: &str, port: u16, ssh_key_path: &str) -> PortResult<()> {
121 let mut ssh = self.create_ssh_client(host, ssh_key_path).await?;
122
123 crate::drop_database(&mut ssh, name)
124 .await
125 .map_err(convert_error)?;
126
127 Ok(())
128 }
129
130 async fn list_databases(&self, host: &str, port: u16, ssh_key_path: &str) -> PortResult<Vec<String>> {
131 let mut ssh = self.create_ssh_client(host, ssh_key_path).await?;
132
133 let databases = crate::list_databases(&mut ssh)
134 .await
135 .map_err(convert_error)?;
136
137 Ok(databases.into_iter().map(|db| db.name).collect())
138 }
139
140 async fn database_exists(&self, name: &str, host: &str, port: u16, ssh_key_path: &str) -> PortResult<bool> {
141 let mut ssh = self.create_ssh_client(host, ssh_key_path).await?;
142
143 crate::database_exists(&mut ssh, name)
144 .await
145 .map_err(convert_error)
146 }
147
148 async fn create_user(&self, request: DatabaseUserRequest) -> PortResult<DatabaseUser> {
149 let mut ssh = self.create_ssh_client(&request.host, &request.ssh_key_path).await?;
150
151 crate::create_user_with_options(
153 &mut ssh,
154 &request.username,
155 &request.password,
156 request.superuser,
157 false, false, Some(-1), )
161 .await
162 .map_err(convert_error)?;
163
164 Ok(DatabaseUser {
165 username: request.username,
166 superuser: request.superuser,
167 })
168 }
169
170 async fn drop_user(&self, username: &str, host: &str, port: u16, ssh_key_path: &str) -> PortResult<()> {
171 let mut ssh = self.create_ssh_client(host, ssh_key_path).await?;
172
173 crate::drop_user(&mut ssh, username)
174 .await
175 .map_err(convert_error)?;
176
177 Ok(())
178 }
179
180 async fn grant_privileges(&self, database: &str, username: &str, host: &str, port: u16, ssh_key_path: &str) -> PortResult<()> {
181 let mut ssh = self.create_ssh_client(host, ssh_key_path).await?;
182
183 crate::grant_privileges(
186 &mut ssh,
187 database,
188 username,
189 &[Privilege::All],
190 )
191 .await
192 .map_err(convert_error)?;
193
194 Ok(())
195 }
196}