Skip to content

Commit a8e9050

Browse files
✨ Optimize SAST check (#2191)
* Optimize SAST * Address PR feedback * split checkruns into separate graphql query * Enable SAST check in the releasetest cron worker Co-authored-by: Azeem Shaikh <azeemshaikh38@gmail.com>
1 parent 11ff78e commit a8e9050

File tree

6 files changed

+167
-27
lines changed

6 files changed

+167
-27
lines changed

checks/sast.go

Lines changed: 2 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -123,19 +123,17 @@ func SAST(c *checker.CheckRequest) checker.CheckResult {
123123
return checker.CreateRuntimeErrorResult(CheckSAST, sce.WithMessage(sce.ErrScorecardInternal, "contact team"))
124124
}
125125

126-
//nolint
127126
func sastToolInCheckRuns(c *checker.CheckRequest) (int, error) {
128127
commits, err := c.RepoClient.ListCommits()
129128
if err != nil {
130-
//nolint
131129
return checker.InconclusiveResultScore,
132130
sce.WithMessage(sce.ErrScorecardInternal, fmt.Sprintf("RepoClient.ListCommits: %v", err))
133131
}
134132

135133
totalMerged := 0
136134
totalTested := 0
137-
for _, commit := range commits {
138-
pr := commit.AssociatedMergeRequest
135+
for i := range commits {
136+
pr := commits[i].AssociatedMergeRequest
139137
// TODO(#575): We ignore associated PRs if Scorecard is being run on a fork
140138
// but the PR was created in the original repo.
141139
if pr.MergedAt.IsZero() {
@@ -187,7 +185,6 @@ func sastToolInCheckRuns(c *checker.CheckRequest) (int, error) {
187185
return checker.CreateProportionalScore(totalTested, totalMerged), nil
188186
}
189187

190-
//nolint
191188
func codeQLInCheckDefinitions(c *checker.CheckRequest) (int, error) {
192189
searchRequest := clients.SearchRequest{
193190
Query: "github/codeql-action/analyze",
@@ -228,7 +225,6 @@ type sonarConfig struct {
228225
file checker.File
229226
}
230227

231-
//nolint
232228
func sonarEnabled(c *checker.CheckRequest) (int, error) {
233229
var config []sonarConfig
234230
err := fileparser.OnMatchingFileContentDo(c.RepoClient, fileparser.PathMatcher{

clients/githubrepo/client.go

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -192,7 +192,15 @@ func (client *Client) ListSuccessfulWorkflowRuns(filename string) ([]clients.Wor
192192

193193
// ListCheckRunsForRef implements RepoClient.ListCheckRunsForRef.
194194
func (client *Client) ListCheckRunsForRef(ref string) ([]clients.CheckRun, error) {
195-
return client.checkruns.listCheckRunsForRef(ref)
195+
cachedCrs, err := client.graphClient.listCheckRunsForRef(ref)
196+
if errors.Is(err, errNotCached) {
197+
crs, err := client.checkruns.listCheckRunsForRef(ref)
198+
if err == nil {
199+
client.graphClient.cacheCheckRunsForRef(ref, crs)
200+
}
201+
return crs, err
202+
}
203+
return cachedCrs, err
196204
}
197205

198206
// ListStatuses implements RepoClient.ListStatuses.

clients/githubrepo/graphql.go

Lines changed: 140 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ package githubrepo
1616

1717
import (
1818
"context"
19+
"errors"
1920
"fmt"
2021
"strings"
2122
"sync"
@@ -25,18 +26,22 @@ import (
2526

2627
"github.com/ossf/scorecard/v4/clients"
2728
sce "github.com/ossf/scorecard/v4/errors"
29+
"github.com/ossf/scorecard/v4/log"
2830
)
2931

3032
const (
3133
pullRequestsToAnalyze = 1
34+
checksToAnalyze = 30
3235
issuesToAnalyze = 30
3336
issueCommentsToAnalyze = 30
3437
reviewsToAnalyze = 30
3538
labelsToAnalyze = 30
3639
commitsToAnalyze = 30
3740
)
3841

39-
//nolint: govet
42+
var errNotCached = errors.New("result not cached")
43+
44+
//nolint:govet
4045
type graphqlData struct {
4146
Repository struct {
4247
IsArchived githubv4.Boolean
@@ -121,34 +126,77 @@ type graphqlData struct {
121126
}
122127
}
123128

129+
//nolint:govet
130+
type checkRunsGraphqlData struct {
131+
Repository struct {
132+
Object struct {
133+
Commit struct {
134+
History struct {
135+
Nodes []struct {
136+
AssociatedPullRequests struct {
137+
Nodes []struct {
138+
HeadRefOid githubv4.String
139+
Commits struct {
140+
Nodes []struct {
141+
Commit struct {
142+
CheckSuites struct {
143+
Nodes []struct {
144+
App struct {
145+
Slug githubv4.String
146+
}
147+
Conclusion githubv4.CheckConclusionState
148+
Status githubv4.CheckStatusState
149+
}
150+
} `graphql:"checkSuites(first: $checksToAnalyze)"`
151+
}
152+
}
153+
} `graphql:"commits(last:1)"`
154+
}
155+
} `graphql:"associatedPullRequests(first: $pullRequestsToAnalyze)"`
156+
}
157+
} `graphql:"history(first: $commitsToAnalyze)"`
158+
} `graphql:"... on Commit"`
159+
} `graphql:"object(expression: $commitExpression)"`
160+
} `graphql:"repository(owner: $owner, name: $name)"`
161+
RateLimit struct {
162+
Cost *int
163+
}
164+
}
165+
166+
type checkRunCache = map[string][]clients.CheckRun
167+
124168
type graphqlHandler struct {
125-
client *githubv4.Client
126-
data *graphqlData
127-
once *sync.Once
128-
ctx context.Context
129-
errSetup error
130-
repourl *repoURL
131-
commits []clients.Commit
132-
issues []clients.Issue
133-
archived bool
169+
checkRuns checkRunCache
170+
client *githubv4.Client
171+
data *graphqlData
172+
setupOnce *sync.Once
173+
checkData *checkRunsGraphqlData
174+
setupCheckRunsOnce *sync.Once
175+
errSetupCheckRuns error
176+
logger *log.Logger
177+
ctx context.Context
178+
errSetup error
179+
repourl *repoURL
180+
commits []clients.Commit
181+
issues []clients.Issue
182+
archived bool
134183
}
135184

136185
func (handler *graphqlHandler) init(ctx context.Context, repourl *repoURL) {
137186
handler.ctx = ctx
138187
handler.repourl = repourl
139188
handler.data = new(graphqlData)
140189
handler.errSetup = nil
141-
handler.once = new(sync.Once)
190+
handler.setupOnce = new(sync.Once)
191+
handler.checkData = new(checkRunsGraphqlData)
192+
handler.setupCheckRunsOnce = new(sync.Once)
193+
handler.checkRuns = checkRunCache{}
194+
handler.logger = log.NewLogger(log.DefaultLevel)
142195
}
143196

144197
func (handler *graphqlHandler) setup() error {
145-
handler.once.Do(func() {
146-
commitExpression := handler.repourl.commitSHA
147-
if strings.EqualFold(handler.repourl.commitSHA, clients.HeadSHA) {
148-
// TODO(#575): Confirm that this works as expected.
149-
commitExpression = fmt.Sprintf("heads/%s", handler.repourl.defaultBranch)
150-
}
151-
198+
handler.setupOnce.Do(func() {
199+
commitExpression := handler.commitExpression()
152200
vars := map[string]interface{}{
153201
"owner": githubv4.String(handler.repourl.owner),
154202
"name": githubv4.String(handler.repourl.repo),
@@ -174,13 +222,54 @@ func (handler *graphqlHandler) setup() error {
174222
return handler.errSetup
175223
}
176224

225+
func (handler *graphqlHandler) setupCheckRuns() error {
226+
handler.setupCheckRunsOnce.Do(func() {
227+
commitExpression := handler.commitExpression()
228+
vars := map[string]interface{}{
229+
"owner": githubv4.String(handler.repourl.owner),
230+
"name": githubv4.String(handler.repourl.repo),
231+
"pullRequestsToAnalyze": githubv4.Int(pullRequestsToAnalyze),
232+
"commitsToAnalyze": githubv4.Int(commitsToAnalyze),
233+
"commitExpression": githubv4.String(commitExpression),
234+
"checksToAnalyze": githubv4.Int(checksToAnalyze),
235+
}
236+
if err := handler.client.Query(handler.ctx, handler.checkData, vars); err != nil {
237+
// quit early without setting crsErrSetup for "Resource not accessible by integration" error
238+
// for whatever reason, this check doesn't work with a GITHUB_TOKEN, only a PAT
239+
if strings.Contains(err.Error(), "Resource not accessible by integration") {
240+
return
241+
}
242+
handler.errSetupCheckRuns = err
243+
return
244+
}
245+
handler.checkRuns = parseCheckRuns(handler.checkData)
246+
})
247+
return handler.errSetupCheckRuns
248+
}
249+
177250
func (handler *graphqlHandler) getCommits() ([]clients.Commit, error) {
178251
if err := handler.setup(); err != nil {
179252
return nil, fmt.Errorf("error during graphqlHandler.setup: %w", err)
180253
}
181254
return handler.commits, nil
182255
}
183256

257+
func (handler *graphqlHandler) cacheCheckRunsForRef(ref string, crs []clients.CheckRun) {
258+
handler.checkRuns[ref] = crs
259+
}
260+
261+
func (handler *graphqlHandler) listCheckRunsForRef(ref string) ([]clients.CheckRun, error) {
262+
if err := handler.setupCheckRuns(); err != nil {
263+
return nil, fmt.Errorf("error during graphqlHandler.setupCheckRuns: %w", err)
264+
}
265+
if crs, ok := handler.checkRuns[ref]; ok {
266+
return crs, nil
267+
}
268+
msg := fmt.Sprintf("listCheckRunsForRef cache miss: %s/%s:%s", handler.repourl.owner, handler.repourl.repo, ref)
269+
handler.logger.Info(msg)
270+
return nil, errNotCached
271+
}
272+
184273
func (handler *graphqlHandler) getIssues() ([]clients.Issue, error) {
185274
if !strings.EqualFold(handler.repourl.commitSHA, clients.HeadSHA) {
186275
return nil, fmt.Errorf("%w: ListIssues only supported for HEAD queries", clients.ErrUnsupportedFeature)
@@ -201,7 +290,39 @@ func (handler *graphqlHandler) isArchived() (bool, error) {
201290
return handler.archived, nil
202291
}
203292

204-
//nolint
293+
func (handler *graphqlHandler) commitExpression() string {
294+
if strings.EqualFold(handler.repourl.commitSHA, clients.HeadSHA) {
295+
// TODO(#575): Confirm that this works as expected.
296+
return fmt.Sprintf("heads/%s", handler.repourl.defaultBranch)
297+
}
298+
return handler.repourl.commitSHA
299+
}
300+
301+
func parseCheckRuns(data *checkRunsGraphqlData) checkRunCache {
302+
checkCache := checkRunCache{}
303+
for _, commit := range data.Repository.Object.Commit.History.Nodes {
304+
for _, pr := range commit.AssociatedPullRequests.Nodes {
305+
var crs []clients.CheckRun
306+
for _, c := range pr.Commits.Nodes {
307+
for _, checkRun := range c.Commit.CheckSuites.Nodes {
308+
crs = append(crs, clients.CheckRun{
309+
// the REST API returns lowercase. the graphQL API returns upper
310+
Status: strings.ToLower(string(checkRun.Status)),
311+
Conclusion: strings.ToLower(string(checkRun.Conclusion)),
312+
App: clients.CheckRunApp{
313+
Slug: string(checkRun.App.Slug),
314+
},
315+
})
316+
}
317+
}
318+
headRef := string(pr.HeadRefOid)
319+
checkCache[headRef] = crs
320+
}
321+
}
322+
return checkCache
323+
}
324+
325+
//nolint:all
205326
func commitsFrom(data *graphqlData, repoOwner, repoName string) ([]clients.Commit, error) {
206327
ret := make([]clients.Commit, 0)
207328
for _, commit := range data.Repository.Object.Commit.History.Nodes {

clients/githubrepo/graphql_e2e_test.go

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -57,5 +57,17 @@ var _ = Describe("E2E TEST: githubrepo.graphqlHandler", func() {
5757
Expect(graphqlhandler.data.RateLimit.Cost).ShouldNot(BeNil())
5858
Expect(*graphqlhandler.data.RateLimit.Cost).Should(BeNumerically("<=", 1))
5959
})
60+
It("Should not have increased for check run query", func() {
61+
repourl := &repoURL{
62+
owner: "ossf",
63+
repo: "scorecard",
64+
commitSHA: clients.HeadSHA,
65+
}
66+
graphqlhandler.init(context.Background(), repourl)
67+
Expect(graphqlhandler.setupCheckRuns()).Should(BeNil())
68+
Expect(graphqlhandler.checkData).ShouldNot(BeNil())
69+
Expect(graphqlhandler.checkData.RateLimit.Cost).ShouldNot(BeNil())
70+
Expect(*graphqlhandler.checkData.RateLimit.Cost).Should(BeNumerically("<=", 1))
71+
})
6072
})
6173
})

clients/pull_request.go

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,8 @@ import (
1919
)
2020

2121
// PullRequest struct represents a PR as returned by RepoClient.
22-
//nolint: govet
22+
//
23+
//nolint:govet
2324
type PullRequest struct {
2425
Number int
2526
MergedAt time.Time

cron/k8s/worker.release.yaml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,8 @@ spec:
4444
value: "10.4.4.210:80"
4545
- name: "SCORECARD_API_RESULTS_BUCKET_URL"
4646
value: "gs://ossf-scorecard-cron-releasetest-results"
47+
- name: "SCORECARD_BLACKLISTED_CHECKS"
48+
value: "CI-Tests,Contributors"
4749
resources:
4850
requests:
4951
memory: 5Gi

0 commit comments

Comments
 (0)