Skip to content

Commit ecb1591

Browse files
committed
Gitlab support fixes
1 parent fcbbc42 commit ecb1591

File tree

3 files changed

+44
-6
lines changed

3 files changed

+44
-6
lines changed

api/api.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -98,8 +98,8 @@ func NewAPIWithVersion(ctx context.Context, globalConfig *conf.GlobalConfigurati
9898
}
9999

100100
corsHandler := cors.New(cors.Options{
101-
AllowedMethods: []string{http.MethodGet, http.MethodPost, http.MethodPut, http.MethodDelete, http.MethodPatch},
102-
AllowedHeaders: []string{"Accept", "Authorization", "Content-Type", audHeaderName},
101+
AllowedMethods: []string{http.MethodGet, http.MethodHead, http.MethodPost, http.MethodPut, http.MethodDelete, http.MethodPatch},
102+
AllowedHeaders: []string{"Accept", "Authorization", "Private-Token", "Content-Type", audHeaderName},
103103
AllowCredentials: true,
104104
})
105105

api/gitlab.go

Lines changed: 40 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import (
66
"net/http/httputil"
77
"net/url"
88
"regexp"
9+
"strings"
910
)
1011

1112
// GitLabGateway acts as a proxy to Gitlab
@@ -34,7 +35,11 @@ func gitlabDirector(r *http.Request) {
3435
r.Host = target.Host
3536
r.URL.Scheme = target.Scheme
3637
r.URL.Host = target.Host
37-
r.URL.Path = singleJoiningSlash(target.Path, gitlabPathRegexp.ReplaceAllString(r.URL.Path, "/"))
38+
// We need to set URL.Opaque using the target and r.URL EscapedPath
39+
// methods, because the Go stdlib URL parsing automatically converts
40+
// %2F to / in URL paths, and GitLab requires %2F to be preserved
41+
// as-is.
42+
r.URL.Opaque = "//" + target.Host + singleJoiningSlash(target.EscapedPath(), gitlabPathRegexp.ReplaceAllString(r.URL.EscapedPath(), "/"))
3843
if targetQuery == "" || r.URL.RawQuery == "" {
3944
r.URL.RawQuery = targetQuery + r.URL.RawQuery
4045
} else {
@@ -44,8 +49,9 @@ func gitlabDirector(r *http.Request) {
4449
// explicitly disable User-Agent so it's not set to default value
4550
r.Header.Set("User-Agent", "")
4651
}
52+
r.Header.Del("Authorization")
4753
if r.Method != http.MethodOptions {
48-
r.Header.Set("Authorization", "Bearer "+accessToken)
54+
r.Header.Set("Private-Token", accessToken)
4955
}
5056

5157
log := getLogEntry(r)
@@ -66,7 +72,10 @@ func (gl *GitLabGateway) ServeHTTP(w http.ResponseWriter, r *http.Request) {
6672
}
6773

6874
endpoint := config.GitLab.Endpoint
69-
apiURL := singleJoiningSlash(endpoint, "/repos/"+config.GitLab.Repo)
75+
// repos in the form of userName/repoName must be encoded as
76+
// userName%2FrepoName
77+
repo := url.PathEscape(config.GitLab.Repo)
78+
apiURL := singleJoiningSlash(endpoint, "/projects/"+repo)
7079
target, err := url.Parse(apiURL)
7180
if err != nil {
7281
handleError(internalServerError("Unable to process GitLab endpoint"), w, r)
@@ -110,13 +119,40 @@ func (gl *GitLabGateway) authenticate(w http.ResponseWriter, r *http.Request) er
110119
return errors.New("Access to endpoint not allowed: your role doesn't allow access")
111120
}
112121

122+
var gitlabLinkRegex = regexp.MustCompile("<(.*?)>")
123+
var gitlabLinkRelRegex = regexp.MustCompile("rel=\"(.*?)\"")
124+
125+
func rewriteGitlabLinks(linkHeader, endpointAPIURL, proxyAPIURL string) string {
126+
linkEntries := strings.Split(linkHeader, ",")
127+
finalLinkEntries := []string{}
128+
for _, linkEntry := range linkEntries {
129+
linkAndRel := strings.Split(strings.TrimSpace(linkEntry), ";")
130+
link := proxyAPIURL + strings.TrimPrefix(gitlabLinkRegex.FindStringSubmatch(linkAndRel[0])[1], endpointAPIURL)
131+
rel := gitlabLinkRelRegex.FindStringSubmatch(linkAndRel[1])[1]
132+
finalLinkEntries = append(finalLinkEntries, "<"+link+">; rel=\""+rel+"\"")
133+
}
134+
finalLinkHeader := strings.Join(finalLinkEntries, ",")
135+
return finalLinkHeader
136+
}
137+
113138
type GitLabTransport struct{}
114139

115140
func (t *GitLabTransport) RoundTrip(r *http.Request) (*http.Response, error) {
141+
ctx := r.Context()
142+
config := getConfig(ctx)
116143
resp, err := http.DefaultTransport.RoundTrip(r)
117144
if err == nil {
118-
// remove CORS headers from GitHub and use our own
145+
// remove CORS headers from GitLab and use our own
119146
resp.Header.Del("Access-Control-Allow-Origin")
147+
linkHeader := resp.Header.Get("Link")
148+
if linkHeader != "" {
149+
endpoint := config.GitLab.Endpoint
150+
repo := url.PathEscape(config.GitLab.Repo)
151+
apiURL := singleJoiningSlash(endpoint, "/projects/"+repo)
152+
newLinkHeader := rewriteGitlabLinks(linkHeader, apiURL, "")
153+
resp.Header.Set("Link", newLinkHeader)
154+
}
120155
}
156+
121157
return resp, err
122158
}

conf/configuration.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import (
99
)
1010

1111
const DefaultGitHubEndpoint = "https://api.github.com"
12+
const DefaultGitLabEndpoint = "https://gitlab.com/api/v4"
1213

1314
type GitHubConfig struct {
1415
AccessToken string `envconfig:"ACCESS_TOKEN" json:"access_token,omitempty"`
@@ -105,5 +106,6 @@ func LoadConfig(filename string) (*Configuration, error) {
105106
func (config *Configuration) ApplyDefaults() {
106107
if config.GitHub.Endpoint == "" {
107108
config.GitHub.Endpoint = DefaultGitHubEndpoint
109+
config.GitLab.Endpoint = DefaultGitLabEndpoint
108110
}
109111
}

0 commit comments

Comments
 (0)