Skip to content

Commit 037a3f3

Browse files
authored
✨ Raw result for Maintained check (#1780)
* draft * draft * raw results for Maintained check * updates * updates * missing files * updates * unit tests * e2e tests * tests * linter * updates
1 parent 682e6ea commit 037a3f3

File tree

14 files changed

+471
-110
lines changed

14 files changed

+471
-110
lines changed

checker/raw_result.go

Lines changed: 52 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,15 @@ type RawResults struct {
2525
DependencyUpdateToolResults DependencyUpdateToolData
2626
BranchProtectionResults BranchProtectionsData
2727
CodeReviewResults CodeReviewData
28+
MaintainedResults MaintainedData
29+
}
30+
31+
// MaintainedData contains the raw results
32+
// for the Maintained check.
33+
type MaintainedData struct {
34+
Issues []Issue
35+
DefaultBranchCommits []DefaultBranchCommit
36+
ArchivedStatus ArchivedStatus
2837
}
2938

3039
// CodeReviewData contains the raw results
@@ -107,9 +116,25 @@ type Run struct {
107116
// TODO: add fields, e.g., Result=["success", "failure"]
108117
}
109118

119+
// Comment represents a comment for a pull request or an issue.
120+
type Comment struct {
121+
CreatedAt *time.Time
122+
Author *User
123+
// TODO: add ields if needed, e.g., content.
124+
}
125+
126+
// ArchivedStatus definess the archived status.
127+
type ArchivedStatus struct {
128+
Status bool
129+
// TODO: add fields, e.g., date of archival.
130+
}
131+
110132
// Issue represents an issue.
111133
type Issue struct {
112-
URL string
134+
CreatedAt *time.Time
135+
Author *User
136+
URL string
137+
Comments []Comment
113138
// TODO: add fields, e.g., state=[opened|closed]
114139
}
115140

@@ -121,6 +146,7 @@ type DefaultBranchCommit struct {
121146
SHA string
122147
CommitMessage string
123148
MergeRequest *MergeRequest
149+
CommitDate *time.Time
124150
Committer User
125151
}
126152

@@ -143,8 +169,31 @@ type Review struct {
143169

144170
// User represent a user.
145171
type User struct {
146-
Login string
147-
}
172+
RepoAssociation *RepoAssociation
173+
Login string
174+
}
175+
176+
// RepoAssociation represents a user relationship with a repo.
177+
type RepoAssociation string
178+
179+
const (
180+
// RepoAssociationCollaborator has been invited to collaborate on the repository.
181+
RepoAssociationCollaborator RepoAssociation = RepoAssociation("collaborator")
182+
// RepoAssociationContributor is an contributor to the repository.
183+
RepoAssociationContributor RepoAssociation = RepoAssociation("contributor")
184+
// RepoAssociationOwner is an owner of the repository.
185+
RepoAssociationOwner RepoAssociation = RepoAssociation("owner")
186+
// RepoAssociationMember is a member of the organization that owns the repository.
187+
RepoAssociationMember RepoAssociation = RepoAssociation("member")
188+
// RepoAssociationFirstTimer has previously committed to the repository.
189+
RepoAssociationFirstTimer RepoAssociation = RepoAssociation("first-timer")
190+
// RepoAssociationFirstTimeContributor has not previously committed to the repository.
191+
RepoAssociationFirstTimeContributor RepoAssociation = RepoAssociation("first-timer-contributor")
192+
// RepoAssociationMannequin is a placeholder for an unclaimed user.
193+
RepoAssociationMannequin RepoAssociation = RepoAssociation("unknown")
194+
// RepoAssociationNone has no association with the repository.
195+
RepoAssociationNone RepoAssociation = RepoAssociation("none")
196+
)
148197

149198
// File represents a file.
150199
type File struct {

checks/cii_best_practices.go

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -26,10 +26,10 @@ const (
2626
// CheckCIIBestPractices is the registered name for CIIBestPractices.
2727
CheckCIIBestPractices = "CII-Best-Practices"
2828
silverScore = 7
29-
// Note: if this value is changed, please update the action's threshold score
29+
// Note: if this value is changed, please update the action's threshold score
3030
// https://github.com/ossf/scorecard-action/blob/main/policies/template.yml#L61.
31-
passingScore = 5
32-
inProgressScore = 2
31+
passingScore = 5
32+
inProgressScore = 2
3333
)
3434

3535
//nolint:gochecknoinits

checks/evaluation/maintained.go

Lines changed: 103 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,103 @@
1+
// Copyright 2021 Security Scorecard Authors
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// http://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
15+
package evaluation
16+
17+
import (
18+
"fmt"
19+
"time"
20+
21+
"github.com/ossf/scorecard/v4/checker"
22+
sce "github.com/ossf/scorecard/v4/errors"
23+
)
24+
25+
const (
26+
lookBackDays = 90
27+
activityPerWeek = 1
28+
daysInOneWeek = 7
29+
)
30+
31+
// Maintained applies the score policy for the Maintained check.
32+
func Maintained(name string, dl checker.DetailLogger, r *checker.MaintainedData) checker.CheckResult {
33+
if r == nil {
34+
e := sce.WithMessage(sce.ErrScorecardInternal, "empty raw data")
35+
return checker.CreateRuntimeErrorResult(name, e)
36+
}
37+
38+
if r.ArchivedStatus.Status {
39+
return checker.CreateMinScoreResult(name, "repo is marked as archived")
40+
}
41+
42+
// If not explicitly marked archived, look for activity in past `lookBackDays`.
43+
threshold := time.Now().AddDate(0 /*years*/, 0 /*months*/, -1*lookBackDays /*days*/)
44+
commitsWithinThreshold := 0
45+
for i := range r.DefaultBranchCommits {
46+
if r.DefaultBranchCommits[i].CommitDate.After(threshold) {
47+
commitsWithinThreshold++
48+
}
49+
}
50+
51+
issuesUpdatedWithinThreshold := 0
52+
for i := range r.Issues {
53+
if hasActivityByCollaboratorOrHigher(&r.Issues[i], threshold) {
54+
issuesUpdatedWithinThreshold++
55+
}
56+
}
57+
58+
return checker.CreateProportionalScoreResult(name, fmt.Sprintf(
59+
"%d commit(s) out of %d and %d issue activity out of %d found in the last %d days",
60+
commitsWithinThreshold, len(r.DefaultBranchCommits), issuesUpdatedWithinThreshold, len(r.Issues), lookBackDays),
61+
commitsWithinThreshold+issuesUpdatedWithinThreshold, activityPerWeek*lookBackDays/daysInOneWeek)
62+
}
63+
64+
// hasActivityByCollaboratorOrHigher returns true if the issue was created or commented on by an
65+
// owner/collaborator/member since the threshold.
66+
func hasActivityByCollaboratorOrHigher(issue *checker.Issue, threshold time.Time) bool {
67+
if issue == nil {
68+
return false
69+
}
70+
71+
if isCollaboratorOrHigher(issue.Author) && issue.CreatedAt != nil && issue.CreatedAt.After(threshold) {
72+
// The creator of the issue is a collaborator or higher.
73+
return true
74+
}
75+
for _, comment := range issue.Comments {
76+
if isCollaboratorOrHigher(comment.Author) && comment.CreatedAt != nil &&
77+
comment.CreatedAt.After(threshold) {
78+
// The author of the comment is a collaborator or higher.
79+
return true
80+
}
81+
}
82+
return false
83+
}
84+
85+
// isCollaboratorOrHigher returns true if the user is a collaborator or higher.
86+
func isCollaboratorOrHigher(user *checker.User) bool {
87+
if user == nil || user.RepoAssociation == nil {
88+
return false
89+
}
90+
91+
priviledgedRoles := []checker.RepoAssociation{
92+
checker.RepoAssociationOwner,
93+
checker.RepoAssociationCollaborator,
94+
checker.RepoAssociationContributor,
95+
checker.RepoAssociationMember,
96+
}
97+
for _, role := range priviledgedRoles {
98+
if role == *user.RepoAssociation {
99+
return true
100+
}
101+
}
102+
return false
103+
}

checks/maintained.go

Lines changed: 12 additions & 85 deletions
Original file line numberDiff line numberDiff line change
@@ -15,108 +15,35 @@
1515
package checks
1616

1717
import (
18-
"fmt"
19-
"time"
20-
2118
"github.com/ossf/scorecard/v4/checker"
22-
"github.com/ossf/scorecard/v4/clients"
19+
"github.com/ossf/scorecard/v4/checks/evaluation"
20+
"github.com/ossf/scorecard/v4/checks/raw"
2321
sce "github.com/ossf/scorecard/v4/errors"
2422
)
2523

26-
const (
27-
// CheckMaintained is the exported check name for Maintained.
28-
CheckMaintained = "Maintained"
29-
lookBackDays = 90
30-
activityPerWeek = 1
31-
daysInOneWeek = 7
32-
)
24+
// CheckMaintained is the exported check name for Maintained.
25+
const CheckMaintained = "Maintained"
3326

3427
//nolint:gochecknoinits
3528
func init() {
36-
if err := registerCheck(CheckMaintained, IsMaintained, nil); err != nil {
29+
if err := registerCheck(CheckMaintained, Maintained, nil); err != nil {
3730
// this should never happen
3831
panic(err)
3932
}
4033
}
4134

42-
// IsMaintained runs Maintained check.
43-
func IsMaintained(c *checker.CheckRequest) checker.CheckResult {
44-
archived, err := c.RepoClient.IsArchived()
45-
if err != nil {
46-
e := sce.WithMessage(sce.ErrScorecardInternal, err.Error())
47-
return checker.CreateRuntimeErrorResult(CheckMaintained, e)
48-
}
49-
if archived {
50-
return checker.CreateMinScoreResult(CheckMaintained, "repo is marked as archived")
51-
}
52-
53-
// If not explicitly marked archived, look for activity in past `lookBackDays`.
54-
threshold := time.Now().AddDate(0 /*years*/, 0 /*months*/, -1*lookBackDays /*days*/)
55-
56-
commits, err := c.RepoClient.ListCommits()
57-
if err != nil {
58-
e := sce.WithMessage(sce.ErrScorecardInternal, err.Error())
59-
return checker.CreateRuntimeErrorResult(CheckMaintained, e)
60-
}
61-
commitsWithinThreshold := 0
62-
for i := range commits {
63-
if commits[i].CommittedDate.After(threshold) {
64-
commitsWithinThreshold++
65-
}
66-
}
67-
68-
issues, err := c.RepoClient.ListIssues()
35+
// Maintained runs Maintained check.
36+
func Maintained(c *checker.CheckRequest) checker.CheckResult {
37+
rawData, err := raw.Maintained(c)
6938
if err != nil {
7039
e := sce.WithMessage(sce.ErrScorecardInternal, err.Error())
7140
return checker.CreateRuntimeErrorResult(CheckMaintained, e)
7241
}
73-
issuesUpdatedWithinThreshold := 0
74-
for i := range issues {
75-
if hasActivityByCollaboratorOrHigher(&issues[i], threshold) {
76-
issuesUpdatedWithinThreshold++
77-
}
78-
}
79-
80-
return checker.CreateProportionalScoreResult(CheckMaintained, fmt.Sprintf(
81-
"%d commit(s) out of %d and %d issue activity out of %d found in the last %d days",
82-
commitsWithinThreshold, len(commits), issuesUpdatedWithinThreshold, len(issues), lookBackDays),
83-
commitsWithinThreshold+issuesUpdatedWithinThreshold, activityPerWeek*lookBackDays/daysInOneWeek)
84-
}
8542

86-
// hasActivityByCollaboratorOrHigher returns true if the issue was created or commented on by an
87-
// owner/collaborator/member since the threshold.
88-
func hasActivityByCollaboratorOrHigher(issue *clients.Issue, threshold time.Time) bool {
89-
if issue == nil {
90-
return false
43+
// Set the raw results.
44+
if c.RawResults != nil {
45+
c.RawResults.MaintainedResults = rawData
9146
}
92-
if isCollaboratorOrHigher(issue.AuthorAssociation) && issue.CreatedAt != nil && issue.CreatedAt.After(threshold) {
93-
// The creator of the issue is a collaborator or higher.
94-
return true
95-
}
96-
for _, comment := range issue.Comments {
97-
if isCollaboratorOrHigher(comment.AuthorAssociation) && comment.CreatedAt != nil &&
98-
comment.CreatedAt.After(threshold) {
99-
// The author of the comment is a collaborator or higher.
100-
return true
101-
}
102-
}
103-
return false
104-
}
10547

106-
// isCollaboratorOrHigher returns true if the user is a collaborator or higher.
107-
func isCollaboratorOrHigher(repoAssociation *clients.RepoAssociation) bool {
108-
if repoAssociation == nil {
109-
return false
110-
}
111-
priviledgedRoles := []clients.RepoAssociation{
112-
clients.RepoAssociationCollaborator,
113-
clients.RepoAssociationMember,
114-
clients.RepoAssociationOwner,
115-
}
116-
for _, role := range priviledgedRoles {
117-
if role == *repoAssociation {
118-
return true
119-
}
120-
}
121-
return false
48+
return evaluation.Maintained(CheckMaintained, c.Dlogger, &rawData)
12249
}

0 commit comments

Comments
 (0)