|
| 1 | +use anyhow::{anyhow, Context}; |
1 | 2 | use std::collections::HashMap;
|
| 3 | +use std::io::Write; |
2 | 4 | use std::path::{Path, PathBuf};
|
| 5 | +use std::process::Command; |
3 | 6 |
|
4 | 7 | use swirl::PerformError;
|
5 | 8 | use tempfile::TempDir;
|
@@ -48,6 +51,45 @@ impl Credentials {
|
48 | 51 | }
|
49 | 52 | }
|
50 | 53 | }
|
| 54 | + |
| 55 | + /// Write the SSH key to a temporary file and return the path. The file is |
| 56 | + /// deleted once the returned path is dropped. |
| 57 | + /// |
| 58 | + /// This function can be used when running `git push` instead of using the |
| 59 | + /// `git2` crate for pushing commits to remote git servers. |
| 60 | + /// |
| 61 | + /// Note: On Linux this function creates the temporary file in `/dev/shm` to |
| 62 | + /// avoid writing it to disk. |
| 63 | + /// |
| 64 | + /// # Errors |
| 65 | + /// |
| 66 | + /// - If non-SSH credentials are use, `Err` is returned. |
| 67 | + /// - If creation of the temporary file fails, `Err` is returned. |
| 68 | + /// |
| 69 | + fn write_temporary_ssh_key(&self) -> anyhow::Result<tempfile::TempPath> { |
| 70 | + let key = match self { |
| 71 | + Credentials::Ssh { key } => key, |
| 72 | + _ => return Err(anyhow!("SSH key not available")), |
| 73 | + }; |
| 74 | + |
| 75 | + let dir = if cfg!(target_os = "linux") { |
| 76 | + // When running on production, ensure the file is created in tmpfs and not persisted to disk |
| 77 | + "/dev/shm".into() |
| 78 | + } else { |
| 79 | + // For other platforms, default to std::env::tempdir() |
| 80 | + std::env::temp_dir() |
| 81 | + }; |
| 82 | + |
| 83 | + let mut temp_key_file = tempfile::Builder::new() |
| 84 | + .tempfile_in(dir) |
| 85 | + .context("Failed to create temporary file")?; |
| 86 | + |
| 87 | + temp_key_file |
| 88 | + .write_all(key.as_bytes()) |
| 89 | + .context("Failed to write SSH key to temporary file")?; |
| 90 | + |
| 91 | + Ok(temp_key_file.into_temp_path()) |
| 92 | + } |
51 | 93 | }
|
52 | 94 |
|
53 | 95 | #[derive(Serialize, Deserialize, Debug)]
|
@@ -137,10 +179,9 @@ impl RepositoryConfig {
|
137 | 179 | }
|
138 | 180 |
|
139 | 181 | pub struct Repository {
|
140 |
| - /// bla |
141 |
| - pub checkout_path: TempDir, |
| 182 | + checkout_path: TempDir, |
142 | 183 | repository: git2::Repository,
|
143 |
| - pub credentials: Credentials, |
| 184 | + credentials: Credentials, |
144 | 185 | }
|
145 | 186 |
|
146 | 187 | impl Repository {
|
@@ -346,4 +387,32 @@ impl Repository {
|
346 | 387 |
|
347 | 388 | Ok(())
|
348 | 389 | }
|
| 390 | + |
| 391 | + /// Runs the specified `git` command in the working directory of the local |
| 392 | + /// crate index repository. |
| 393 | + /// |
| 394 | + /// This function also temporarily sets the `GIT_SSH_COMMAND` environment |
| 395 | + /// variable to ensure that `git push` commands are able to succeed. |
| 396 | + pub fn run_command(&self, command: &mut Command) -> Result<(), PerformError> { |
| 397 | + let checkout_path = self.checkout_path.path(); |
| 398 | + command.current_dir(checkout_path); |
| 399 | + |
| 400 | + let temp_key_path = self.credentials.write_temporary_ssh_key()?; |
| 401 | + command.env( |
| 402 | + "GIT_SSH_COMMAND", |
| 403 | + format!( |
| 404 | + "ssh -o StrictHostKeyChecking=accept-new -i {}", |
| 405 | + temp_key_path.display() |
| 406 | + ), |
| 407 | + ); |
| 408 | + |
| 409 | + let output = command.output()?; |
| 410 | + if !output.status.success() { |
| 411 | + let stderr = String::from_utf8_lossy(&output.stderr); |
| 412 | + let message = format!("Running git command failed with: {}", stderr); |
| 413 | + return Err(message.into()); |
| 414 | + } |
| 415 | + |
| 416 | + Ok(()) |
| 417 | + } |
349 | 418 | }
|
0 commit comments