diff options
| author | Aleksandr Nogikh <nogikh@google.com> | 2023-04-24 19:52:41 +0200 |
|---|---|---|
| committer | Aleksandr Nogikh <wp32pw@gmail.com> | 2023-04-27 11:42:09 +0200 |
| commit | 703f643f91e8becd2495f1f102346a0b7fa63867 (patch) | |
| tree | c304e8d97b58127b6c539096f7d283d8582d12c8 /dashboard/app/reporting_email.go | |
| parent | cef7cc0993422a96dff2c22d1afc9f87502bb289 (diff) | |
dashboard: support bug labels
Let bug labels come in three flavours:
1) Bug labels with multiple values (e.g. `subsystems`).
2) Bug labels with only one value (e.g. `prio`).
3) Flags.
Let users configure bug labels via email by issuing the following
commands:
#syz set subsystems: abc, def
#syz set no-reminders
#syz unset no-reminders
Also let users set tags for invididual bugs in reported bug lists:
#syz set <1> some-tag
Diffstat (limited to 'dashboard/app/reporting_email.go')
| -rw-r--r-- | dashboard/app/reporting_email.go | 220 |
1 files changed, 166 insertions, 54 deletions
diff --git a/dashboard/app/reporting_email.go b/dashboard/app/reporting_email.go index e76aa39e6..6038a8539 100644 --- a/dashboard/app/reporting_email.go +++ b/dashboard/app/reporting_email.go @@ -22,7 +22,6 @@ import ( "github.com/google/syzkaller/pkg/email" "github.com/google/syzkaller/pkg/email/lore" "github.com/google/syzkaller/pkg/html" - "github.com/google/syzkaller/pkg/subsystem" "golang.org/x/net/context" "google.golang.org/appengine/v2" db "google.golang.org/appengine/v2/datastore" @@ -584,7 +583,9 @@ func handleBugCommand(c context.Context, bugInfo *bugInfoResult, msg *email.Emai case email.CmdTest: return handleTestCommand(c, bugInfo, msg, command) case email.CmdSet: - return handleSetCommand(c, bugInfo, msg, command) + return handleSetCommand(c, bugInfo.bug, msg, command) + case email.CmdUnset: + return handleUnsetCommand(c, bugInfo.bug, msg, command) case email.CmdUpstream, email.CmdInvalid, email.CmdUnDup: case email.CmdFix: if command.Args == "" { @@ -695,59 +696,120 @@ func handleTestCommand(c context.Context, info *bugInfoResult, } var ( - // The format is `#syz set KEY: value(s)`. We also tolerate `#syz set KEY value(s)`. - setCmdRe = regexp.MustCompile(`^(\w+):?\s*(.*?)\s*$`) - setCmdArgSplitRe = regexp.MustCompile(`[\s,]+`) - cmdParseFailureFmt = `I've failed to parse your command. Please use the following format: -#syz set subsystems: some-subsystem-A, some-subsystem-B - -The list of subsystems can be found at %s` - cmdNoSubsystems = `Subsystems assignment is not yet supported for this kernel. + // The supported formats are: + // For bugs: + // #syz set LABEL[: value_1, [value_2, ....]] + // For bug lists: + // #syz set <N> LABEL[: value_1, [value_2, ....]] + setCmdRe = regexp.MustCompile(`(?m)\s*([-\w]+)\s*(?:\:\s*([,\-\w\s]*?))?$`) + setCmdArgSplitRe = regexp.MustCompile(`[\s,]+`) + setBugCmdFormat = `I've failed to parse your command. Please use the following format(s): +#syz set some-flag +#syz set label: value +#syz set subsystems: one-subsystem, another-subsystem + +Or, for bug lists, +#syz set <Ref> some-flag +#syz set <Ref> label: value +#syz set <Ref> subsystems: one-subsystem, another-subsystem + +The following labels are suported: +%s` + setCmdUnknownLabel = `The specified label %q is unknown. +Please use one of the supported labels. + +The following labels are suported: +%s` + setCmdUnknownValue = `The specified label value is incorrect. +%s. +Please use one of the supported label values. + +The following labels are suported: +%s` + cmdInternalErrorReply = `The command was not executed due to an internal error. Please contact the bot's maintainers.` - cmdUnknownSubsystemFmt = `Please use subsystem names from the list of supported subsystems: -%s - -If you believe that the subsystem list should be changed, please contact the bot's maintainers.` - cmdNoSubsystemsSetFmt = `You must specify at least one subsystem name. -The list of available subsystems can be found at %s` ) -func handleSetCommand(c context.Context, info *bugInfoResult, - msg *email.Email, command *email.SingleCommand) string { - subsystemsURL := fmt.Sprintf("%v/%v/subsystems?all=true", appURL(c), info.bug.Namespace) +func handleSetCommand(c context.Context, bug *Bug, msg *email.Email, + command *email.SingleCommand) string { + labelSet := makeLabelSet(c, bug.Namespace) + match := setCmdRe.FindStringSubmatch(command.Args) - if len(match) != 3 { - return fmt.Sprintf(cmdParseFailureFmt, subsystemsURL) - } - cmd, args := match[1], match[2] - // Now we only support setting bug's subsystems. - // Also let's tolerate both subsystem spellings. - if cmd != "subsystem" && cmd != "subsystems" { - return fmt.Sprintf(cmdParseFailureFmt, subsystemsURL) - } - service := getSubsystemService(c, info.bug.Namespace) - if service == nil { - return cmdNoSubsystems - } - if args == "" { - return fmt.Sprintf(cmdNoSubsystemsSetFmt, subsystemsURL) - } - subsystems := []*subsystem.Subsystem{} - for _, name := range setCmdArgSplitRe.Split(args, -1) { - item := service.ByName(name) - if item == nil { - return fmt.Sprintf(cmdUnknownSubsystemFmt, subsystemsURL) - } - subsystems = append(subsystems, item) + if match == nil { + return fmt.Sprintf(setBugCmdFormat, labelSet.Help()) + } + label, values := BugLabelType(match[1]), match[2] + log.Infof(c, "label=%s values=%s", label, values) + if !labelSet.FindLabel(label) { + return fmt.Sprintf(setCmdUnknownLabel, label, labelSet.Help()) + } + + log.Infof(c, "setting %#v values for %q (label %q)", values, bug.displayTitle(), label) + var labels []BugLabel + for _, value := range unique(setCmdArgSplitRe.Split(values, -1)) { + labels = append(labels, BugLabel{ + Label: label, + Value: value, + SetBy: msg.Author, + Link: msg.Link, + }) + } + var setError error + err := updateSingleBug(c, bug.key(c), func(bug *Bug) error { + setError = bug.SetLabels(labelSet, labels) + return setError + }) + if setError != nil { + return fmt.Sprintf(setCmdUnknownValue, setError, labelSet.Help()) } - // All the replies below will only be sent to the initiator and the mailing list. - msg.Cc = []string{info.reporting.Config.(*EmailConfig).Email} - err := updateBugSubsystems(c, info.bugKey, subsystems, userAssignment(msg.Author)) if err != nil { - log.Errorf(c, "failed to assign bug's subsystems: %s", err) - return "I've failed to update subsystems due to an internal error.\n" + log.Errorf(c, "failed to set bug tags: %s", err) + return cmdInternalErrorReply } - return "Thank you!\n\nI've successfully updated the bug's subsystems." + return "" +} + +var ( + unsetBugCmdFormat = `I've failed to parse your command. Please use the following format(s): +#syz unset any-label + +Or, for bug lists, +#syz unset <Ref> any-label +` + unsetLabelsNotFound = `The following labels did not exist: %s` +) + +func handleUnsetCommand(c context.Context, bug *Bug, msg *email.Email, + command *email.SingleCommand) string { + match := setCmdRe.FindStringSubmatch(command.Args) + if match == nil { + return unsetBugCmdFormat + } + var labels []BugLabelType + for _, name := range unique(setCmdArgSplitRe.Split(command.Args, -1)) { + labels = append(labels, BugLabelType(name)) + } + + var notFound map[BugLabelType]struct{} + var notFoundErr = fmt.Errorf("some labels were not found") + err := updateSingleBug(c, bug.key(c), func(bug *Bug) error { + notFound = bug.UnsetLabels(labels...) + if len(notFound) > 0 { + return notFoundErr + } + return nil + }) + if err == notFoundErr { + var names []string + for label := range notFound { + names = append(names, string(label)) + } + return fmt.Sprintf(unsetLabelsNotFound, strings.Join(names, ", ")) + } else if err != nil { + log.Errorf(c, "failed to unset bug labels: %s", err) + return cmdInternalErrorReply + } + return "" } func handleEmailBounce(w http.ResponseWriter, r *http.Request) { @@ -765,6 +827,18 @@ func handleEmailBounce(w http.ResponseWriter, r *http.Request) { log.Infof(c, "%s", body) } +var ( + setGroupCmdRe = regexp.MustCompile(`(?m)\s*<(\d+)>\s*(.*)$`) + setGroupCmdFormat = `I've failed to parse your command. Please use the following format(s): +#syz set <Ref> some-label, another-label +#syz set <Ref> subsystems: one-subsystem, another-subsystem +#syz unset <Ref> some-label +` + setGroupCmdBadRef = `The specified <Ref> number is invalid. It must be one of the <NUM> values +listed in the bug list table. +` +) + func handleBugListCommand(c context.Context, bugListInfo *bugListInfoResult, msg *email.Email, command *email.SingleCommand) string { upd := &dashapi.BugListUpdate{ @@ -772,18 +846,46 @@ func handleBugListCommand(c context.Context, bugListInfo *bugListInfoResult, ExtID: msg.MessageID, Link: msg.Link, } - if command.Command == email.CmdUpstream { + switch command.Command { + case email.CmdUpstream: upd.Command = dashapi.BugListUpstreamCmd - } else if command.Command == email.CmdRegenerate { + case email.CmdRegenerate: upd.Command = dashapi.BugListRegenerateCmd - } else { + case email.CmdSet, email.CmdUnset: + // Extract and cut the <Ref> part. + match := setGroupCmdRe.FindStringSubmatch(command.Args) + if match == nil { + return setGroupCmdFormat + } + ref, args := match[1], match[2] + numRef, err := strconv.Atoi(ref) + if err != nil { + return setGroupCmdFormat + } + if numRef < 1 || numRef > len(bugListInfo.keys) { + return setGroupCmdBadRef + } + bugKey := bugListInfo.keys[numRef-1] + bug := new(Bug) + if err := db.Get(c, bugKey, bug); err != nil { + log.Errorf(c, "failed to fetch bug by key %s: %s", bugKey, err) + return cmdInternalErrorReply + } + command.Args = args + switch command.Command { + case email.CmdSet: + return handleSetCommand(c, bug, msg, command) + case email.CmdUnset: + return handleUnsetCommand(c, bug, msg, command) + } + default: upd.Command = dashapi.BugListUpdateCmd } log.Infof(c, "bug list update: id=%s, cmd=%v", upd.ID, upd.Command) reply, err := reportingBugListCommand(c, upd) if err != nil { log.Errorf(c, "bug list command failed: %s", err) - return "" + return cmdInternalErrorReply } return reply } @@ -794,6 +896,7 @@ var nonCriticalBounceRe = regexp.MustCompile(`\*\* Address not found \*\*|550 #5 type bugListInfoResult struct { id string config *EmailConfig + keys []*db.Key } func identifyEmail(c context.Context, msg *email.Email) (*bugInfoResult, *bugListInfoResult, *EmailConfig) { @@ -803,7 +906,7 @@ func identifyEmail(c context.Context, msg *email.Email) (*bugInfoResult, *bugLis bugID = msg.BugIDs[0] } if isBugListHash(bugID) { - subsystem, _, stage, err := findSubsystemReportByID(c, bugID) + subsystem, report, stage, err := findSubsystemReportByID(c, bugID) if err != nil { log.Errorf(c, "findBugListByID failed: %s", err) return nil, nil, nil @@ -822,7 +925,16 @@ func identifyEmail(c context.Context, msg *email.Email) (*bugInfoResult, *bugLis log.Errorf(c, "bug list's reporting config is not EmailConfig (id=%v)", bugID) return nil, nil, nil } - return nil, &bugListInfoResult{id: bugID, config: emailConfig}, emailConfig + keys, err := report.getBugKeys() + if err != nil { + log.Errorf(c, "failed to extract keys from bug list: %s", err) + return nil, nil, nil + } + return nil, &bugListInfoResult{ + id: bugID, + config: emailConfig, + keys: keys, + }, emailConfig } bugInfo := loadBugInfo(c, msg) if bugInfo == nil { |
