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

🐛 Support self-hosted GitLab instances where base URL has a path component #3819

Merged
merged 15 commits into from
Jan 31, 2024
Merged
Show file tree
Hide file tree
Changes from 9 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
11 changes: 11 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -437,6 +437,17 @@ scorecard --repo gitlab.com/<org>/<project>/<subproject>

For an example of using Scorecard in GitLab CI/CD, see [here](https://gitlab.com/ossf-test/scorecard-pipeline-example).

###### Self Hosted Editions
While we focus on GitLab.com support, Scorecard also works with self-hosted GitLab installations.
If your platform is hosted at a subdomain (e.g. `gitlab.foo.com`), Scorecard should work out of the box.
If your platform is hosted at some slug (e.g. `foo.com/bar/`), you will need to set the `GL_HOST` environment variable.

```bash
export GITLAB_AUTH_TOKEN=glpat-xxxx
export GL_HOST=foo.com/bar
scorecard --repo foo.com/bar/<org>/<project>
```

##### Using GitHub Enterprise Server (GHES) based Repository

To use a GitHub Enterprise host `github.corp.com`, use the `GH_HOST` environment variable.
Expand Down
37 changes: 30 additions & 7 deletions clients/gitlabrepo/repo.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
"errors"
"fmt"
"net/url"
"os"
"strings"

"github.com/xanzy/go-gitlab"
Expand Down Expand Up @@ -61,14 +62,26 @@
t = input
}

// Allow skipping scheme for ease-of-use, default to https.
if !strings.Contains(t, "://") {
t = "https://" + t
u, err := url.Parse(withDefaultScheme(t))
if err != nil {
return sce.WithMessage(sce.ErrScorecardInternal, fmt.Sprintf("url.Parse: %v", err))

Check warning on line 67 in clients/gitlabrepo/repo.go

View check run for this annotation

Codecov / codecov/patch

clients/gitlabrepo/repo.go#L67

Added line #L67 was not covered by tests
}

u, e := url.Parse(t)
if e != nil {
return sce.WithMessage(sce.ErrScorecardInternal, fmt.Sprintf("url.Parse: %v", e))
// fixup the URL, for situations where GL_HOST contains part of the path
// https://github.com/ossf/scorecard/issues/3696
if h := os.Getenv("GL_HOST"); h != "" {
hostURL, err := url.Parse(withDefaultScheme(h))
if err != nil {
return sce.WithMessage(sce.ErrScorecardInternal, fmt.Sprintf("url.Parse GL_HOST: %v", err))
}

Check warning on line 76 in clients/gitlabrepo/repo.go

View check run for this annotation

Codecov / codecov/patch

clients/gitlabrepo/repo.go#L75-L76

Added lines #L75 - L76 were not covered by tests

// only modify behavior of repos which fall under GL_HOST
if hostURL.Host == u.Host {
// without the scheme and without trailing slashes
u.Host = hostURL.Host + strings.TrimRight(hostURL.Path, "/")
// remove any part of the path which belongs to the host
u.Path = strings.TrimPrefix(u.Path, hostURL.Path)
}
}

const splitLen = 2
Expand All @@ -81,6 +94,14 @@
return nil
}

// Allow skipping scheme for ease-of-use, default to https.
func withDefaultScheme(uri string) string {
if strings.Contains(uri, "://") {
return uri
}
return "https://" + uri
}

// URI implements Repo.URI().
func (r *repoURL) URI() string {
return fmt.Sprintf("%s/%s/%s", r.host, r.owner, r.project)
Expand All @@ -105,7 +126,9 @@
return fmt.Errorf("%w: %s", errInvalidGitlabRepoURL, r.host)
}

client, err := gitlab.NewClient("", gitlab.WithBaseURL(fmt.Sprintf("%s://%s", r.scheme, r.host)))
token := os.Getenv("GITLAB_AUTH_TOKEN")
spencerschrock marked this conversation as resolved.
Show resolved Hide resolved
baseURL := r.Host()
client, err := gitlab.NewClient(token, gitlab.WithBaseURL(baseURL))
if err != nil {
return sce.WithMessage(err,
fmt.Sprintf("couldn't create gitlab client for %s", r.host),
Expand Down
80 changes: 80 additions & 0 deletions clients/gitlabrepo/repo_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -178,3 +178,83 @@ func TestRepoURL_MakeGitLabRepo(t *testing.T) {
}
}
}

//nolint:paralleltest // uses t.Setenv, can't be parallelized
func TestRepoURL_parse_GL_HOST(t *testing.T) {
tests := []struct {
name string
url string
host, owner, project string
glHost string
wantErr bool
}{
{
name: "GL_HOST ends with slash",
glHost: "https://foo.com/gitlab/",
url: "foo.com/gitlab/ssdlc/scorecard-scanner",
host: "foo.com/gitlab",
owner: "ssdlc",
project: "scorecard-scanner",
},
{
name: "GL_HOST doesn't end with slash",
glHost: "https://foo.com/gitlab",
url: "foo.com/gitlab/ssdlc/scorecard-scanner",
host: "foo.com/gitlab",
owner: "ssdlc",
project: "scorecard-scanner",
},
{
name: "GL_HOST doesn't match url",
glHost: "https://foo.com/gitlab",
url: "bar.com/gitlab/ssdlc/scorecard-scanner",
host: "bar.com",
owner: "gitlab",
project: "ssdlc/scorecard-scanner",
},
{
name: "GL_HOST has no path component",
glHost: "https://foo.com",
url: "foo.com/ssdlc/scorecard-scanner",
host: "foo.com",
owner: "ssdlc",
project: "scorecard-scanner",
},
{
name: "GL_HOST path has multiple slashes",
glHost: "https://foo.com/bar/baz/",
url: "foo.com/bar/baz/ssdlc/scorecard-scanner",
host: "foo.com/bar/baz",
owner: "ssdlc",
project: "scorecard-scanner",
},
{
name: "GL_HOST has no scheme",
glHost: "foo.com/bar/",
url: "foo.com/bar/ssdlc/scorecard-scanner",
host: "foo.com/bar",
owner: "ssdlc",
project: "scorecard-scanner",
},
}

for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
t.Setenv("GL_HOST", tt.glHost)
var r repoURL
err := r.parse(tt.url)
if (err != nil) != tt.wantErr {
t.Fatalf("wanted err: %t, got: %v", tt.wantErr, err)
}
if r.host != tt.host {
t.Errorf("got host: %s, want: %s", r.host, tt.host)
}
if r.owner != tt.owner {
t.Errorf("got owner: %s, want: %s", r.owner, tt.owner)
}
if r.project != tt.project {
t.Errorf("got project: %s, want: %s", r.project, tt.project)
}
})
}
}
Loading