Skip to content

Commit

Permalink
✨ Gitlab support: RepoClient (ossf#2655)
Browse files Browse the repository at this point in the history
* Add make targets and E2E test target for GitLab only

Signed-off-by: Raghav Kaul <[email protected]>

* Add GitLab support to RepoClient

Signed-off-by: Raghav Kaul <[email protected]>

* Build

* Make target for e2e-gitlab-token
* Only run Gitlab tests in CI that don't require a token

Signed-off-by: Raghav Kaul <[email protected]>

* Add tests

Signed-off-by: Raghav Kaul <[email protected]>

* Remove spurious printf

Signed-off-by: Raghav Kaul <[email protected]>

* 🐛 Check OSS Fuzz build file for Fuzzing check (ossf#2719)

* Check OSS-Fuzz using project list

Signed-off-by: Spencer Schrock <[email protected]>

* Use clients.RepoClient interface to perform the new OSS Fuzz check

Signed-off-by: Spencer Schrock <[email protected]>

* wip: add eager client for better repeated lookup of projects

Signed-off-by: Spencer Schrock <[email protected]>

* Split lazy and eager behavior into different implementations.

Signed-off-by: Spencer Schrock <[email protected]>

* Add tests and benchmarks

Signed-off-by: Spencer Schrock <[email protected]>

* Switch to always parsing JSON to determine if a project is present. The other approach of looking for a substring match would lead to false positives.

Signed-off-by: Spencer Schrock <[email protected]>

* Add eager constructor to surface status file errors sooner.

Signed-off-by: Spencer Schrock <[email protected]>

* Switch existing users to new OSS Fuzz client

Signed-off-by: Spencer Schrock <[email protected]>

* Mark old method as deprecated in the godoc

Signed-off-by: Spencer Schrock <[email protected]>

* remove unused comment.

Signed-off-by: Spencer Schrock <[email protected]>

* Use new OSS Fuzz client in e2e test.

Signed-off-by: Spencer Schrock <[email protected]>

* fix typo.

Signed-off-by: Spencer Schrock <[email protected]>

* Fix potential path bug with test server.

Signed-off-by: Spencer Schrock <[email protected]>

* Force include the two JSON files which were being ignored by .gitignore

Signed-off-by: Spencer Schrock <[email protected]>

* trim the status json file

Signed-off-by: Spencer Schrock <[email protected]>

---------

Signed-off-by: Spencer Schrock <[email protected]>

---------

Signed-off-by: Raghav Kaul <[email protected]>
Signed-off-by: Spencer Schrock <[email protected]>
Co-authored-by: Spencer Schrock <[email protected]>
  • Loading branch information
raghavkaul and spencerschrock authored Mar 13, 2023
1 parent 5625dda commit 110e352
Show file tree
Hide file tree
Showing 27 changed files with 289 additions and 158 deletions.
14 changes: 14 additions & 0 deletions .github/workflows/integration.yml
Original file line number Diff line number Diff line change
Expand Up @@ -71,3 +71,17 @@ jobs:
with:
files: ./e2e-coverage.out
verbose: true

- name: Run GitLab E2E #using retry because the GitHub token is being throttled.
uses: nick-invision/retry@943e742917ac94714d2f408a0e8320f2d1fcafcd
with:
max_attempts: 3
retry_on: error
timeout_minutes: 30
command: make e2e-gitlab

- name: codecov
uses: codecov/codecov-action@81cd2dc8148241f03f5839d295e000b8f761e378 # 2.1.0
with:
files: ./e2e-coverage.out
verbose: true
6 changes: 6 additions & 0 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -334,6 +334,12 @@ e2e-gh-token: build-scorecard check-env | $(GINKGO)
# Run e2e tests. GITHUB_AUTH_TOKEN set to secrets.GITHUB_TOKEN must be used to run this.
TOKEN_TYPE="GITHUB_TOKEN" $(GINKGO) --race -p -v -cover -coverprofile=e2e-coverage.out --keep-separate-coverprofiles ./...

e2e-gitlab-token: ## Runs e2e tests that require a GITLAB_TOKEN
TOKEN_TYPE="GITLAB_PAT" $(GINKGO) --race -p -vv --focus '.*GitLab Token' ./...

e2e-gitlab: ## Runs e2e tests for GitLab only. TOKEN_TYPE is not used (since these are public APIs), but must be set to something
TOKEN_TYPE="GITLAB_PAT" $(GINKGO) --race -p -vv --focus '.*GitLab' ./...

e2e-attestor: ## Runs e2e tests for scorecard-attestor
cd attestor/e2e; go test -covermode=atomic -coverprofile=e2e-coverage.out; cd ../..

Expand Down
54 changes: 43 additions & 11 deletions checker/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,9 +17,11 @@ package checker
import (
"context"
"fmt"
"os"

"github.com/ossf/scorecard/v4/clients"
ghrepo "github.com/ossf/scorecard/v4/clients/githubrepo"
glrepo "github.com/ossf/scorecard/v4/clients/gitlabrepo"
"github.com/ossf/scorecard/v4/clients/localdir"
"github.com/ossf/scorecard/v4/clients/ossfuzz"
"github.com/ossf/scorecard/v4/log"
Expand All @@ -35,7 +37,9 @@ func GetClients(ctx context.Context, repoURI, localURI string, logger *log.Logge
clients.VulnerabilitiesClient, // vulnClient
error,
) {
var githubRepo clients.Repo
var repo clients.Repo
var makeRepoError error

if localURI != "" {
localRepo, errLocal := localdir.MakeLocalDirRepo(localURI)
var retErr error
Expand All @@ -50,18 +54,46 @@ func GetClients(ctx context.Context, repoURI, localURI string, logger *log.Logge
retErr
}

githubRepo, errGitHub := ghrepo.MakeGithubRepo(repoURI)
if errGitHub != nil {
return githubRepo,
nil,
nil,
nil,
nil,
fmt.Errorf("getting local directory client: %w", errGitHub)
_, experimental := os.LookupEnv("SCORECARD_EXPERIMENTAL")
var repoClient clients.RepoClient

//nolint:nestif
if experimental && glrepo.DetectGitLab(repoURI) {
repo, makeRepoError = glrepo.MakeGitlabRepo(repoURI)
if makeRepoError != nil {
return repo,
nil,
nil,
nil,
nil,
fmt.Errorf("getting local directory client: %w", makeRepoError)
}

var err error
repoClient, err = glrepo.CreateGitlabClientWithToken(ctx, os.Getenv("GITLAB_AUTH_TOKEN"), repo)
if err != nil {
return repo,
nil,
nil,
nil,
nil,
fmt.Errorf("error creating gitlab client: %w", err)
}
} else {
repo, makeRepoError = ghrepo.MakeGithubRepo(repoURI)
if makeRepoError != nil {
return repo,
nil,
nil,
nil,
nil,
fmt.Errorf("getting local directory client: %w", makeRepoError)
}
repoClient = ghrepo.CreateGithubRepoClient(ctx, logger)
}

return githubRepo, /*repo*/
ghrepo.CreateGithubRepoClient(ctx, logger), /*repoClient*/
return repo, /*repo*/
repoClient, /*repoClient*/
ossfuzz.CreateOSSFuzzClient(ossfuzz.StatusURL), /*ossFuzzClient*/
clients.DefaultCIIBestPracticesClient(), /*ciiClient*/
clients.DefaultVulnerabilitiesClient(), /*vulnClient*/
Expand Down
4 changes: 4 additions & 0 deletions clients/githubrepo/repo.go
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,10 @@ func (r *repoURL) URI() string {
return fmt.Sprintf("%s/%s/%s", r.host, r.owner, r.repo)
}

func (r *repoURL) Host() string {
return r.host
}

// String implements Repo.String.
func (r *repoURL) String() string {
return fmt.Sprintf("%s-%s-%s", r.host, r.owner, r.repo)
Expand Down
4 changes: 4 additions & 0 deletions clients/githubrepo/repo_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -97,6 +97,10 @@ func TestRepoURL_IsValid(t *testing.T) {
if !tt.wantErr && !cmp.Equal(tt.expected, r, cmp.AllowUnexported(repoURL{})) {
t.Errorf("Got diff: %s", cmp.Diff(tt.expected, r))
}

if !cmp.Equal(r.Host(), tt.expected.host) {
t.Errorf("%s expected host: %s got host %s", tt.inputURL, tt.expected.host, r.Host())
}
})
}
}
18 changes: 9 additions & 9 deletions clients/gitlabrepo/branches.go
Original file line number Diff line number Diff line change
Expand Up @@ -46,21 +46,21 @@ func (handler *branchesHandler) setup() error {
return
}

proj, _, err := handler.glClient.Projects.GetProject(handler.repourl.projectID, &gitlab.GetProjectOptions{})
proj, _, err := handler.glClient.Projects.GetProject(handler.repourl.project, &gitlab.GetProjectOptions{})
if err != nil {
handler.errSetup = fmt.Errorf("requirest for project failed with error %w", err)
return
}

branch, _, err := handler.glClient.Branches.GetBranch(handler.repourl.projectID, proj.DefaultBranch)
branch, _, err := handler.glClient.Branches.GetBranch(handler.repourl.project, proj.DefaultBranch)
if err != nil {
handler.errSetup = fmt.Errorf("request for default branch failed with error %w", err)
return
}

if branch.Protected {
protectedBranch, resp, err := handler.glClient.ProtectedBranches.GetProtectedBranch(
handler.repourl.projectID, branch.Name)
handler.repourl.project, branch.Name)
if err != nil && resp.StatusCode != 403 {
handler.errSetup = fmt.Errorf("request for protected branch failed with error %w", err)
return
Expand All @@ -70,13 +70,13 @@ func (handler *branchesHandler) setup() error {
}

projectStatusChecks, resp, err := handler.glClient.ExternalStatusChecks.ListProjectStatusChecks(
handler.repourl.projectID, &gitlab.ListOptions{})
handler.repourl.project, &gitlab.ListOptions{})
if err != nil && resp.StatusCode != 404 {
handler.errSetup = fmt.Errorf("request for external status checks failed with error %w", err)
return
}

projectApprovalRule, resp, err := handler.glClient.Projects.GetApprovalConfiguration(handler.repourl.projectID)
projectApprovalRule, resp, err := handler.glClient.Projects.GetApprovalConfiguration(handler.repourl.project)
if err != nil && resp.StatusCode != 404 {
handler.errSetup = fmt.Errorf("request for project approval rule failed with %w", err)
return
Expand Down Expand Up @@ -105,24 +105,24 @@ func (handler *branchesHandler) getDefaultBranch() (*clients.BranchRef, error) {
}

func (handler *branchesHandler) getBranch(branch string) (*clients.BranchRef, error) {
bran, _, err := handler.glClient.Branches.GetBranch(handler.repourl.projectID, branch)
bran, _, err := handler.glClient.Branches.GetBranch(handler.repourl.project, branch)
if err != nil {
return nil, fmt.Errorf("error getting branch in branchsHandler.getBranch: %w", err)
}

if bran.Protected {
protectedBranch, _, err := handler.glClient.ProtectedBranches.GetProtectedBranch(handler.repourl.projectID, bran.Name)
protectedBranch, _, err := handler.glClient.ProtectedBranches.GetProtectedBranch(handler.repourl.project, bran.Name)
if err != nil {
return nil, fmt.Errorf("request for protected branch failed with error %w", err)
}

projectStatusChecks, resp, err := handler.glClient.ExternalStatusChecks.ListProjectStatusChecks(
handler.repourl.projectID, &gitlab.ListOptions{})
handler.repourl.project, &gitlab.ListOptions{})
if err != nil && resp.StatusCode != 404 {
return nil, fmt.Errorf("request for external status checks failed with error %w", err)
}

projectApprovalRule, resp, err := handler.glClient.Projects.GetApprovalConfiguration(handler.repourl.projectID)
projectApprovalRule, resp, err := handler.glClient.Projects.GetApprovalConfiguration(handler.repourl.project)
if err != nil && resp.StatusCode != 404 {
return nil, fmt.Errorf("request for project approval rule failed with %w", err)
}
Expand Down
2 changes: 1 addition & 1 deletion clients/gitlabrepo/checkruns.go
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ func (handler *checkrunsHandler) init(repourl *repoURL) {

func (handler *checkrunsHandler) listCheckRunsForRef(ref string) ([]clients.CheckRun, error) {
pipelines, _, err := handler.glClient.Pipelines.ListProjectPipelines(
handler.repourl.projectID, &gitlab.ListProjectPipelinesOptions{})
handler.repourl.project, &gitlab.ListProjectPipelinesOptions{})
if err != nil {
return nil, fmt.Errorf("request for pipelines returned error: %w", err)
}
Expand Down
31 changes: 21 additions & 10 deletions clients/gitlabrepo/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -52,8 +52,7 @@ type Client struct {
languages *languagesHandler
licenses *licensesHandler
ctx context.Context
// tarball tarballHandler
commitDepth int
commitDepth int
}

// InitRepo sets up the GitLab project in local storage for improving performance and GitLab token usage efficiency.
Expand All @@ -64,9 +63,10 @@ func (client *Client) InitRepo(inputRepo clients.Repo, commitSHA string, commitD
}

// Sanity check.
repo, _, err := client.glClient.Projects.GetProject(glRepo.projectID, &gitlab.GetProjectOptions{})
proj := fmt.Sprintf("%s/%s", glRepo.owner, glRepo.project)
repo, _, err := client.glClient.Projects.GetProject(proj, &gitlab.GetProjectOptions{})
if err != nil {
return sce.WithMessage(sce.ErrRepoUnreachable, err.Error())
return sce.WithMessage(sce.ErrRepoUnreachable, proj+"\t"+err.Error())
}
if commitDepth <= 0 {
client.commitDepth = 30 // default
Expand All @@ -75,8 +75,9 @@ func (client *Client) InitRepo(inputRepo clients.Repo, commitSHA string, commitD
}
client.repo = repo
client.repourl = &repoURL{
hostname: inputRepo.URI(),
projectID: fmt.Sprint(repo.ID),
scheme: glRepo.scheme,
host: glRepo.host,
project: fmt.Sprint(repo.ID),
defaultBranch: repo.DefaultBranch,
commitSHA: commitSHA,
}
Expand Down Expand Up @@ -127,13 +128,11 @@ func (client *Client) InitRepo(inputRepo clients.Repo, commitSHA string, commitD
// Init languagesHandler
client.licenses.init(client.repourl)

// Init tarballHandler.
// client.tarball.init(client.ctx, client.repourl, client.repo, commitSHA)
return nil
}

func (client *Client) URI() string {
return fmt.Sprintf("%s/%s/%s", client.repourl.hostname, client.repourl.owner, client.repourl.projectID)
return fmt.Sprintf("%s/%s/%s", client.repourl.host, client.repourl.owner, client.repourl.project)
}

func (client *Client) LocalPath() (string, error) {
Expand Down Expand Up @@ -222,7 +221,7 @@ func (client *Client) Close() error {
}

func CreateGitlabClientWithToken(ctx context.Context, token string, repo clients.Repo) (clients.RepoClient, error) {
client, err := gitlab.NewClient(token, gitlab.WithBaseURL(repo.URI()))
client, err := gitlab.NewClient(token, gitlab.WithBaseURL(repo.Host()))
if err != nil {
return nil, fmt.Errorf("could not create gitlab client with error: %w", err)
}
Expand Down Expand Up @@ -269,10 +268,22 @@ func CreateGitlabClientWithToken(ctx context.Context, token string, repo clients
languages: &languagesHandler{
glClient: client,
},
licenses: &licensesHandler{},
}, nil
}

// TODO(#2266): implement CreateOssFuzzRepoClient.
func CreateOssFuzzRepoClient(ctx context.Context, logger *log.Logger) (clients.RepoClient, error) {
return nil, fmt.Errorf("%w, oss fuzz currently only supported for github repos", clients.ErrUnsupportedFeature)
}

// DetectGitLab: check whether the repoURI is a GitLab URI
// Makes HTTP request to GitLab API.
func DetectGitLab(repoURI string) bool {
var repo repoURL
if err := repo.parse(repoURI); err != nil {
return false
}

return repo.IsValid() == nil
}
4 changes: 2 additions & 2 deletions clients/gitlabrepo/commits.go
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ func (handler *commitsHandler) init(repourl *repoURL) {
// nolint: gocognit
func (handler *commitsHandler) setup() error {
handler.once.Do(func() {
commits, _, err := handler.glClient.Commits.ListCommits(handler.repourl.projectID, &gitlab.ListCommitsOptions{})
commits, _, err := handler.glClient.Commits.ListCommits(handler.repourl.project, &gitlab.ListCommitsOptions{})
if err != nil {
handler.errSetup = fmt.Errorf("request for commits failed with %w", err)
return
Expand Down Expand Up @@ -76,7 +76,7 @@ func (handler *commitsHandler) setup() error {

// Commits are able to be a part of multiple merge requests, but the only one that will be important
// here is the earliest one.
mergeRequests, _, err := handler.glClient.Commits.ListMergeRequestsByCommit(handler.repourl.projectID, commit.ID)
mergeRequests, _, err := handler.glClient.Commits.ListMergeRequestsByCommit(handler.repourl.project, commit.ID)
if err != nil {
handler.errSetup = fmt.Errorf("unable to find merge requests associated with commit: %w", err)
return
Expand Down
2 changes: 1 addition & 1 deletion clients/gitlabrepo/contributors.go
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ func (handler *contributorsHandler) setup() error {
return
}
contribs, _, err := handler.glClient.Repositories.Contributors(
handler.repourl.projectID, &gitlab.ListContributorsOptions{})
handler.repourl.project, &gitlab.ListContributorsOptions{})
if err != nil {
handler.errSetup = fmt.Errorf("error during ListContributors: %w", err)
return
Expand Down
4 changes: 2 additions & 2 deletions clients/gitlabrepo/issues.go
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ func (handler *issuesHandler) init(repourl *repoURL) {
func (handler *issuesHandler) setup() error {
handler.once.Do(func() {
issues, _, err := handler.glClient.Issues.ListProjectIssues(
handler.repourl.projectID, &gitlab.ListProjectIssuesOptions{})
handler.repourl.project, &gitlab.ListProjectIssuesOptions{})
if err != nil {
handler.errSetup = fmt.Errorf("unable to find issues associated with the project id: %w", err)
return
Expand All @@ -49,7 +49,7 @@ func (handler *issuesHandler) setup() error {
// There doesn't seem to be a good way to get user access_levels in gitlab so the following way may seem incredibly
// barberic, however I couldn't find a better way in the docs.
projectAccessTokens, resp, err := handler.glClient.ProjectAccessTokens.ListProjectAccessTokens(
handler.repourl.projectID, &gitlab.ListProjectAccessTokensOptions{})
handler.repourl.project, &gitlab.ListProjectAccessTokensOptions{})
if err != nil && resp.StatusCode != 401 {
handler.errSetup = fmt.Errorf("unable to find access tokens associated with the project id: %w", err)
return
Expand Down
2 changes: 1 addition & 1 deletion clients/gitlabrepo/languages.go
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ func (handler *languagesHandler) init(repourl *repoURL) {
func (handler *languagesHandler) setup() error {
handler.once.Do(func() {
client := handler.glClient
languageMap, _, err := client.Projects.GetProjectLanguages(handler.repourl.projectID)
languageMap, _, err := client.Projects.GetProjectLanguages(handler.repourl.project)
if err != nil || languageMap == nil {
handler.errSetup = fmt.Errorf("request for repo languages failed with %w", err)
return
Expand Down
2 changes: 1 addition & 1 deletion clients/gitlabrepo/project.go
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ func (handler *projectHandler) init(repourl *repoURL) {

func (handler *projectHandler) setup() error {
handler.once.Do(func() {
proj, _, err := handler.glClient.Projects.GetProject(handler.repourl.projectID, &gitlab.GetProjectOptions{})
proj, _, err := handler.glClient.Projects.GetProject(handler.repourl.project, &gitlab.GetProjectOptions{})
if err != nil {
handler.errSetup = fmt.Errorf("request for project failed with error %w", err)
return
Expand Down
2 changes: 1 addition & 1 deletion clients/gitlabrepo/releases.go
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ func (handler *releasesHandler) setup() error {
handler.errSetup = fmt.Errorf("%w: ListReleases only supported for HEAD queries", clients.ErrUnsupportedFeature)
return
}
releases, _, err := handler.glClient.Releases.ListReleases(handler.repourl.projectID, &gitlab.ListReleasesOptions{})
releases, _, err := handler.glClient.Releases.ListReleases(handler.repourl.project, &gitlab.ListReleasesOptions{})
if err != nil {
handler.errSetup = fmt.Errorf("%w: ListReleases failed", err)
return
Expand Down
Loading

0 comments on commit 110e352

Please sign in to comment.