Skip to content

Commit 7807a89

Browse files
jimrobisonraghavkaul
authored andcommitted
✨ Enable gitlab Packaging Reporting (ossf#2941)
* feat: Added yaml file that contains the full, flattened gitlab ci/cd contents Signed-off-by: Robison, Jim B <[email protected]> * refactor: Updated to meet linting failures Signed-off-by: Robison, Jim B <[email protected]> * refactor: Updated filename for flattened gitlab Signed-off-by: Robison, Jim B <[email protected]> * refactor: Updated to include the generated, flattened ci yaml in the file listing Signed-off-by: Robison, Jim B <[email protected]> * refactor: Updated the apiFunction to be part of the handler Signed-off-by: Robison, Jim B <[email protected]> * refactor: Moved packaging collection to be a repoClient specific sub-package Signed-off-by: Robison, Jim B <[email protected]> * feat: Added path for gitlab projects, including a basic search for lines containing nuget, poetry, twine publishes Signed-off-by: Robison, Jim B <[email protected]> * test: Added tests for gitlab packaging finders Signed-off-by: Robison, Jim B <[email protected]> * test: Added more tests for parsing through the client and grouping packaging data Signed-off-by: Robison, Jim B <[email protected]> * refactor: Utilizing existing mock objects to prevent race condition exception Signed-off-by: Robison, Jim B <[email protected]> * refactor: Addressed linting errors Signed-off-by: Robison, Jim B <[email protected]> * test: Updated expectation for log message Signed-off-by: Robison, Jim B <[email protected]> * refactor: Reverted back to the original error Signed-off-by: Robison, Jim B <[email protected]> --------- Signed-off-by: Robison, Jim B <[email protected]> Co-authored-by: raghavkaul <[email protected]>
1 parent f151159 commit 7807a89

14 files changed

Lines changed: 690 additions & 28 deletions

File tree

checks/evaluation/packaging.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -55,7 +55,7 @@ func Packaging(name string, dl checker.DetailLogger, r *checker.PackagingData) c
5555
}
5656

5757
dl.Warn(&checker.LogMessage{
58-
Text: "no GitHub publishing workflow detected",
58+
Text: "no GitHub/GitLab publishing workflow detected",
5959
})
6060

6161
return checker.CreateInconclusiveResult(name,
@@ -83,7 +83,7 @@ func createLogMessage(p checker.Package) (checker.LogMessage, error) {
8383
return msg, sce.WithMessage(sce.ErrScorecardInternal, "no run data")
8484
}
8585

86-
msg.Text = fmt.Sprintf("GitHub publishing workflow used in run %s", p.Runs[0].URL)
86+
msg.Text = fmt.Sprintf("GitHub/GitLab publishing workflow used in run %s", p.Runs[0].URL)
8787

8888
return msg, nil
8989
}

checks/evaluation/packaging_test.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -80,7 +80,7 @@ func Test_createLogMessage(t *testing.T) {
8080
},
8181
},
8282
want: checker.LogMessage{
83-
Text: "GitHub publishing workflow used in run ",
83+
Text: "GitHub/GitLab publishing workflow used in run ",
8484
Path: "path",
8585
},
8686
},
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
// Copyright 2021 OpenSSF 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 fileparser
16+
17+
// IsGitlabWorkflowFile determines if a file is a workflow
18+
// as a callback to use for repo client's ListFiles() API.
19+
func IsGitlabWorkflowFile(pathfn string) (bool, error) {
20+
return pathfn == "gitlabscorecard_flattened_ci.yaml", nil
21+
}

checks/packaging.go

Lines changed: 16 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,10 @@ package checks
1717
import (
1818
"github.com/ossf/scorecard/v4/checker"
1919
"github.com/ossf/scorecard/v4/checks/evaluation"
20-
"github.com/ossf/scorecard/v4/checks/raw"
20+
"github.com/ossf/scorecard/v4/checks/raw/github"
21+
"github.com/ossf/scorecard/v4/checks/raw/gitlab"
22+
"github.com/ossf/scorecard/v4/clients/githubrepo"
23+
"github.com/ossf/scorecard/v4/clients/gitlabrepo"
2124
sce "github.com/ossf/scorecard/v4/errors"
2225
)
2326

