aboutsummaryrefslogtreecommitdiffstats
path: root/dashboard/app/reporting_email.go
diff options
context:
space:
mode:
authorAleksandr Nogikh <nogikh@google.com>2023-04-24 19:52:41 +0200
committerAleksandr Nogikh <wp32pw@gmail.com>2023-04-27 11:42:09 +0200
commit703f643f91e8becd2495f1f102346a0b7fa63867 (patch)
treec304e8d97b58127b6c539096f7d283d8582d12c8 /dashboard/app/reporting_email.go
parentcef7cc0993422a96dff2c22d1afc9f87502bb289 (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.go220
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 {