From 8523130ebb2cc1272892fd11c093377f9ce6e66f Mon Sep 17 00:00:00 2001 From: Eelco Dolstra Date: Wed, 19 Nov 2014 14:44:04 +0100 Subject: [PATCH] Use Email::MIME instead of Email::Simple Email::Simple cannot handle non-ASCII characters. Fixes #191. --- release.nix | 1 + src/lib/Hydra/Controller/User.pm | 7 ++- src/lib/Hydra/Helper/CatalystUtils.pm | 26 ----------- src/lib/Hydra/Helper/Email.pm | 49 +++++++++++++++++++++ src/lib/Hydra/Plugin/EmailNotification.pm | 53 +++++++---------------- src/script/hydra-evaluator | 49 +++++++++------------ 6 files changed, 90 insertions(+), 95 deletions(-) create mode 100644 src/lib/Hydra/Helper/Email.pm diff --git a/release.nix b/release.nix index 79dd6f05..b5846ff4 100644 --- a/release.nix +++ b/release.nix @@ -99,6 +99,7 @@ in rec { DataDump DateTime DigestSHA1 + EmailMIME EmailSender FileSlurp IOCompress diff --git a/src/lib/Hydra/Controller/User.pm b/src/lib/Hydra/Controller/User.pm index 54e58dfe..a8f5a82f 100644 --- a/src/lib/Hydra/Controller/User.pm +++ b/src/lib/Hydra/Controller/User.pm @@ -8,6 +8,7 @@ use Crypt::RandPasswd; use Digest::SHA1 qw(sha1_hex); use Hydra::Helper::Nix; use Hydra::Helper::CatalystUtils; +use Hydra::Helper::Email; use LWP::UserAgent; use JSON; use HTML::Entities; @@ -279,13 +280,15 @@ sub reset_password :Chained('user') :PathPart('reset-password') :Args(0) { my $password = Crypt::RandPasswd->word(8,10); setPassword($user, $password); - sendEmail($c, + sendEmail( + $c->config, $user->emailaddress, "Hydra password reset", "Hi,\n\n". "Your password has been reset. Your new password is '$password'.\n\n". "You can change your password at " . $c->uri_for($self->action_for('edit'), [$user->username]) . ".\n\n". - "With regards,\n\nHydra.\n" + "With regards,\n\nHydra.\n", + [] ); $c->flash->{successMsg} = "A new password has been sent to ${\$user->emailaddress}."; diff --git a/src/lib/Hydra/Helper/CatalystUtils.pm b/src/lib/Hydra/Helper/CatalystUtils.pm index d4ede730..032081f3 100644 --- a/src/lib/Hydra/Helper/CatalystUtils.pm +++ b/src/lib/Hydra/Helper/CatalystUtils.pm @@ -4,10 +4,6 @@ use utf8; use strict; use Exporter; use Readonly; -use Email::Simple; -use Email::Sender::Simple qw(sendmail); -use Email::Sender::Transport::SMTP; -use Sys::Hostname::Long; use Nix::Store; use Hydra::Helper::Nix; use feature qw/switch/; @@ -19,7 +15,6 @@ our @EXPORT = qw( forceLogin requireUser requireProjectOwner requireAdmin requirePost isAdmin isProjectOwner trim getLatestFinishedEval - sendEmail paramToList backToReferer $pathCompRE $relPathRE $relNameRE $projectNameRE $jobsetNameRE $jobNameRE $systemRE $userNameRE $inputNameRE @@ -193,27 +188,6 @@ sub getLatestFinishedEval { } -sub sendEmail { - my ($c, $to, $subject, $body) = @_; - - my $sender = $c->config->{'notification_sender'} || - (($ENV{'USER'} || "hydra") . "@" . hostname_long); - - my $email = Email::Simple->create( - header => [ - To => $to, - From => "Hydra <$sender>", - Subject => $subject - ], - body => $body - ); - - print STDERR "Sending email:\n", $email->as_string if $ENV{'HYDRA_MAIL_TEST'}; - - sendmail($email); -} - - # Catalyst request parameters can be an array or a scalar or # undefined, making them annoying to handle. So this utility function # always returns a request parameter as a list. diff --git a/src/lib/Hydra/Helper/Email.pm b/src/lib/Hydra/Helper/Email.pm new file mode 100644 index 00000000..47fc5629 --- /dev/null +++ b/src/lib/Hydra/Helper/Email.pm @@ -0,0 +1,49 @@ +package Hydra::Helper::Email; + +use strict; +use Exporter 'import'; +use Email::Sender::Simple qw(sendmail); +use Email::MIME; +use File::Slurp; +use Sys::Hostname::Long; + +our @EXPORT = qw(sendEmail getBaseUrl); + +sub sendEmail { + my ($config, $to, $subject, $body, $extraHeaders) = @_; + + my $url = getBaseUrl($config); + my $sender = $config->{'notification_sender'} // (($ENV{'USER'} // "hydra") . "@" . $url); + + my @headers = ( + To => $to, + From => "Hydra Build Daemon <$sender>", + Subject => $subject, + 'X-Hydra-Instance' => $url, @{$extraHeaders} + ); + + my $email = Email::MIME->create( + attributes => { + encoding => 'quoted-printable', + charset => 'UTF-8', + }, + header_str => [ @headers ], + body_str => $body + ); + + print STDERR "sending email:\n", $email->as_string if $ENV{'HYDRA_MAIL_TEST'}; + + 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, { from => $sender }); + } +} + +sub getBaseUrl { + my ($config) = @_; + return $config->{'base_uri'} // "http://" . hostname_long . ":3000"; +} + +1; diff --git a/src/lib/Hydra/Plugin/EmailNotification.pm b/src/lib/Hydra/Plugin/EmailNotification.pm index 511fc1ed..dbe4772f 100644 --- a/src/lib/Hydra/Plugin/EmailNotification.pm +++ b/src/lib/Hydra/Plugin/EmailNotification.pm @@ -1,23 +1,19 @@ package Hydra::Plugin::EmailNotification; +use utf8; use strict; use parent 'Hydra::Plugin'; 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 File::Slurp; use Template; use Hydra::Helper::Nix; use Hydra::Helper::CatalystUtils; +use Hydra::Helper::Email; my $template = < $build, prevBuild => getPreviousBuild($build) , dependents => [grep { $_->id != $build->id } @builds] - , baseurl => $self->{config}->{'base_uri'} || "http://localhost:3000" + , baseurl => getBaseUrl($self->{config}) , showJobName => \&showJobName, showStatus => \&showStatus , showSystem => index($build->job->name, $build->system) == -1 , nrCommits => $nrCommits @@ -114,37 +110,18 @@ sub buildFinished { # stripping trailing spaces from lines $body =~ s/[\ ]+$//gm; - my $sender = $self->{config}->{'notification_sender'} || - (($ENV{'USER'} || "hydra") . "@" . hostname_long); + my $subject = + showStatus($build) . ": Hydra job " . showJobName($build) + . ($vars->{showSystem} ? " on " . $build->system : "") + . (scalar @{$vars->{dependents}} > 0 ? " (and " . scalar @{$vars->{dependents}} . " others)" : ""); - #my $loglines = 50; - #my $logtext = logContents($build->drvpath, $loglines); - #$logtext = removeAsciiEscapes($logtext); - - my $email = Email::Simple->create( - header => [ - To => $to, - From => "Hydra Build Daemon <$sender>", - Subject => - showStatus($build) . ": Hydra job " . showJobName($build) - . ($vars->{showSystem} ? " on " . $build->system : "") - . (scalar @{$vars->{dependents}} > 0 ? " (and " . scalar @{$vars->{dependents}} . " others)" : ""), - 'X-Hydra-Instance' => $vars->{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); - } + sendEmail( + $self->{config}, $to, $subject, $body, + [ 'X-Hydra-Project' => $build->project->name, + , 'X-Hydra-Jobset' => $build->jobset->name, + , 'X-Hydra-Job' => $build->job->name, + , 'X-Hydra-System' => $build->system + ]); } } diff --git a/src/script/hydra-evaluator b/src/script/hydra-evaluator index 3641bb66..93912827 100755 --- a/src/script/hydra-evaluator +++ b/src/script/hydra-evaluator @@ -7,17 +7,16 @@ use Hydra::Schema; use Hydra::Plugin; use Hydra::Helper::Nix; use Hydra::Helper::AddBuilds; +use Hydra::Helper::Email; use Hydra::Model::DB; use Digest::SHA qw(sha256_hex); -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 Data::Dump qw(dump); +use Try::Tiny; STDOUT->autoflush(); +STDERR->autoflush(1); +binmode STDERR, ":encoding(utf8)"; my $db = Hydra::Model::DB->new(); my $config = getHydraConfig(); @@ -49,7 +48,7 @@ sub setJobsetError { $jobset->update({ errormsg => $errorMsg, errortime => time, fetcherrormsg => undef }); }); }; - if (defined $errorMsg && $errorMsg ne ($prevError // "")) { + if (defined $errorMsg && $errorMsg ne ($prevError // "") || $ENV{'HYDRA_MAIL_TEST'}) { sendJobsetErrorNotification($jobset, $errorMsg); } } @@ -58,42 +57,34 @@ sub setJobsetError { sub sendJobsetErrorNotification() { my ($jobset, $errorMsg) = @_; + chomp $errorMsg; + return if $jobset->project->owner->emailonerror == 0; return if $errorMsg eq ""; - my $url = hostname_long; my $projectName = $jobset->project->name; my $jobsetName = $jobset->name; - - my $sender = $config->{'notification_sender'} || - (($ENV{'USER'} || "hydra") . "@" . $url); - my $body = "Hi,\n" . "\n" - . "This is to let you know that Hydra jobset evaluation of $projectName:$jobsetName " + . "This is to let you know that evaluation of the Hydra jobset ‘$projectName:$jobsetName’\n" . "resulted in the following error:\n" . "\n" . "$errorMsg" . "\n" . "Regards,\n\nThe Hydra build daemon.\n"; - my $email = Email::Simple->create( - header => [ - To => $jobset->project->owner->emailaddress, - From => "Hydra Build Daemon <$sender>", - Subject => "Hydra $projectName:$jobsetName evaluation error", - - 'X-Hydra-Instance' => $url, - 'X-Hydra-Project' => $projectName, - 'X-Hydra-Jobset' => $jobsetName - ], - body => "" - ); - $email->body_set($body); - - print STDERR $email->as_string if $ENV{'HYDRA_MAIL_TEST'}; - - sendmail($email); + try { + sendEmail( + $config, + $jobset->project->owner->emailaddress, + "Hydra $projectName:$jobsetName evaluation error", + $body, + [ 'X-Hydra-Project' => $projectName + , 'X-Hydra-Jobset' => $jobsetName + ]); + } catch { + warn "error sending email: $_\n"; + }; }