@@ -34,7 +37,18 @@ func init() {
3437

3538
// Packaging runs Packaging check.
3639
func Packaging(c *checker.CheckRequest) checker.CheckResult {
37-
rawData, err := raw.Packaging(c)
40+
var rawData checker.PackagingData
41+
var err error
42+
43+
switch v := c.RepoClient.(type) {
44+
case *githubrepo.Client:
45+
rawData, err = github.Packaging(c)
46+
case *gitlabrepo.Client:
47+
rawData, err = gitlab.Packaging(c)
48+
default:
49+
_ = v
50+
}
51+
3852
if err != nil {
3953
e := sce.WithMessage(sce.ErrScorecardInternal, err.Error())
4054
return checker.CreateRuntimeErrorResult(CheckPackaging, e)
Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@
1212
// See the License for the specific language governing permissions and
1313
// limitations under the License.
1414

15-
package raw
15+
package github
1616

1717
import (
1818
"fmt"
@@ -100,7 +100,7 @@ func Packaging(c *checker.CheckRequest) (checker.PackagingData, error) {
100100
data.Packages = append(data.Packages,
101101
checker.Package{
102102
// Debug message.
103-
Msg: stringPointer(fmt.Sprintf("GitHub publishing workflow not used in runs: %v", fp)),
103+
Msg: StringPointer(fmt.Sprintf("GitHub publishing workflow not used in runs: %v", fp)),
104104
File: &checker.File{
105105
Path: fp,
106106
Type: finding.FileTypeSource,
@@ -115,6 +115,6 @@ func Packaging(c *checker.CheckRequest) (checker.PackagingData, error) {
115115
return data, nil
116116
}
117117

118-
func stringPointer(s string) *string {
118+
func StringPointer(s string) *string {
119119
return &s
120120
}

checks/raw/gitlab/packaging.go

Lines changed: 86 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,86 @@
1+
// Copyright 2020 OpenSSF 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 gitlab
16+
17+
import (
18+
"fmt"
19+
"strings"
20+
21+
"github.com/ossf/scorecard/v4/checker"
22+
"github.com/ossf/scorecard/v4/checks/fileparser"
23+
"github.com/ossf/scorecard/v4/finding"
24+
)
25+
26+
// Packaging checks for packages.
27+
func Packaging(c *checker.CheckRequest) (checker.PackagingData, error) {
28+
var data checker.PackagingData
29+
matchedFiles, err := c.RepoClient.ListFiles(fileparser.IsGitlabWorkflowFile)
30+
if err != nil {
31+
return data, fmt.Errorf("RepoClient.ListFiles: %w", err)
32+
}
33+
34+
for _, fp := range matchedFiles {
35+
fc, err := c.RepoClient.GetFileContent(fp)
36+
if err != nil {
37+
return data, fmt.Errorf("RepoClient.GetFileContent: %w", err)
38+
}
39+
40+
file, found := isGitlabPackagingWorkflow(fc, fp)
41+
42+
if found {
43+
data.Packages = append(data.Packages, checker.Package{
44+
Name: new(string),
45+
Job: &checker.WorkflowJob{},
46+
File: &file,
47+
Msg: nil,
48+
Runs: []checker.Run{{URL: c.Repo.URI()}},
49+
})
50+
return data, nil
51+
}
52+
}
53+
54+
return data, nil
55+
}
56+
57+
func StringPointer(s string) *string {
58+
return &s
59+
}
60+
61+
func isGitlabPackagingWorkflow(fc []byte, fp string) (checker.File, bool) {
62+
lineNumber := checker.OffsetDefault
63+
64+
packagingStrings := []string{
65+
"docker push",
66+
"nuget push",
67+
"poetry publish",
68+
"twine upload",
69+
}
70+
71+
ParseLines:
72+
for idx, val := range strings.Split(string(fc[:]), "\n") {
73+
for _, element := range packagingStrings {
74+
if strings.Contains(val, element) {
75+
lineNumber = uint(idx + 1)
76+
break ParseLines
77+
}
78+
}
79+
}
80+
81+
return checker.File{
82+
Path: fp,
83+
Offset: lineNumber,
84+
Type: finding.FileTypeSource,
85+
}, lineNumber != checker.OffsetDefault
86+
}
Lines changed: 187 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,187 @@
1+
// Copyright 2020 OpenSSF 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 gitlab
16+
17+
import (
18+
"os"
19+
"testing"
20+
21+
"github.com/golang/mock/gomock"
22+
23+
"github.com/ossf/scorecard/v4/checker"
24+
mockrepo "github.com/ossf/scorecard/v4/clients/mockclients"
25+
)
26+
27+
func TestGitlabPackagingYamlCheck(t *testing.T) {
28+
t.Parallel()
29+
30+
//nolint
31+
tests := []struct {
32+
name string
33+
lineNumber uint
34+
filename string
35+
exists bool
36+
}{
37+
{
38+
name: "No Publishing Detected",
39+
filename: "./testdata/no-publishing.yaml",
40+
lineNumber: 1,
41+
exists: false,
42+
},
43+
{
44+
name: "Docker",
45+
filename: "./testdata/docker.yaml",
46+
lineNumber: 31,
47+
exists: true,
48+
},
49+
{
50+
name: "Nuget",
51+
filename: "./testdata/nuget.yaml",
52+
lineNumber: 21,
53+
exists: true,
54+
},
55+
{
56+
name: "Poetry",
57+
filename: "./testdata/poetry.yaml",
58+
lineNumber: 30,
59+
exists: true,
60+
},
61+
{
62+
name: "Twine",
63+
filename: "./testdata/twine.yaml",
64+
lineNumber: 26,
65+
exists: true,
66+
},
67+
}
68+
69+
for _, tt := range tests {
70+
tt := tt
71+
t.Run(tt.name, func(t *testing.T) {
72+
t.Parallel()
73+
var content []byte
74+
var err error
75+
76+
content, err = os.ReadFile(tt.filename)
77+
if err != nil {
78+
t.Errorf("cannot read file: %v", err)
79+
}
80+
81+
file, found := isGitlabPackagingWorkflow(content, tt.filename)
82+
83+
if tt.exists && !found {
84+
t.Errorf("Packaging %q should exist", tt.name)
85+
} else if !tt.exists && found {
86+
t.Errorf("No packaging information should have been found in %q", tt.name)
87+
}
88+
89+
if file.Offset != tt.lineNumber {
90+
t.Errorf("Expected line number: %d != %d", tt.lineNumber, file.Offset)
91+
}
92+
93+
if err != nil {
94+
return
95+
}
96+
})
97+
}
98+
}
99+
100+
func TestGitlabPackagingPackager(t *testing.T) {
101+
t.Parallel()
102+
103+
//nolint
104+
tests := []struct {
105+
name string
106+
lineNumber uint
107+
filename string
108+
exists bool
109+
}{
110+
{
111+
name: "No Publishing Detected",
112+
filename: "./testdata/no-publishing.yaml",
113+
lineNumber: 1,
114+
exists: false,
115+
},
116+
{
117+
name: "Docker",
118+
filename: "./testdata/docker.yaml",
119+
lineNumber: 31,
120+
exists: true,
121+
},
122+
}
123+
124+
for _, tt := range tests {
125+
tt := tt
126+
t.Run(tt.name, func(t *testing.T) {
127+
t.Parallel()
128+
129+
ctrl := gomock.NewController(t)
130+
defer ctrl.Finish()
131+
moqRepoClient := mockrepo.NewMockRepoClient(ctrl)
132+
moqRepo := mockrepo.NewMockRepo(ctrl)
133+
134+
moqRepoClient.EXPECT().ListFiles(gomock.Any()).
135+
Return([]string{tt.filename}, nil).AnyTimes()
136+
137+
moqRepoClient.EXPECT().GetFileContent(tt.filename).
138+
DoAndReturn(func(b string) ([]byte, error) {
139+
//nolint: errcheck
140+
content, _ := os.ReadFile(b)
141+
return content, nil
142+
}).AnyTimes()
143+
144+
if tt.exists {
145+
moqRepo.EXPECT().URI().Return("myurl.com/owner/project")
146+
}
147+
148+
req := checker.CheckRequest{
149+
RepoClient: moqRepoClient,
150+
Repo: moqRepo,
151+
}
152+
153+
//nolint: errcheck
154+
packagingData, _ := Packaging(&req)
155+
156+
if !tt.exists {
157+
if len(packagingData.Packages) != 0 {
158+
t.Errorf("Repo should not contain any packages")
159+
}
160+
return
161+
}
162+
163+
if len(packagingData.Packages) == 0 {
164+
t.Fatalf("Repo should contain related packages")
165+
}
166+
167+
pkg := packagingData.Packages[0].File
168+
169+
if pkg.Offset != tt.lineNumber {
170+
t.Errorf("Expected line number: %d != %d", tt.lineNumber, pkg.Offset)
171+
}
172+
if pkg.Path != tt.filename {
173+
t.Errorf("Expected filename: %v != %v", tt.filename, pkg.Path)
174+
}
175+
176+
runs := packagingData.Packages[0].Runs
177+
178+
if len(runs) != 1 {
179+
t.Errorf("Expected only a single run count, but received %d", len(runs))
180+
}
181+
182+
if runs[0].URL != "myurl.com/owner/project" {
183+
t.Errorf("URL did not match expected value %q", runs[0].URL)
184+
}
185+
})
186+
}
187+
}

0 commit comments

Comments
 (0)