Created
October 11, 2015 17:53
-
-
Save pnathan/30e283cbcf2bf4f46346 to your computer and use it in GitHub Desktop.
A pre-1.0 command runner in Rust
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| /* | |
| 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