diff --git a/main.go b/main.go index 01743eb..06f56a9 100644 --- a/main.go +++ b/main.go @@ -49,6 +49,8 @@ type config struct { GithubToken string `yaml:"github_token"` GithubProjectMappings map[string]string `yaml:"github_project_mappings"` + + SlackWebhookURL string `yaml:"slack_webhook_url"` } func basicAuth(handler http.Handler, username, password, realm string) http.Handler { @@ -89,6 +91,14 @@ func main() { ghClient = github.NewClient(tc) } + var slack *slackClient + + if cfg.SlackWebhookURL == "" { + fmt.Println("No slack_webhook_url configured. Reporting bugs to slack is disabled.") + } else { + slack = NewSlackClient(cfg.SlackWebhookURL) + } + apiPrefix := cfg.APIPrefix if apiPrefix == "" { _, port, err := net.SplitHostPort(*bindAddr) @@ -102,7 +112,7 @@ func main() { } log.Printf("Using %s/listing as public URI", apiPrefix) - http.Handle("/api/submit", &submitServer{ghClient, apiPrefix, cfg.GithubProjectMappings}) + http.Handle("/api/submit", &submitServer{ghClient, apiPrefix, cfg.GithubProjectMappings, slack}) // Make sure bugs directory exists _ = os.Mkdir("bugs", os.ModePerm) diff --git a/rageshake.sample.yaml b/rageshake.sample.yaml index 1891b07..ea68887 100644 --- a/rageshake.sample.yaml +++ b/rageshake.sample.yaml @@ -16,3 +16,7 @@ github_token: secrettoken # mappings from app name (as submitted in the API) to github repo for issue reporting. github_project_mappings: my-app: octocat/HelloWorld + +# 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 diff --git a/slack.go b/slack.go new file mode 100644 index 0000000..5159bc5 --- /dev/null +++ b/slack.go @@ -0,0 +1,48 @@ +package main + +import ( + "strings" + "fmt" + "net/http" +) + +type slackClient struct { + webHook string + name string + face string +} + +func NewSlackClient(webHook string) *slackClient { + return &slackClient{ + webHook: webHook, + name: "Notifier", + face: "robot_face"} +} + +func (slack *slackClient) Name(name string) { + slack.name = name +} + +func (slack *slackClient) Face(face string) { + slack.face = face +} + +func (slack slackClient) Notify(text string) error { + json := buildRequest(text, slack) + + req, err := http.NewRequest("POST", slack.webHook, strings.NewReader(json)) + if err != nil { + return fmt.Errorf("Can't connect to host %s: %s", slack.webHook, err.Error()) + } + + req.Header.Set("Content-Type", "application/json") + + client := http.Client{} + _, err = client.Do(req) + + return err +} + +func buildRequest(text string, slack slackClient) string { + return fmt.Sprintf(`{"text":"%s", "username": "%s", "icon_emoji": ":%s:"}`, text, slack.name, slack.face) +} diff --git a/submit.go b/submit.go index e34687c..d6b5d0a 100644 --- a/submit.go +++ b/submit.go @@ -51,6 +51,8 @@ type submitServer struct { // mappings from application to github owner/project githubProjectMappings map[string]string + + slack *slackClient } // the type of payload which can be uploaded as JSON to the submit endpoint @@ -460,38 +462,64 @@ func (s *submitServer) saveReport(ctx context.Context, p parsedPayload, reportDi return nil, err } - if s.ghClient == nil { - // we're done here - log.Println("GH issue submission disabled") - return &resp, nil - } - - // submit a github issue - ghProj := s.githubProjectMappings[p.AppName] - if ghProj == "" { - log.Println("Not creating GH issue for unknown app", p.AppName) - return &resp, 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 { + if err := s.submitGithubIssue(ctx, p, listingURL, &resp); err != nil { return nil, err } - log.Println("Created issue:", *issue.HTMLURL) - - resp.ReportURL = *issue.HTMLURL + if err := s.submitSlackNotification(p, listingURL); 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 +} + +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 +} + func buildGithubIssueRequest(p parsedPayload, listingURL string) github.IssueRequest { // set the title to the first (non-empty) line of the user's report, if any var title string