about summary refs log tree commit diff
path: root/lib/jitterbug
diff options
context:
space:
mode:
Diffstat (limited to 'lib/jitterbug')
-rw-r--r--lib/jitterbug/Builder.pm172
-rw-r--r--lib/jitterbug/Emailer.pm78
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;