You've already forked drone-email
This commit is contained in:
278
pkg/mail/assets/mail.txt
Normal file
278
pkg/mail/assets/mail.txt
Normal file
@ -0,0 +1,278 @@
|
||||
Date: {{ .TimeNowFormat "Mon, 02 Jan 2006 15:04:05" }}
|
||||
From: {{ .SMTPSettings.FromName }} <{{ .SMTPSettings.FromAddress }}>
|
||||
To: {{ .Recipient }}
|
||||
Subject: [{{ .CIVars.Build.Status }}] {{ .CIVars.Repo.Name }} ({{ .CIVars.Commit.Branch }} - {{ .CIVars.Commit.Sha }})
|
||||
Content-Type: multipart/alternative;
|
||||
boundary=3399d59dca7fb53c0236f440e2a402d670fc0abe57faa6f0233e85338b03
|
||||
|
||||
--3399d59dca7fb53c0236f440e2a402d670fc0abe57faa6f0233e85338b03
|
||||
Content-Transfer-Encoding: quoted-printable
|
||||
Content-Type: text/plain; charset=UTF-8
|
||||
|
||||
{{- if .CIVars.Build.IsStatus "success" -}}
|
||||
Success build #{{ .CIVars.Build.Number }}
|
||||
{{- else -}}
|
||||
Failed build #{{ .CIVars.Build.Number }}
|
||||
{{- end }}
|
||||
|
||||
Name: {{ .CIVars.Repo.Name }}
|
||||
Author: {{ .CIVars.Commit.Author.Name }} <{{ .CIVars.Commit.Author.Email }}>
|
||||
Branch: {{ .CIVars.Repo.Branch }}
|
||||
Commit: {{ .CIVars.Commit.Sha }}
|
||||
Started At: {{ .CIVars.Build.StartedToTimeFormat "2006-02-01 15:04:05" }}
|
||||
Link: {{ .CIVars.Build.Link }}
|
||||
|
||||
--3399d59dca7fb53c0236f440e2a402d670fc0abe57faa6f0233e85338b03
|
||||
Content-Transfer-Encoding: quoted-printable
|
||||
Content-Type: text/html; charset=UTF-8
|
||||
|
||||
<!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" />
|
||||
<style>
|
||||
* {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
font-family: "Helvetica Neue", "Helvetica", Helvetica, Arial, sans-serif;
|
||||
box-sizing: border-box;
|
||||
font-size: 14px;
|
||||
}
|
||||
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>
|
||||
{{ if .CIVars.Build.IsStatus "success" }}
|
||||
<td class="alert alert-good">
|
||||
<a href="{{ .CIVars.Build.Link }}">
|
||||
Successful build #{{ .CIVars.Build.Number }}
|
||||
</a>
|
||||
</td>
|
||||
{{ else }}
|
||||
<td class="alert alert-bad">
|
||||
<a href="{{ .CIVars.Build.Link }}">
|
||||
Failed build #{{ .CIVars.Build.Number }}
|
||||
</a>
|
||||
</td>
|
||||
{{ end }}
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="content-wrap">
|
||||
<table width="100%" cellpadding="0" cellspacing="0">
|
||||
<tr>
|
||||
<td>
|
||||
Repo:
|
||||
</td>
|
||||
<td>
|
||||
{{ .CIVars.Repo.Name }}
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
Author:
|
||||
</td>
|
||||
<td>
|
||||
{{ .CIVars.Commit.Author.Name }} <{{ .CIVars.Commit.Author.Email }}>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
Branch:
|
||||
</td>
|
||||
<td>
|
||||
{{ .CIVars.Commit.Branch }}
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
Commit:
|
||||
</td>
|
||||
<td>
|
||||
{{ .CIVars.Commit.Sha }}
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
Started at:
|
||||
</td>
|
||||
<td>
|
||||
{{ .CIVars.Build.StartedToTimeFormat "2006-02-01 15:04:05" }}
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
<hr>
|
||||
<table width="100%" cellpadding="0" cellspacing="0">
|
||||
<tr>
|
||||
<td>
|
||||
{{ .CIVars.Commit.Message }}
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
</div>
|
||||
</td>
|
||||
<td></td>
|
||||
</tr>
|
||||
</table>
|
||||
</body>
|
||||
</html>
|
||||
--3399d59dca7fb53c0236f440e2a402d670fc0abe57faa6f0233e85338b03--
|
180
pkg/mail/mail.go
Normal file
180
pkg/mail/mail.go
Normal file
@ -0,0 +1,180 @@
|
||||
package mail
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"crypto/tls"
|
||||
"fmt"
|
||||
"io"
|
||||
"net"
|
||||
"net/smtp"
|
||||
"text/template"
|
||||
"time"
|
||||
|
||||
"git.cryptic.systems/volker.raschek/drone-email-docker/pkg/domain"
|
||||
|
||||
_ "embed"
|
||||
)
|
||||
|
||||
const (
|
||||
DefaultSMTPFromAddress = "root@localhost"
|
||||
DefaultSMTPFromName = "root"
|
||||
DefaultSMTPHost = "localhost"
|
||||
DefaultSMTPPort = 587
|
||||
DefaultSMTPStartTLS = true
|
||||
DefaultSMTPTLSInsecureSkipVerify = false
|
||||
DefaultSMTPToAddress = "root@localhost"
|
||||
)
|
||||
|
||||
//go:embed assets/mail.txt
|
||||
var mailTemplate string
|
||||
|
||||
type CIVars struct {
|
||||
Build *domain.Build
|
||||
Commit *domain.Commit
|
||||
DeployTo string
|
||||
Job *domain.Job
|
||||
Prev *domain.Prev
|
||||
PullRequest int
|
||||
Remote *domain.Remote
|
||||
Repo *domain.Repo
|
||||
Tag string
|
||||
Yaml *domain.Yaml
|
||||
}
|
||||
|
||||
type templateVars struct {
|
||||
CIVars *CIVars
|
||||
Recipient string
|
||||
SMTPSettings *domain.SMTPSettings
|
||||
}
|
||||
|
||||
func (t *templateVars) TimeNowFormat(layout string) string {
|
||||
return time.Now().Format(layout)
|
||||
}
|
||||
|
||||
type Plugin struct {
|
||||
smtpSettings *domain.SMTPSettings
|
||||
}
|
||||
|
||||
// Exec will send emails over SMTP
|
||||
func (p *Plugin) Exec(ctx context.Context, recipients []string, ciVars *CIVars) error {
|
||||
exists := false
|
||||
for _, recipient := range recipients {
|
||||
if recipient == ciVars.Commit.Author.Email {
|
||||
exists = true
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if !exists {
|
||||
recipients = append(recipients, ciVars.Commit.Author.Email)
|
||||
}
|
||||
|
||||
tpl, err := template.New("mail").Parse(mailTemplate)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to parse template: %w", err)
|
||||
}
|
||||
|
||||
buf := make([]byte, 0)
|
||||
buffer := bytes.NewBuffer(buf)
|
||||
|
||||
for _, recipient := range recipients {
|
||||
err = tpl.Execute(buffer, &templateVars{
|
||||
CIVars: ciVars,
|
||||
Recipient: recipient,
|
||||
SMTPSettings: p.smtpSettings,
|
||||
})
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to generate template: %w", err)
|
||||
}
|
||||
|
||||
err := p.sendMail(recipient, buffer)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to send mail: %w", err)
|
||||
}
|
||||
|
||||
buffer.Reset()
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (p *Plugin) sendMail(recipient string, r io.Reader) error {
|
||||
// log.Printf("FROM_ADDRESS: %s", p.smtpSettings.FromAddress)
|
||||
// log.Printf("FROM_NAME: %s", p.smtpSettings.FromName)
|
||||
// log.Printf("HELO: %s", p.smtpSettings.HELOName)
|
||||
// log.Printf("HOST: %s", p.smtpSettings.Host)
|
||||
// log.Printf("PASSWORD: %s", p.smtpSettings.Password)
|
||||
// log.Printf("USERNAME: %s", p.smtpSettings.Username)
|
||||
// log.Printf("PORT: %v", p.smtpSettings.Port)
|
||||
// log.Printf("START_TLS: %v", p.smtpSettings.StartTLS)
|
||||
// log.Printf("INSECURE: %v", p.smtpSettings.TLSInsecureSkipVerify)
|
||||
|
||||
address := fmt.Sprintf("%s:%d", p.smtpSettings.Host, p.smtpSettings.Port)
|
||||
tcpConn, err := net.Dial("tcp", address)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to dial a connection to %s: %w", address, err)
|
||||
}
|
||||
defer func() { _ = tcpConn.Close() }()
|
||||
|
||||
smtpClient, err := smtp.NewClient(tcpConn, p.smtpSettings.Host)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to initialize a new smtp client: %w", err)
|
||||
}
|
||||
defer func() { _ = smtpClient.Close() }()
|
||||
|
||||
err = smtpClient.Hello(p.smtpSettings.HELOName)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to send helo command: %w", err)
|
||||
}
|
||||
|
||||
// #nosec G402
|
||||
err = smtpClient.StartTLS(&tls.Config{
|
||||
InsecureSkipVerify: p.smtpSettings.TLSInsecureSkipVerify,
|
||||
MinVersion: tls.VersionTLS12,
|
||||
ServerName: p.smtpSettings.Host,
|
||||
})
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed initialize starttls session: %w", err)
|
||||
}
|
||||
|
||||
smtpAuth := smtp.PlainAuth(p.smtpSettings.FromAddress, p.smtpSettings.FromAddress, p.smtpSettings.Password, p.smtpSettings.Host)
|
||||
err = smtpClient.Auth(smtpAuth)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to authenticate client: %w", err)
|
||||
}
|
||||
|
||||
err = smtpClient.Mail(p.smtpSettings.FromAddress)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to sent mail command: %w", err)
|
||||
}
|
||||
|
||||
err = smtpClient.Rcpt(recipient)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to sent rcpt command for %s: %w", recipient, err)
|
||||
}
|
||||
|
||||
wc, err := smtpClient.Data()
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to send data command: %w", err)
|
||||
}
|
||||
defer func() { _ = wc.Close() }()
|
||||
|
||||
_, err = io.Copy(wc, r)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to copy input from passed reader to smtp writer: %w", err)
|
||||
}
|
||||
|
||||
err = smtpClient.Quit()
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to send quit command: %w", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func NewPlugin(config *domain.SMTPSettings) *Plugin {
|
||||
return &Plugin{
|
||||
smtpSettings: config,
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user