Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

✨ Gitlab support: RepoClient #2655

Merged
merged 7 commits into from
Mar 13, 2023
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 16 additions & 0 deletions .github/workflows/integration.yml
Original file line number Diff line number Diff line change
Expand Up @@ -71,3 +71,19 @@ 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
env:
GITLAB_AUTH_TOKEN: ${{ secrets.GITLAB_AUTH_TOKEN }}
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
raghavkaul marked this conversation as resolved.
Show resolved Hide resolved
3 changes: 3 additions & 0 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -334,6 +334,9 @@ 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: ## Runs e2e tests for GitLab only. TOKEN_TYPE is arbitrary, but must be set to something
TOKEN_TYPE="GITLAB_PAT" $(GINKGO) --race -p -vv --focus '.*GitLab' ./...
raghavkaul marked this conversation as resolved.
Show resolved Hide resolved

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

Expand Down
66 changes: 52 additions & 14 deletions checker/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,11 +17,12 @@ 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 +36,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,20 +53,55 @@ 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)
}

//nolint:staticcheck
ossFuzzRepoClient, errOssFuzz := ghrepo.CreateOssFuzzRepoClient(ctx, logger)
var retErr error
if errOssFuzz != nil {
retErr = fmt.Errorf("getting OSS-Fuzz repo client: %w", errOssFuzz)
raghavkaul marked this conversation as resolved.
Show resolved Hide resolved
}

return githubRepo, /*repo*/
ghrepo.CreateGithubRepoClient(ctx, logger), /*repoClient*/
ossfuzz.CreateOSSFuzzClient(ossfuzz.StatusURL), /*ossFuzzClient*/
return repo, /*repo*/
repoClient, /*repoClient*/
ossFuzzRepoClient, /*ossFuzzClient*/
clients.DefaultCIIBestPracticesClient(), /*ciiClient*/
clients.DefaultVulnerabilitiesClient(), /*vulnClient*/
nil
retErr
}
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()))
raghavkaul marked this conversation as resolved.
Show resolved Hide resolved
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 {
raghavkaul marked this conversation as resolved.
Show resolved Hide resolved
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