Add a plugin mechanism

[?]
May 8, 2013, 3:30 PM
5EQYVRWECBDJORGI5DRIOUEJXSXMRCQNT2562BM4Z4U52LT7JUHAC

Dependencies

  • [2] ZDNXMJ3J * Typo.
  • [3] EUWLW7FY Don't use the Switch module
  • [4] LSZLZHJY Allow users to edit their own settings
  • [5] R6SX4KID Put build status in front of the notification mail subject
  • [6] KSD75RNJ Hydra/18: fixed uninitialized value error when logfile is null
  • [7] LGNML7VJ Don't use a prepared statement for the active build steps query
  • [8] TULPZ62Y * Perform builds in parallel.
  • [9] TJK27WSB Open the DB using Hydra::Model::DB->new
  • [10] 7YBYT2LQ
  • [11] NZYFWV6M hydra-build: Add system info to the subject and extra headers.
  • [12] WAZFSDSL using backquote as argument resulted in only first line as first argument to removeAsciiEscapes
  • [13] 4R7OGJEY Do not send emails when build is cancelled/aborted. Also, ignore aborted/cancelled builds in comparing to previous build.
  • [14] AFTXA575 * $HYDRA_DATA environment variable.
  • [15] RWIBJ5L4 * Autoflush stdout.
  • [16] ZCQOOAGY Remove redundant dot in status emails
  • [17] PMNWRTGJ Add multiple output support
  • [18] QUTWJR7P * Include more info in notification emails.
  • [19] 4IXVBLUI hack to try and prevent too many newlines
  • [20] 6US6LEC7 * Add a NarSize field to Hydra manifests. This allows nix-env
  • [21] PY4WQF5G remove ascii escapes from log in tail page and emails
  • [22] HPEG2RHV Merge the BuildResultInfo table into the Builds table
  • [23] Y6AHH4TH Remove the logfile and logSize columns from the database
  • [24] TRFRGOBD Remove Twitter notification support
  • [25] JK2QWPH6
  • [26] LA27PR4U hydra: fix enable email notification bug
  • [27] ALXRI3Y5 hydra: removed need for HYDRA_BUILD_BASEURL env variable
  • [28] WQXF2T3D hydra-evaluator: Don't require $HYDRA_CONFIG
  • [29] FHAVPTZ6 Hydra/23: added some X-headers with meta info in email notifications, added more descriptive status
  • [30] HDMOHBZM Hydra/57: Unknown failure -> Failed
  • [31] KA45EBF5 * Send email if a build fails.
  • [32] A22P7HCO hydra: at evaluation, check if path is already built, and mark as built in stead of adding to the queue.
  • [33] VPKMUFF3 hydra-build: only send email if the status differs from the previous build
  • [34] XZ7ZIKCV * Allow overriding the sender email address.
  • [35] RTDA3GQ4
  • [36] RBHHV7P7 * Read logs using logContents function in stead of handling it everywhere separately.
  • [37] FYO6NECE hydra
  • [38] 5O6E5SU5 hydra: store logfile/output path/closure size
  • [39] QBQSQOSY hydra: moved getbuildlog
  • [40] MPGVCHVF * Fix an apparent incompatibility with recent DBIx::Class.
  • [41] K7IRNVRF hydra: fixed email notification bug, when build is performed for the first time (it always said succeeded in the body of the mail
  • [42] GAIBDEZZ * Store the name of the machine that performed a build step in the
  • [43] LOMVF2KH do not send email for builds with status 'aborted'
  • [44] IS32JFKX * Typo.
  • [45] IW2LHCLL fixed email bug
  • [46] BOFOHCPK removed debug print, added last 50 lines in failure emails
  • [47] 3BKF6P72 * Use Nix's negative caching.
  • [48] CXRCPDSQ * added support for twitter notification
  • [49] 6KCP6ODP * Get the URI for use in notification mails from the Hydra config
  • [50] AJKTRRDJ rename var
  • [51] OG7BEM57
  • [52] YFPZ46YK * hydra: added variant of build input type, 'build output (same system)' to allow better continous integration in one jobset for multiple system. it makes sure that the system of the build that is passed as input for a job has the same system as the job.
  • [*] D5QIOJGP * Move everything up one directory.
  • [*] 64K7R4Y6 Forgot to change Nix to Nix::Store in one place

Change contents

  • file addition: Plugin (d--r------)
    [54.53]
  • file addition: EmailNotification.pm (----------)
    [0.1]
    package Hydra::Plugin::EmailNotification;
    use strict;
    use feature qw/switch/;
    use POSIX qw(strftime);
    use Email::Sender::Simple qw(sendmail);
    use Email::Sender::Transport::SMTP;
    use Email::Simple;
    use Email::Simple::Creator;
    use Sys::Hostname::Long;
    use Text::Table;
    use File::Slurp;
    use Hydra::Helper::Nix;
    sub statusDescription {
    my ($buildstatus) = @_;
    my $status = "Failed";
    given ($buildstatus) {
    when (0) { $status = "Success"; }
    when (1) { $status = "Failed with non-zero exit code"; }
    when (2) { $status = "Dependency failed"; }
    when (4) { $status = "Cancelled"; }
    }
    return $status;
    }
    sub buildFinished {
    my ($self, $db, $config, $build, $dependents) = @_;
    die unless $build->finished;
    my $prevBuild;
    ($prevBuild) = $db->resultset('Builds')->search(
    { project => $build->project->name
    , jobset => $build->jobset->name
    , job => $build->job->name
    , system => $build->system
    , finished => 1
    , id => { '<', $build->id }
    , -not => { buildstatus => { -in => [4, 3]} }
    }, { order_by => ["id DESC"] }
    );
    # Do we want to send mail?
    unless ($ENV{'HYDRA_FORCE_SEND_MAIL'}) {
    return unless $build->jobset->enableemail && ($build->maintainers ne "" || $build->jobset->emailoverride ne "");
    # If build is cancelled or aborted, do not send email.
    return if $build->buildstatus == 4 || $build->buildstatus == 3;
    # If there is a previous (that is not cancelled or aborted) build
    # with same buildstatus, do not send email.
    return if defined $prevBuild && ($build->buildstatus == $prevBuild->buildstatus);
    }
    # Send mail.
    # !!! should use the Template Toolkit here.
    my $to = (!$build->jobset->emailoverride eq "") ? $build->jobset->emailoverride : $build->maintainers;
    print STDERR "sending mail notification to ", $to, "\n";
    my $jobName = $build->project->name . ":" . $build->jobset->name . ":" . $build->job->name;
    my $status = statusDescription($build->buildstatus);
    my $baseurl = hostname_long;
    my $sender = $config->{'notification_sender'} ||
    (($ENV{'USER'} || "hydra") . "@" . $baseurl);
    my $selfURI = $config->{'base_uri'} || "http://localhost:3000";
    sub showTime { my ($x) = @_; return strftime('%Y-%m-%d %H:%M:%S', localtime($x)); }
    my $infoTable = Text::Table->new({ align => "left" }, \ " | ", { align => "left" });
    my @lines = (
    [ "Build ID:", $build->id ],
    [ "Nix name:", $build->nixname ],
    [ "Short description:", $build->description || '(not given)' ],
    [ "Maintainer(s):", $build->maintainers ],
    [ "System:", $build->system ],
    [ "Derivation store path:", $build->drvpath ],
    [ "Output store path:", join(", ", map { $_->path } $build->buildoutputs) ],
    [ "Time added:", showTime $build->timestamp ],
    );
    push @lines, (
    [ "Build started:", showTime $build->starttime ],
    [ "Build finished:", showTime $build->stoptime ],
    [ "Duration:", $build->stoptime - $build->starttime . "s" ],
    ) if $build->starttime;
    $infoTable->load(@lines);
    my $inputsTable = Text::Table->new(
    { title => "Name", align => "left" }, \ " | ",
    { title => "Type", align => "left" }, \ " | ",
    { title => "Value", align => "left" });
    @lines = ();
    foreach my $input ($build->inputs) {
    my $type = $input->type;
    push @lines,
    [ $input->name
    , $input->type
    , ( $input->type eq "build" || $input->type eq "sysbuild")
    ? $input->dependency->id
    : ($input->type eq "string" || $input->type eq "boolean")
    ? $input->value : ($input->uri . ':' . $input->revision)
    ];
    }
    $inputsTable->load(@lines);
    my $loglines = 50;
    my $logtext = logContents($build->drvpath, $loglines);
    $logtext = removeAsciiEscapes($logtext);
    my $body = "Hi,\n"
    . "\n"
    . "This is to let you know that Hydra build " . $build->id
    . " of job " . $jobName . " " . (defined $prevBuild ? "has changed from '" . statusDescription($prevBuild->buildstatus) . "' to '$status'" : "is '$status'" ) .".\n"
    . "\n"
    . "Complete build information can be found on this page: "
    . "$selfURI/build/" . $build->id . "\n"
    . ($build->buildstatus != 0 ? "\nThe last $loglines lines of the build log are shown at the bottom of this email.\n" : "")
    . "\n"
    . "A summary of the build information follows:\n"
    . "\n"
    . $infoTable->body
    . "\n"
    . "The build inputs were:\n"
    . "\n"
    . $inputsTable->title
    . $inputsTable->rule('-', '+')
    . $inputsTable->body
    . "\n"
    . "Regards,\n\nThe Hydra build daemon.\n"
    . ($build->buildstatus != 0 ? "\n---\n$logtext" : "");
    # stripping trailing spaces from lines
    $body =~ s/[\ ]+$//gm;
    my $email = Email::Simple->create(
    header => [
    To => $to,
    From => "Hydra Build Daemon <$sender>",
    Subject => "$status: Hydra job $jobName on " . $build->system . ", build " . $build->id,
    'X-Hydra-Instance' => $baseurl,
    'X-Hydra-Project' => $build->project->name,
    'X-Hydra-Jobset' => $build->jobset->name,
    'X-Hydra-Job' => $build->job->name,
    'X-Hydra-System' => $build->system
    ],
    body => "",
    );
    $email->body_set($body);
    if (defined $ENV{'HYDRA_MAIL_SINK'}) {
    # For testing, redirect all mail to a file.
    write_file($ENV{'HYDRA_MAIL_SINK'}, { append => 1 }, $email->as_string . "\n");
    } else {
    sendmail($email);
    }
    }
    1;
  • file addition: Plugin.pm (----------)
    [54.53]
    package Hydra::Plugin;
    use Module::Pluggable
    search_path => "Hydra::Plugin",
    require => 1;
    # $plugin->buildFinished($db, $config, $build, $dependents):
    #
    # Called when build $build has finished. If the build failed, then
    # $dependents is an array ref to a list of builds that have also
    # failed as a result (i.e. because they depend on $build or a failed
    # dependeny of $build).
    1;
  • edit in src/script/hydra-build at line 8
    [55.16]
    [6.54]
    use Hydra::Plugin;
  • edit in src/script/hydra-build at line 13
    [6.30][6.276:399](),[6.1070][6.276:399](),[6.399][6.0:25](),[6.25][6.0:21](),[6.21][6.28:69](),[6.55][6.2328:2353](),[6.2353][3.0:24]()
    use Email::Sender::Simple qw(sendmail);
    use Email::Sender::Transport::SMTP;
    use Email::Simple;
    use Email::Simple::Creator;
    use Sys::Hostname::Long;
    use Config::General;
    use Text::Table;
    use POSIX qw(strftime);
    use Data::Dump qw(dump);
    use feature qw/switch/;
  • edit in src/script/hydra-build at line 19
    [6.749][6.401:402](),[6.401][6.401:402](),[6.935][6.885:886](),[6.15667][6.13:66](),[6.1033][6.13:66](),[6.66][6.0:27](),[6.27][3.25:255](),[3.255][6.330:336](),[6.330][6.330:336](),[6.336][6.887:888](),[6.888][6.341:362](),[6.341][6.341:362](),[6.362][6.15668:15669](),[6.15669][6.362:363](),[6.362][6.362:363](),[6.363][6.402:452](),[6.1033][6.402:452](),[6.402][6.402:452](),[6.452][6.26:27](),[6.27][6.9719:9752](),[6.9752][6.889:890](),[6.890][5.0:117](),[6.69][6.115:147](),[5.117][6.115:147](),[6.152][6.115:147](),[6.1382][6.115:147](),[6.3790][6.115:147](),[6.115][6.115:147](),[6.147][6.452:453](),[6.452][6.452:453](),[6.453][6.3791:3863](),[6.3863][6.309:352](),[6.352][6.3899:3975](),[6.3899][6.3899:3975](),[6.3975][6.891:926](),[6.926][6.4011:4035](),[6.4011][6.4011:4035](),[6.4035][6.0:36](),[6.36][6.0:54](),[6.54][6.353:392](),[6.68][6.4113:4125](),[6.392][6.4113:4125](),[6.4113][6.4113:4125](),[6.4125][5.118:450](),[5.450][6.330:348](),[6.330][6.330:348](),[6.349][6.349:573](),[6.573][5.451:508](),[6.434][6.661:662](),[5.508][6.661:662](),[6.4574][6.661:662](),[6.9989][6.661:662](),[6.661][6.661:662](),[6.662][4.6942:6975](),[4.6975][6.393:446](),[6.34][6.393:446](),[6.446][6.35:90](),[6.51][6.35:90](),[6.90][6.3075:3076](),[6.118][6.3075:3076](),[6.130][6.3075:3076](),[6.153][6.3075:3076](),[6.207][6.3075:3076](),[6.503][6.3075:3076](),[6.517][6.3075:3076](),[6.736][6.3075:3076](),[6.1093][6.3075:3076](),[6.3075][6.3075:3076](),[6.3076][6.447:515](),[6.515][6.131:221](),[6.153][6.131:221](),[6.221][2.0:89](),[2.89][6.309:623](),[6.309][6.309:623](),[6.623][6.15670:15755](),[6.15755][6.674:759](),[6.674][6.674:759](),[6.759][6.9990:10203](),[6.10203][6.1032:1062](),[6.1032][6.1032:1062]()
    sub statusDescription {
    my ($buildstatus) = @_;
    my $status = "Failed";
    given ($buildstatus) {
    when (0) { $status = "Success"; }
    when (1) { $status = "Failed with non-zero exit code"; }
    when (2) { $status = "Dependency failed"; }
    when (4) { $status = "Cancelled"; }
    }
    return $status;
    }
    sub sendEmailNotification {
    my ($build) = @_;
    die unless $build->finished;
    return unless $build->jobset->enableemail && ($build->maintainers ne "" || $build->jobset->emailoverride ne "");
    # Do we want to send mail?
    my $prevBuild;
    ($prevBuild) = $db->resultset('Builds')->search(
    { project => $build->project->name
    , jobset => $build->jobset->name
    , job => $build->job->name
    , system => $build->system
    , finished => 1
    , id => { '<', $build->id }
    , -not => { buildstatus => { -in => [4, 3]} }
    }, { order_by => ["id DESC"] }
    );
    # If build is cancelled or aborted, do not send email.
    return if $build->buildstatus == 4 || $build->buildstatus == 3;
    # If there is a previous (that is not cancelled or aborted) build
    # with same buildstatus, do not send email.
    return if defined $prevBuild && ($build->buildstatus == $prevBuild->buildstatus);
    # Send mail.
    # !!! should use the Template Toolkit here.
    print STDERR "sending mail notification to ", $build->maintainers, "\n";
    my $jobName = $build->project->name . ":" . $build->jobset->name . ":" . $build->job->name;
    my $status = statusDescription($build->buildstatus);
    my $baseurl = hostname_long;
    my $sender = $config->{'notification_sender'} ||
    (($ENV{'USER'} || "hydra") . "@" . $baseurl);
    my $selfURI = $config->{'base_uri'} || "http://localhost:3000";
    sub showTime { my ($x) = @_; return strftime('%Y-%m-%d %H:%M:%S', localtime($x)); }
    my $infoTable = Text::Table->new({ align => "left" }, \ " | ", { align => "left" });
    my @lines = (
    [ "Build ID:", $build->id ],
    [ "Nix name:", $build->nixname ],
    [ "Short description:", $build->description || '(not given)' ],
    [ "Maintainer(s):", $build->maintainers ],
    [ "System:", $build->system ],
    [ "Derivation store path:", $build->drvpath ],
    [ "Output store path:", join(", ", map { $_->path } $build->buildoutputs) ],
    [ "Time added:", showTime $build->timestamp ],
    );
    push @lines, (
    [ "Build started:", showTime $build->starttime ],
    [ "Build finished:", showTime $build->stoptime ],
    [ "Duration:", $build->stoptime - $build->starttime . "s" ],
    ) if $build->starttime;
    $infoTable->load(@lines);
  • edit in src/script/hydra-build at line 20
    [6.1063][6.1063:1103](),[6.1103][2.90:200](),[2.200][6.1211:1425](),[6.1211][6.1211:1425](),[6.1425][6.2354:2425](),[6.2425][6.1463:1698](),[6.1463][6.1463:1698]()
    my $inputsTable = Text::Table->new(
    { title => "Name", align => "left" }, \ " | ",
    { title => "Type", align => "left" }, \ " | ",
    { title => "Value", align => "left" });
    @lines = ();
    foreach my $input ($build->inputs) {
    my $type = $input->type;
    push @lines,
    [ $input->name
    , $input->type
    , ( $input->type eq "build" || $input->type eq "sysbuild")
    ? $input->dependency->id
    : ($input->type eq "string" || $input->type eq "boolean")
    ? $input->value : ($input->uri . ':' . $input->revision)
    ];
    }
    $inputsTable->load(@lines);
  • edit in src/script/hydra-build at line 21
    [6.1][6.1:24](),[6.24][6.10650:10709](),[6.1020][6.205:250](),[6.10709][6.205:250](),[6.205][6.205:250](),[6.108][6.807:846](),[6.132][6.807:846](),[6.153][6.807:846](),[6.250][6.807:846](),[6.386][6.807:846](),[6.1698][6.807:846](),[6.807][6.807:846](),[6.846][6.0:67](),[6.67][6.10240:10414](),[6.183][6.965:980](),[6.186][6.965:980](),[6.701][6.965:980](),[6.4696][6.965:980](),[6.10414][6.965:980](),[6.965][6.965:980](),[6.980][2.201:268](),[2.268][6.1039:1087](),[6.1763][6.1039:1087](),[6.1039][6.1039:1087](),[6.1087][6.10415:10546](),[6.276][6.1087:1102](),[6.10546][6.1087:1102](),[6.1087][6.1087:1102](),[6.1102][6.1764:2044](),[6.2044][6.277:327](),[6.327][6.1021:1084](),[6.1084][6.403:404](),[6.10611][6.403:404](),[6.403][6.403:404](),[6.404][6.4697:4767](),[6.1153][6.4697:4767](),[6.540][6.3076:3077](),[6.1153][6.3076:3077](),[6.4767][6.3076:3077](),[6.3076][6.3076:3077](),[6.3077][6.4768:4875](),[6.4875][6.541:600](),[6.3077][6.541:600](),[6.600][6.4876:4904](),[6.4904][6.1154:1209](),[6.644][6.1154:1209](),[6.1209][5.509:610](),[5.610][6.91:135](),[6.782][6.91:135](),[6.135][6.822:935](),[6.822][6.822:935](),[6.935][6.107:209](),[6.209][6.760:771](),[6.987][6.760:771](),[6.1287][6.760:771](),[6.760][6.760:771](),[6.771][6.0:20](),[6.20][6.808:815](),[6.1311][6.808:815](),[6.808][6.808:815](),[6.815][6.21:50](),[6.50][6.815:816](),[6.815][6.815:816](),[6.816][6.1312:1368](),[6.1368][6.845:846](),[6.845][6.845:846](),[6.846][6.786:808](),[6.808][6.868:870](),[6.1009][6.868:870](),[6.868][6.868:870](),[6.870][6.15756:15757](),[6.15757][6.870:871](),[6.870][6.870:871]()
    my $loglines = 50;
    my $logtext = logContents($build->drvpath, $loglines);
    $logtext = removeAsciiEscapes($logtext);
    my $body = "Hi,\n"
    . "\n"
    . "This is to let you know that Hydra build " . $build->id
    . " of job " . $jobName . " " . (defined $prevBuild ? "has changed from '" . statusDescription($prevBuild->buildstatus) . "' to '$status'" : "is '$status'" ) .".\n"
    . "\n"
    . "Complete build information can be found on this page: "
    . "$selfURI/build/" . $build->id . "\n"
    . ($build->buildstatus != 0 ? "\nThe last $loglines lines of the build log are shown at the bottom of this email.\n" : "")
    . "\n"
    . "A summary of the build information follows:\n"
    . "\n"
    . $infoTable->body
    . "\n"
    . "The build inputs were:\n"
    . "\n"
    . $inputsTable->title
    . $inputsTable->rule('-', '+')
    . $inputsTable->body
    . "\n"
    . "Regards,\n\nThe Hydra build daemon.\n"
    . ($build->buildstatus != 0 ? "\n---\n$logtext" : "");
    # stripping trailing spaces from lines
    $body =~ s/[\ ]+$//gm;
    my $to = (!$build->jobset->emailoverride eq "") ? $build->jobset->emailoverride : $build->maintainers;
    my $email = Email::Simple->create(
    header => [
    To => $to,
    From => "Hydra Build Daemon <$sender>",
    Subject => "$status: Hydra job $jobName on " . $build->system . ", build " . $build->id,
    'X-Hydra-Instance' => $baseurl,
    'X-Hydra-Project' => $build->project->name,
    'X-Hydra-Jobset' => $build->jobset->name,
    'X-Hydra-Job' => $build->job->name,
    'X-Hydra-System' => $build->system
    ],
    body => "",
    );
    $email->body_set($body);
    print $email->as_string if $ENV{'HYDRA_MAIL_TEST'};
    sendmail($email);
    }
  • replacement in src/script/hydra-build at line 291
    [6.874][6.1369:1403]()
    sendEmailNotification $build;
    [6.874]
    [6.6647]
    foreach my $plugin (Hydra::Plugin->plugins) {
    eval {
    $plugin->buildFinished($db, $config, $build, []);
    };
    if ($@) {
    print STDERR "$plugin->buildFinished: $@\n";
    }
    }
  • replacement in src/script/hydra-build at line 306
    [6.1435][6.1435:1503]()
    sendEmailNotification $db->resultset('Builds')->find($buildId);
    [6.1435]
    [6.1071]
    my $build = $db->resultset('Builds')->find($buildId);
    $_->buildFinished($db, $config, $build, []) foreach Hydra::Plugin->plugins;