diff --git a/ofborg/src/bin/build-filter.rs b/ofborg/src/bin/github-comment-filter.rs similarity index 89% rename from ofborg/src/bin/build-filter.rs rename to ofborg/src/bin/github-comment-filter.rs index b145f5f..9745c9e 100644 --- a/ofborg/src/bin/build-filter.rs +++ b/ofborg/src/bin/github-comment-filter.rs @@ -39,12 +39,12 @@ fn main() { let mut channel = session.open_channel(2).unwrap(); channel.basic_consume( - worker::new(tasks::buildfilter::BuildFilterWorker::new( + worker::new(tasks::githubcommentfilter::GitHubCommentWorker::new( cfg.acl(), cfg.github() )), "build-inputs", - format!("{}-build-filter", cfg.whoami()).as_ref(), + format!("{}-github-comment-filter", cfg.whoami()).as_ref(), false, false, false, diff --git a/ofborg/src/commentparser.rs b/ofborg/src/commentparser.rs index 134d3e0..68f9ffe 100644 --- a/ofborg/src/commentparser.rs +++ b/ofborg/src/commentparser.rs @@ -1,5 +1,5 @@ -pub fn parse(text: &str) -> Option { +pub fn parse(text: &str) -> Option> { let tokens: Vec = text.split_whitespace() .map(|s| s.to_owned()).collect(); @@ -7,21 +7,37 @@ pub fn parse(text: &str) -> Option { return None; } - let (targeter_, params_) = tokens.split_at(2); - let targeter: Vec = targeter_.iter() - .map(|s| s.to_lowercase()).collect(); - let params: Vec = params_.to_vec(); - - if targeter == ["@grahamcofborg", "build"] { - return Some(Instruction::Build(params)); + if tokens[0].to_lowercase() != "@grahamcofborg" { + return None; } - return None; + let commands: Vec<&[String]> = tokens + .split(|token| token.to_lowercase() == "@grahamcofborg") + .filter(|token| token.len() > 0) + .collect(); + + let mut instructions: Vec = vec![]; + for command in commands { + let (left, right) = command.split_at(1); + match left[0].as_ref() { + "build" => { + instructions.push(Instruction::Build(right.to_vec())) + } + "eval" => { + instructions.push(Instruction::Eval) + } + _ => {} + } + } + + return Some(instructions); } #[derive(PartialEq, Debug)] pub enum Instruction { - Build(Vec) + Build(Vec), + Eval + } #[cfg(test)] @@ -37,37 +53,94 @@ mod tests { #[test] fn bogus_comment() { - assert_eq!(None, parse(":) :) :)")); + assert_eq!(None, parse(":) :) :) @grahamcofborg build hi")); + } + + #[test] + fn eval_comment() { + assert_eq!(Some(vec![Instruction::Eval]), + parse("@grahamcofborg eval")); + } + + #[test] + fn eval_and_build_comment() { + assert_eq!(Some(vec![ + Instruction::Eval, + Instruction::Build(vec![ + String::from("foo"), + ]) + ]), + parse("@grahamcofborg eval @grahamcofborg build foo")); + } + + #[test] + fn build_and_eval_and_build_comment() { + assert_eq!(Some(vec![ + Instruction::Build(vec![ + String::from("bar"), + ]), + Instruction::Eval, + Instruction::Build(vec![ + String::from("foo"), + ]) + ]), + parse(" +@grahamcofborg build bar +@grahamcofborg eval +@grahamcofborg build foo")); + } + + #[test] + fn build_and_eval_comment() { + assert_eq!(Some(vec![ + Instruction::Build(vec![ + String::from("foo"), + ]), + Instruction::Eval, + ]), + parse("@grahamcofborg build foo @grahamcofborg eval")); } #[test] fn build_comment() { - assert_eq!(Some(Instruction::Build(vec![ + assert_eq!(Some(vec![Instruction::Build(vec![ String::from("foo"), String::from("bar"), String::from("baz") - ])), + ])]), + parse("@GrahamCOfBorg build foo bar + +baz")); + } + + #[test] + fn build_comment_newlines() { + assert_eq!(Some(vec![Instruction::Build(vec![ + String::from("foo"), + String::from("bar"), + String::from("baz") + ])]), parse("@GrahamCOfBorg build foo bar baz")); } #[test] fn build_comment_lower() { - assert_eq!(Some(Instruction::Build(vec![ + assert_eq!(Some(vec![Instruction::Build(vec![ String::from("foo"), String::from("bar"), String::from("baz") - ])), + ])]), parse("@grahamcofborg build foo bar baz")); } #[test] fn build_whitespace_disregarded() { - assert_eq!(Some(Instruction::Build(vec![ + assert_eq!(Some(vec![Instruction::Build(vec![ String::from("foo"), String::from("bar"), String::from("baz") - ])), + ])]), parse(" @@ -84,11 +157,11 @@ mod tests { #[test] fn build_comment_lower_package_case_retained() { - assert_eq!(Some(Instruction::Build(vec![ + assert_eq!(Some(vec![Instruction::Build(vec![ String::from("foo"), String::from("bar"), String::from("baz.Baz") - ])), + ])]), parse("@grahamcofborg build foo bar baz.Baz")); } diff --git a/ofborg/src/tasks/buildfilter.rs b/ofborg/src/tasks/buildfilter.rs deleted file mode 100644 index 6533a9b..0000000 --- a/ofborg/src/tasks/buildfilter.rs +++ /dev/null @@ -1,126 +0,0 @@ -extern crate amqp; -extern crate env_logger; - -use ofborg::ghevent; -use ofborg::acl; -use serde_json; - -use hubcaps; -use ofborg::message::{Repo, Pr, buildjob}; -use ofborg::worker; -use ofborg::commentparser; -use amqp::protocol::basic::{Deliver,BasicProperties}; - - -pub struct BuildFilterWorker { - acl: acl::ACL, - github: hubcaps::Github -} - -impl BuildFilterWorker { - pub fn new(acl: acl::ACL, github: hubcaps::Github) -> BuildFilterWorker { - return BuildFilterWorker{ - acl: acl, - github: github, - }; - } -} - -impl worker::SimpleWorker for BuildFilterWorker { - type J = ghevent::IssueComment; - - fn msg_to_job(&self, _: &Deliver, _: &BasicProperties, - body: &Vec) -> Result { - return match serde_json::from_slice(body) { - Ok(e) => { Ok(e) } - Err(e) => { - println!("Failed to deserialize IsssueComment: {:?}", String::from_utf8(body.clone())); - panic!("{:?}", e); - } - } - } - - fn consumer(&self, job: &ghevent::IssueComment) -> worker::Actions { - let instructions = commentparser::parse(&job.comment.body); - if instructions == None { - return vec![ - worker::Action::Ack - ]; - } - - if !self.acl.can_build(&job.comment.user.login, &job.repository.full_name) { - println!("ACL prohibits {} from building {:?} for {}", - job.comment.user.login, - instructions, - job.repository.full_name); - return vec![ - worker::Action::Ack - ]; - } - - println!("Got job: {:?}", job); - - let instructions = commentparser::parse(&job.comment.body); - println!("Instructions: {:?}", instructions); - - let pr = self.github - .repo(job.repository.owner.login.clone(), job.repository.name.clone()) - .pulls() - .get(job.issue.number) - .get(); - - if let Err(x) = pr { - info!("fetching PR {}#{} from GitHub yielded error {}", - job.repository.full_name, - job.issue.number, - x - ); - return vec![ - worker::Action::Ack - ]; - } - - let pr = pr.unwrap(); - - match instructions { - Some(commentparser::Instruction::Build(attrs)) => { - let msg = buildjob::BuildJob{ - repo: Repo { - clone_url: job.repository.clone_url.clone(), - full_name: job.repository.full_name.clone(), - owner: job.repository.owner.login.clone(), - name: job.repository.name.clone(), - }, - pr: Pr { - number: job.issue.number.clone(), - head_sha: pr.head.sha, - target_branch: Some(pr.base.commit_ref) - }, - attrs: attrs - }; - - let props = BasicProperties { - content_type: Some("application/json".to_owned()), - ..Default::default() - }; - - return vec![ - worker::Action::Publish(worker::QueueMsg{ - exchange: Some("build-jobs".to_owned()), - routing_key: None, - mandatory: true, - immediate: false, - properties: Some(props), - content: serde_json::to_string(&msg).unwrap().into_bytes() - }), - worker::Action::Ack - ]; - } - _ => { - return vec![ - worker::Action::Ack - ]; - } - } - } -} diff --git a/ofborg/src/tasks/githubcommentfilter.rs b/ofborg/src/tasks/githubcommentfilter.rs new file mode 100644 index 0000000..10ecb53 --- /dev/null +++ b/ofborg/src/tasks/githubcommentfilter.rs @@ -0,0 +1,160 @@ +extern crate amqp; +extern crate env_logger; + +use ofborg::ghevent; +use ofborg::acl; +use serde_json; + +use hubcaps; +use ofborg::message::{Repo, Pr, buildjob, massrebuildjob}; +use ofborg::worker; +use ofborg::commentparser; +use amqp::protocol::basic::{Deliver,BasicProperties}; + + +pub struct GitHubCommentWorker { + acl: acl::ACL, + github: hubcaps::Github +} + +impl GitHubCommentWorker { + pub fn new(acl: acl::ACL, github: hubcaps::Github) -> GitHubCommentWorker { + return GitHubCommentWorker{ + acl: acl, + github: github, + }; + } +} + +impl worker::SimpleWorker for GitHubCommentWorker { + type J = ghevent::IssueComment; + + fn msg_to_job(&self, _: &Deliver, _: &BasicProperties, + body: &Vec) -> Result { + return match serde_json::from_slice(body) { + Ok(e) => { Ok(e) } + Err(e) => { + println!("Failed to deserialize IsssueComment: {:?}", String::from_utf8(body.clone())); + panic!("{:?}", e); + } + } + } + + fn consumer(&self, job: &ghevent::IssueComment) -> worker::Actions { + let instructions = commentparser::parse(&job.comment.body); + if instructions == None { + return vec![ + worker::Action::Ack + ]; + } + + if !self.acl.can_build(&job.comment.user.login, &job.repository.full_name) { + println!("ACL prohibits {} from building {:?} for {}", + job.comment.user.login, + instructions, + job.repository.full_name); + return vec![ + worker::Action::Ack + ]; + } + + println!("Got job: {:?}", job); + + let instructions = commentparser::parse(&job.comment.body); + println!("Instructions: {:?}", instructions); + + let pr = self.github + .repo(job.repository.owner.login.clone(), job.repository.name.clone()) + .pulls() + .get(job.issue.number) + .get(); + + if let Err(x) = pr { + info!("fetching PR {}#{} from GitHub yielded error {}", + job.repository.full_name, + job.issue.number, + x + ); + return vec![ + worker::Action::Ack + ]; + } + + let pr = pr.unwrap(); + + let mut response: Vec = vec![]; + if let Some(instructions) = instructions { + for instruction in instructions { + match instruction { + commentparser::Instruction::Build(attrs) => { + let msg = buildjob::BuildJob{ + repo: Repo { + clone_url: job.repository.clone_url.clone(), + full_name: job.repository.full_name.clone(), + owner: job.repository.owner.login.clone(), + name: job.repository.name.clone(), + }, + pr: Pr { + number: job.issue.number.clone(), + head_sha: pr.head.sha.clone(), + target_branch: Some(pr.base.commit_ref.clone()) + }, + attrs: attrs + }; + + let props = BasicProperties { + content_type: Some("application/json".to_owned()), + ..Default::default() + }; + + response.push( + worker::Action::Publish(worker::QueueMsg{ + exchange: Some("build-jobs".to_owned()), + routing_key: None, + mandatory: true, + immediate: false, + properties: Some(props), + content: serde_json::to_string(&msg).unwrap().into_bytes() + }) + ); + } + commentparser::Instruction::Eval => { + let msg = massrebuildjob::MassRebuildJob{ + repo: Repo { + clone_url: job.repository.clone_url.clone(), + full_name: job.repository.full_name.clone(), + owner: job.repository.owner.login.clone(), + name: job.repository.name.clone(), + }, + pr: Pr { + number: job.issue.number.clone(), + head_sha: pr.head.sha.clone(), + target_branch: Some(pr.base.commit_ref.clone()), + }, + }; + + let props = BasicProperties { + content_type: Some("application/json".to_owned()), + ..Default::default() + }; + + response.push( + worker::Action::Publish(worker::QueueMsg{ + exchange: None, + routing_key: Some("mass-rebuild-check-jobs".to_owned()), + mandatory: true, + immediate: false, + properties: Some(props), + content: serde_json::to_string(&msg).unwrap().into_bytes() + }) + ); + } + + } + } + } + + response.push(worker::Action::Ack); + return response; + } +} diff --git a/ofborg/src/tasks/mod.rs b/ofborg/src/tasks/mod.rs index e0794d5..107e143 100644 --- a/ofborg/src/tasks/mod.rs +++ b/ofborg/src/tasks/mod.rs @@ -2,4 +2,4 @@ pub mod heartbeat; pub mod build; pub mod massrebuilder; -pub mod buildfilter; +pub mod githubcommentfilter;