Skip to content

Commit 1aff8bf

Browse files
authored
Merge pull request netlify#17 from Benaiah/gitlab-fixes
Gitlab support fixes
2 parents fcbbc42 + a72049a commit 1aff8bf

File tree

3 files changed

+62
-6
lines changed

3 files changed

+62
-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: 58 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,58 @@ 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 rewriteGitlabLinkEntry(linkEntry, endpointAPIURL, proxyAPIURL string) string {
126+
linkAndRel := strings.Split(strings.TrimSpace(linkEntry), ";")
127+
if len(linkAndRel) != 2 {
128+
return linkEntry
129+
}
130+
131+
linkMatch := gitlabLinkRegex.FindStringSubmatch(linkAndRel[0])
132+
if len(linkMatch) < 2 {
133+
return linkEntry
134+
}
135+
136+
relMatch := gitlabLinkRelRegex.FindStringSubmatch(linkAndRel[1])
137+
if len(relMatch) < 2 {
138+
return linkEntry
139+
}
140+
141+
proxiedLink := proxyAPIURL + strings.TrimPrefix(linkMatch[1], endpointAPIURL)
142+
rel := relMatch[1]
143+
return "<" + proxiedLink + ">; rel=\"" + rel + "\""
144+
}
145+
146+
func rewriteGitlabLinks(linkHeader, endpointAPIURL, proxyAPIURL string) string {
147+
linkEntries := strings.Split(linkHeader, ",")
148+
finalLinkEntries := make([]string, len(linkEntries), len(linkEntries))
149+
for i, linkEntry := range linkEntries {
150+
finalLinkEntries[i] = rewriteGitlabLinkEntry(linkEntry, endpointAPIURL, proxyAPIURL)
151+
}
152+
finalLinkHeader := strings.Join(finalLinkEntries, ",")
153+
return finalLinkHeader
154+
}
155+
113156
type GitLabTransport struct{}
114157

115158
func (t *GitLabTransport) RoundTrip(r *http.Request) (*http.Response, error) {
159+
ctx := r.Context()
160+
config := getConfig(ctx)
116161
resp, err := http.DefaultTransport.RoundTrip(r)
117162
if err == nil {
118-
// remove CORS headers from GitHub and use our own
163+
// remove CORS headers from GitLab and use our own
119164
resp.Header.Del("Access-Control-Allow-Origin")
165+
linkHeader := resp.Header.Get("Link")
166+
if linkHeader != "" {
167+
endpoint := config.GitLab.Endpoint
168+
repo := url.PathEscape(config.GitLab.Repo)
169+
apiURL := singleJoiningSlash(endpoint, "/projects/"+repo)
170+
newLinkHeader := rewriteGitlabLinks(linkHeader, apiURL, "")
171+
resp.Header.Set("Link", newLinkHeader)
172+
}
120173
}
174+
121175
return resp, err
122176
}

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)