mirror of
https://github.com/ossf/scorecard.git
synced 2024-09-11 08:55:27 +03:00
🐛 Support self-hosted GitLab instances where base URL has a path component (#3819)
* Add GL_HOST env flag Self-hosted instances which dont use a subdomain result in broken API links. This change may not be finished, but is intended to evaluate the solution. Previously, self hosted instances where the instance is part of the path (foo.com/gitlab/owner/repo) would have their API base URL registered as foo.com/api/v4/ instead of foo.com/gitlab/api/v4/ Signed-off-by: Spencer Schrock <sschrock@google.com> * include token in gitlab project probe Signed-off-by: Spencer Schrock <sschrock@google.com> * consider GL_HOST when parsing gitlab repo urls Signed-off-by: Spencer Schrock <sschrock@google.com> * remove unneeded GL_HOST parsing now that repoURL_parse handles GL_HOST, we dont need it elsewhere. Signed-off-by: Spencer Schrock <sschrock@google.com> * cleanup Signed-off-by: Spencer Schrock <sschrock@google.com> * mention GL_HOST in readme Signed-off-by: Spencer Schrock <sschrock@google.com> * fix linter Signed-off-by: Spencer Schrock <sschrock@google.com> * handle GL_HOST without scheme Signed-off-by: Spencer Schrock <sschrock@google.com> * move api-less check earlier if we can avoid an API call, do it. Signed-off-by: Spencer Schrock <sschrock@google.com> * try listing projects with and without auth token Signed-off-by: Spencer Schrock <sschrock@google.com> * fix linter Signed-off-by: Spencer Schrock <sschrock@google.com> * revert passing token to list projects the simpler the better Signed-off-by: Spencer Schrock <sschrock@google.com> --------- Signed-off-by: Spencer Schrock <sschrock@google.com>
This commit is contained in:
parent
83ff808f0d
commit
e10dbb1531
11
README.md
11
README.md
@ -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.
|
||||
|
@ -19,7 +19,9 @@ package gitlabrepo
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"os"
|
||||
"strings"
|
||||
|
||||
"github.com/xanzy/go-gitlab"
|
||||
@ -61,14 +63,26 @@ func (r *repoURL) parse(input string) error {
|
||||
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))
|
||||
}
|
||||
|
||||
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))
|
||||
}
|
||||
|
||||
// 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
|
||||
@ -81,6 +95,14 @@ func (r *repoURL) parse(input string) error {
|
||||
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)
|
||||
@ -97,6 +119,10 @@ func (r *repoURL) String() string {
|
||||
|
||||
// IsValid implements Repo.IsValid.
|
||||
func (r *repoURL) IsValid() error {
|
||||
if strings.TrimSpace(r.owner) == "" || strings.TrimSpace(r.project) == "" {
|
||||
return sce.WithMessage(sce.ErrorInvalidURL, "expected full project url: "+r.URI())
|
||||
}
|
||||
|
||||
if strings.Contains(r.host, "gitlab.") {
|
||||
return nil
|
||||
}
|
||||
@ -105,7 +131,10 @@ func (r *repoURL) IsValid() error {
|
||||
return fmt.Errorf("%w: %s", errInvalidGitlabRepoURL, r.host)
|
||||
}
|
||||
|
||||
client, err := gitlab.NewClient("", gitlab.WithBaseURL(fmt.Sprintf("%s://%s", r.scheme, r.host)))
|
||||
// intentionally pass empty token
|
||||
// "When accessed without authentication, only public projects with simple fields are returned."
|
||||
// https://docs.gitlab.com/ee/api/projects.html#list-all-projects
|
||||
client, err := gitlab.NewClient("", gitlab.WithBaseURL(r.Host()))
|
||||
if err != nil {
|
||||
return sce.WithMessage(err,
|
||||
fmt.Sprintf("couldn't create gitlab client for %s", r.host),
|
||||
@ -113,7 +142,7 @@ func (r *repoURL) IsValid() error {
|
||||
}
|
||||
|
||||
_, resp, err := client.Projects.ListProjects(&gitlab.ListProjectsOptions{})
|
||||
if resp == nil || resp.StatusCode != 200 {
|
||||
if resp == nil || resp.StatusCode != http.StatusOK {
|
||||
return sce.WithMessage(sce.ErrRepoUnreachable,
|
||||
fmt.Sprintf("couldn't reach gitlab instance at %s", r.host),
|
||||
)
|
||||
@ -124,10 +153,6 @@ func (r *repoURL) IsValid() error {
|
||||
)
|
||||
}
|
||||
|
||||
if strings.TrimSpace(r.owner) == "" || strings.TrimSpace(r.project) == "" {
|
||||
return sce.WithMessage(sce.ErrorInvalidURL,
|
||||
fmt.Sprintf("%v. Expected the full project url", r.URI()))
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
|
@ -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…
Reference in New Issue
Block a user