Skip to content

Instantly share code, notes, and snippets.

@pnathan
Created October 11, 2015 17:53
Show Gist options
  • Select an option

  • Save pnathan/30e283cbcf2bf4f46346 to your computer and use it in GitHub Desktop.

Select an option

Save pnathan/30e283cbcf2bf4f46346 to your computer and use it in GitHub Desktop.
A pre-1.0 command runner in Rust
/*
action module.
The action module is a simplification of running processes. In
particular, it takes a bash-executable string and runs it under -xe
context.
*/
use std::io::File;
use std::io::process::Command;
use std::str::from_utf8_lossy;
/*
An action is always the same: a string that is ginned up into a
bash script and executed.
*/
#[deriving(Show, Eq, PartialEq, Clone)]
pub struct Action {
pub bash : String
}
// Helper function
pub fn new(bash: String) -> Action {
return Action { bash : bash};
}
#[deriving(Show, Clone, PartialEq)]
pub enum ActionResult {
ActionStillGoingOn,
ActionSuccess(String, String),
ActionFailure(String, String),
ActionError(String),
ActionSuccessCombined(String, String, String)
}
fn concatenate(first: String, second: String) -> String {
let pair = [first, second];
pair.concat()
}
/*
TODO:
- add tempfile calls
*/
impl Action {
fn write_commands_to_file(&self, filename: &String ) -> () {
let execution = self.bash.clone();
let path = Path::new(filename.clone());
let mut fh = File::create(&path);
fh.write_line(execution.as_slice());
}
pub fn run(&self) -> ActionResult {
// TODO: need tempfile.
let filename = "temprun.sh".to_string();
self.write_commands_to_file(&filename);
let process = Command::new("bash").arg("-e").arg(filename).output();
match process {
Ok(output) => {
match output.status.success() {
true => {
ActionSuccess(
format!("{}", from_utf8_lossy(output.output.as_slice())),
format!("{}", from_utf8_lossy(output.error.as_slice())))
}
false => {
ActionFailure(
format!("{}", from_utf8_lossy(output.output.as_slice())),
format!("{}", from_utf8_lossy(output.error.as_slice())))
}
}
}
Err(e) => {
ActionError(format!("error executing job {}", e).to_string())
}
}
}
pub fn run_long(&self, trigger: |String| -> ()) -> ActionResult {
let (tx, rx) = channel();
let (finaltx, finalrx) = sync_channel::<ActionResult>(0);
let clone = self.clone();
spawn(proc() {
let sender = |s:String| -> () {
tx.send(s.clone());
};
let result = clone.run_sending_to_buffers(sender);
finaltx.send(result);
});
let result = finalrx.recv();
for msg in rx.iter() {
trigger(msg);
}
return result;
}
/*
The trigger is called on each stdout/stderr buffer consumption.
*/
pub fn run_sending_to_buffers(&self, trigger: |String| -> ()) -> ActionResult {
let filename = "temprun.sh".to_string();
self.write_commands_to_file(&filename);
let mut pobject = match Command::new("bash").arg("-e").arg(filename).spawn() {
Ok(child) => child,
Err(e) => { return ActionError(format!("Error spawning: {}", e)); }
};
let mut combined = String::new();
let mut stdouts = String::new();
let mut stderrs = String::new();
// Attempt to read up to buffersize. 1024 was chosen as a nice
// buffer slurp. it is entirely possible that interleaving
// will happen, badly.
let buffersize = 1024;
loop {
let mut temp_stdout: Vec<u8> = Vec::new();
let mut temp_stderr: Vec<u8> = Vec::new();
// Poke our child process with a signal to see if he's alive.
let cont = match pobject.signal(0) {
Ok(_) => { true } // alive!
Err(_) => { false } // exited
};
pobject.stdout.get_mut_ref().push_at_least(1, buffersize, &mut temp_stdout);
pobject.stderr.get_mut_ref().push_at_least(1, buffersize, &mut temp_stderr);
match String::from_utf8(temp_stdout) {
Ok(string) => {
trigger(string.clone());
combined = concatenate(combined, string.clone());
stdouts = concatenate(stdouts, string);
},
Err(e) => { return ActionError(format!("Unable to read string {} ", e)); }
}
match String::from_utf8(temp_stderr) {
Ok(string) => {
trigger(string.clone());
combined = concatenate(combined, string.clone());
stderrs = concatenate(stderrs, string);
},
Err(e) => { return ActionError(format!("Unable to read string {} ", e)); }
}
if cont == false {
break
}
}
return ActionSuccessCombined(combined.clone(),
stdouts.clone(),
stderrs.clone());
}
/* Runs the action over on `host`, storing the script in /tmp
*/
pub fn run_remotely(&self, host: String) -> ActionResult {
let destfile = "remotefile.sh".to_string();
self.write_commands_to_file(&destfile);
// copy the command file over
let copier = new(format!("scp remotefile.sh {}:/tmp", host).to_string());
let result : ActionResult = copier.run();
match result {
ActionStillGoingOn => {/* fail here?*/}
// no-op and keep moving
ActionSuccess(_,_) => {},
ActionSuccessCombined(_,_,_) => {},
ActionError(_) => {
return result
}
ActionFailure(_,_) => {
return result
}
}
let new_command = format!("ssh {} bash -e /tmp/{}", host, destfile);
let runner = new(new_command);
runner.run()
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment