github.com/google/syzkaller@v0.0.0-20240517125934-c0f1611a36d6/dashboard/dashapi/dashapi.go (about) 1 // Copyright 2017 syzkaller project authors. All rights reserved. 2 // Use of this source code is governed by Apache 2 LICENSE that can be found in the LICENSE file. 3 4 // Package dashapi defines data structures used in dashboard communication 5 // and provides client interface. 6 package dashapi 7 8 import ( 9 "bytes" 10 "compress/gzip" 11 "encoding/json" 12 "fmt" 13 "io" 14 "mime/multipart" 15 "net/http" 16 "net/mail" 17 "reflect" 18 "time" 19 20 "github.com/google/syzkaller/pkg/auth" 21 ) 22 23 type Dashboard struct { 24 Client string 25 Addr string 26 Key string 27 ctor RequestCtor 28 doer RequestDoer 29 logger RequestLogger 30 errorHandler func(error) 31 } 32 33 func New(client, addr, key string) (*Dashboard, error) { 34 return NewCustom(client, addr, key, http.NewRequest, http.DefaultClient.Do, nil, nil) 35 } 36 37 type ( 38 RequestCtor func(method, url string, body io.Reader) (*http.Request, error) 39 RequestDoer func(req *http.Request) (*http.Response, error) 40 RequestLogger func(msg string, args ...interface{}) 41 ) 42 43 // key == "" indicates that the ambient GCE service account authority 44 // should be used as a bearer token. 45 func NewCustom(client, addr, key string, ctor RequestCtor, doer RequestDoer, 46 logger RequestLogger, errorHandler func(error)) (*Dashboard, error) { 47 wrappedDoer := doer 48 if key == "" { 49 tokenCache, err := auth.MakeCache(ctor, doer) 50 if err != nil { 51 return nil, err 52 } 53 wrappedDoer = func(req *http.Request) (*http.Response, error) { 54 token, err := tokenCache.Get(time.Now()) 55 if err != nil { 56 return nil, err 57 } 58 req.Header.Add("Authorization", token) 59 return doer(req) 60 } 61 } 62 return &Dashboard{ 63 Client: client, 64 Addr: addr, 65 Key: key, 66 ctor: ctor, 67 doer: wrappedDoer, 68 logger: logger, 69 errorHandler: errorHandler, 70 }, nil 71 } 72 73 // Build describes all aspects of a kernel build. 74 type Build struct { 75 Manager string 76 ID string 77 OS string 78 Arch string 79 VMArch string 80 SyzkallerCommit string 81 SyzkallerCommitDate time.Time 82 CompilerID string 83 KernelRepo string 84 KernelBranch string 85 KernelCommit string 86 KernelCommitTitle string 87 KernelCommitDate time.Time 88 KernelConfig []byte 89 Commits []string // see BuilderPoll 90 FixCommits []Commit 91 Assets []NewAsset 92 } 93 94 type Commit struct { 95 Hash string 96 Title string 97 Author string 98 AuthorName string 99 CC []string // deprecated in favor of Recipients 100 Recipients Recipients 101 BugIDs []string // ID's extracted from Reported-by tags 102 Date time.Time 103 Link string // set if the commit is a part of a reply 104 } 105 106 func (dash *Dashboard) UploadBuild(build *Build) error { 107 return dash.Query("upload_build", build, nil) 108 } 109 110 // BuilderPoll request is done by kernel builder before uploading a new build 111 // with UploadBuild request. Response contains list of commit titles that 112 // dashboard is interested in (i.e. commits that fix open bugs) and email that 113 // appears in Reported-by tags for bug ID extraction. When uploading a new build 114 // builder will pass subset of the commit titles that are present in the build 115 // in Build.Commits field and list of {bug ID, commit title} pairs extracted 116 // from git log. 117 118 type BuilderPollReq struct { 119 Manager string 120 } 121 122 type BuilderPollResp struct { 123 PendingCommits []string 124 ReportEmail string 125 } 126 127 func (dash *Dashboard) BuilderPoll(manager string) (*BuilderPollResp, error) { 128 req := &BuilderPollReq{ 129 Manager: manager, 130 } 131 resp := new(BuilderPollResp) 132 err := dash.Query("builder_poll", req, resp) 133 return resp, err 134 } 135 136 // Jobs workflow: 137 // - syz-ci sends JobResetReq to indicate that no previously started jobs 138 // are any longer in progress. 139 // - syz-ci sends JobPollReq periodically to check for new jobs, 140 // request contains list of managers that this syz-ci runs. 141 // - dashboard replies with JobPollResp that contains job details, 142 // if no new jobs available ID is set to empty string. 143 // - when syz-ci finishes the job, it sends JobDoneReq which contains 144 // job execution result (Build, Crash or Error details), 145 // ID must match JobPollResp.ID. 146 147 type JobResetReq struct { 148 Managers []string 149 } 150 151 type JobPollReq struct { 152 Managers map[string]ManagerJobs 153 } 154 155 type ManagerJobs struct { 156 TestPatches bool 157 BisectCause bool 158 BisectFix bool 159 } 160 161 func (m ManagerJobs) Any() bool { 162 return m.TestPatches || m.BisectCause || m.BisectFix 163 } 164 165 type JobPollResp struct { 166 ID string 167 Type JobType 168 Manager string 169 KernelRepo string 170 // KernelBranch is used for patch testing and serves as the current HEAD 171 // for bisections. 172 KernelBranch string 173 MergeBaseRepo string 174 MergeBaseBranch string 175 // Bisection starts from KernelCommit. 176 KernelCommit string 177 KernelCommitTitle string 178 KernelConfig []byte 179 SyzkallerCommit string 180 Patch []byte 181 ReproOpts []byte 182 ReproSyz []byte 183 ReproC []byte 184 } 185 186 type JobDoneReq struct { 187 ID string 188 Build Build 189 Error []byte 190 Log []byte // bisection log 191 CrashTitle string 192 CrashAltTitles []string 193 CrashLog []byte 194 CrashReport []byte 195 // Bisection results: 196 // If there is 0 commits: 197 // - still happens on HEAD for fix bisection 198 // - already happened on the oldest release 199 // If there is 1 commits: bisection result (cause or fix). 200 // If there are more than 1: suspected commits due to skips (broken build/boot). 201 Commits []Commit 202 Flags JobDoneFlags 203 } 204 205 type JobType int 206 207 const ( 208 JobTestPatch JobType = iota 209 JobBisectCause 210 JobBisectFix 211 ) 212 213 type JobDoneFlags int64 214 215 const ( 216 BisectResultMerge JobDoneFlags = 1 << iota // bisected to a merge commit 217 BisectResultNoop // commit does not affect resulting kernel binary 218 BisectResultRelease // commit is a kernel release 219 BisectResultIgnore // this particular commit should be ignored, see syz-ci/jobs.go 220 BisectResultInfraError // the bisect failed due to an infrastructure problem 221 ) 222 223 func (flags JobDoneFlags) String() string { 224 if flags&BisectResultInfraError != 0 { 225 return "[infra failure]" 226 } 227 res := "" 228 if flags&BisectResultMerge != 0 { 229 res += "merge " 230 } 231 if flags&BisectResultNoop != 0 { 232 res += "no-op " 233 } 234 if flags&BisectResultRelease != 0 { 235 res += "release " 236 } 237 if flags&BisectResultIgnore != 0 { 238 res += "ignored " 239 } 240 if res == "" { 241 return res 242 } 243 return "[" + res + "commit]" 244 } 245 246 func (dash *Dashboard) JobPoll(req *JobPollReq) (*JobPollResp, error) { 247 resp := new(JobPollResp) 248 err := dash.Query("job_poll", req, resp) 249 return resp, err 250 } 251 252 func (dash *Dashboard) JobDone(req *JobDoneReq) error { 253 return dash.Query("job_done", req, nil) 254 } 255 256 func (dash *Dashboard) JobReset(req *JobResetReq) error { 257 return dash.Query("job_reset", req, nil) 258 } 259 260 type BuildErrorReq struct { 261 Build Build 262 Crash Crash 263 } 264 265 func (dash *Dashboard) ReportBuildError(req *BuildErrorReq) error { 266 return dash.Query("report_build_error", req, nil) 267 } 268 269 type CommitPollResp struct { 270 ReportEmail string 271 Repos []Repo 272 Commits []string 273 } 274 275 type CommitPollResultReq struct { 276 Commits []Commit 277 } 278 279 type Repo struct { 280 URL string 281 Branch string 282 } 283 284 func (dash *Dashboard) CommitPoll() (*CommitPollResp, error) { 285 resp := new(CommitPollResp) 286 err := dash.Query("commit_poll", nil, resp) 287 return resp, err 288 } 289 290 func (dash *Dashboard) UploadCommits(commits []Commit) error { 291 if len(commits) == 0 { 292 return nil 293 } 294 return dash.Query("upload_commits", &CommitPollResultReq{commits}, nil) 295 } 296 297 type CrashFlags int64 298 299 const ( 300 CrashUnderStrace CrashFlags = 1 << iota 301 ) 302 303 // Crash describes a single kernel crash (potentially with repro). 304 type Crash struct { 305 BuildID string // refers to Build.ID 306 Title string 307 AltTitles []string // alternative titles, used for better deduplication 308 Corrupted bool // report is corrupted (corrupted title, no stacks, etc) 309 Suppressed bool 310 Maintainers []string // deprecated in favor of Recipients 311 Recipients Recipients 312 Log []byte 313 Flags CrashFlags 314 Report []byte 315 MachineInfo []byte 316 Assets []NewAsset 317 GuiltyFiles []string 318 // The following is optional and is filled only after repro. 319 ReproOpts []byte 320 ReproSyz []byte 321 ReproC []byte 322 ReproLog []byte 323 OriginalTitle string // Title before we began bug reproduction. 324 } 325 326 type ReportCrashResp struct { 327 NeedRepro bool 328 } 329 330 func (dash *Dashboard) ReportCrash(crash *Crash) (*ReportCrashResp, error) { 331 resp := new(ReportCrashResp) 332 err := dash.Query("report_crash", crash, resp) 333 return resp, err 334 } 335 336 // CrashID is a short summary of a crash for repro queries. 337 type CrashID struct { 338 BuildID string 339 Title string 340 Corrupted bool 341 Suppressed bool 342 MayBeMissing bool 343 ReproLog []byte 344 } 345 346 type NeedReproResp struct { 347 NeedRepro bool 348 } 349 350 // NeedRepro checks if dashboard needs a repro for this crash or not. 351 func (dash *Dashboard) NeedRepro(crash *CrashID) (bool, error) { 352 resp := new(NeedReproResp) 353 err := dash.Query("need_repro", crash, resp) 354 return resp.NeedRepro, err 355 } 356 357 // ReportFailedRepro notifies dashboard about a failed repro attempt for the crash. 358 func (dash *Dashboard) ReportFailedRepro(crash *CrashID) error { 359 return dash.Query("report_failed_repro", crash, nil) 360 } 361 362 type LogToReproReq struct { 363 BuildID string 364 } 365 366 type LogToReproResp struct { 367 Title string 368 CrashLog []byte 369 } 370 371 // LogToRepro are crash logs for older bugs that need to be reproduced on the 372 // querying instance. 373 func (dash *Dashboard) LogToRepro(req *LogToReproReq) (*LogToReproResp, error) { 374 resp := new(LogToReproResp) 375 err := dash.Query("log_to_repro", req, resp) 376 return resp, err 377 } 378 379 type LogEntry struct { 380 Name string 381 Text string 382 } 383 384 // Centralized logging on dashboard. 385 func (dash *Dashboard) LogError(name, msg string, args ...interface{}) { 386 req := &LogEntry{ 387 Name: name, 388 Text: fmt.Sprintf(msg, args...), 389 } 390 dash.Query("log_error", req, nil) 391 } 392 393 // BugReport describes a single bug. 394 // Used by dashboard external reporting. 395 type BugReport struct { 396 Type ReportType 397 BugStatus BugStatus 398 Namespace string 399 Config []byte 400 ID string 401 JobID string 402 ExtID string // arbitrary reporting ID forwarded from BugUpdate.ExtID 403 First bool // Set for first report for this bug (Type == ReportNew). 404 Moderation bool 405 NoRepro bool // We don't expect repro (e.g. for build/boot errors). 406 Title string 407 Link string // link to the bug on dashboard 408 CreditEmail string // email for the Reported-by tag 409 Maintainers []string // deprecated in favor of Recipients 410 CC []string // deprecated in favor of Recipients 411 Recipients Recipients 412 OS string 413 Arch string 414 VMArch string 415 UserSpaceArch string // user-space arch as kernel developers know it (rather than Go names) 416 BuildID string 417 BuildTime time.Time 418 CompilerID string 419 KernelRepo string 420 KernelRepoAlias string 421 KernelBranch string 422 KernelCommit string 423 KernelCommitTitle string 424 KernelCommitDate time.Time 425 KernelConfig []byte 426 KernelConfigLink string 427 SyzkallerCommit string 428 Log []byte 429 LogLink string 430 LogHasStrace bool 431 Report []byte 432 ReportLink string 433 ReproC []byte 434 ReproCLink string 435 ReproSyz []byte 436 ReproSyzLink string 437 ReproOpts []byte 438 MachineInfo []byte 439 MachineInfoLink string 440 Manager string 441 CrashID int64 // returned back in BugUpdate 442 CrashTime time.Time 443 NumCrashes int64 444 HappenedOn []string // list of kernel repo aliases 445 446 CrashTitle string // job execution crash title 447 Error []byte // job execution error 448 ErrorLink string 449 ErrorTruncated bool // full Error text is too large and was truncated 450 PatchLink string 451 BisectCause *BisectResult 452 BisectFix *BisectResult 453 Assets []Asset 454 Subsystems []BugSubsystem 455 ReportElements *ReportElements 456 LabelMessages map[string]string // notification messages for bug labels 457 } 458 459 type ReportElements struct { 460 GuiltyFiles []string 461 } 462 463 type BugSubsystem struct { 464 Name string 465 Link string 466 SetBy string 467 } 468 469 type Asset struct { 470 Title string 471 DownloadURL string 472 Type AssetType 473 } 474 475 type AssetType string 476 477 // Asset types used throughout the system. 478 // DO NOT change them, this will break compatibility with DB content. 479 const ( 480 BootableDisk AssetType = "bootable_disk" 481 NonBootableDisk AssetType = "non_bootable_disk" 482 KernelObject AssetType = "kernel_object" 483 KernelImage AssetType = "kernel_image" 484 HTMLCoverageReport AssetType = "html_coverage_report" 485 MountInRepro AssetType = "mount_in_repro" 486 ) 487 488 type BisectResult struct { 489 Commit *Commit // for conclusive bisection 490 Commits []*Commit // for inconclusive bisection 491 LogLink string 492 CrashLogLink string 493 CrashReportLink string 494 Fix bool 495 CrossTree bool 496 // In case a missing backport was backported. 497 Backported *Commit 498 } 499 500 type BugListReport struct { 501 ID string 502 Created time.Time 503 Config []byte 504 Bugs []BugListItem 505 TotalStats BugListReportStats 506 PeriodStats BugListReportStats 507 PeriodDays int 508 Link string 509 Subsystem string 510 Maintainers []string 511 Moderation bool 512 } 513 514 type BugListReportStats struct { 515 Reported int 516 LowPrio int 517 Fixed int 518 } 519 520 // BugListItem represents a single bug from the BugListReport entity. 521 type BugListItem struct { 522 ID string 523 Title string 524 Link string 525 ReproLevel ReproLevel 526 Hits int64 527 } 528 529 type BugListUpdate struct { 530 ID string // copied from BugListReport 531 ExtID string 532 Link string 533 Command BugListUpdateCommand 534 } 535 536 type BugListUpdateCommand string 537 538 const ( 539 BugListSentCmd BugListUpdateCommand = "sent" 540 BugListUpdateCmd BugListUpdateCommand = "update" 541 BugListUpstreamCmd BugListUpdateCommand = "upstream" 542 BugListRegenerateCmd BugListUpdateCommand = "regenerate" 543 ) 544 545 type BugUpdate struct { 546 ID string // copied from BugReport 547 JobID string // copied from BugReport 548 ExtID string 549 Link string 550 Status BugStatus 551 StatusReason BugStatusReason 552 Labels []string // the reported labels 553 ReproLevel ReproLevel 554 DupOf string 555 OnHold bool // If set for open bugs, don't upstream this bug. 556 Notification bool // Reply to a notification. 557 ResetFixCommits bool // Remove all commits (empty FixCommits means leave intact). 558 FixCommits []string // Titles of commits that fix this bug. 559 CC []string // Additional emails to add to CC list in future emails. 560 561 CrashID int64 // This is a deprecated field, left here for backward compatibility. 562 563 // The new interface that allows to report and unreport several crashes at the same time. 564 // This is not relevant for emails, but may be important for external reportings. 565 ReportCrashIDs []int64 566 UnreportCrashIDs []int64 567 } 568 569 type BugUpdateReply struct { 570 // Bug update can fail for 2 reason: 571 // - update does not pass logical validataion, in this case OK=false 572 // - internal/datastore error, in this case Error=true 573 OK bool 574 Error bool 575 Text string 576 } 577 578 type PollBugsRequest struct { 579 Type string 580 } 581 582 type PollBugsResponse struct { 583 Reports []*BugReport 584 } 585 586 type BugNotification struct { 587 Type BugNotif 588 Namespace string 589 Config []byte 590 ID string 591 ExtID string // arbitrary reporting ID forwarded from BugUpdate.ExtID 592 Title string 593 Text string // meaning depends on Type 594 Label string // for BugNotifLabel Type specifies the exact label 595 CC []string // deprecated in favor of Recipients 596 Maintainers []string // deprecated in favor of Recipients 597 Link string 598 Recipients Recipients 599 TreeJobs []*JobInfo // set for some BugNotifLabel 600 // Public is what we want all involved people to see (e.g. if we notify about a wrong commit title, 601 // people need to see it and provide the right title). Not public is what we want to send only 602 // to a minimal set of recipients (our mailing list) (e.g. notification about an obsoleted bug 603 // is mostly "for the record"). 604 Public bool 605 } 606 607 type PollNotificationsRequest struct { 608 Type string 609 } 610 611 type PollNotificationsResponse struct { 612 Notifications []*BugNotification 613 } 614 615 type PollClosedRequest struct { 616 IDs []string 617 } 618 619 type PollClosedResponse struct { 620 IDs []string 621 } 622 623 type DiscussionSource string 624 625 const ( 626 NoDiscussion DiscussionSource = "" 627 DiscussionLore DiscussionSource = "lore" 628 ) 629 630 type DiscussionType string 631 632 const ( 633 DiscussionReport DiscussionType = "report" 634 DiscussionPatch DiscussionType = "patch" 635 DiscussionReminder DiscussionType = "reminder" 636 DiscussionMention DiscussionType = "mention" 637 ) 638 639 type Discussion struct { 640 ID string 641 Source DiscussionSource 642 Type DiscussionType 643 Subject string 644 BugIDs []string 645 Messages []DiscussionMessage 646 } 647 648 type DiscussionMessage struct { 649 ID string 650 External bool // true if the message is not from the bot itself 651 Time time.Time 652 Email string // not saved to the DB 653 } 654 655 type SaveDiscussionReq struct { 656 // If the discussion already exists, Messages and BugIDs will be appended to it. 657 Discussion *Discussion 658 } 659 660 func (dash *Dashboard) SaveDiscussion(req *SaveDiscussionReq) error { 661 return dash.Query("save_discussion", req, nil) 662 } 663 664 type TestPatchRequest struct { 665 BugID string 666 Link string 667 User string 668 Repo string 669 Branch string 670 Patch []byte 671 } 672 673 type TestPatchReply struct { 674 ErrorText string 675 } 676 677 func (dash *Dashboard) ReportingPollBugs(typ string) (*PollBugsResponse, error) { 678 req := &PollBugsRequest{ 679 Type: typ, 680 } 681 resp := new(PollBugsResponse) 682 if err := dash.Query("reporting_poll_bugs", req, resp); err != nil { 683 return nil, err 684 } 685 return resp, nil 686 } 687 688 func (dash *Dashboard) ReportingPollNotifications(typ string) (*PollNotificationsResponse, error) { 689 req := &PollNotificationsRequest{ 690 Type: typ, 691 } 692 resp := new(PollNotificationsResponse) 693 if err := dash.Query("reporting_poll_notifs", req, resp); err != nil { 694 return nil, err 695 } 696 return resp, nil 697 } 698 699 func (dash *Dashboard) ReportingPollClosed(ids []string) ([]string, error) { 700 req := &PollClosedRequest{ 701 IDs: ids, 702 } 703 resp := new(PollClosedResponse) 704 if err := dash.Query("reporting_poll_closed", req, resp); err != nil { 705 return nil, err 706 } 707 return resp.IDs, nil 708 } 709 710 func (dash *Dashboard) ReportingUpdate(upd *BugUpdate) (*BugUpdateReply, error) { 711 resp := new(BugUpdateReply) 712 if err := dash.Query("reporting_update", upd, resp); err != nil { 713 return nil, err 714 } 715 return resp, nil 716 } 717 718 func (dash *Dashboard) NewTestJob(upd *TestPatchRequest) (*TestPatchReply, error) { 719 resp := new(TestPatchReply) 720 if err := dash.Query("new_test_job", upd, resp); err != nil { 721 return nil, err 722 } 723 return resp, nil 724 } 725 726 type ManagerStatsReq struct { 727 Name string 728 Addr string 729 730 // Current level: 731 UpTime time.Duration 732 Corpus uint64 733 PCs uint64 // coverage 734 Cover uint64 // what we call feedback signal everywhere else 735 CrashTypes uint64 736 737 // Delta since last sync: 738 FuzzingTime time.Duration 739 Crashes uint64 740 SuppressedCrashes uint64 741 Execs uint64 742 743 // Non-zero only when set. 744 TriagedCoverage uint64 745 TriagedPCs uint64 746 } 747 748 func (dash *Dashboard) UploadManagerStats(req *ManagerStatsReq) error { 749 return dash.Query("manager_stats", req, nil) 750 } 751 752 // Asset lifetime: 753 // 1. syz-ci uploads it to GCS and reports to the dashboard via add_build_asset. 754 // 2. dashboard periodically checks if the asset is still needed. 755 // 3. syz-ci queries needed_assets to figure out which assets are still needed. 756 // 4. Once an asset is not needed, syz-ci removes the corresponding file. 757 type NewAsset struct { 758 DownloadURL string 759 Type AssetType 760 } 761 762 type AddBuildAssetsReq struct { 763 BuildID string 764 Assets []NewAsset 765 } 766 767 func (dash *Dashboard) AddBuildAssets(req *AddBuildAssetsReq) error { 768 return dash.Query("add_build_assets", req, nil) 769 } 770 771 type NeededAssetsResp struct { 772 DownloadURLs []string 773 } 774 775 func (dash *Dashboard) NeededAssetsList() (*NeededAssetsResp, error) { 776 resp := new(NeededAssetsResp) 777 err := dash.Query("needed_assets", nil, resp) 778 return resp, err 779 } 780 781 type BugListResp struct { 782 List []string 783 } 784 785 func (dash *Dashboard) BugList() (*BugListResp, error) { 786 resp := new(BugListResp) 787 err := dash.Query("bug_list", nil, resp) 788 return resp, err 789 } 790 791 type LoadBugReq struct { 792 ID string 793 } 794 795 func (dash *Dashboard) LoadBug(id string) (*BugReport, error) { 796 req := LoadBugReq{id} 797 resp := new(BugReport) 798 err := dash.Query("load_bug", req, resp) 799 return resp, err 800 } 801 802 type LoadFullBugReq struct { 803 BugID string 804 } 805 806 type FullBugInfo struct { 807 SimilarBugs []*SimilarBugInfo 808 BisectCause *BugReport 809 BisectFix *BugReport 810 Crashes []*BugReport 811 TreeJobs []*JobInfo 812 FixCandidate *BugReport 813 } 814 815 type SimilarBugInfo struct { 816 Title string 817 Status BugStatus 818 Namespace string 819 Link string 820 ReportLink string 821 Closed time.Time 822 ReproLevel ReproLevel 823 } 824 825 func (dash *Dashboard) LoadFullBug(req *LoadFullBugReq) (*FullBugInfo, error) { 826 resp := new(FullBugInfo) 827 err := dash.Query("load_full_bug", req, resp) 828 return resp, err 829 } 830 831 type UpdateReportReq struct { 832 BugID string 833 CrashID int64 834 GuiltyFiles *[]string 835 } 836 837 func (dash *Dashboard) UpdateReport(req *UpdateReportReq) error { 838 return dash.Query("update_report", req, nil) 839 } 840 841 type ( 842 BugStatus int 843 BugStatusReason string 844 BugNotif int 845 ReproLevel int 846 ReportType int 847 ) 848 849 const ( 850 BugStatusOpen BugStatus = iota 851 BugStatusUpstream 852 BugStatusInvalid 853 BugStatusDup 854 BugStatusUpdate // aux info update (i.e. ExtID/Link/CC) 855 BugStatusUnCC // don't CC sender on any future communication 856 BugStatusFixed 857 ) 858 859 const ( 860 InvalidatedByRevokedRepro = BugStatusReason("invalid_no_repro") 861 InvalidatedByNoActivity = BugStatusReason("invalid_no_activity") 862 ) 863 864 const ( 865 // Upstream bug into next reporting. 866 // If the action succeeds, reporting sends BugStatusUpstream update. 867 BugNotifUpstream BugNotif = iota 868 // Bug needs to be closed as obsoleted. 869 // If the action succeeds, reporting sends BugStatusInvalid update. 870 BugNotifObsoleted 871 // Bug fixing commit can't be discovered (wrong commit title). 872 BugNotifBadCommit 873 // New bug label has been assigned (only if enabled). 874 // Text contains the custome message that needs to be delivered to the user. 875 BugNotifLabel 876 ) 877 878 const ( 879 ReproLevelNone ReproLevel = iota 880 ReproLevelSyz 881 ReproLevelC 882 ) 883 884 const ( 885 ReportNew ReportType = iota // First report for this bug in the reporting stage. 886 ReportRepro // Found repro for an already reported bug. 887 ReportTestPatch // Patch testing result. 888 ReportBisectCause // Cause bisection result for an already reported bug. 889 ReportBisectFix // Fix bisection result for an already reported bug. 890 ) 891 892 type JobInfo struct { 893 JobKey string 894 Type JobType 895 Flags JobDoneFlags 896 Created time.Time 897 BugLink string 898 ExternalLink string 899 User string 900 Reporting string 901 Namespace string 902 Manager string 903 BugTitle string 904 BugID string 905 KernelRepo string 906 KernelBranch string 907 KernelAlias string 908 KernelCommit string 909 KernelCommitLink string 910 KernelLink string 911 PatchLink string 912 Attempts int 913 Started time.Time 914 Finished time.Time 915 Duration time.Duration 916 CrashTitle string 917 CrashLogLink string 918 CrashReportLink string 919 LogLink string 920 ErrorLink string 921 ReproCLink string 922 ReproSyzLink string 923 Commit *Commit // for conclusive bisection 924 Commits []*Commit // for inconclusive bisection 925 Reported bool 926 InvalidatedBy string 927 TreeOrigin bool 928 OnMergeBase bool 929 } 930 931 func (dash *Dashboard) Query(method string, req, reply interface{}) error { 932 if dash.logger != nil { 933 dash.logger("API(%v): %#v", method, req) 934 } 935 err := dash.queryImpl(method, req, reply) 936 if err != nil { 937 if dash.logger != nil { 938 dash.logger("API(%v): ERROR: %v", method, err) 939 } 940 if dash.errorHandler != nil { 941 dash.errorHandler(err) 942 } 943 return err 944 } 945 if dash.logger != nil { 946 dash.logger("API(%v): REPLY: %#v", method, reply) 947 } 948 return nil 949 } 950 951 func (dash *Dashboard) queryImpl(method string, req, reply interface{}) error { 952 if reply != nil { 953 // json decoding behavior is somewhat surprising 954 // (see // https://github.com/golang/go/issues/21092). 955 // To avoid any surprises, we zero the reply. 956 typ := reflect.TypeOf(reply) 957 if typ.Kind() != reflect.Ptr { 958 return fmt.Errorf("resp must be a pointer") 959 } 960 reflect.ValueOf(reply).Elem().Set(reflect.New(typ.Elem()).Elem()) 961 } 962 body := &bytes.Buffer{} 963 mWriter := multipart.NewWriter(body) 964 err := mWriter.WriteField("client", dash.Client) 965 if err != nil { 966 return err 967 } 968 err = mWriter.WriteField("key", dash.Key) 969 if err != nil { 970 return err 971 } 972 err = mWriter.WriteField("method", method) 973 if err != nil { 974 return err 975 } 976 if req != nil { 977 w, err := mWriter.CreateFormField("payload") 978 if err != nil { 979 return err 980 } 981 data, err := json.Marshal(req) 982 if err != nil { 983 return fmt.Errorf("failed to marshal request: %w", err) 984 } 985 gz := gzip.NewWriter(w) 986 if _, err := gz.Write(data); err != nil { 987 return err 988 } 989 if err := gz.Close(); err != nil { 990 return err 991 } 992 } 993 mWriter.Close() 994 r, err := dash.ctor("POST", fmt.Sprintf("%v/api", dash.Addr), body) 995 if err != nil { 996 return err 997 } 998 r.Header.Set("Content-Type", mWriter.FormDataContentType()) 999 resp, err := dash.doer(r) 1000 if err != nil { 1001 return fmt.Errorf("http request failed: %w", err) 1002 } 1003 defer resp.Body.Close() 1004 if resp.StatusCode != http.StatusOK { 1005 data, _ := io.ReadAll(resp.Body) 1006 return fmt.Errorf("request failed with %v: %s", resp.Status, data) 1007 } 1008 if reply != nil { 1009 if err := json.NewDecoder(resp.Body).Decode(reply); err != nil { 1010 return fmt.Errorf("failed to unmarshal response: %w", err) 1011 } 1012 } 1013 return nil 1014 } 1015 1016 type RecipientType int 1017 1018 const ( 1019 To RecipientType = iota 1020 Cc 1021 ) 1022 1023 func (t RecipientType) String() string { 1024 return [...]string{"To", "Cc"}[t] 1025 } 1026 1027 type RecipientInfo struct { 1028 Address mail.Address 1029 Type RecipientType 1030 } 1031 1032 type Recipients []RecipientInfo 1033 1034 func (r Recipients) Len() int { return len(r) } 1035 func (r Recipients) Less(i, j int) bool { return r[i].Address.Address < r[j].Address.Address } 1036 func (r Recipients) Swap(i, j int) { r[i], r[j] = r[j], r[i] }