Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

pre/post hooks #25

Merged
merged 4 commits into from
Nov 8, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ mod procdata;
mod profile;
mod rootfs;
mod scanner;
mod shcall;
use crate::profile::Profile;
use clap::{ArgMatches, Command};
use colored::Colorize;
Expand Down
36 changes: 36 additions & 0 deletions src/procdata.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ use crate::{
profile::Profile,
rootfs,
scanner::{binlib::ElfScanner, debpkg::DebPackageScanner, dlst::ContentFormatter, general::Scanner},
shcall::ShellScript,
};
use std::fs::{self, canonicalize, remove_file, DirEntry, File};
use std::{
Expand Down Expand Up @@ -141,14 +142,42 @@ impl TintProcessor {
np
}

/// Call a script hook
fn call_script(s: String) -> Result<(), Error> {
// XXX: It can run args, but from where pass them? Profile? CLI? Both? None at all?..
let (stdout, stderr) = ShellScript::new(s, None).run()?;

if !stdout.is_empty() {
log::debug!("Post-hook stdout:");
log::debug!("{}", stdout);
}

if !stderr.is_empty() {
log::error!("Post-hook error:");
log::error!("{}", stderr);
}

Ok(())
}

// Start tint processor
pub fn start(&self) -> Result<(), Error> {
self.switch_root()?;

// Bail-out if the image is already processed
if self.lockfile.exists() {
return Err(Error::new(std::io::ErrorKind::AlreadyExists, "This container seems already tinted."));
}

// Run pre-hook, if any
if self.profile.has_pre_hook() {
if self.dry_run {
log::debug!("Pre-hook:\n{}", self.profile.get_pre_hook());
} else {
Self::call_script(self.profile.get_pre_hook())?;
}
}

// Paths to keep
let mut paths: HashSet<PathBuf> = HashSet::default();

Expand Down Expand Up @@ -210,8 +239,15 @@ impl TintProcessor {
paths.sort();

if self.dry_run {
if self.profile.has_post_hook() {
log::debug!("Post-hook:\n{}", self.profile.get_post_hook());
}
ContentFormatter::new(&paths).set_removed(&p).format();
} else {
// Run post-hook (doesn't affect changes apply)
if self.profile.has_post_hook() {
Self::call_script(self.profile.get_post_hook())?;
}
self.apply_changes(p)?;
}

Expand Down
45 changes: 45 additions & 0 deletions src/profile.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,13 @@ pub struct PTargets {
targets: Vec<String>,
packages: Option<Vec<String>>,
config: Option<PConfig>,
hooks: Option<PHooks>,
}

#[derive(Serialize, Deserialize, PartialEq, Debug)]
pub struct PHooks {
before: Option<String>,
after: Option<String>,
}

/// Profile
Expand All @@ -33,6 +40,10 @@ pub struct Profile {
packages: Vec<String>,
dropped_packages: Vec<String>,
targets: Vec<String>,

// hooks
s_pre: String,
s_post: String,
}

impl Profile {
Expand All @@ -48,11 +59,15 @@ impl Profile {
f_log: true,
f_img: true,
f_arc: true,

packages: vec![],
dropped_packages: vec![],
targets: vec![],
f_expl_prune: vec![],
f_expl_keep: vec![],

s_post: String::from(""),
s_pre: String::from(""),
}
}

Expand Down Expand Up @@ -129,6 +144,16 @@ impl Profile {
}
}

// Get hooks
if let Some(hooks) = p.hooks {
if let Some(pre) = hooks.before {
self.s_pre = pre;
}
if let Some(post) = hooks.after {
self.s_post = post;
}
}

Ok(())
}

Expand Down Expand Up @@ -270,4 +295,24 @@ impl Profile {
pub fn get_dropped_packages(&self) -> &Vec<String> {
&self.dropped_packages
}

/// Returns True if pre-hook defined
pub fn has_pre_hook(&self) -> bool {
!self.s_pre.is_empty()
}

/// Get pre-hook
pub fn get_pre_hook(&self) -> String {
self.s_pre.to_owned()
}

/// Returns True if post-hook defined
pub fn has_post_hook(&self) -> bool {
!self.s_post.is_empty()
}

/// Get post-hook
pub fn get_post_hook(&self) -> String {
self.s_post.to_owned()
}
}
63 changes: 63 additions & 0 deletions src/shcall.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
/*
Shell call
*/

use std::{
io::{Error, Write},
path::PathBuf,
process::{Command, Stdio},
};

const SHELL_DEFAULT: &str = "/usr/bin/sh";
const SHELLS: &[&str] = &["/usr/bin/bash", "/usr/bin/ksh", "/usr/bin/dash", "/usr/bin/zsh", "/usr/bin/ash"];

pub struct ShellScript {
data: String,

#[allow(dead_code)]
args: Vec<String>,
}

impl ShellScript {
/// Create a new Script wrapper
pub fn new(data: String, args: Option<Vec<String>>) -> Self {
let mut a: Vec<String> = Vec::default();
if let Some(args) = args {
a.extend(args);
}

let s = data.trim().to_string();
Self { data: if s.is_empty() { format!("#!{}\n", SHELL_DEFAULT) } else { s }, args: a }
}

/// Get script shebang or suggest one
fn detach_shebang(&self) -> Result<(String, String), Error> {
let data = self.data.split('\n').collect::<Vec<&str>>();
let shebang = data[0].trim().strip_prefix("#!").unwrap_or_default().to_string();
if PathBuf::from(&shebang).exists() {
return Ok((shebang, data[1..].join("\n")));
}

for s in SHELLS {
if PathBuf::from(s).exists() {
return Ok((s.to_string(), data.join("\n")));
}
}

Err(Error::new(std::io::ErrorKind::NotFound, "No supported shell has been found"))
}

/// Run script
pub fn run(&self) -> Result<(String, String), Error> {
let (shebang, script) = self.detach_shebang()?;

let mut p = Command::new(shebang).stdin(Stdio::piped()).stdout(Stdio::piped()).spawn()?;
p.stdin.as_mut().unwrap().write_all(script.as_bytes())?;

let out = p.wait_with_output()?;
Ok((
String::from_utf8(out.stdout).unwrap_or_else(|e| format!("Cannot get STDOUT: {}", e)),
String::from_utf8(out.stderr).unwrap_or_else(|e| format!("Cannot get STDERR: {}", e)),
))
}
}