Compare commits

...
This repository has been archived on 2022-08-18. You can view files and clone it, but cannot push or open issues or pull requests.

7 Commits

Author SHA1 Message Date
Mathieu Velten b571c45a24
Update rageshake.sample.yaml
Co-authored-by: Richard van der Hoff <1389908+richvdh@users.noreply.github.com>
2020-09-11 13:54:38 +02:00
Mathieu Velten dc3e3f2700 Com'on, go away go 1.14 2020-09-09 18:39:22 +02:00
Mathieu Velten 7edc3a3692 Fix when no email specified 2020-09-09 18:38:50 +02:00
Mathieu Velten b7e9408dd2 Remove go 1.14 2020-09-09 18:37:19 +02:00
Mathieu Velten d1df8c2853 Fix myself 2020-09-09 18:36:30 +02:00
Mathieu Velten 20cfb8a28c Remove "XXX disabled" logs 2020-09-09 14:51:48 +02:00
Mathieu Velten ba72ef4ef1 Add email support 2020-09-04 11:08:03 +02:00
7 changed files with 148 additions and 53 deletions

View File

@ -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
View File

@ -0,0 +1 @@
Add email support.

1
go.mod
View File

@ -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
View File

@ -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
View File

@ -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)

View File

@ -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
View File

@ -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("{}"))