You've already forked drone-email
							
							
		
			All checks were successful
		
		
	
	continuous-integration/drone/push Build is passing
				
			
		
			
				
	
	
		
			189 lines
		
	
	
		
			4.7 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
			
		
		
	
	
			189 lines
		
	
	
		
			4.7 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
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)
 | 
						|
	}
 | 
						|
 | 
						|
	// close smtpClient before defer to avoid returning an error of
 | 
						|
	// smtpClient.Quit() like the following example:
 | 
						|
	// Error: failed to execute mail plugin: failed to send mail: failed to send quit command: 250 2.0.0 Ok: queued as C7F009B4ED
 | 
						|
	err = wc.Close()
 | 
						|
	if err != nil {
 | 
						|
		return fmt.Errorf("failed to close smtp client connection: %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,
 | 
						|
	}
 | 
						|
}
 |