All checks were successful
CI / build (push) Successful in 1m50s
pkh context allows to manage contexts (local, ssh) and run contextualized commands (deb) in other words, this allows building binary packages over ssh
114 lines
3.4 KiB
Rust
114 lines
3.4 KiB
Rust
use serde::{Deserialize, Serialize};
|
|
use std::ffi::OsStr;
|
|
use std::io;
|
|
use std::path::{Path, PathBuf};
|
|
|
|
use super::local::LocalDriver;
|
|
use super::ssh::SshDriver;
|
|
|
|
/// Internal trait defining the strategy for executing a command.
|
|
///
|
|
/// This allows to have different execution behaviors (Local, SSH, ...) while keeping the
|
|
/// `ContextCommand` API consistent.
|
|
pub trait CommandRunner {
|
|
fn add_arg(&mut self, arg: String);
|
|
fn status(&mut self) -> io::Result<std::process::ExitStatus>;
|
|
fn output(&mut self) -> io::Result<std::process::Output>;
|
|
}
|
|
|
|
pub trait ContextDriver {
|
|
fn ensure_available(&self, src: &Path, dest_root: &str) -> io::Result<PathBuf>;
|
|
fn create_runner(&self, program: String) -> Box<dyn CommandRunner>;
|
|
fn prepare_work_dir(&self) -> io::Result<String>;
|
|
}
|
|
|
|
/// Represents an execution environment (Local or via SSH).
|
|
///
|
|
/// This is the data model that configuration files store. It defines *where* operations happen.
|
|
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
|
|
#[serde(tag = "type")]
|
|
#[derive(Default)]
|
|
pub enum Context {
|
|
#[serde(rename = "local")]
|
|
#[default]
|
|
Local,
|
|
#[serde(rename = "ssh")]
|
|
Ssh {
|
|
host: String,
|
|
user: Option<String>,
|
|
port: Option<u16>,
|
|
},
|
|
}
|
|
|
|
impl Context {
|
|
pub fn command<S: AsRef<OsStr>>(&self, program: S) -> ContextCommand {
|
|
let runner = self
|
|
.driver()
|
|
.create_runner(program.as_ref().to_string_lossy().to_string());
|
|
ContextCommand { runner }
|
|
}
|
|
|
|
/// Ensures that the source file/directory exists at the destination context.
|
|
///
|
|
/// If Local: Returns the absolute path of `src`.
|
|
/// If Remote: Copies `src` to `dest_root` on the remote and returns the path to the copied entity.
|
|
pub fn ensure_available(&self, src: &Path, dest_root: &str) -> io::Result<PathBuf> {
|
|
self.driver().ensure_available(src, dest_root)
|
|
}
|
|
|
|
pub fn prepare_work_dir(&self) -> io::Result<String> {
|
|
self.driver().prepare_work_dir()
|
|
}
|
|
|
|
fn driver(&self) -> Box<dyn ContextDriver> {
|
|
match self {
|
|
Context::Local => Box::new(LocalDriver),
|
|
Context::Ssh { host, user, port } => Box::new(SshDriver {
|
|
host: host.clone(),
|
|
user: user.clone(),
|
|
port: *port,
|
|
}),
|
|
}
|
|
}
|
|
}
|
|
|
|
/// A builder and executor for commands within a specific `Context`.
|
|
///
|
|
/// This struct acts as a high-level facade (API) for command creation. It hides the implementation
|
|
/// details of *how* the command is actually executed, allowing the user to simple chain arguments
|
|
/// and call `status()` or `output()`.
|
|
///
|
|
/// It delegates the actual work to a `CommandRunner`.
|
|
pub struct ContextCommand {
|
|
runner: Box<dyn CommandRunner>,
|
|
}
|
|
|
|
impl ContextCommand {
|
|
pub fn arg<S: AsRef<OsStr>>(&mut self, arg: S) -> &mut Self {
|
|
self.runner
|
|
.add_arg(arg.as_ref().to_string_lossy().to_string());
|
|
self
|
|
}
|
|
|
|
// Support chaining args
|
|
pub fn args<I, S>(&mut self, args: I) -> &mut Self
|
|
where
|
|
I: IntoIterator<Item = S>,
|
|
S: AsRef<OsStr>,
|
|
{
|
|
for arg in args {
|
|
self.arg(arg);
|
|
}
|
|
self
|
|
}
|
|
|
|
pub fn status(&mut self) -> io::Result<std::process::ExitStatus> {
|
|
self.runner.status()
|
|
}
|
|
|
|
// Capture output
|
|
pub fn output(&mut self) -> io::Result<std::process::Output> {
|
|
self.runner.output()
|
|
}
|
|
}
|