From 006ac1fc03b7ec7219208ce47416d28713faed6f Mon Sep 17 00:00:00 2001 From: Shea Levy Date: Mon, 29 Feb 2016 14:48:36 -0500 Subject: [PATCH] Add slack plugin. Respects blocks in the hydra config, with attributes: * jobs: a regexp matching the job name (in the format project:jobset:job) * url: The URL to a slack incoming webhook * force: If true, always send messages. Otherwise, only when the build status changes Multiple blocks are allowed --- hydra-module.nix | 1 + src/lib/Hydra/Plugin/SlackNotification.pm | 93 +++++++++++++++++++++++ 2 files changed, 94 insertions(+) create mode 100644 src/lib/Hydra/Plugin/SlackNotification.pm diff --git a/hydra-module.nix b/hydra-module.nix index 059769fa..085529c5 100644 --- a/hydra-module.nix +++ b/hydra-module.nix @@ -20,6 +20,7 @@ let { NIX_REMOTE = "daemon"; SSL_CERT_FILE = "/etc/ssl/certs/ca-certificates.crt"; OPENSSL_X509_CERT_FILE = "/etc/ssl/certs/ca-certificates.crt"; # FIXME: remove on NixOS >= 15.07 + PERL_LWP_SSL_CA_FILE = "/etc/ssl/certs/ca-certificates.crt"; PGPASSFILE = "${baseDir}/pgpass"; } // hydraEnv // cfg.extraEnv; diff --git a/src/lib/Hydra/Plugin/SlackNotification.pm b/src/lib/Hydra/Plugin/SlackNotification.pm new file mode 100644 index 00000000..1e282407 --- /dev/null +++ b/src/lib/Hydra/Plugin/SlackNotification.pm @@ -0,0 +1,93 @@ +package Hydra::Plugin::SlackNotification; + +use strict; +use parent 'Hydra::Plugin'; +use HTTP::Request; +use LWP::UserAgent; +use Hydra::Helper::CatalystUtils; +use JSON; + +sub buildFinished { + my ($self, $build, $dependents) = @_; + my $cfg = $self->{config}->{slack}; + my @config = defined $cfg ? ref $cfg eq "ARRAY" ? @$cfg : ($cfg) : (); + + my $baseurl = $self->{config}->{'base_uri'} || "http://localhost:3000"; + + # Figure out to which channelss to send notification. For each channel + # we send one aggregate message. + my %channels; + foreach my $b ($build, @{$dependents}) { + my $prevBuild = getPreviousBuild($b); + my $jobName = showJobName $b; + + foreach my $channel (@config) { + my $force = $channel->{force}; + next unless $jobName =~ /^$channel->{jobs}$/; + + # If build is cancelled or aborted, do not send email. + next if ! $force && ($b->buildstatus == 4 || $b->buildstatus == 3); + + # If there is a previous (that is not cancelled or aborted) build + # with same buildstatus, do not send email. + next if ! $force && defined $prevBuild && ($b->buildstatus == $prevBuild->buildstatus); + + $channels{$channel->{url}} //= { channel => $channel, builds => [] }; + push @{$channels{$channel->{url}}->{builds}}, $b; + } + } + + return if scalar keys %channels == 0; + + my ($authors, $nrCommits) = getResponsibleAuthors($build, $self->{plugins}); + + # Send a message to each room. + foreach my $url (keys %channels) { + my $channel = $channels{$url}; + my @deps = grep { $_->id != $build->id } @{$channel->{builds}}; + + my $imgBase = "http://hydra.nixos.org"; + my $img = + $build->buildstatus == 0 ? "$imgBase/static/images/checkmark_16.png" : + $build->buildstatus == 2 ? "$imgBase/static/images/dependency_16.png" : + $build->buildstatus == 4 ? "$imgBase/static/images/cancelled_16.png" : + "$imgBase/static/images/error_16.png"; + + my $color = + $build->buildstatus == 0 ? "good" : + $build->buildstatus == 4 ? "warning" : + "danger"; + + my $text = ""; + $text .= "Job <$baseurl/job/${\$build->project->name}/${\$build->jobset->name}/${\$build->job->name}|${\showJobName($build)}>"; + $text .= " (and ${\scalar @deps} others)" if scalar @deps > 0; + $text .= ": <$baseurl/build/${\$build->id}|" . showStatus($build) . ">"; + + if (scalar keys %{$authors} > 0) { + # FIXME: escaping + my @x = map { "{$_}|$_>" } (sort keys %{$authors}); + $text .= ", likely due to "; + $text .= "$nrCommits commits by " if $nrCommits > 1; + $text .= join(" or ", scalar @x > 1 ? join(", ", @x[0..scalar @x - 2]) : (), $x[-1]); + } + + my $msg = + { attachments => + [{ fallback => "Job " . showJobName($build) . " build number " . $build->id . ": " . showStatus($build), + text => $text, + thumb_url => $img, + color => $color, + title => "Job " . showJobName($build) . " build number " . $build->id, + title_link => "$baseurl/build/${\$build->id}" + }] + }; + + my $req = HTTP::Request->new('POST', $url); + $req->header('Content-Type' => 'application/json'); + $req->content(encode_json($msg)); + my $ua = LWP::UserAgent->new(); + $ua->request($req); + } +} + +1;