Support eval instructions

This commit is contained in:
Graham Christensen 2017-11-23 08:27:56 -05:00
parent dfbd52480e
commit 2150acacc6
No known key found for this signature in database
GPG key ID: ACA1C1D120C83D5C
5 changed files with 255 additions and 148 deletions

View file

@ -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,

View file

@ -1,5 +1,5 @@
pub fn parse(text: &str) -> Option<Instruction> {
pub fn parse(text: &str) -> Option<Vec<Instruction>> {
let tokens: Vec<String> = text.split_whitespace()
.map(|s| s.to_owned()).collect();
@ -7,21 +7,37 @@ pub fn parse(text: &str) -> Option<Instruction> {
return None;
}
let (targeter_, params_) = tokens.split_at(2);
let targeter: Vec<String> = targeter_.iter()
.map(|s| s.to_lowercase()).collect();
let params: Vec<String> = 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<Instruction> = 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<String>)
Build(Vec<String>),
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"));
}

View file

@ -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<u8>) -> Result<Self::J, String> {
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
];
}
}
}
}

View file

@ -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<u8>) -> Result<Self::J, String> {
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<worker::Action> = 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;
}
}

View file

@ -2,4 +2,4 @@
pub mod heartbeat;
pub mod build;
pub mod massrebuilder;
pub mod buildfilter;
pub mod githubcommentfilter;