Initial attempt at a generic webhook for reports.
Background submission to a notification server that doesn't block rageshake submission.
This commit is contained in:
parent
29c2f9a48f
commit
81865b0193
5 changed files with 130 additions and 9 deletions
4
Makefile
Normal file
4
Makefile
Normal file
|
@ -0,0 +1,4 @@
|
|||
docker:
|
||||
DOCKER_BUILDKIT=1 docker build -t rageshake:latest .
|
||||
docker run --rm --name rageshake --network rageshake-search --mount type=bind,source=$(shell pwd)/bugs,target=/bugs --mount type=bind,source=$(shell pwd)/rageshake.yaml,target=/rageshake.yaml -p 127.0.0.1:9110:9110 rageshake:latest -listen :9110
|
||||
|
10
README.md
10
README.md
|
@ -105,3 +105,13 @@ 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.
|
||||
|
||||
### Generic Webhook Notifications
|
||||
|
||||
You can receive a webhook notifications when a new rageshake arrives on the server.
|
||||
|
||||
These requests contain all the parsed metadata, and links to the uploaded files, and any github/gitlab
|
||||
issues created.
|
||||
|
||||
Details on the request and expected response are [available](docs/generic\_webhook.md).
|
||||
|
||||
|
|
38
docs/generic_webhook.md
Normal file
38
docs/generic_webhook.md
Normal file
|
@ -0,0 +1,38 @@
|
|||
## Generic webhook request
|
||||
|
||||
If the configuration option `generic_webhook_url` is set, then a synchronous request to
|
||||
the endpoint will be sent after the incoming request is parsed and the files are uploaded.
|
||||
|
||||
The webhook is designed for notification or other tracking services, and does not contain
|
||||
the original log files uploaded.
|
||||
|
||||
(If you want the original log files, we suggest to implement the rageshake interface itself).
|
||||
|
||||
A sample JSON body is as follows:
|
||||
|
||||
```
|
||||
{
|
||||
'user_text': 'test\r\n\r\nIssue: No issue link given',
|
||||
'app': 'element-web',
|
||||
'data': {
|
||||
'User-Agent': 'Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:96.0) Gecko/20100101 Firefox/96.0',
|
||||
'Version': '0f15ba34cdf5-react-0f15ba34cdf5-js-0f15ba34cdf5',
|
||||
...
|
||||
'user_id': '@michaelgoesforawalk:matrix.org'},
|
||||
'labels': None,
|
||||
'logs': [
|
||||
'logs-0000.log.gz',
|
||||
'logs-0001.log.gz',
|
||||
'logs-0002.log.gz',
|
||||
],
|
||||
'logErrors': None,
|
||||
'files': [
|
||||
'screenshot.png'
|
||||
],
|
||||
'fileErrors': None,
|
||||
'report_url': 'https://github.com/your-org/your-repo/issues/1251',
|
||||
'listing_url': 'http://your-rageshake-server/api/listing/2022-01-25/154742-OOXBVGIX'
|
||||
}
|
||||
```
|
||||
|
||||
The log and other files can be individually downloaded by concatenating the `listing_url` and the `logs` or `files` name.
|
13
main.go
13
main.go
|
@ -71,6 +71,8 @@ type config struct {
|
|||
SMTPUsername string `yaml:"smtp_username"`
|
||||
|
||||
SMTPPassword string `yaml:"smtp_password"`
|
||||
|
||||
GenericWebhookURL string `yaml:"generic_webhook_url"`
|
||||
}
|
||||
|
||||
func basicAuth(handler http.Handler, username, password, realm string) http.Handler {
|
||||
|
@ -133,6 +135,15 @@ func main() {
|
|||
if len(cfg.EmailAddresses) > 0 && cfg.SMTPServer == "" {
|
||||
log.Fatal("Email address(es) specified but no smtp_server configured. Wrong configuration, aborting...")
|
||||
}
|
||||
var genericWebhookClient *http.Client
|
||||
if cfg.GenericWebhookURL == "" {
|
||||
fmt.Println("No generic_webhook_url configured.")
|
||||
} else {
|
||||
fmt.Println("Will forward metadata of all requests to ", cfg.GenericWebhookURL)
|
||||
genericWebhookClient = &http.Client{
|
||||
Timeout: time.Second * 300,
|
||||
}
|
||||
}
|
||||
|
||||
apiPrefix := cfg.APIPrefix
|
||||
if apiPrefix == "" {
|
||||
|
@ -148,7 +159,7 @@ func main() {
|
|||
log.Printf("Using %s/listing as public URI", apiPrefix)
|
||||
|
||||
rand.Seed(time.Now().UnixNano())
|
||||
http.Handle("/api/submit", &submitServer{ghClient, glClient, apiPrefix, slack, cfg})
|
||||
http.Handle("/api/submit", &submitServer{ghClient, glClient, apiPrefix, slack, genericWebhookClient, cfg})
|
||||
|
||||
// Make sure bugs directory exists
|
||||
_ = os.Mkdir("bugs", os.ModePerm)
|
||||
|
|
74
submit.go
74
submit.go
|
@ -57,6 +57,7 @@ type submitServer struct {
|
|||
|
||||
slack *slackClient
|
||||
|
||||
genericWebhookClient *http.Client
|
||||
cfg *config
|
||||
}
|
||||
|
||||
|
@ -76,16 +77,23 @@ type jsonLogEntry struct {
|
|||
Lines string `json:"lines"`
|
||||
}
|
||||
|
||||
|
||||
type genericWebhookPayload struct {
|
||||
parsedPayload
|
||||
ReportURL string `json:"report_url"`
|
||||
ListingURL string `json:"listing_url"`
|
||||
}
|
||||
|
||||
// the payload after parsing
|
||||
type parsedPayload struct {
|
||||
UserText string
|
||||
AppName string
|
||||
Data map[string]string
|
||||
Labels []string
|
||||
Logs []string
|
||||
LogErrors []string
|
||||
Files []string
|
||||
FileErrors []string
|
||||
UserText string `json:"user_text"`
|
||||
AppName string `json:"app"`
|
||||
Data map[string]string `json:"data"`
|
||||
Labels []string `json:"labels"`
|
||||
Logs []string `json:"logs"`
|
||||
LogErrors []string `json:"logErrors"`
|
||||
Files []string `json:"files"`
|
||||
FileErrors []string `json:"fileErrors"`
|
||||
}
|
||||
|
||||
func (p parsedPayload) WriteTo(out io.Writer) {
|
||||
|
@ -492,9 +500,59 @@ func (s *submitServer) saveReport(ctx context.Context, p parsedPayload, reportDi
|
|||
return nil, err
|
||||
}
|
||||
|
||||
if err := s.submitGenericWebhook(p, listingURL, resp.ReportURL); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &resp, nil
|
||||
}
|
||||
|
||||
// submitGenericWebhook submits a basic JSON body to an endpoint configured in the config
|
||||
//
|
||||
// The request does not include the log body, only the metadata in the parsedPayload,
|
||||
// with the required listingURL to obtain the logs over http if required.
|
||||
//
|
||||
// If a github or gitlab issue was previously made, the reportURL will also be passed.
|
||||
//
|
||||
// Uses a goroutine to handle the http request asynchronously as by this point all critical
|
||||
// information has been stored.
|
||||
|
||||
func (s *submitServer) submitGenericWebhook(p parsedPayload, listingURL string, reportURL string) error {
|
||||
if s.genericWebhookClient == nil {
|
||||
return nil
|
||||
}
|
||||
url := s.cfg.GenericWebhookURL
|
||||
log.Println("Submitting json to URL", url)
|
||||
// Enrich the parsedPayload with a reportURL and listingURL, to convert a single struct
|
||||
// to JSON easily
|
||||
genericHookPayload := genericWebhookPayload{
|
||||
parsedPayload: p,
|
||||
ReportURL: reportURL,
|
||||
ListingURL: listingURL,
|
||||
}
|
||||
|
||||
payloadBuffer := new(bytes.Buffer)
|
||||
json.NewEncoder(payloadBuffer).Encode(genericHookPayload)
|
||||
req, err := http.NewRequest("POST", url, payloadBuffer)
|
||||
req.Header.Set("Content-Type", "application/json")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
go s.sendGenericWebhook(req)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *submitServer) sendGenericWebhook(req *http.Request) {
|
||||
resp, err := s.genericWebhookClient.Do(req)
|
||||
if err != nil {
|
||||
log.Println("Unable to submit notification", err)
|
||||
} else {
|
||||
defer resp.Body.Close()
|
||||
log.Println("Got response", resp.Status)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
func (s *submitServer) submitGithubIssue(ctx context.Context, p parsedPayload, listingURL string, resp *submitResponse) error {
|
||||
if s.ghClient == nil {
|
||||
return nil
|
||||
|
|
Reference in a new issue