diff options
Diffstat (limited to 'lib/jitterbug')
-rw-r--r-- | lib/jitterbug/Builder.pm | 172 | ||||
-rw-r--r-- | lib/jitterbug/Emailer.pm | 78 |
2 files changed, 250 insertions, 0 deletions
diff --git a/lib/jitterbug/Builder.pm b/lib/jitterbug/Builder.pm new file mode 100644 index 0000000..a836f70 --- /dev/null +++ b/lib/jitterbug/Builder.pm @@ -0,0 +1,172 @@ +package jitterbug::Builder; + +use strict; +use warnings; + +use YAML qw/LoadFile Dump/; +use JSON; +use File::Path qw/rmtree/; +use Path::Class; +use Getopt::Long qw/:config no_ignore_case/; +use File::Basename; +use Git::Repository; +use jitterbug::Schema; +#use Data::Dumper; + +local $| = 1; +use constant DEBUG => 1; + +sub new { + my $self = bless {} => shift; + + GetOptions( + 'C|cron' => \$self->{'cron'}, + 'c|configfile=s' => \$self->{'configfile'}, + 's|sleep=i' => \$self->{'sleep'}, + ) or die "Cannot get options\n"; + + $self->{'configfile'} + or die qq{missing config.yml, use "-c config.yml" to help us find it\n}; + + die "Does not exist!: " . $self->{'configfile'} unless -e $self->{'configfile'}; + + return $self; +} + +sub debug { + warn @_ if DEBUG; +} + +sub run { + my $self = shift || die "Must call run() from object\n"; + my $conf = $self->{'conf'} = LoadFile( $self->{'configfile'} ); + my $dbix_conf = $conf->{'plugins'}{'DBIC'}{'schema'}; + + debug("Loaded config file: " . $self->{'configfile'}); + debug("Connection Info: " . join ':', @{ $dbix_conf->{'connect_info'} }); + + $self->{'schema'} = jitterbug::Schema->connect( @{ $dbix_conf->{'connect_info'} } ); + $self->{'interval'} = $self->{'sleep'} || + $conf->{'jitterbug'}{'builder'}{'sleep'} || + 30; + + return $self->build; +} + +sub build { + my $self = shift; + + while (1) { + my @tasks = $self->{'schema'}->resultset('Task')->all(); + debug("Found " . scalar(@tasks) . " tasks"); + + foreach my $task (@tasks) { + $task ? $self->run_task($task) : $self->sleep; + } + + $self->{'cron'} and return 0; + + $self->sleep(5); + } + + return 1; +} + +sub sleep { + my ($self, $interval) = @_; + $interval ||= $self->{'interval'}; + debug("sleeping for $interval seconds\n"); + sleep $interval; +} + +sub run_task { + my $self = shift; + my ($task) = @_; + my $desc = JSON::decode_json( $task->commit->content ); + my $conf = $self->{'conf'}; + + $desc->{'build'}{'start_time'} = time(); + debug("Build Start"); + + my $report_path = dir( + $conf->{'jitterbug'}{'reports'}{'dir'}, + $task->project->name, + $task->commit->sha256, + ); + + my $build_dir = dir( + $conf->{'jitterbug'}{'build'}{'dir'}, + $task->project->name, + ); + + debug("Removing $build_dir"); + rmtree($build_dir, { error => \my $err } ); + warn @$err if @$err; + + $self->sleep(1); # avoid race conditions + + my $repo = $task->project->url . '.git'; + my $r = Git::Repository->create( clone => $repo => $build_dir ); + + debug("Checking out " . $task->commit->sha256 . " from $repo into $build_dir\n"); + $r->run( 'checkout', $task->commit->sha256 ); + + my $builder = $conf->{'jitterbug'}{'build_process'}{'builder'}; + + my $perlbrew = $conf->{'options'}{'perlbrew'} || 1; + + my $builder_command = "$builder $build_dir $report_path $perlbrew"; + + debug("Going to run builder : $builder_command"); + my $res = `$builder_command`; + debug($res); + + $desc->{'build'}{'end_time'} = time(); + + my @versions = glob( $report_path . '/*' ); + foreach my $version (@versions) { + open my $fh, '<', $version; + my ($result, $lines); + while (<$fh>){ + $lines .= $_; + } + ($result) = $lines =~ /Result:\s(\w+)/; + my ( $name, ) = basename($version); + $name =~ s/\.txt//; + if ( !$result || ($result && $result !~ /PASS/ )) { + # mail author of the commit + $result = "FAIL"; + my $message = $desc->{'message'}; + my $commiter = $desc->{'author'}{'email'}; + my $output = $lines; + my $sha = $desc->{'id'}; + my $on_failure = $conf->{'jitterbug'}{'build_process'}{'on_failure'}; + my $on_failure_cc_email = $conf->{'jitterbug'}{'build_process'}{'on_failure_email'}; + + $message =~ s/'/\\'/g; $commiter =~ s/'/\\'/g; $output =~ s/'/\\'/g; + my $failure_cmd = sprintf("%s '%s' %s '%s' '%s' %s %s", $on_failure, $commiter, $task->project->name, $message, $output, $sha, $on_failure_cc_email); + debug("Running failure command: $failure_cmd"); + + # does it look like a module name? + if ($on_failure =~ /::/) { + # we should do some error checking here + eval "require $on_failure"; + $on_failure->new($conf,$task,$output)->run; + } else { + system($failure_cmd); + } + } + $desc->{'build'}{'version'}{$name} = $result; + close $fh; + } + + $task->commit->update( { + content => JSON::encode_json($desc), + } ); + debug("Task completed for " . $task->commit->sha256 . "\n"); + + $task->delete(); + + debug("Task removed from " . $task->project->name . "\n"); +} + diff --git a/lib/jitterbug/Emailer.pm b/lib/jitterbug/Emailer.pm new file mode 100644 index 0000000..d21028f --- /dev/null +++ b/lib/jitterbug/Emailer.pm @@ -0,0 +1,78 @@ +package jitterbug::Emailer; + +use strict; +use warnings; +use Email::Stuff; +use JSON; + +sub new { + my $self = bless {} => shift; + my ($conf,$task,$tap_output) = @_; + # smelly + $self->{'conf'} = $conf; + $self->{'task'} = $task; + $self->{'tap_output'} = $tap_output; + + return $self; +} + +sub _make_body { + my ($header, $message, $tap, $footer) = @_; + + no warnings 'uninitialized'; + return <<BODY; +$header +Commit Message: +$message + +TAP Output: +$tap +$footer +BODY + +} +sub run { + my $self = shift; + my $task = $self->{'task'}; + my $buildconf = $self->{'conf'}->{'jitterbug'}{'build_process'}; + my $project = $task->project->name; + my $tap = $self->{'tap_output'}; + my $sha1 = $task->commit->sha256; + my $shortsha1 = substr($sha1, 0, 8); + my $desc = JSON::decode_json( $task->commit->content ); + my $email = $desc->{'author'}{'email'}; + my $message = $desc->{'message'}; + my $header = $buildconf->{'on_failure_header'}; + my $footer = $buildconf->{'on_failure_footer'}; + my $body = _make_body($header,$message, $tap, $footer); + my $summary = ''; + + if ( $tap =~ m/^(Test Summary Report.*)/ms ) { + $summary = $1; + } + + # Expand placeholders in our failure email + $body =~ s/%%PROJECT%%/$project/g; + $body =~ s/%%SHA1%%/$sha1/g; + $body =~ s/%%SUMMARY%%/$summary/g; + + + my $stuff = Email::Stuff->from($buildconf->{'on_failure_from_email'}) + # bug in Email::Stuff brakes chaining if $email is empty + ->to($email || " ") + ->cc($buildconf->{'on_failure_cc_email'}) + ->text_body($body) + ->subject( + $buildconf->{'on_failure_subject_prefix'} . "$project @ $shortsha1 $message" + ); + # Should we attach a build log for convenience? + # ->attach(io('dead_bunbun_faked.gif')->all, + # filename => 'dead_bunbun_proof.gif') + $self->{'last_email_sent'} = $stuff; + + $stuff->send; + + return $self; +} + +1; |