Skip to content

Commit

Permalink
Merge branch 'main' into feat/lastapproval
Browse files Browse the repository at this point in the history
Signed-off-by: laurentsimon <[email protected]>
  • Loading branch information
laurentsimon authored Dec 14, 2022
2 parents 7e41527 + 746b6e9 commit 675163b
Show file tree
Hide file tree
Showing 8 changed files with 127 additions and 20 deletions.
4 changes: 0 additions & 4 deletions .goreleaser.yml
Original file line number Diff line number Diff line change
Expand Up @@ -31,8 +31,6 @@ builds:
goarch:
- amd64
- arm64
- 386
- arm
ldflags:
- -s {{.Env.VERSION_LDFLAGS}}

Expand Down Expand Up @@ -82,9 +80,7 @@ builds:
- windows
goarch:
- amd64
- 386
- arm64
- arm
ldflags:
- -buildmode=exe
- -s {{.Env.VERSION_LDFLAGS}}
Expand Down
3 changes: 2 additions & 1 deletion checker/raw_result.go
Original file line number Diff line number Diff line change
Expand Up @@ -252,7 +252,8 @@ type WebhooksData struct {
// BranchProtectionsData contains the raw results
// for the Branch-Protection check.
type BranchProtectionsData struct {
Branches []clients.BranchRef
Branches []clients.BranchRef
CodeownersFiles []string
}

// Tool represents a tool.
Expand Down
2 changes: 2 additions & 0 deletions checks/branch_protection_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,7 @@ func TestReleaseAndDevBranchProtected(t *testing.T) {
defaultBranch string
releases []string
nonadmin bool
repoFiles []string
}{
{
name: "Nil release and main branch names",
Expand Down Expand Up @@ -418,6 +419,7 @@ func TestReleaseAndDevBranchProtected(t *testing.T) {
DoAndReturn(func(b string) (*clients.BranchRef, error) {
return getBranch(tt.branches, b, tt.nonadmin), nil
}).AnyTimes()
mockRepoClient.EXPECT().ListFiles(gomock.Any()).AnyTimes().Return(tt.repoFiles, nil)
dl := scut.TestDetailLogger{}
req := checker.CheckRequest{
Dlogger: &dl,
Expand Down
12 changes: 9 additions & 3 deletions checks/evaluation/branch_protection.go
Original file line number Diff line number Diff line change
Expand Up @@ -78,7 +78,7 @@ func BranchProtection(name string, dl checker.DetailLogger,
score.scores.thoroughReview, score.maxes.thoroughReview = nonAdminThoroughReviewProtection(&b, dl)
// Do we want this?
score.scores.adminThoroughReview, score.maxes.adminThoroughReview = adminThoroughReviewProtection(&b, dl)
score.scores.codeownerReview, score.maxes.codeownerReview = codeownersBranchProtection(&b, dl)
score.scores.codeownerReview, score.maxes.codeownerReview = codeownerBranchProtection(&b, r.CodeownersFiles, dl)

scores = append(scores, score)
}
Expand Down Expand Up @@ -449,7 +449,9 @@ func nonAdminThoroughReviewProtection(branch *clients.BranchRef, dl checker.Deta
return score, max
}

func codeownersBranchProtection(branch *clients.BranchRef, dl checker.DetailLogger) (int, int) {
func codeownerBranchProtection(
branch *clients.BranchRef, codeownersFiles []string, dl checker.DetailLogger,
) (int, int) {
score := 0
max := 1

Expand All @@ -459,7 +461,11 @@ func codeownersBranchProtection(branch *clients.BranchRef, dl checker.DetailLogg
switch *branch.BranchProtectionRule.RequiredPullRequestReviews.RequireCodeOwnerReviews {
case true:
info(dl, log, "codeowner review is required on branch '%s'", *branch.Name)
score++
if len(codeownersFiles) == 0 {
warn(dl, log, "codeowners branch protection is being ignored - but no codeowners file found in repo")
} else {
score++
}
default:
warn(dl, log, "codeowner review is not required on branch '%s'", *branch.Name)
}
Expand Down
46 changes: 39 additions & 7 deletions checks/evaluation/branch_protection_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ import (
scut "github.com/ossf/scorecard/v4/utests"
)

func testScore(branch *clients.BranchRef, dl checker.DetailLogger) (int, error) {
func testScore(branch *clients.BranchRef, codeownersFiles []string, dl checker.DetailLogger) (int, error) {
var score levelScore
score.scores.basic, score.maxes.basic = basicNonAdminProtection(branch, dl)
score.scores.adminBasic, score.maxes.adminBasic = basicAdminProtection(branch, dl)
Expand All @@ -31,7 +31,7 @@ func testScore(branch *clients.BranchRef, dl checker.DetailLogger) (int, error)
score.scores.context, score.maxes.context = nonAdminContextProtection(branch, dl)
score.scores.thoroughReview, score.maxes.thoroughReview = nonAdminThoroughReviewProtection(branch, dl)
score.scores.adminThoroughReview, score.maxes.adminThoroughReview = adminThoroughReviewProtection(branch, dl)
score.scores.codeownerReview, score.maxes.codeownerReview = codeownersBranchProtection(branch, dl)
score.scores.codeownerReview, score.maxes.codeownerReview = codeownerBranchProtection(branch, codeownersFiles, dl)

return computeScore([]levelScore{score})
}
Expand All @@ -44,9 +44,10 @@ func TestIsBranchProtected(t *testing.T) {
var oneVal int32 = 1
branchVal := "branch-name"
tests := []struct {
name string
branch *clients.BranchRef
expected scut.TestReturn
name string
branch *clients.BranchRef
codeownersFiles []string
expected scut.TestReturn
}{
{
name: "Nothing is enabled",
Expand Down Expand Up @@ -344,6 +345,37 @@ func TestIsBranchProtected(t *testing.T) {
},
{
name: "Branches are protected and require codeowner review",
expected: scut.TestReturn{
Error: nil,
Score: 8,
NumberOfWarn: 1,
NumberOfInfo: 7,
NumberOfDebug: 0,
},
branch: &clients.BranchRef{
Name: &branchVal,
Protected: &trueVal,
BranchProtectionRule: clients.BranchProtectionRule{
EnforceAdmins: &trueVal,
RequireLinearHistory: &trueVal,
AllowForcePushes: &falseVal,
AllowDeletions: &falseVal,
CheckRules: clients.StatusChecksRule{
RequiresStatusChecks: &trueVal,
UpToDateBeforeMerge: &trueVal,
Contexts: []string{"foo"},
},
RequiredPullRequestReviews: clients.PullRequestReviewRule{
DismissStaleReviews: &trueVal,
RequireCodeOwnerReviews: &trueVal,
RequiredApprovingReviewCount: &oneVal,
},
},
},
codeownersFiles: []string{".github/CODEOWNERS"},
},
{
name: "Branches are protected and require codeowner review, but file is not present",
expected: scut.TestReturn{
Error: nil,
Score: 8,
Expand Down Expand Up @@ -398,7 +430,7 @@ func TestIsBranchProtected(t *testing.T) {
},
RequiredPullRequestReviews: clients.PullRequestReviewRule{
DismissStaleReviews: &trueVal,
RequireCodeOwnerReviews: &falseVal,
RequireCodeOwnerReviews: &trueVal,
RequiredApprovingReviewCount: &oneVal,
},
},
Expand All @@ -410,7 +442,7 @@ func TestIsBranchProtected(t *testing.T) {
t.Run(tt.name, func(t *testing.T) {
t.Parallel()
dl := scut.TestDetailLogger{}
score, err := testScore(tt.branch, &dl)
score, err := testScore(tt.branch, tt.codeownersFiles, &dl)
actual := &checker.CheckResult{
Score: score,
Error: err,
Expand Down
46 changes: 45 additions & 1 deletion checks/raw/branch_protection.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,9 +16,11 @@ package raw

import (
"fmt"
"reflect"
"regexp"

"github.com/ossf/scorecard/v4/checker"
"github.com/ossf/scorecard/v4/checks/fileparser"
"github.com/ossf/scorecard/v4/clients"
)

Expand Down Expand Up @@ -105,12 +107,54 @@ func BranchProtection(c clients.RepoClient) (checker.BranchProtectionsData, erro
// Branch doesn't exist or was deleted. Continue.
}

codeownersFiles := []string{}
if err := collectCodeownersFiles(c, &codeownersFiles); err != nil {
return checker.BranchProtectionsData{}, err
}

// No error, return the data.
return checker.BranchProtectionsData{
Branches: branches.set,
Branches: branches.set,
CodeownersFiles: codeownersFiles,
}, nil
}

func collectCodeownersFiles(c clients.RepoClient, codeownersFiles *[]string) error {
return fileparser.OnMatchingFileContentDo(c, fileparser.PathMatcher{
Pattern: "CODEOWNERS",
CaseSensitive: true,
}, addCodeownersFile, codeownersFiles)
}

var addCodeownersFile fileparser.DoWhileTrueOnFileContent = func(
path string,
content []byte,
args ...interface{},
) (bool, error) {
fmt.Printf("got codeowners file at %s\n", path)

if len(args) != 1 {
return false, fmt.Errorf(
"addCodeownersFile requires exactly 1 arguments: got %v: %w",
len(args), errInvalidArgLength)
}

codeownersFiles := dataAsStringSlicePtr(args[0])

*codeownersFiles = append(*codeownersFiles, path)

return true, nil
}

func dataAsStringSlicePtr(data interface{}) *[]string {
pdata, ok := data.(*[]string)
if !ok {
// panic if it is not correct type
panic(fmt.Sprintf("expected type *[]string, got %v", reflect.TypeOf(data)))
}
return pdata
}

func branchRedirect(name string) string {
// Ideally, we should check using repositories.GetBranch if there was a branch redirect.
// See https://github.com/google/go-github/issues/1895
Expand Down
19 changes: 18 additions & 1 deletion checks/raw/branch_protection_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,7 @@ func TestBranchProtection(t *testing.T) {
tests := []struct {
name string
branches branchesArg
repoFiles []string
releases []clients.Release
releasesErr error
want checker.BranchProtectionsData
Expand All @@ -80,6 +81,9 @@ func TestBranchProtection(t *testing.T) {
err: errBPTest,
},
},
want: checker.BranchProtectionsData{
CodeownersFiles: []string{},
},
},
{
name: "null-default-branch-only",
Expand All @@ -90,6 +94,9 @@ func TestBranchProtection(t *testing.T) {
branchRef: nil,
},
},
want: checker.BranchProtectionsData{
CodeownersFiles: []string{},
},
},
{
name: "default-branch-only",
Expand All @@ -108,6 +115,7 @@ func TestBranchProtection(t *testing.T) {
Name: &defaultBranchName,
},
},
CodeownersFiles: []string{},
},
},
{
Expand All @@ -117,6 +125,9 @@ func TestBranchProtection(t *testing.T) {
},
{
name: "no-releases",
want: checker.BranchProtectionsData{
CodeownersFiles: []string{},
},
},
{
name: "empty-targetcommitish",
Expand Down Expand Up @@ -155,6 +166,9 @@ func TestBranchProtection(t *testing.T) {
branchRef: nil,
},
},
want: checker.BranchProtectionsData{
CodeownersFiles: []string{},
},
},
{
name: "add-release-branch",
Expand All @@ -177,6 +191,7 @@ func TestBranchProtection(t *testing.T) {
Name: &releaseBranchName,
},
},
CodeownersFiles: []string{},
},
},
{
Expand All @@ -200,6 +215,7 @@ func TestBranchProtection(t *testing.T) {
Name: &mainBranchName,
},
},
CodeownersFiles: []string{},
},
},
{
Expand Down Expand Up @@ -233,6 +249,7 @@ func TestBranchProtection(t *testing.T) {
Name: &releaseBranchName,
},
},
CodeownersFiles: []string{},
},
},
// TODO: Add tests for commitSHA regex matching.
Expand All @@ -255,7 +272,7 @@ func TestBranchProtection(t *testing.T) {
DoAndReturn(func() ([]clients.Release, error) {
return tt.releases, tt.releasesErr
})

mockRepoClient.EXPECT().ListFiles(gomock.Any()).AnyTimes().Return(tt.repoFiles, nil)
rawData, err := BranchProtection(mockRepoClient)
if !errors.Is(err, tt.wantErr) {
t.Errorf("failed. expected: %v, got: %v", tt.wantErr, err)
Expand Down
15 changes: 12 additions & 3 deletions pkg/json_raw_results.go
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,11 @@ type jsonBranchProtection struct {
Name string `json:"name"`
}

type jsonBranchProtectionMetadata struct {
Branches []jsonBranchProtection `json:"branches"`
CodeownersFiles []string `json:"codeownersFiles"`
}

type jsonReview struct {
State string `json:"state"`
Reviewer jsonUser `json:"reviewer"`
Expand Down Expand Up @@ -265,7 +270,7 @@ type jsonRawResults struct {
// Note: we return one at most.
DependencyUpdateTools []jsonTool `json:"dependencyUpdateTools"`
// Branch protection settings for development and release branches.
BranchProtections []jsonBranchProtection `json:"branchProtections"`
BranchProtections jsonBranchProtectionMetadata `json:"branchProtections"`
// Contributors. Note: we could use the list of commits instead to store this data.
// However, it's harder to get statistics using commit list, so we have a dedicated
// structure for it.
Expand Down Expand Up @@ -711,7 +716,7 @@ func (r *jsonScorecardRawResult) addDependencyUpdateToolRawResults(dut *checker.

//nolint:unparam
func (r *jsonScorecardRawResult) addBranchProtectionRawResults(bp *checker.BranchProtectionsData) error {
r.Results.BranchProtections = []jsonBranchProtection{}
branches := []jsonBranchProtection{}
for _, v := range bp.Branches {
var bp *jsonBranchProtectionSettings
if v.Protected != nil && *v.Protected {
Expand All @@ -728,11 +733,15 @@ func (r *jsonScorecardRawResult) addBranchProtectionRawResults(bp *checker.Branc
StatusCheckContexts: v.BranchProtectionRule.CheckRules.Contexts,
}
}
r.Results.BranchProtections = append(r.Results.BranchProtections, jsonBranchProtection{
branches = append(branches, jsonBranchProtection{
Name: *v.Name,
Protection: bp,
})
}
r.Results.BranchProtections.Branches = branches

r.Results.BranchProtections.CodeownersFiles = bp.CodeownersFiles

return nil
}

Expand Down

0 comments on commit 675163b

Please sign in to comment.