package main import ( "bufio" "crypto/tls" "fmt" "os" log "github.com/Sirupsen/logrus" "github.com/antonmedv/expr" "github.com/aymerick/douceur/inliner" "github.com/drone/drone-go/template" "github.com/jaytaylor/html2text" "github.com/urfave/cli" gomail "gopkg.in/mail.v2" ) type ( Repo struct { FullName string Owner string Name string SCM string Link string Avatar string Branch string Private bool Trusted bool } Remote struct { URL string } Author struct { Name string Email string Avatar string } Commit struct { Sha string Ref string Branch string Link string Message string Author Author } Build struct { Number int Event string Status string Link string Created int64 Started int64 Finished int64 } PrevBuild struct { Status string Number int } PrevCommit struct { Sha string } Prev struct { Build PrevBuild Commit PrevCommit } Job struct { Status string ExitCode int Started int64 Finished int64 } Yaml struct { Signed bool Verified bool } Config struct { FromAddress string FromName string Host string Port int Username string Password string SkipVerify bool NoStartTLS bool Recipients []string RecipientsFile string RecipientsOnly bool Subject string Body string Attachment string Attachments []string ClientHostname string Evaluation string } Plugin struct { Context *cli.Context Repo Repo Remote Remote Commit Commit Build Build Prev Prev Job Job Yaml Yaml Tag string PullRequest int DeployTo string Config Config } ) // Exec will send emails over SMTP func (p Plugin) Exec() error { if p.Config.Evaluation != "" { env := p.Environ() fmt.Printf("%+v\n", expr.Env(env)) out, err := expr.Compile(p.Config.Evaluation, expr.Env(env), expr.AsBool()) if err != nil { return err } result, err := expr.Run(out, env) if err != nil { return err } if result.(bool) == false { return nil } } var dialer *gomail.Dialer if !p.Config.RecipientsOnly { exists := false for _, recipient := range p.Config.Recipients { if recipient == p.Commit.Author.Email { exists = true } } if !exists { p.Config.Recipients = append(p.Config.Recipients, p.Commit.Author.Email) } } if p.Config.RecipientsFile != "" { f, err := os.Open(p.Config.RecipientsFile) if err == nil { scanner := bufio.NewScanner(f) for scanner.Scan() { p.Config.Recipients = append(p.Config.Recipients, scanner.Text()) } } else { log.Errorf("Could not open RecipientsFile %s: %v", p.Config.RecipientsFile, err) } } if p.Config.Username == "" && p.Config.Password == "" { dialer = &gomail.Dialer{Host: p.Config.Host, Port: p.Config.Port} } else { dialer = gomail.NewDialer(p.Config.Host, p.Config.Port, p.Config.Username, p.Config.Password) } if p.Config.SkipVerify { dialer.TLSConfig = &tls.Config{InsecureSkipVerify: true} } if p.Config.NoStartTLS { dialer.StartTLSPolicy = gomail.NoStartTLS } dialer.LocalName = p.Config.ClientHostname closer, err := dialer.Dial() if err != nil { log.Errorf("Error while dialing SMTP server: %v", err) return err } type Context struct { Repo Repo Remote Remote Commit Commit Build Build Prev Prev Job Job Yaml Yaml Tag string PullRequest int DeployTo string } ctx := Context{ Repo: p.Repo, Remote: p.Remote, Commit: p.Commit, Build: p.Build, Prev: p.Prev, Job: p.Job, Yaml: p.Yaml, Tag: p.Tag, PullRequest: p.PullRequest, DeployTo: p.DeployTo, } // Render body in HTML and plain text renderedBody, err := template.RenderTrim(p.Config.Body, ctx) if err != nil { log.Errorf("Could not render body template: %v", err) return err } html, err := inliner.Inline(renderedBody) if err != nil { log.Errorf("Could not inline rendered body: %v", err) return err } plainBody, err := html2text.FromString(html) if err != nil { log.Errorf("Could not convert html to text: %v", err) return err } // Render subject subject, err := template.RenderTrim(p.Config.Subject, ctx) if err != nil { log.Errorf("Could not render subject template: %v", err) return err } // Send emails message := gomail.NewMessage() for _, recipient := range p.Config.Recipients { if len(recipient) == 0 { continue } message.SetAddressHeader("From", p.Config.FromAddress, p.Config.FromName) message.SetAddressHeader("To", recipient, "") message.SetHeader("Subject", subject) message.AddAlternative("text/plain", plainBody) message.AddAlternative("text/html", html) if p.Config.Attachment != "" { attach(message, p.Config.Attachment) } for _, attachment := range p.Config.Attachments { attach(message, attachment) } if err := gomail.Send(closer, message); err != nil { log.Errorf("Could not send email to %q: %v", recipient, err) return err } message.Reset() } return nil } func (p Plugin) Environ() map[string]string { return map[string]string{ "CI_REPO_OWNER": p.Context.String("repo.owner"), "CI_REPO_NAME": p.Context.String("repo.name"), "CI_REPO_SCM": p.Context.String("repo.scm"), "CI_REPO_LINK": p.Context.String("repo.link"), "DRONE_REPO_AVATAR": p.Context.String("repo.avatar"), "CI_REPO_DEFAULT_BRANCH": p.Context.String("repo.branch"), "CI_REPO_PRIVATE": p.Context.String("repo.private"), "DRONE_REPO_TRUSTED": p.Context.String("repo.trusted"), "CI_REPO_CLONE_URL": p.Context.String("remote.url"), "CI_COMMIT_SHA": p.Context.String("commit.sha"), "CI_COMMIT_REF": p.Context.String("commit.ref"), "CI_COMMIT_BRANCH": p.Context.String("commit.branch"), "CI_COMMIT_LINK": p.Context.String("commit.link"), "CI_COMMIT_MESSAGE": p.Context.String("commit.message"), "CI_COMMIT_AUTHOR": p.Context.String("commit.author.name"), "CI_COMMIT_AUTHOR_EMAIL": p.Context.String("commit.author.email"), "CI_COMMIT_AUTHOR_AVATAR": p.Context.String("commit.author.avatar"), "CI_BUILD_NUMBER": p.Context.String("build.number"), "CI_BUILD_EVENT": p.Context.String("build.event"), "CI_PIPELINE_STATUS": p.Context.String("build.status"), "CI_PIPELINE_LINK": p.Context.String("build.link"), "CI_PIPELINE_CREATED": p.Context.String("build.created"), "CI_PIPELINE_STARTED": p.Context.String("build.started"), "CI_PIPELINE_FINISHED": p.Context.String("build.finished"), "CI_PREV_PIPELINE_STATUS": p.Context.String("prev.build.status"), "CI_PREV_PIPELINE_NUMBER": p.Context.String("prev.build.number"), "CI_PREV_COMMIT_SHA": p.Context.String("prev.commit.sha"), "CI_STEP_NUMBER": p.Context.String("job.number"), "CI_STEP_STATUS": p.Context.String("job.status"), "DRONE_JOB_EXIT_CODE": p.Context.String("job.exitCode"), "CI_STEP_STARTED": p.Context.String("job.started"), "CI_STEP_FINISHED": p.Context.String("job.finished"), "DRONE_YAML_SIGNED": p.Context.String("yaml.signed"), "DRONE_YAML_VERIFIED": p.Context.String("yaml.verified"), "CI_COMMIT_TAG": p.Context.String("tag"), "CI_COMMIT_PULL_REQUEST": p.Context.String("pullRequest"), "CI_PIPELINE_DEPLOY_TARGET": p.Context.String("deployTo"), } } func attach(message *gomail.Message, attachment string) { if _, err := os.Stat(attachment); err == nil { message.Attach(attachment) } }