Skip to content

Commit 0f87094

Browse files
N8BWertnathaniel.wert
andauthored
✨ Gitlab support (#2265)
* updated readme to reflect gitlab usage * bugfixes after a good deal of testing * removed unnecessary files from branch * cleaning up my mess * requested changes + unit tests * style fixes * updated readme to reflect gitlab usage * bugfixes after a good deal of testing * removed unnecessary files from branch * cleaning up my mess * requested changes + unit tests * style fixes * merge main into gitlab_support * check-linter fixes Signed-off-by: Nathaniel Wert <N8.Wert.B@gmail.com> Co-authored-by: nathaniel.wert <nathaniel.wert@kudelskisecurity.com>
1 parent a6983ed commit 0f87094

21 files changed

+1982
-10
lines changed

clients/gitlabrepo/branches.go

Lines changed: 196 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,196 @@
1+
// Copyright 2022 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 gitlabrepo
16+
17+
import (
18+
"fmt"
19+
"strings"
20+
"sync"
21+
22+
"github.com/xanzy/go-gitlab"
23+
24+
"github.com/ossf/scorecard/v4/clients"
25+
)
26+
27+
type branchesHandler struct {
28+
glClient *gitlab.Client
29+
once *sync.Once
30+
errSetup error
31+
repourl *repoURL
32+
defaultBranchRef *clients.BranchRef
33+
}
34+
35+
func (handler *branchesHandler) init(repourl *repoURL) {
36+
handler.repourl = repourl
37+
handler.errSetup = nil
38+
handler.once = new(sync.Once)
39+
}
40+
41+
// nolint: nestif
42+
func (handler *branchesHandler) setup() error {
43+
handler.once.Do(func() {
44+
if !strings.EqualFold(handler.repourl.commitSHA, clients.HeadSHA) {
45+
handler.errSetup = fmt.Errorf("%w: branches only supported for HEAD queries", clients.ErrUnsupportedFeature)
46+
return
47+
}
48+
49+
proj, _, err := handler.glClient.Projects.GetProject(handler.repourl.projectID, &gitlab.GetProjectOptions{})
50+
if err != nil {
51+
handler.errSetup = fmt.Errorf("requirest for project failed with error %w", err)
52+
return
53+
}
54+
55+
branch, _, err := handler.glClient.Branches.GetBranch(handler.repourl.projectID, proj.DefaultBranch)
56+
if err != nil {
57+
handler.errSetup = fmt.Errorf("request for default branch failed with error %w", err)
58+
return
59+
}
60+
61+
if branch.Protected {
62+
protectedBranch, resp, err := handler.glClient.ProtectedBranches.GetProtectedBranch(
63+
handler.repourl.projectID, branch.Name)
64+
if err != nil && resp.StatusCode != 403 {
65+
handler.errSetup = fmt.Errorf("request for protected branch failed with error %w", err)
66+
return
67+
} else if resp.StatusCode == 403 {
68+
handler.errSetup = fmt.Errorf("incorrect permissions to fully check branch protection %w", err)
69+
return
70+
}
71+
72+
projectStatusChecks, resp, err := handler.glClient.ExternalStatusChecks.ListProjectStatusChecks(
73+
handler.repourl.projectID, &gitlab.ListOptions{})
74+
if err != nil && resp.StatusCode != 404 {
75+
handler.errSetup = fmt.Errorf("request for external status checks failed with error %w", err)
76+
return
77+
}
78+
79+
projectApprovalRule, resp, err := handler.glClient.Projects.GetApprovalConfiguration(handler.repourl.projectID)
80+
if err != nil && resp.StatusCode != 404 {
81+
handler.errSetup = fmt.Errorf("request for project approval rule failed with %w", err)
82+
return
83+
}
84+
85+
handler.defaultBranchRef = makeBranchRefFrom(branch, protectedBranch,
86+
projectStatusChecks, projectApprovalRule)
87+
} else {
88+
handler.defaultBranchRef = &clients.BranchRef{
89+
Name: &branch.Name,
90+
Protected: &branch.Protected,
91+
}
92+
}
93+
handler.errSetup = nil
94+
})
95+
return handler.errSetup
96+
}
97+
98+
func (handler *branchesHandler) getDefaultBranch() (*clients.BranchRef, error) {
99+
err := handler.setup()
100+
if err != nil {
101+
return nil, fmt.Errorf("error during branchesHandler.setup: %w", err)
102+
}
103+
104+
return handler.defaultBranchRef, nil
105+
}
106+
107+
func (handler *branchesHandler) getBranch(branch string) (*clients.BranchRef, error) {
108+
bran, _, err := handler.glClient.Branches.GetBranch(handler.repourl.projectID, branch)
109+
if err != nil {
110+
return nil, fmt.Errorf("error getting branch in branchsHandler.getBranch: %w", err)
111+
}
112+
113+
if bran.Protected {
114+
protectedBranch, _, err := handler.glClient.ProtectedBranches.GetProtectedBranch(handler.repourl.projectID, bran.Name)
115+
if err != nil {
116+
return nil, fmt.Errorf("request for protected branch failed with error %w", err)
117+
}
118+
119+
projectStatusChecks, resp, err := handler.glClient.ExternalStatusChecks.ListProjectStatusChecks(
120+
handler.repourl.projectID, &gitlab.ListOptions{})
121+
if err != nil && resp.StatusCode != 404 {
122+
return nil, fmt.Errorf("request for external status checks failed with error %w", err)
123+
}
124+
125+
projectApprovalRule, resp, err := handler.glClient.Projects.GetApprovalConfiguration(handler.repourl.projectID)
126+
if err != nil && resp.StatusCode != 404 {
127+
return nil, fmt.Errorf("request for project approval rule failed with %w", err)
128+
}
129+
130+
return makeBranchRefFrom(bran, protectedBranch, projectStatusChecks, projectApprovalRule), nil
131+
} else {
132+
ret := &clients.BranchRef{
133+
Name: &bran.Name,
134+
Protected: &bran.Protected,
135+
}
136+
return ret, nil
137+
}
138+
}
139+
140+
func makeContextsFromResp(checks []*gitlab.ProjectStatusCheck) []string {
141+
ret := make([]string, len(checks))
142+
for i, statusCheck := range checks {
143+
ret[i] = statusCheck.Name
144+
}
145+
return ret
146+
}
147+
148+
func makeBranchRefFrom(branch *gitlab.Branch, protectedBranch *gitlab.ProtectedBranch,
149+
projectStatusChecks []*gitlab.ProjectStatusCheck,
150+
projectApprovalRule *gitlab.ProjectApprovals,
151+
) *clients.BranchRef {
152+
requiresStatusChecks := newFalse()
153+
if len(projectStatusChecks) > 0 {
154+
requiresStatusChecks = newTrue()
155+
}
156+
157+
statusChecksRule := clients.StatusChecksRule{
158+
UpToDateBeforeMerge: newTrue(),
159+
RequiresStatusChecks: requiresStatusChecks,
160+
Contexts: makeContextsFromResp(projectStatusChecks),
161+
}
162+
163+
pullRequestReviewRule := clients.PullRequestReviewRule{
164+
DismissStaleReviews: newTrue(),
165+
RequireCodeOwnerReviews: &protectedBranch.CodeOwnerApprovalRequired,
166+
}
167+
168+
if projectApprovalRule != nil {
169+
requiredApprovalNum := int32(projectApprovalRule.ApprovalsBeforeMerge)
170+
pullRequestReviewRule.RequiredApprovingReviewCount = &requiredApprovalNum
171+
}
172+
173+
ret := &clients.BranchRef{
174+
Name: &branch.Name,
175+
Protected: &branch.Protected,
176+
BranchProtectionRule: clients.BranchProtectionRule{
177+
RequiredPullRequestReviews: pullRequestReviewRule,
178+
AllowDeletions: newFalse(),
179+
AllowForcePushes: &protectedBranch.AllowForcePush,
180+
EnforceAdmins: newTrue(),
181+
CheckRules: statusChecksRule,
182+
},
183+
}
184+
185+
return ret
186+
}
187+
188+
func newTrue() *bool {
189+
b := true
190+
return &b
191+
}
192+
193+
func newFalse() *bool {
194+
b := false
195+
return &b
196+
}

clients/gitlabrepo/checkruns.go

Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
// Copyright 2022 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 gitlabrepo
16+
17+
import (
18+
"fmt"
19+
"strings"
20+
21+
"github.com/xanzy/go-gitlab"
22+
23+
"github.com/ossf/scorecard/v4/clients"
24+
)
25+
26+
type checkrunsHandler struct {
27+
glClient *gitlab.Client
28+
repourl *repoURL
29+
}
30+
31+
func (handler *checkrunsHandler) init(repourl *repoURL) {
32+
handler.repourl = repourl
33+
}
34+
35+
func (handler *checkrunsHandler) listCheckRunsForRef(ref string) ([]clients.CheckRun, error) {
36+
pipelines, _, err := handler.glClient.Pipelines.ListProjectPipelines(
37+
handler.repourl.projectID, &gitlab.ListProjectPipelinesOptions{})
38+
if err != nil {
39+
return nil, fmt.Errorf("request for pipelines returned error: %w", err)
40+
}
41+
42+
return checkRunsFrom(pipelines, ref), nil
43+
}
44+
45+
// Conclusion does not exist in the pipelines for gitlab so I have a placeholder "" as the value.
46+
func checkRunsFrom(data []*gitlab.PipelineInfo, ref string) []clients.CheckRun {
47+
var checkRuns []clients.CheckRun
48+
for _, pipelineInfo := range data {
49+
if strings.EqualFold(pipelineInfo.Ref, ref) {
50+
checkRuns = append(checkRuns, clients.CheckRun{
51+
Status: pipelineInfo.Status,
52+
Conclusion: "",
53+
URL: pipelineInfo.WebURL,
54+
App: clients.CheckRunApp{Slug: pipelineInfo.Source},
55+
})
56+
}
57+
}
58+
return checkRuns
59+
}

0 commit comments

Comments
 (0)