@@ -15,16 +15,60 @@ use crate::infrastructure::adapters::ssh::SshCredentials;
15
15
#[ derive( Debug , thiserror:: Error ) ]
16
16
pub enum SshKeySetupError {
17
17
/// Failed to read SSH public key file
18
- #[ error( "Failed to read SSH public key from {path}: {source}" ) ]
18
+ #[ error( "Failed to read SSH public key from ' {path}' for user '{ssh_user}' : {source}" ) ]
19
19
SshKeyFileRead {
20
20
path : String ,
21
+ ssh_user : String ,
21
22
#[ source]
22
23
source : std:: io:: Error ,
23
24
} ,
24
25
25
- /// Failed to execute SSH key setup command in container
26
- #[ error( "Failed to setup SSH keys in container: {source}" ) ]
26
+ /// SSH public key file is empty or invalid
27
+ #[ error(
28
+ "SSH public key file '{path}' is empty or contains invalid data for user '{ssh_user}'"
29
+ ) ]
30
+ SshKeyFileEmpty { path : String , ssh_user : String } ,
31
+
32
+ /// Failed to create SSH directory in container
33
+ #[ error( "Failed to create SSH directory '/home/{ssh_user}/.ssh' in container: {source}" ) ]
34
+ SshDirectoryCreationFailed {
35
+ ssh_user : String ,
36
+ #[ source]
37
+ source : testcontainers:: TestcontainersError ,
38
+ } ,
39
+
40
+ /// Failed to write `authorized_keys` file in container
41
+ #[ error( "Failed to write authorized_keys file for user '{ssh_user}' in container: {source}" ) ]
42
+ AuthorizedKeysWriteFailed {
43
+ ssh_user : String ,
44
+ #[ source]
45
+ source : testcontainers:: TestcontainersError ,
46
+ } ,
47
+
48
+ /// Failed to set SSH directory permissions in container
49
+ #[ error(
50
+ "Failed to set SSH directory permissions for user '{ssh_user}' in container: {source}"
51
+ ) ]
52
+ SshPermissionsFailed {
53
+ ssh_user : String ,
54
+ #[ source]
55
+ source : testcontainers:: TestcontainersError ,
56
+ } ,
57
+
58
+ /// Failed to change ownership of SSH directory in container
59
+ #[ error(
60
+ "Failed to change ownership of SSH directory to user '{ssh_user}' in container: {source}"
61
+ ) ]
62
+ SshOwnershipFailed {
63
+ ssh_user : String ,
64
+ #[ source]
65
+ source : testcontainers:: TestcontainersError ,
66
+ } ,
67
+
68
+ /// Generic SSH key setup failure (fallback for unspecific errors)
69
+ #[ error( "SSH key setup failed for user '{ssh_user}' in container: {source}" ) ]
27
70
SshKeySetupFailed {
71
+ ssh_user : String ,
28
72
#[ source]
29
73
source : testcontainers:: TestcontainersError ,
30
74
} ,
@@ -91,6 +135,7 @@ impl SshKeySetupAction {
91
135
fs:: read_to_string ( & ssh_credentials. ssh_pub_key_path ) . map_err ( |source| {
92
136
SshKeySetupError :: SshKeyFileRead {
93
137
path : ssh_credentials. ssh_pub_key_path . display ( ) . to_string ( ) ,
138
+ ssh_user : ssh_credentials. ssh_username . clone ( ) ,
94
139
source,
95
140
}
96
141
} ) ?;
@@ -101,14 +146,15 @@ impl SshKeySetupAction {
101
146
let authorized_keys_path = format ! ( "{user_ssh_dir}/authorized_keys" ) ;
102
147
103
148
// Execute each command separately for better error handling
104
- Self :: create_ssh_directory ( container, & user_ssh_dir) ?;
149
+ Self :: create_ssh_directory ( container, ssh_user , & user_ssh_dir) ?;
105
150
Self :: add_public_key_to_authorized_keys (
106
151
container,
152
+ ssh_user,
107
153
& public_key_content,
108
154
& authorized_keys_path,
109
155
) ?;
110
- Self :: set_ssh_directory_permissions ( container, & user_ssh_dir) ?;
111
- Self :: set_authorized_keys_permissions ( container, & authorized_keys_path) ?;
156
+ Self :: set_ssh_directory_permissions ( container, ssh_user , & user_ssh_dir) ?;
157
+ Self :: set_authorized_keys_permissions ( container, ssh_user , & authorized_keys_path) ?;
112
158
113
159
info ! (
114
160
ssh_user = ssh_user,
@@ -120,19 +166,27 @@ impl SshKeySetupAction {
120
166
}
121
167
122
168
/// Create the SSH directory for the user
123
- fn create_ssh_directory < T : ContainerExecutor > ( container : & T , user_ssh_dir : & str ) -> Result < ( ) > {
169
+ fn create_ssh_directory < T : ContainerExecutor > (
170
+ container : & T ,
171
+ ssh_user : & str ,
172
+ user_ssh_dir : & str ,
173
+ ) -> Result < ( ) > {
124
174
let command = ExecCommand :: new ( [ "sh" , "-c" , & format ! ( "mkdir -p {user_ssh_dir}" ) ] ) ;
125
175
126
176
container
127
177
. exec ( command)
128
- . map_err ( |source| SshKeySetupError :: SshKeySetupFailed { source } ) ?;
178
+ . map_err ( |source| SshKeySetupError :: SshDirectoryCreationFailed {
179
+ ssh_user : ssh_user. to_string ( ) ,
180
+ source,
181
+ } ) ?;
129
182
130
183
Ok ( ( ) )
131
184
}
132
185
133
186
/// Add the public key to the `authorized_keys` file
134
187
fn add_public_key_to_authorized_keys < T : ContainerExecutor > (
135
188
container : & T ,
189
+ ssh_user : & str ,
136
190
public_key_content : & str ,
137
191
authorized_keys_path : & str ,
138
192
) -> Result < ( ) > {
@@ -147,35 +201,46 @@ impl SshKeySetupAction {
147
201
148
202
container
149
203
. exec ( command)
150
- . map_err ( |source| SshKeySetupError :: SshKeySetupFailed { source } ) ?;
204
+ . map_err ( |source| SshKeySetupError :: AuthorizedKeysWriteFailed {
205
+ ssh_user : ssh_user. to_string ( ) ,
206
+ source,
207
+ } ) ?;
151
208
152
209
Ok ( ( ) )
153
210
}
154
211
155
212
/// Set permissions on the SSH directory (700)
156
213
fn set_ssh_directory_permissions < T : ContainerExecutor > (
157
214
container : & T ,
215
+ ssh_user : & str ,
158
216
user_ssh_dir : & str ,
159
217
) -> Result < ( ) > {
160
218
let command = ExecCommand :: new ( [ "sh" , "-c" , & format ! ( "chmod 700 {user_ssh_dir}" ) ] ) ;
161
219
162
220
container
163
221
. exec ( command)
164
- . map_err ( |source| SshKeySetupError :: SshKeySetupFailed { source } ) ?;
222
+ . map_err ( |source| SshKeySetupError :: SshPermissionsFailed {
223
+ ssh_user : ssh_user. to_string ( ) ,
224
+ source,
225
+ } ) ?;
165
226
166
227
Ok ( ( ) )
167
228
}
168
229
169
230
/// Set permissions on the `authorized_keys` file (600)
170
231
fn set_authorized_keys_permissions < T : ContainerExecutor > (
171
232
container : & T ,
233
+ ssh_user : & str ,
172
234
authorized_keys_path : & str ,
173
235
) -> Result < ( ) > {
174
236
let command = ExecCommand :: new ( [ "sh" , "-c" , & format ! ( "chmod 600 {authorized_keys_path}" ) ] ) ;
175
237
176
238
container
177
239
. exec ( command)
178
- . map_err ( |source| SshKeySetupError :: SshKeySetupFailed { source } ) ?;
240
+ . map_err ( |source| SshKeySetupError :: SshOwnershipFailed {
241
+ ssh_user : ssh_user. to_string ( ) ,
242
+ source,
243
+ } ) ?;
179
244
180
245
Ok ( ( ) )
181
246
}
@@ -209,17 +274,20 @@ mod tests {
209
274
fn it_should_have_proper_error_display_messages ( ) {
210
275
let error = SshKeySetupError :: SshKeyFileRead {
211
276
path : "/path/to/key" . to_string ( ) ,
277
+ ssh_user : "testuser" . to_string ( ) ,
212
278
source : std:: io:: Error :: new ( std:: io:: ErrorKind :: NotFound , "file not found" ) ,
213
279
} ;
214
280
assert ! ( error. to_string( ) . contains( "Failed to read SSH public key" ) ) ;
215
281
assert ! ( error. to_string( ) . contains( "/path/to/key" ) ) ;
282
+ assert ! ( error. to_string( ) . contains( "testuser" ) ) ;
216
283
}
217
284
218
285
#[ test]
219
286
fn it_should_preserve_error_chain_for_ssh_key_file_read ( ) {
220
287
let io_error = std:: io:: Error :: new ( std:: io:: ErrorKind :: NotFound , "file not found" ) ;
221
288
let error = SshKeySetupError :: SshKeyFileRead {
222
289
path : "/path/to/key" . to_string ( ) ,
290
+ ssh_user : "testuser" . to_string ( ) ,
223
291
source : io_error,
224
292
} ;
225
293
@@ -230,10 +298,12 @@ mod tests {
230
298
fn it_should_preserve_error_chain_for_ssh_key_setup_failed ( ) {
231
299
let testcontainers_error = testcontainers:: TestcontainersError :: other ( "test error" ) ;
232
300
let error = SshKeySetupError :: SshKeySetupFailed {
301
+ ssh_user : "testuser" . to_string ( ) ,
233
302
source : testcontainers_error,
234
303
} ;
235
304
236
305
assert ! ( error. source( ) . is_some( ) ) ;
306
+ assert ! ( error. to_string( ) . contains( "testuser" ) ) ;
237
307
}
238
308
239
309
// Helper function to create mock SSH credentials for testing
0 commit comments