Compare commits
7 commits
master
...
add-email-
Author | SHA1 | Date | |
---|---|---|---|
|
b571c45a24 | ||
|
dc3e3f2700 | ||
|
7edc3a3692 | ||
|
b7e9408dd2 | ||
|
d1df8c2853 | ||
|
20cfb8a28c | ||
|
ba72ef4ef1 |
7 changed files with 148 additions and 53 deletions
|
@ -91,3 +91,11 @@ The response (if successful) will be a JSON object with the following fields:
|
|||
|
||||
* `report_url`: A URL where the user can track their bug report. Omitted if
|
||||
issue submission was disabled.
|
||||
|
||||
## Notifications
|
||||
|
||||
You can get notifications when a new rageshake arrives on the server.
|
||||
|
||||
Currently this tool supports pushing notifications as GitHub issues in a repo,
|
||||
through a Slack webhook or by email, cf sample config file for how to
|
||||
configure them.
|
1
changelog.d/35.feature
Normal file
1
changelog.d/35.feature
Normal file
|
@ -0,0 +1 @@
|
|||
Add email support.
|
1
go.mod
1
go.mod
|
@ -7,6 +7,7 @@ require (
|
|||
github.com/google/go-github v0.0.0-20170401000335-12363ffc1001
|
||||
github.com/google/go-querystring v0.0.0-20170111101155-53e6ce116135
|
||||
github.com/googleapis/gax-go v0.0.0-20170321005343-9af46dd5a171
|
||||
github.com/jordan-wright/email v4.0.1-0.20200824153738-3f5bafa1cd84+incompatible
|
||||
github.com/pkg/errors v0.0.0-20171018195549-f15c970de5b7
|
||||
golang.org/x/net v0.0.0-20170329170435-ffcf1bedda3b
|
||||
golang.org/x/oauth2 v0.0.0-20170321013421-7fdf09982454
|
||||
|
|
3
go.sum
3
go.sum
|
@ -3,9 +3,12 @@ github.com/golang/protobuf v0.0.0-20170331031902-2bba0603135d/go.mod h1:6lQm79b+
|
|||
github.com/google/go-genproto v0.0.0-20170404132009-411e09b969b1/go.mod h1:3Rcd9jSoLVkV/osPrt5CogLvLiarfI8U9/x78NwhuDU=
|
||||
github.com/google/go-github v0.0.0-20170401000335-12363ffc1001 h1:OK4gfzCBCtPg14E4sYsczwFhjVu1jQJZI+OEOpiTigw=
|
||||
github.com/google/go-github v0.0.0-20170401000335-12363ffc1001/go.mod h1:zLgOLi98H3fifZn+44m+umXrS52loVEgC2AApnigrVQ=
|
||||
github.com/google/go-github v17.0.0+incompatible h1:N0LgJ1j65A7kfXrZnUDaYCs/Sf4rEjNlfyDHW9dolSY=
|
||||
github.com/google/go-querystring v0.0.0-20170111101155-53e6ce116135 h1:zLTLjkaOFEFIOxY5BWLFLwh+cL8vOBW4XJ2aqLE/Tf0=
|
||||
github.com/google/go-querystring v0.0.0-20170111101155-53e6ce116135/go.mod h1:odCYkC5MyYFN7vkCjXpyrEuKhc/BUO6wN/zVPAxq5ck=
|
||||
github.com/googleapis/gax-go v0.0.0-20170321005343-9af46dd5a171/go.mod h1:SFVmujtThgffbyetf+mdk2eWhX2bMyUtNHzFKcPA9HY=
|
||||
github.com/jordan-wright/email v4.0.1-0.20200824153738-3f5bafa1cd84+incompatible h1:d60x4RsAHk/UX/0OT8Gc6D7scVvhBbEANpTAWrDhA/I=
|
||||
github.com/jordan-wright/email v4.0.1-0.20200824153738-3f5bafa1cd84+incompatible/go.mod h1:1c7szIrayyPPB/987hsnvNzLushdWf4o/79s3P08L8A=
|
||||
github.com/pkg/errors v0.0.0-20171018195549-f15c970de5b7/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||
golang.org/x/net v0.0.0-20170329170435-ffcf1bedda3b h1:Co3zyosPfwWowmu8+roHGC+aDgizpCPH3ukhubZ0Ttg=
|
||||
golang.org/x/net v0.0.0-20170329170435-ffcf1bedda3b/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
|
|
21
main.go
21
main.go
|
@ -21,8 +21,6 @@ import (
|
|||
"crypto/subtle"
|
||||
"flag"
|
||||
"fmt"
|
||||
"github.com/google/go-github/github"
|
||||
"golang.org/x/oauth2"
|
||||
"io/ioutil"
|
||||
"log"
|
||||
"net"
|
||||
|
@ -31,6 +29,9 @@ import (
|
|||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/google/go-github/github"
|
||||
"golang.org/x/oauth2"
|
||||
|
||||
yaml "gopkg.in/yaml.v2"
|
||||
)
|
||||
|
||||
|
@ -51,6 +52,16 @@ type config struct {
|
|||
GithubProjectMappings map[string]string `yaml:"github_project_mappings"`
|
||||
|
||||
SlackWebhookURL string `yaml:"slack_webhook_url"`
|
||||
|
||||
EmailAddresses []string `yaml:"email_addresses"`
|
||||
|
||||
EmailFrom string `yaml:"email_from"`
|
||||
|
||||
SMTPServer string `yaml:"smtp_server"`
|
||||
|
||||
SMTPUsername string `yaml:"smtp_username"`
|
||||
|
||||
SMTPPassword string `yaml:"smtp_password"`
|
||||
}
|
||||
|
||||
func basicAuth(handler http.Handler, username, password, realm string) http.Handler {
|
||||
|
@ -99,6 +110,10 @@ func main() {
|
|||
slack = newSlackClient(cfg.SlackWebhookURL)
|
||||
}
|
||||
|
||||
if len(cfg.EmailAddresses) > 0 && cfg.SMTPServer == "" {
|
||||
log.Fatal("Email address(es) specified but no smtp_server configured. Wrong configuration, aborting...")
|
||||
}
|
||||
|
||||
apiPrefix := cfg.APIPrefix
|
||||
if apiPrefix == "" {
|
||||
_, port, err := net.SplitHostPort(*bindAddr)
|
||||
|
@ -112,7 +127,7 @@ func main() {
|
|||
}
|
||||
log.Printf("Using %s/listing as public URI", apiPrefix)
|
||||
|
||||
http.Handle("/api/submit", &submitServer{ghClient, apiPrefix, cfg.GithubProjectMappings, slack})
|
||||
http.Handle("/api/submit", &submitServer{ghClient, apiPrefix, slack, cfg})
|
||||
|
||||
// Make sure bugs directory exists
|
||||
_ = os.Mkdir("bugs", os.ModePerm)
|
||||
|
|
|
@ -20,3 +20,16 @@ github_project_mappings:
|
|||
# a Slack personal webhook URL (https://api.slack.com/incoming-webhooks), which
|
||||
# will be used to post a notification on Slack for each report.
|
||||
slack_webhook_url: https://hooks.slack.com/services/TTTTTTT/XXXXXXXXXX/YYYYYYYYYYY
|
||||
|
||||
# notification can also be pushed by email.
|
||||
# this param controls the target emails
|
||||
email_addresses:
|
||||
- support@matrix.org
|
||||
|
||||
# this is the from field that will be used in the email notifications
|
||||
email_from: Rageshake <rageshake@matrix.org>
|
||||
|
||||
# SMTP server configuration
|
||||
smtp_server: localhost:25
|
||||
smtp_username: myemailuser
|
||||
smtp_password: myemailpass
|
||||
|
|
154
submit.go
154
submit.go
|
@ -28,6 +28,7 @@ import (
|
|||
"mime"
|
||||
"mime/multipart"
|
||||
"net/http"
|
||||
"net/smtp"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"regexp"
|
||||
|
@ -37,6 +38,7 @@ import (
|
|||
"time"
|
||||
|
||||
"github.com/google/go-github/github"
|
||||
"github.com/jordan-wright/email"
|
||||
)
|
||||
|
||||
var maxPayloadSize = 1024 * 1024 * 55 // 55 MB
|
||||
|
@ -49,10 +51,9 @@ type submitServer struct {
|
|||
// External URI to /api
|
||||
apiPrefix string
|
||||
|
||||
// mappings from application to github owner/project
|
||||
githubProjectMappings map[string]string
|
||||
|
||||
slack *slackClient
|
||||
|
||||
cfg *config
|
||||
}
|
||||
|
||||
// the type of payload which can be uploaded as JSON to the submit endpoint
|
||||
|
@ -470,70 +471,77 @@ func (s *submitServer) saveReport(ctx context.Context, p parsedPayload, reportDi
|
|||
return nil, err
|
||||
}
|
||||
|
||||
if err := s.sendEmail(p, reportDir); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &resp, nil
|
||||
}
|
||||
|
||||
func (s *submitServer) submitGithubIssue(ctx context.Context, p parsedPayload, listingURL string, resp *submitResponse) error {
|
||||
if s.ghClient == nil {
|
||||
log.Println("GH issue submission disabled")
|
||||
} else {
|
||||
// submit a github issue
|
||||
ghProj := s.githubProjectMappings[p.AppName]
|
||||
if ghProj == "" {
|
||||
log.Println("Not creating GH issue for unknown app", p.AppName)
|
||||
return nil
|
||||
}
|
||||
splits := strings.SplitN(ghProj, "/", 2)
|
||||
if len(splits) < 2 {
|
||||
log.Println("Can't create GH issue for invalid repo", ghProj)
|
||||
}
|
||||
owner, repo := splits[0], splits[1]
|
||||
|
||||
issueReq := buildGithubIssueRequest(p, listingURL)
|
||||
|
||||
issue, _, err := s.ghClient.Issues.Create(ctx, owner, repo, &issueReq)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
log.Println("Created issue:", *issue.HTMLURL)
|
||||
|
||||
resp.ReportURL = *issue.HTMLURL
|
||||
return nil
|
||||
}
|
||||
|
||||
// submit a github issue
|
||||
ghProj := s.cfg.GithubProjectMappings[p.AppName]
|
||||
if ghProj == "" {
|
||||
log.Println("Not creating GH issue for unknown app", p.AppName)
|
||||
return nil
|
||||
}
|
||||
splits := strings.SplitN(ghProj, "/", 2)
|
||||
if len(splits) < 2 {
|
||||
log.Println("Can't create GH issue for invalid repo", ghProj)
|
||||
}
|
||||
owner, repo := splits[0], splits[1]
|
||||
|
||||
issueReq := buildGithubIssueRequest(p, listingURL)
|
||||
|
||||
issue, _, err := s.ghClient.Issues.Create(ctx, owner, repo, &issueReq)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
log.Println("Created issue:", *issue.HTMLURL)
|
||||
|
||||
resp.ReportURL = *issue.HTMLURL
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *submitServer) submitSlackNotification(p parsedPayload, listingURL string) error {
|
||||
if s.slack == nil {
|
||||
log.Println("Slack notifications disabled")
|
||||
} else {
|
||||
slackBuf := fmt.Sprintf(
|
||||
"%s\nApplication: %s\nReport: %s",
|
||||
p.UserText, p.AppName, listingURL,
|
||||
)
|
||||
|
||||
err := s.slack.Notify(slackBuf)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
slackBuf := fmt.Sprintf(
|
||||
"%s\nApplication: %s\nReport: %s",
|
||||
p.UserText, p.AppName, listingURL,
|
||||
)
|
||||
|
||||
err := s.slack.Notify(slackBuf)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func buildGithubIssueRequest(p parsedPayload, listingURL string) github.IssueRequest {
|
||||
func buildReportTitle(p parsedPayload) string {
|
||||
// set the title to the first (non-empty) line of the user's report, if any
|
||||
var title string
|
||||
trimmedUserText := strings.TrimSpace(p.UserText)
|
||||
if trimmedUserText == "" {
|
||||
title = "Untitled report"
|
||||
} else {
|
||||
if i := strings.IndexAny(trimmedUserText, "\r\n"); i < 0 {
|
||||
title = trimmedUserText
|
||||
} else {
|
||||
title = trimmedUserText[0:i]
|
||||
}
|
||||
return "Untitled report"
|
||||
}
|
||||
|
||||
if i := strings.IndexAny(trimmedUserText, "\r\n"); i >= 0 {
|
||||
return trimmedUserText[0:i]
|
||||
}
|
||||
|
||||
return trimmedUserText
|
||||
}
|
||||
|
||||
func buildReportBody(p parsedPayload, quoteChar string) *bytes.Buffer {
|
||||
var bodyBuf bytes.Buffer
|
||||
fmt.Fprintf(&bodyBuf, "User message:\n\n%s\n\n", p.UserText)
|
||||
var dataKeys []string
|
||||
|
@ -543,19 +551,29 @@ func buildGithubIssueRequest(p parsedPayload, listingURL string) github.IssueReq
|
|||
sort.Strings(dataKeys)
|
||||
for _, k := range dataKeys {
|
||||
v := p.Data[k]
|
||||
fmt.Fprintf(&bodyBuf, "%s: `%s`\n", k, v)
|
||||
fmt.Fprintf(&bodyBuf, "%s: %s%s%s\n", k, quoteChar, v, quoteChar)
|
||||
}
|
||||
fmt.Fprintf(&bodyBuf, "[Logs](%s)", listingURL)
|
||||
|
||||
return &bodyBuf
|
||||
}
|
||||
|
||||
func buildGithubIssueRequest(p parsedPayload, listingURL string) github.IssueRequest {
|
||||
bodyBuf := buildReportBody(p, "`")
|
||||
|
||||
// Add log links to the body
|
||||
fmt.Fprintf(bodyBuf, "[Logs](%s)", listingURL)
|
||||
|
||||
for _, file := range p.Files {
|
||||
fmt.Fprintf(
|
||||
&bodyBuf,
|
||||
bodyBuf,
|
||||
" / [%s](%s)",
|
||||
file,
|
||||
listingURL+"/"+file,
|
||||
)
|
||||
}
|
||||
|
||||
title := buildReportTitle(p)
|
||||
|
||||
body := bodyBuf.String()
|
||||
|
||||
labels := p.Labels
|
||||
|
@ -570,6 +588,42 @@ func buildGithubIssueRequest(p parsedPayload, listingURL string) github.IssueReq
|
|||
}
|
||||
}
|
||||
|
||||
func (s *submitServer) sendEmail(p parsedPayload, reportDir string) error {
|
||||
if len(s.cfg.EmailAddresses) == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
e := email.NewEmail()
|
||||
|
||||
e.From = "Rageshake <rageshake@matrix.org>"
|
||||
if s.cfg.EmailFrom != "" {
|
||||
e.From = s.cfg.EmailFrom
|
||||
}
|
||||
|
||||
e.To = s.cfg.EmailAddresses
|
||||
|
||||
e.Subject = fmt.Sprintf("[%s] %s", p.AppName, buildReportTitle(p))
|
||||
|
||||
e.Text = buildReportBody(p, "\"").Bytes()
|
||||
|
||||
allFiles := append(p.Files, p.Logs...)
|
||||
for _, file := range allFiles {
|
||||
fullPath := filepath.Join(reportDir, file)
|
||||
e.AttachFile(fullPath)
|
||||
}
|
||||
|
||||
var auth smtp.Auth = nil
|
||||
if s.cfg.SMTPPassword != "" || s.cfg.SMTPUsername != "" {
|
||||
auth = smtp.PlainAuth("", s.cfg.SMTPUsername, s.cfg.SMTPPassword, s.cfg.SMTPServer)
|
||||
}
|
||||
err := e.Send(s.cfg.SMTPServer, auth)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func respond(code int, w http.ResponseWriter) {
|
||||
w.WriteHeader(code)
|
||||
w.Write([]byte("{}"))
|
||||
|
|
Reference in a new issue