diff options
author | GitLab Bot <gitlab-bot@gitlab.com> | 2021-06-30 18:08:27 +0300 |
---|---|---|
committer | GitLab Bot <gitlab-bot@gitlab.com> | 2021-06-30 18:08:27 +0300 |
commit | 9376fdc13edb5fb822431df943fa088b6a273316 (patch) | |
tree | f12ac6debd2c3efc59f1629062628f40996e752f /workhorse/internal/api | |
parent | 7c28a677895df5195ed6342921934734646c90c9 (diff) |
Add latest changes from gitlab-org/gitlab@master
Diffstat (limited to 'workhorse/internal/api')
-rw-r--r-- | workhorse/internal/api/api.go | 46 | ||||
-rw-r--r-- | workhorse/internal/api/api_test.go | 74 |
2 files changed, 120 insertions, 0 deletions
diff --git a/workhorse/internal/api/api.go b/workhorse/internal/api/api.go index 5dae6eb01bb..db1c4cbbc27 100644 --- a/workhorse/internal/api/api.go +++ b/workhorse/internal/api/api.go @@ -3,6 +3,7 @@ package api import ( "bytes" "encoding/json" + "errors" "fmt" "io" "net/http" @@ -29,6 +30,8 @@ const ( ResponseContentType = "application/vnd.gitlab-workhorse+json" failureResponseLimit = 32768 + + geoProxyEndpointPath = "/api/v4/geo/proxy" ) type API struct { @@ -37,6 +40,8 @@ type API struct { Version string } +var ErrNotGeoSecondary = errors.New("this is not a Geo secondary site") + var ( requestsCounter = promauto.NewCounterVec( prometheus.CounterOpts{ @@ -61,6 +66,10 @@ func NewAPI(myURL *url.URL, version string, roundTripper http.RoundTripper) *API } } +type GeoProxyEndpointResponse struct { + GeoProxyURL string `json:"geo_proxy_url"` +} + type HandleFunc func(http.ResponseWriter, *http.Request, *Response) type MultipartUploadParams struct { @@ -389,3 +398,40 @@ func bufferResponse(r io.Reader) (*bytes.Buffer, error) { func validResponseContentType(resp *http.Response) bool { return helper.IsContentType(ResponseContentType, resp.Header.Get("Content-Type")) } + +// TODO: Cache the result of the API requests https://gitlab.com/gitlab-org/gitlab/-/issues/329671 +func (api *API) GetGeoProxyURL() (*url.URL, error) { + geoProxyApiUrl := *api.URL + geoProxyApiUrl.Path, geoProxyApiUrl.RawPath = joinURLPath(api.URL, geoProxyEndpointPath) + geoProxyApiReq := &http.Request{ + Method: "GET", + URL: &geoProxyApiUrl, + Header: make(http.Header), + } + + httpResponse, err := api.doRequestWithoutRedirects(geoProxyApiReq) + if err != nil { + return nil, fmt.Errorf("GetGeoProxyURL: do request: %v", err) + } + defer httpResponse.Body.Close() + + if httpResponse.StatusCode != http.StatusOK { + return nil, fmt.Errorf("GetGeoProxyURL: Received HTTP status code: %v", httpResponse.StatusCode) + } + + response := &GeoProxyEndpointResponse{} + if err := json.NewDecoder(httpResponse.Body).Decode(response); err != nil { + return nil, fmt.Errorf("GetGeoProxyURL: decode response: %v", err) + } + + if response.GeoProxyURL == "" { + return nil, ErrNotGeoSecondary + } + + geoProxyURL, err := url.Parse(response.GeoProxyURL) + if err != nil { + return nil, fmt.Errorf("GetGeoProxyURL: Could not parse Geo proxy URL: %v, err: %v", response.GeoProxyURL, err) + } + + return geoProxyURL, nil +} diff --git a/workhorse/internal/api/api_test.go b/workhorse/internal/api/api_test.go new file mode 100644 index 00000000000..5ab677c4233 --- /dev/null +++ b/workhorse/internal/api/api_test.go @@ -0,0 +1,74 @@ +package api + +import ( + "fmt" + "net/http" + "net/http/httptest" + "net/url" + "regexp" + "testing" + + "github.com/stretchr/testify/require" + "gitlab.com/gitlab-org/labkit/log" + + "gitlab.com/gitlab-org/gitlab-workhorse/internal/helper" + "gitlab.com/gitlab-org/gitlab-workhorse/internal/secret" + "gitlab.com/gitlab-org/gitlab-workhorse/internal/testhelper" + "gitlab.com/gitlab-org/gitlab-workhorse/internal/upstream/roundtripper" +) + +func TestGetGeoProxyURLWhenGeoSecondary(t *testing.T) { + geoProxyURL, err := getGeoProxyURLGivenResponse(t, `{"geo_proxy_url":"http://primary"}`) + + require.NoError(t, err) + require.NotNil(t, geoProxyURL) + require.Equal(t, "http://primary", geoProxyURL.String()) +} + +func TestGetGeoProxyURLWhenGeoPrimaryOrNonGeo(t *testing.T) { + geoProxyURL, err := getGeoProxyURLGivenResponse(t, "{}") + + require.Error(t, err) + require.Equal(t, ErrNotGeoSecondary, err) + require.Nil(t, geoProxyURL) +} + +func getGeoProxyURLGivenResponse(t *testing.T, givenInternalApiResponse string) (*url.URL, error) { + t.Helper() + ts := testRailsServer(regexp.MustCompile(`/api/v4/geo/proxy`), 200, givenInternalApiResponse) + defer ts.Close() + backend := helper.URLMustParse(ts.URL) + version := "123" + rt := roundtripper.NewTestBackendRoundTripper(backend) + testhelper.ConfigureSecret() + + apiClient := NewAPI(backend, version, rt) + + geoProxyURL, err := apiClient.GetGeoProxyURL() + + return geoProxyURL, err +} + +func testRailsServer(url *regexp.Regexp, code int, body string) *httptest.Server { + return testhelper.TestServerWithHandler(url, func(w http.ResponseWriter, r *http.Request) { + // return a 204 No Content response if we don't receive the JWT header + if r.Header.Get(secret.RequestHeader) == "" { + w.WriteHeader(204) + return + } + + w.Header().Set("Content-Type", ResponseContentType) + + logEntry := log.WithFields(log.Fields{ + "method": r.Method, + "url": r.URL, + }) + logEntryWithCode := logEntry.WithField("code", code) + + // Write pure string + logEntryWithCode.Info("UPSTREAM") + + w.WriteHeader(code) + fmt.Fprint(w, body) + }) +} |