From e83220f0f4721bbec8d4927a3c51d6c51298a523 Mon Sep 17 00:00:00 2001 From: Markus Pettersson Date: Fri, 13 Dec 2024 08:49:04 +0100 Subject: [PATCH] Add better APIs for making shh:ed files executable --- test/test-manager/src/vm/provision.rs | 53 +++++++++++++++++++++------ 1 file changed, 41 insertions(+), 12 deletions(-) diff --git a/test/test-manager/src/vm/provision.rs b/test/test-manager/src/vm/provision.rs index 7bda85f2a4bf..1ca962cbfb2a 100644 --- a/test/test-manager/src/vm/provision.rs +++ b/test/test-manager/src/vm/provision.rs @@ -4,7 +4,7 @@ use crate::{ tests::config::BOOTSTRAP_SCRIPT, }; use anyhow::{bail, Context, Result}; -use ssh2::Session; +use ssh2::{File, Session}; use std::{ io::{self, Read}, net::{IpAddr, SocketAddr, TcpStream}, @@ -115,27 +115,27 @@ fn blocking_ssh( let temp_dir = Path::new(remote_temp_dir); // Transfer a test runner let source = local_runner_dir.join("test-runner"); - ssh_send_file(&session, &source, temp_dir) + ssh_send_file_with_opts(&session, &source, temp_dir, FileOpts { executable: true }) .with_context(|| format!("Failed to send '{source:?}' to remote"))?; // Transfer connection-checker let source = local_runner_dir.join("connection-checker"); - ssh_send_file(&session, &source, temp_dir) + ssh_send_file_with_opts(&session, &source, temp_dir, FileOpts { executable: true }) .with_context(|| format!("Failed to send '{source:?}' to remote"))?; // Transfer app packages let source = &local_app_manifest.app_package_path; - ssh_send_file(&session, source, temp_dir) + ssh_send_file_with_opts(&session, source, temp_dir, FileOpts { executable: true }) .with_context(|| format!("Failed to send '{source:?}' to remote"))?; if let Some(source) = &local_app_manifest.app_package_to_upgrade_from_path { - ssh_send_file(&session, source, temp_dir) + ssh_send_file_with_opts(&session, source, temp_dir, FileOpts { executable: true }) .with_context(|| format!("Failed to send '{source:?}' to remote"))?; } else { log::warn!("No previous app package to upgrade from to send to remote") } if let Some(source) = &local_app_manifest.gui_package_path { - ssh_send_file(&session, source, temp_dir) + ssh_send_file_with_opts(&session, source, temp_dir, FileOpts { executable: true }) .with_context(|| format!("Failed to send '{source:?}' to remote"))?; } else { log::warn!("No UI e2e test to send to remote") @@ -182,13 +182,14 @@ fn blocking_ssh( Ok(remote_dir.to_string()) } -/// Copy a `source` file to `dest_dir` in the test runner. +/// Copy a `source` file to `dest_dir` in the test runner with opts. /// -/// Returns the aboslute path in the test runner where the file is stored. -fn ssh_send_file + Copy>( +/// Returns the absolute path in the test runner where the file is stored. +fn ssh_send_file_with_opts + Copy>( session: &Session, source: P, dest_dir: &Path, + opts: FileOpts, ) -> Result { let dest = dest_dir.join( source @@ -206,18 +207,46 @@ fn ssh_send_file + Copy>( let source = std::fs::read(source) .with_context(|| format!("Failed to open file at {}", source.as_ref().display()))?; - ssh_write(session, &dest, &source[..])?; + ssh_write_with_opts(session, &dest, &source[..], opts)?; Ok(dest) } -/// Analogues to [`std::fs::write`], but over ssh! -fn ssh_write>(session: &Session, dest: P, mut source: impl Read) -> Result<()> { +/// Create a new file with opts at location `dest` and write the content of `source` into it. +/// Returns a handle to the newly created file. +fn ssh_write_with_opts>( + session: &Session, + dest: P, + mut source: impl Read, + opts: FileOpts, +) -> Result { let sftp = session.sftp()?; let mut remote_file = sftp.create(dest.as_ref())?; io::copy(&mut source, &mut remote_file).context("failed to write file")?; + if opts.executable { + make_executable(&mut remote_file)?; + }; + + Ok(remote_file) +} + +/// Extra options that may be necessary to configure for files written to the test runner VM. +/// Used in conjunction with the `ssh_*_with_opts` functions. +#[derive(Clone, Copy, Debug, Default)] +struct FileOpts { + /// If file should be executable. + executable: bool, +} + +fn make_executable(file: &mut File) -> Result<()> { + // Make sure that the script is executable! + let mut file_stat = file.stat()?; + // 0x111 is the executable bit for Owner/Group/Public + let perm = file_stat.perm.map(|perm| perm | 0x111).unwrap_or(0x111); + file_stat.perm = Some(perm); + file.setstat(file_stat)?; Ok(()) }