github.com/ouraigua/jenkins-library@v0.0.0-20231028010029-fbeaf2f3aa9b/pkg/reporting/github.go (about) 1 package reporting 2 3 import ( 4 "context" 5 "fmt" 6 7 "github.com/SAP/jenkins-library/pkg/log" 8 "github.com/google/go-github/v45/github" 9 ) 10 11 type githubIssueService interface { 12 Create(ctx context.Context, owner string, repo string, issue *github.IssueRequest) (*github.Issue, *github.Response, error) 13 CreateComment(ctx context.Context, owner string, repo string, number int, comment *github.IssueComment) (*github.IssueComment, *github.Response, error) 14 Edit(ctx context.Context, owner string, repo string, number int, issue *github.IssueRequest) (*github.Issue, *github.Response, error) 15 } 16 17 type githubSearchService interface { 18 Issues(ctx context.Context, query string, opts *github.SearchOptions) (*github.IssuesSearchResult, *github.Response, error) 19 } 20 21 // GitHub contains metadata for reporting towards GitHub 22 type GitHub struct { 23 Owner *string 24 Repository *string 25 Assignees *[]string 26 IssueService githubIssueService 27 SearchService githubSearchService 28 } 29 30 // UploadSingleReport uploads a single report to GitHub 31 func (g *GitHub) UploadSingleReport(ctx context.Context, scanReport IssueDetail) error { 32 // JSON reports are used by step pipelineCreateSummary in order to e.g. prepare an issue creation in GitHub 33 // ignore JSON errors since structure is in our hands 34 title := scanReport.Title() 35 markdownReport, _ := scanReport.ToMarkdown() 36 37 log.Entry().Debugf("Creating/updating GitHub issue with title %v in org %v and repo %v", title, &g.Owner, &g.Repository) 38 if err := g.createIssueOrUpdateIssueComment(ctx, title, string(markdownReport)); err != nil { 39 return fmt.Errorf("failed to upload results for '%v' into GitHub issue: %w", title, err) 40 } 41 return nil 42 } 43 44 // UploadMultipleReports uploads a number of reports to GitHub, one per IssueDetail to create transparency 45 func (g *GitHub) UploadMultipleReports(ctx context.Context, scanReports *[]IssueDetail) error { 46 for _, scanReport := range *scanReports { 47 if err := g.UploadSingleReport(ctx, scanReport); err != nil { 48 return err 49 } 50 } 51 return nil 52 } 53 54 func (g *GitHub) createIssueOrUpdateIssueComment(ctx context.Context, title, issueContent string) error { 55 // check if issue is existing 56 issueNumber, issueBody, err := g.findExistingIssue(ctx, title) 57 if err != nil { 58 return fmt.Errorf("error when looking up issue: %w", err) 59 } 60 61 if issueNumber == 0 { 62 // issue not existing need to create it 63 issue := github.IssueRequest{Title: &title, Body: &issueContent, Assignees: g.Assignees} 64 if _, _, err := g.IssueService.Create(ctx, *g.Owner, *g.Repository, &issue); err != nil { 65 return fmt.Errorf("failed to create issue: %w", err) 66 } 67 return nil 68 } 69 70 // let's compare and only update in case an update is required 71 if issueContent != issueBody { 72 // update of issue required 73 issueRequest := github.IssueRequest{Body: &issueContent} 74 if _, _, err := g.IssueService.Edit(ctx, *g.Owner, *g.Repository, issueNumber, &issueRequest); err != nil { 75 return fmt.Errorf("failed to edit issue: %w", err) 76 } 77 78 // now add a small comment that the issue content has been updated 79 updateText := "issue content has been updated" 80 updateComment := github.IssueComment{Body: &updateText} 81 if _, _, err := g.IssueService.CreateComment(ctx, *g.Owner, *g.Repository, issueNumber, &updateComment); err != nil { 82 return fmt.Errorf("failed to create comment: %w", err) 83 } 84 } 85 return nil 86 } 87 88 func (g *GitHub) findExistingIssue(ctx context.Context, title string) (int, string, error) { 89 queryString := fmt.Sprintf("is:issue repo:%v/%v in:title %v", *g.Owner, *g.Repository, title) 90 searchResult, _, err := g.SearchService.Issues(ctx, queryString, nil) 91 if err != nil { 92 return 0, "", fmt.Errorf("error occurred when looking for existing issue: %w", err) 93 } 94 for _, i := range searchResult.Issues { 95 if i != nil && *i.Title == title { 96 if i.GetState() == "closed" { 97 // reopen issue 98 open := "open" 99 ir := github.IssueRequest{State: &open} 100 if _, _, err := g.IssueService.Edit(ctx, *g.Owner, *g.Repository, i.GetNumber(), &ir); err != nil { 101 return i.GetNumber(), "", fmt.Errorf("failed to re-open issue: %w", err) 102 } 103 } 104 return i.GetNumber(), i.GetBody(), nil 105 } 106 } 107 return 0, "", nil 108 }