Integrated skip verify, gomail and templates

I have switched the mail sending mechanism to gomail to get more power
out of the sending method. Beside that I have added a flag to skip
certificate verification.

On top of that I have integrated the drone templating to make it
possible to overwrite the email templates optionally.

To be more compatible I have created a plaintext message format with the
help of html2text as well.
This commit is contained in:
Thomas Boerger 2015-12-16 11:41:30 +01:00 committed by Thomas Boerger
parent c0654081b5
commit 8484730b82
6 changed files with 431 additions and 117 deletions

56
DOCS.md
View file

@ -22,3 +22,59 @@ notify:
recipients:
- octocat@github.com
```
### Custom Templates
In some cases you may want to customize the look and feel of the email message
so you can use custom templates. For the use case we expose the following
additional parameters, all of the accept a custom handlebars template, directly
provided as a string or as a remote URL which gets fetched and parsed:
* `subject` - A handlebars template to create a custom subject. For more
details take a look at the [docs](http://handlebarsjs.com/). You can see the
default template [here](https://github.com/drone-plugins/drone-email/blob/master/template.go#L4)
* `template` - A handlebars template to create a custom template. For more
details take a look at the [docs](http://handlebarsjs.com/). You can see the
default template [here](https://github.com/drone-plugins/drone-email/blob/master/template.go#L8-L292)
Example configuration that generate a custom email:
```yaml
notify:
email:
from: noreply@github.com
host: smtp.mailgun.org
username: octocat
password: 12345
recipients:
- octocat@github.com
subject: >
[{{ build.status }}]
{{ repo.owner }}/{{ repo.name }}
({{ build.branch }} - {{ truncate build.commit 8 }})
template: >
https://git.io/vgvPz
```
### Skip SSL verify
In some cases you may want to skip SSL verification, even if we discourage that
as it leads to an unsecure environment. Please use this option only within your
intranet and/or with truested resources. For this use case we expose the
following additional parameter:
* `skip_verify` - Skip verification of SSL certificates
Example configuration that skips SSL verification:
```yaml
notify:
email:
from: noreply@github.com
host: smtp.mailgun.org
username: octocat
password: 12345
skip_verify: true
recipients:
- octocat@github.com
```

View file

@ -35,7 +35,7 @@ make deps build
"finished_at": 1421029813,
"message": "Update the Readme",
"author": "johnsmith",
"author_email": "john.smith@gmail.com"
"author_email": "john.smith@gmail.com",
"event": "push",
"branch": "master",
"commit": "436b7a6e2abaddfd35740527353e78a227ddcb2c",

10
main.go
View file

@ -32,6 +32,14 @@ func main() {
}
}
if vargs.Subject == "" {
vargs.Subject = defaultSubject
}
if vargs.Template == "" {
vargs.Template = defaultTemplate
}
if vargs.Port == 0 {
vargs.Port = 587
}
@ -45,8 +53,6 @@ func main() {
if err != nil {
fmt.Println(err)
os.Exit(1)
return
}
}

153
sender.go
View file

@ -1,116 +1,113 @@
package main
import (
"bytes"
"fmt"
"net"
"net/smtp"
"strconv"
"strings"
"crypto/tls"
"github.com/drone/drone-go/drone"
)
const (
Subject = "[%s] %s/%s (%s - %s)"
"github.com/drone/drone-go/template"
"github.com/go-gomail/gomail"
"github.com/jaytaylor/html2text"
)
func Send(context *Context) error {
switch context.Build.Status {
case drone.StatusSuccess:
return SendSuccess(context)
default:
return SendFailure(context)
payload := &drone.Payload{
System: &context.System,
Repo: &context.Repo,
Build: &context.Build,
}
}
// SendFailure sends email notifications to the list of
// recipients indicating the build failed.
func SendFailure(context *Context) error {
// generate the email failure template
var buf bytes.Buffer
err := failureTemplate.ExecuteTemplate(&buf, "_", context)
subject, plain, html, err := build(
payload,
context,
)
if err != nil {
return err
}
// generate the email subject
var subject = fmt.Sprintf(
Subject,
context.Build.Status,
context.Repo.Owner,
context.Repo.Name,
context.Build.Branch,
context.Build.Commit[:8],
return send(
subject,
plain,
html,
context,
)
return send(subject, buf.String(), context)
}
// SendSuccess sends email notifications to the list of
// recipients indicating the build was a success.
func SendSuccess(context *Context) error {
// generate the email success template
var buf bytes.Buffer
err := successTemplate.ExecuteTemplate(&buf, "_", context)
func build(payload *drone.Payload, context *Context) (string, string, string, error) {
subject, err := template.RenderTrim(
context.Vargs.Subject,
payload)
if err != nil {
return err
return "", "", "", err
}
// generate the email subject
var subject = fmt.Sprintf(
Subject,
context.Build.Status,
context.Repo.Owner,
context.Repo.Name,
context.Build.Branch,
context.Build.Commit[:8],
html, err := template.RenderTrim(
context.Vargs.Template,
payload,
)
return send(subject, buf.String(), context)
if err != nil {
return "", "", "", err
}
plain, err := html2text.FromString(
html,
)
if err != nil {
return "", "", "", err
}
return subject, plain, html, nil
}
func send(subject, body string, c *Context) error {
func send(subject, plainBody, htmlBody string, c *Context) error {
if len(c.Vargs.Recipients) == 0 {
c.Vargs.Recipients = []string{
c.Build.Email,
}
}
var auth smtp.Auth
m := gomail.NewMessage()
var addr = net.JoinHostPort(
m.SetHeader(
"To",
c.Vargs.Recipients...,
)
m.SetHeader(
"From",
c.Vargs.From,
)
m.SetHeader(
"Subject",
subject,
)
m.AddAlternative(
"text/plain",
plainBody,
)
m.AddAlternative(
"text/html",
htmlBody,
)
d := gomail.NewPlainDialer(
c.Vargs.Host,
strconv.Itoa(c.Vargs.Port))
c.Vargs.Port,
c.Vargs.Username,
c.Vargs.Password,
)
// setup the authentication to the smtp server
// if the username and password are provided.
if len(c.Vargs.Username) > 0 {
auth = smtp.PlainAuth(
"",
c.Vargs.Username,
c.Vargs.Password,
c.Vargs.Host)
if c.Vargs.SkipVerify {
d.TLSConfig = &tls.Config{
InsecureSkipVerify: true,
}
}
// genereate the raw email message
var to = strings.Join(
c.Vargs.Recipients,
",")
var raw = fmt.Sprintf(
rawMessage,
c.Vargs.From,
to,
subject,
body)
return smtp.SendMail(
addr,
auth,
c.Vargs.From,
c.Vargs.Recipients,
[]byte(raw))
return d.DialAndSend(m)
}

View file

@ -1,41 +1,293 @@
package main
import (
"html/template"
)
var defaultSubject = `
[{{ build.status }}] {{ repo.owner }}/{{ repo.name }} ({{ build.branch }} - {{ truncate build.commit 8 }})
`
// raw email message template
var rawMessage = `From: %s
To: %s
Subject: %s
MIME-version: 1.0
Content-Type: text/html; charset="UTF-8"
%s`
var defaultTemplate = `
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<meta name="viewport" content="width=device-width" />
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
// default success email template
var successTemplate = template.Must(template.New("_").Parse(`
<p>
<b>Build was Successful</b>
(<a href="{{.System.Link}}/{{.Repo.Owner}}/{{.Repo.Name}}/{{.Build.Number}}">see results</a>)
</p>
<p>Repository: {{.Repo.Owner}}/{{.Repo.Name}}</p>
<p>Commit: {{.Build.Commit}}</p>
<p>Author: {{.Build.Author}}</p>
<p>Branch: {{.Build.Branch}}</p>
<p>Message:</p>
<p>{{ .Build.Message }}</p>
`))
<style>
* {
margin: 0;
padding: 0;
font-family: "Helvetica Neue", "Helvetica", Helvetica, Arial, sans-serif;
box-sizing: border-box;
font-size: 14px;
}
// default failure email template
var failureTemplate = template.Must(template.New("_").Parse(`
<p>
<b>Build Failed</b>
(<a href="{{.System.Link}}/{{.Repo.Owner}}/{{.Repo.Name}}/{{.Build.Number}}">see results</a>)
</p>
<p>Repository: {{.Repo.Owner}}/{{.Repo.Name}}</p>
<p>Commit: {{.Build.Commit}}</p>
<p>Author: {{.Build.Author}}</p>
<p>Branch: {{.Build.Branch}}</p>
<p>Message:</p>
<p>{{ .Build.Message }}</p>
`))
body {
-webkit-font-smoothing: antialiased;
-webkit-text-size-adjust: none;
width: 100% !important;
height: 100%;
line-height: 1.6;
background-color: #f6f6f6;
}
table td {
vertical-align: top;
}
.body-wrap {
background-color: #f6f6f6;
width: 100%;
}
.container {
display: block !important;
max-width: 600px !important;
margin: 0 auto !important;
/* makes it centered */
clear: both !important;
}
.content {
max-width: 600px;
margin: 0 auto;
display: block;
padding: 20px;
}
.main {
background: #fff;
border: 1px solid #e9e9e9;
border-radius: 3px;
}
.content-wrap {
padding: 20px;
}
.content-block {
padding: 0 0 20px;
}
.header {
width: 100%;
margin-bottom: 20px;
}
h1, h2, h3 {
font-family: "Helvetica Neue", Helvetica, Arial, "Lucida Grande", sans-serif;
color: #000;
margin: 40px 0 0;
line-height: 1.2;
font-weight: 400;
}
h1 {
font-size: 32px;
font-weight: 500;
}
h2 {
font-size: 24px;
}
h3 {
font-size: 18px;
}
hr {
border: 1px solid #e9e9e9;
margin: 20px 0;
height: 1px;
padding: 0;
}
p,
ul,
ol {
margin-bottom: 10px;
font-weight: normal;
}
p li,
ul li,
ol li {
margin-left: 5px;
list-style-position: inside;
}
a {
color: #348eda;
text-decoration: underline;
}
.last {
margin-bottom: 0;
}
.first {
margin-top: 0;
}
.padding {
padding: 10px 0;
}
.aligncenter {
text-align: center;
}
.alignright {
text-align: right;
}
.alignleft {
text-align: left;
}
.clear {
clear: both;
}
.alert {
font-size: 16px;
color: #fff;
font-weight: 500;
padding: 20px;
text-align: center;
border-radius: 3px 3px 0 0;
}
.alert a {
color: #fff;
text-decoration: none;
font-weight: 500;
font-size: 16px;
}
.alert.alert-warning {
background: #ff9f00;
}
.alert.alert-bad {
background: #d0021b;
}
.alert.alert-good {
background: #68b90f;
}
@media only screen and (max-width: 640px) {
h1,
h2,
h3 {
font-weight: 600 !important;
margin: 20px 0 5px !important;
}
h1 {
font-size: 22px !important;
}
h2 {
font-size: 18px !important;
}
h3 {
font-size: 16px !important;
}
.container {
width: 100% !important;
}
.content,
.content-wrapper {
padding: 10px !important;
}
}
</style>
</head>
<body>
<table class="body-wrap">
<tr>
<td></td>
<td class="container" width="600">
<div class="content">
<table class="main" width="100%" cellpadding="0" cellspacing="0">
<tr>
{{#success build.status}}
<td class="alert alert-good">
<a href="{{ system.link_url }}/{{ repo.owner }}/{{ repo.name }}/{{ build.number }}">
Successful build #{{ build.number }}
</a>
</td>
{{else}}
<td class="alert alert-bad">
<a href="{{ system.link_url }}/{{ repo.owner }}/{{ repo.name }}/{{ build.number }}">
Failed build #{{ build.number }}
</a>
</td>
{{/success}}
</tr>
<tr>
<td class="content-wrap">
<table width="100%" cellpadding="0" cellspacing="0">
<tr>
<td>
Repo:
</td>
<td>
{{ repo.owner }}/{{ repo.name }}
</td>
</tr>
<tr>
<td>
Author:
</td>
<td>
{{ build.author }}
</td>
</tr>
<tr>
<td>
Branch:
</td>
<td>
{{ build.branch }}
</td>
</tr>
<tr>
<td>
Commit:
</td>
<td>
{{ truncate build.commit 8 }}
</td>
</tr>
<tr>
<td>
Time:
</td>
<td>
{{ duration build.started_at build.finished_at }}
</td>
</tr>
</table>
<hr>
<table width="100%" cellpadding="0" cellspacing="0">
<tr>
<td>
{{ build.message }}
</td>
</tr>
</table>
</td>
</tr>
</table>
</div>
</td>
<td></td>
</tr>
</table>
</body>
</html>
`

View file

@ -11,6 +11,9 @@ type Params struct {
From string `json:"from"`
Username string `json:"username"`
Password string `json:"password"`
Subject string `json:"subject"`
Template string `json:"template"`
SkipVerify bool `json:"skip_verify"`
}
type Context struct {