forked from mcuadros/ofelia
-
Notifications
You must be signed in to change notification settings - Fork 2
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
3 changed files
with
242 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,157 @@ | ||
package middlewares | ||
|
||
import ( | ||
"bytes" | ||
"encoding/json" | ||
"fmt" | ||
"html/template" | ||
"io" | ||
"os" | ||
"strings" | ||
|
||
"gopkg.in/gomail.v2" | ||
|
||
"github.com/mcuadros/ofelia/core" | ||
) | ||
|
||
// MailConfig configuration for the Mail middleware | ||
type MailConfig struct { | ||
SMTPHost string `gcfg:"smtp-host"` | ||
SMTPPort int `gcfg:"smtp-port"` | ||
SMTPUser string `gcfg:"smtp-user"` | ||
SMTPPassword string `gcfg:"smtp-password"` | ||
EmailTo string `gcfg:"email-to"` | ||
EmailFrom string `gcfg:"email-from" default:"ofelia@%s"` | ||
MailOnlyOnError bool `gcfg:"mail-only-on-error"` | ||
} | ||
|
||
// NewMail returns a Mail middleware if the given configuration is not empty | ||
func NewMail(c *MailConfig) core.Middleware { | ||
var m core.Middleware | ||
|
||
if !IsEmpty(c) { | ||
m = &Mail{*c} | ||
} | ||
|
||
return m | ||
} | ||
|
||
// Mail middleware delivers a email just after an execution finishes | ||
type Mail struct { | ||
MailConfig | ||
} | ||
|
||
// ContinueOnStop return allways true, we want always report the final status | ||
func (m *Mail) ContinueOnStop() bool { | ||
return true | ||
} | ||
|
||
// Run sents a email with the result of the execution | ||
func (m *Mail) Run(ctx *core.Context) error { | ||
err := ctx.Next() | ||
ctx.Stop(err) | ||
|
||
if ctx.Execution.Failed || !m.MailOnlyOnError { | ||
err := m.sendMail(ctx) | ||
if err != nil { | ||
ctx.Logger.Error("Mail error: %q", err) | ||
} | ||
} | ||
|
||
return err | ||
} | ||
|
||
func (m *Mail) sendMail(ctx *core.Context) error { | ||
msg := gomail.NewMessage() | ||
msg.SetHeader("From", m.from()) | ||
msg.SetHeader("To", strings.Split(m.EmailTo, ",")...) | ||
msg.SetHeader("Subject", m.subject(ctx)) | ||
msg.SetBody("text/html", m.body(ctx)) | ||
|
||
base := fmt.Sprintf("%s_%s", ctx.Job.GetName(), ctx.Execution.ID) | ||
msg.Attach(base+".stdout.log", gomail.SetCopyFunc(func(w io.Writer) error { | ||
_, err := io.Copy(w, ctx.Execution.OutputStream) | ||
return err | ||
})) | ||
|
||
msg.Attach(base+".stderr.log", gomail.SetCopyFunc(func(w io.Writer) error { | ||
_, err := io.Copy(w, ctx.Execution.ErrorStream) | ||
return err | ||
})) | ||
|
||
msg.Attach(base+".stderr.json", gomail.SetCopyFunc(func(w io.Writer) error { | ||
js, _ := json.MarshalIndent(map[string]interface{}{ | ||
"Job": ctx.Job, | ||
"Execution": ctx.Execution, | ||
}, "", " ") | ||
|
||
_, err := w.Write(js) | ||
return err | ||
})) | ||
|
||
d := gomail.NewPlainDialer(m.SMTPHost, m.SMTPPort, m.SMTPUser, m.SMTPPassword) | ||
if err := d.DialAndSend(msg); err != nil { | ||
return err | ||
} | ||
|
||
return nil | ||
} | ||
|
||
func (m *Mail) from() string { | ||
if strings.Index(m.EmailFrom, "%") == -1 { | ||
return m.EmailFrom | ||
} | ||
|
||
hostname, _ := os.Hostname() | ||
return fmt.Sprintf(m.EmailFrom, hostname) | ||
} | ||
|
||
func (m *Mail) subject(ctx *core.Context) string { | ||
buf := bytes.NewBuffer(nil) | ||
mailSubjectTemplate.Execute(buf, ctx) | ||
|
||
return buf.String() | ||
} | ||
|
||
func (m *Mail) body(ctx *core.Context) string { | ||
buf := bytes.NewBuffer(nil) | ||
mailBodyTemplate.Execute(buf, ctx) | ||
|
||
return buf.String() | ||
} | ||
|
||
var mailBodyTemplate, mailSubjectTemplate *template.Template | ||
|
||
func init() { | ||
f := map[string]interface{}{ | ||
"status": executionLabel, | ||
} | ||
|
||
mailBodyTemplate = template.New("mail-body") | ||
mailSubjectTemplate = template.New("mail-subject") | ||
mailBodyTemplate.Funcs(f) | ||
mailSubjectTemplate.Funcs(f) | ||
|
||
template.Must(mailBodyTemplate.Parse(` | ||
<p> | ||
Job <b>{{.Job.GetName}}</b>, | ||
Execution <b>{{status .Execution}}</b> in <b>{{.Execution.Duration}}</b>, | ||
command: <pre>{{.Job.GetCommand}}</pre> | ||
</p> | ||
`)) | ||
|
||
template.Must(mailSubjectTemplate.Parse( | ||
"[Execution {{status .Execution}}] Job {{.Job.GetName}} finished in {{.Execution.Duration}}", | ||
)) | ||
} | ||
|
||
func executionLabel(e *core.Execution) string { | ||
status := "successful" | ||
if e.Skipped { | ||
status = "skipped" | ||
} else if e.Failed { | ||
status = "failed" | ||
} | ||
|
||
return status | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,79 @@ | ||
package middlewares | ||
|
||
import ( | ||
"net" | ||
"strconv" | ||
"strings" | ||
"sync" | ||
|
||
"github.com/bradfitz/go-smtpd/smtpd" | ||
|
||
. "gopkg.in/check.v1" | ||
) | ||
|
||
type MailSuite struct { | ||
BaseSuite | ||
|
||
l net.Listener | ||
smtpd *smtpd.Server | ||
smtpdHost string | ||
smtpdPort int | ||
} | ||
|
||
var _ = Suite(&MailSuite{}) | ||
|
||
func (s *MailSuite) SetUpTest(c *C) { | ||
s.BaseSuite.SetUpTest(c) | ||
|
||
s.smtpd = &smtpd.Server{ | ||
Addr: ":0", | ||
} | ||
|
||
ln, err := net.Listen("tcp", "127.0.0.1:0") | ||
c.Assert(err, IsNil) | ||
|
||
s.l = ln | ||
go func() { | ||
err := s.smtpd.Serve(ln) | ||
c.Assert(err, IsNil) | ||
}() | ||
|
||
p := strings.Split(s.l.Addr().String(), ":") | ||
s.smtpdHost = p[0] | ||
s.smtpdPort, _ = strconv.Atoi(p[1]) | ||
} | ||
|
||
func (s *MailSuite) TearDownTest(c *C) { | ||
s.l.Close() | ||
} | ||
|
||
func (s *MailSuite) TestNewSlackEmpty(c *C) { | ||
c.Assert(NewMail(&MailConfig{}), IsNil) | ||
} | ||
|
||
func (s *MailSuite) TestRunSuccess(c *C) { | ||
s.ctx.Start() | ||
s.ctx.Stop(nil) | ||
|
||
m := NewMail(&MailConfig{ | ||
SMTPHost: s.smtpdHost, | ||
SMTPPort: s.smtpdPort, | ||
EmailTo: "[email protected]", | ||
EmailFrom: "[email protected]", | ||
}) | ||
|
||
var wg sync.WaitGroup | ||
s.smtpd.OnNewMail = func(_ smtpd.Connection, from smtpd.MailAddress) (smtpd.Envelope, error) { | ||
c.Assert(from.Email(), Equals, "[email protected]") | ||
wg.Done() | ||
|
||
return nil, nil | ||
} | ||
|
||
wg.Add(1) | ||
go func() { | ||
c.Assert(m.Run(s.ctx), IsNil) | ||
}() | ||
|
||
wg.Wait() | ||
} |