Skip to content

Commit

Permalink
Rework email plugin 💌 (#25)
Browse files Browse the repository at this point in the history
Signed-off-by: Valentin Pichard <[email protected]>
  • Loading branch information
w3st3ry authored Nov 22, 2019
1 parent b6f8ce4 commit 9570f38
Show file tree
Hide file tree
Showing 28 changed files with 2,236 additions and 59 deletions.
4 changes: 3 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -422,7 +422,9 @@ Browse [builtin actions](./pkg/plugins/builtin)
| || `smtp_password`: password of SMTP server
| || `smtp_port`: port of SMTP server
| || `smtp_hostname`: hostname of SMTP server
| || `from`: from which email you want to send the message
| || `smtp_skip_tls_verif`: Skip or not TLS insecure verify
| || `from_address`: from which email you want to send the message
| || `from_name`: from which name you want to send the message
| || `to`: receiver(s) of your email
| || `subject`: subject of your email
| || `body`: content of your email
Expand Down
2 changes: 2 additions & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -62,8 +62,10 @@ require (
golang.org/x/net v0.0.0-20190921015927-1a5e07d1ff72 // indirect
golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e
google.golang.org/appengine v1.6.3 // indirect
gopkg.in/alexcesaro/quotedprintable.v3 v3.0.0-20150716171945-2caba252f4dc // indirect
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 // indirect
gopkg.in/ini.v1 v1.46.0 // indirect
gopkg.in/mail.v2 v2.3.1
gopkg.in/mgo.v2 v2.0.0-20180705113604-9856a29383ce // indirect
gopkg.in/sourcemap.v1 v1.0.5 // indirect
)
Expand Down
4 changes: 4 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -326,6 +326,8 @@ google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoA
google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
google.golang.org/grpc v1.21.0/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM=
gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw=
gopkg.in/alexcesaro/quotedprintable.v3 v3.0.0-20150716171945-2caba252f4dc h1:2gGKlE2+asNV9m7xrywl36YYNnBG5ZQ0r/BOOxqPpmk=
gopkg.in/alexcesaro/quotedprintable.v3 v3.0.0-20150716171945-2caba252f4dc/go.mod h1:m7x9LTH6d71AHyAX77c9yqWCCa3UKHcVEj9y7hAtKDk=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 h1:YR8cESwS4TdDjEe65xsg0ogRM/Nc3DYOhEAlW+xobZo=
Expand All @@ -340,6 +342,8 @@ gopkg.in/go-playground/validator.v9 v9.26.0 h1:2NPPsBpD0ZoxshmLWewQru8rWmbT5JqSz
gopkg.in/go-playground/validator.v9 v9.26.0/go.mod h1:+c9/zcJMFNgbLvly1L1V+PpxWdVbfP1avr/N00E2vyQ=
gopkg.in/ini.v1 v1.46.0 h1:VeDZbLYGaupuvIrsYCEOe/L/2Pcs5n7hdO1ZTjporag=
gopkg.in/ini.v1 v1.46.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
gopkg.in/mail.v2 v2.3.1 h1:WYFn/oANrAGP2C0dcV6/pbkPzv8yGzqTjPmTeO7qoXk=
gopkg.in/mail.v2 v2.3.1/go.mod h1:htwXN1Qh09vZJ1NVKxQqHPBaCBbzKhp5GzuJEA4VJWw=
gopkg.in/mgo.v2 v2.0.0-20180705113604-9856a29383ce h1:xcEWjVhvbDy+nHP67nPDDpbYrY+ILlfndk4bRioVHaU=
gopkg.in/mgo.v2 v2.0.0-20180705113604-9856a29383ce/go.mod h1:yeKp02qBN3iKW1OzL3MGk2IdtZzaj7SFntXj72NppTA=
gopkg.in/resty.v1 v1.12.0/go.mod h1:mDo4pnntr5jdWRML875a/NmxYqAlA73dVijT2AXvQQo=
Expand Down
20 changes: 18 additions & 2 deletions pkg/plugins/builtin/email/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,12 +14,16 @@ action:
smtp_username: {{.config.smtp.username}}
# mandatory, string
smtp_password: {{.config.smtp.password}}
# optional, uint (default is 25)
# mandatory, string as uint
smtp_port: {{.config.smtp.port}}
# mandatory, string
smtp_hostname: {{.config.smtp.hostname}}
# optional, string as boolean
smtp_skip_tls_verify: "true"
# mandatory, string
from: [email protected]
from_address: [email protected]
# optional, string
from_name: uTask bot
# mandatory, string collection
to: [[email protected], [email protected]]
# mandatory, string
Expand All @@ -31,4 +35,16 @@ action:
## Note
The plugin returns an object to reuse the parameters in a future component:
```json
{
"from_address":"[email protected]",
"from_name":"uTask bot",
"to": ["[email protected]", "[email protected]"],
"subject":"Hello from µTask",
"body":"I love baguette"
}
```

Sensitive data should be retrieved from configstore and accessed through `{{.config.[itemKey]}}` rather than hardcoded in your template.
113 changes: 57 additions & 56 deletions pkg/plugins/builtin/email/email.go
Original file line number Diff line number Diff line change
@@ -1,65 +1,79 @@
package email

import (
"bytes"
"crypto/tls"
"errors"
"fmt"
"mime"
"net/smtp"
"strings"
"text/template"
"net/http"
"strconv"

mail "gopkg.in/mail.v2"

"github.com/ovh/utask/pkg/plugins/taskplugin"
)

// the email plugin send email
var (
Plugin = taskplugin.New("email", "0.1", exec,
Plugin = taskplugin.New("email", "0.2", exec,
taskplugin.WithConfig(validConfig, Config{}),
)
)

type mailParameters struct {
FromAddress string `json:"from_address"`
FromName string `json:"from_name,omitempty"`
To []string `json:"to"`
Subject string `json:"subject"`
Body string `json:"body"`
}

// Config is the configuration needed to send an email
type Config struct {
SMTPUsername string `json:"smtp_username"`
SMTPPassword string `json:"smtp_password"`
SMTPPort uint16 `json:"smtp_port,omitempty"`
SMTPHostname string `json:"smtp_hostname"`
From string `json:"from"`
To []string `json:"to"`
Subject string `json:"subject"`
Body string `json:"body"`
SMTPUsername string `json:"smtp_username"`
SMTPPassword string `json:"smtp_password"`
SMTPPort string `json:"smtp_port"`
SMTPHostname string `json:"smtp_hostname"`
SMTPSkipTLSVerify string `json:"smtp_skip_tls_verify,omitempty"`
mailParameters
}

const emailTemplate = "From: {{.From}}\r\n" +
"To: {{.To}}\r\n" +
"Subject: {{.Subject}}\r\n" +
"MIME-version: 1.0\r\n" +
"Content-Type: text/html; charset=\"UTF-8\"\r\n" +
"\r\n" +
"{{.Body}}"

func validConfig(config interface{}) error {
cfg := config.(*Config)

if cfg.SMTPUsername == "" {
return errors.New("smtp_username is missing")
}

if cfg.SMTPPassword == "" {
return errors.New("smtp_password is missing")
}
if cfg.From == "" {
return errors.New("from is missing")
}
if len(cfg.To) == 0 {
return errors.New("to is missing")

if _, err := strconv.ParseUint(cfg.SMTPPort, 10, 64); err != nil {
return fmt.Errorf("smtp_port is missing or wrong %s", err.Error())
}

if cfg.SMTPHostname == "" {
return errors.New("smtp_hostname is missing")
}

if cfg.SMTPSkipTLSVerify != "" {
if _, err := strconv.ParseBool(cfg.SMTPSkipTLSVerify); err != nil {
return fmt.Errorf("smtp_skip_tls_verify is wrong %s", err)
}
}

if cfg.FromAddress == "" {
return errors.New("from_address is missing")
}

if len(cfg.To) == 0 {
return errors.New("to is missing")
}

if cfg.Subject == "" {
return errors.New("subject is missing")
}

if cfg.Body == "" {
return errors.New("body is missing")
}
Expand All @@ -70,39 +84,26 @@ func validConfig(config interface{}) error {
func exec(stepName string, config interface{}, ctx interface{}) (interface{}, interface{}, error) {
cfg := config.(*Config)

parameters := struct {
From string `json:"from"`
To string `json:"to"`
Subject string `json:"subject"`
Body string `json:"body"`
}{
cfg.From,
strings.Join(cfg.To, ","),
mime.BEncoding.Encode("UTF-8", cfg.Subject),
cfg.Body,
}

buffer := new(bytes.Buffer)

template := template.Must(template.New("emailTemplate").Parse(emailTemplate))
template.Execute(buffer, &parameters)
message := mail.NewMessage()

auth := smtp.PlainAuth("", cfg.SMTPUsername, cfg.SMTPPassword, cfg.SMTPHostname)
message.SetAddressHeader("From", cfg.FromAddress, cfg.FromName)
message.SetHeader("To", cfg.To...)
message.SetHeader("Subject", cfg.Subject)
message.SetBody(
http.DetectContentType([]byte(cfg.Body)),
cfg.Body,
)

port := cfg.SMTPPort
if port == 0 {
port = 25
}
// port and skipTLS already checked at validConfig() lvl
// values must be correct so errors are not evaluated
port, _ := strconv.ParseUint(cfg.SMTPPort, 10, 64) // no defaults, must be set by user
skipTLS, _ := strconv.ParseBool(cfg.SMTPSkipTLSVerify) // defaults to false

err := smtp.SendMail(
fmt.Sprintf("%s:%d", cfg.SMTPHostname, cfg.SMTPPort),
auth,
cfg.From,
cfg.To,
buffer.Bytes())
if err != nil {
d := mail.NewDialer(cfg.SMTPHostname, int(port), cfg.SMTPUsername, cfg.SMTPPassword)
d.TLSConfig = &tls.Config{InsecureSkipVerify: skipTLS, ServerName: cfg.SMTPHostname}
if err := d.DialAndSend(message); err != nil {
return nil, nil, fmt.Errorf("Send email failed: %s", err.Error())
}

return &parameters, nil, nil
return &cfg.mailParameters, nil, nil
}
20 changes: 20 additions & 0 deletions vendor/gopkg.in/alexcesaro/quotedprintable.v3/LICENSE

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

16 changes: 16 additions & 0 deletions vendor/gopkg.in/alexcesaro/quotedprintable.v3/README.md

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading

0 comments on commit 9570f38

Please sign in to comment.