Commit aa73ef0a authored by Ash McKenzie's avatar Ash McKenzie Committed by Ash McKenzie

Geo: Support git-lfs push to secondary redirect

parent f0164abc
...@@ -3,18 +3,90 @@ module EE ...@@ -3,18 +3,90 @@ module EE
module GitHttpClientController module GitHttpClientController
extend ActiveSupport::Concern extend ActiveSupport::Concern
ALLOWED_CONTROLLER_AND_ACTIONS = {
'git_http' => %w{git_receive_pack},
'lfs_api' => %w{batch},
'lfs_locks_api' => %w{create unlock verify}
}.freeze
prepended do prepended do
before_action :redirect_push_to_primary, only: [:info_refs] before_action do
redirect_to(primary_full_url) if redirect?
end
end end
private private
def redirect_push_to_primary class RouteHelper
redirect_to(primary_full_url) if redirect_push_to_primary? attr_reader :controller_name, :action_name
def initialize(controller_name, action_name, params, allowed)
@controller_name = controller_name
@action_name = action_name
@params = params
@allowed = allowed
end end
def primary_full_url def match?(c_name, a_name)
File.join(::Gitlab::Geo.primary_node.url, request_fullpath_for_primary) controller_name == c_name && action_name == a_name
end
def allowed_match?
!!allowed[controller_name]&.include?(action_name)
end
def service_or_action_name
action_name == 'info_refs' ? params[:service] : action_name.dasherize
end
private
attr_reader :params, :allowed
end
class GitLFSHelper
MINIMUM_GIT_LFS_VERSION = '2.4.2'.freeze
def initialize(current_version)
@current_version = current_version
end
def version_ok?
return false unless current_version
::Gitlab::VersionInfo.parse(current_version) >= wanted_version
end
def incorrect_version_opts
{
json: { message: incorrect_version_message },
content_type: ::LfsRequest::CONTENT_TYPE,
status: 403
}
end
private
attr_reader :current_version
def wanted_version
::Gitlab::VersionInfo.parse(MINIMUM_GIT_LFS_VERSION)
end
def incorrect_version_message
_("You need git-lfs version %{min_git_lfs_version} (or greater) to
continue. Please visit https://git-lfs.github.com") %
{ min_git_lfs_version: MINIMUM_GIT_LFS_VERSION }
end
end
def route_helper
@route_helper ||= RouteHelper.new(controller_name, action_name, params,
ALLOWED_CONTROLLER_AND_ACTIONS)
end
def git_lfs_helper
@git_lfs_helper ||= GitLFSHelper.new(current_git_lfs_version)
end end
def request_fullpath_for_primary def request_fullpath_for_primary
...@@ -22,12 +94,30 @@ module EE ...@@ -22,12 +94,30 @@ module EE
request.fullpath.sub(relative_url_root, '') request.fullpath.sub(relative_url_root, '')
end end
def redirect_push_to_primary? def primary_full_url
git_push_request? && ::Gitlab::Geo.secondary_with_primary? File.join(::Gitlab::Geo.primary_node.url, request_fullpath_for_primary)
end
def current_git_lfs_version
request.headers['User-Agent']
end
def redirect?
::Gitlab::Geo.secondary_with_primary? && match? && !filtered?
end
def match?
route_helper.service_or_action_name == 'git-receive-pack' ||
route_helper.allowed_match?
end
def filtered?
if route_helper.match?('lfs_api', 'batch') && !git_lfs_helper.version_ok?
render(git_lfs_helper.incorrect_version_opts)
return true
end end
def git_push_request? false
git_command == 'git-receive-pack'
end end
end end
end end
......
...@@ -3,11 +3,27 @@ module EE ...@@ -3,11 +3,27 @@ module EE
module LfsApiController module LfsApiController
extend ::Gitlab::Utils::Override extend ::Gitlab::Utils::Override
override :batch_operation_disallowed?
def batch_operation_disallowed?
super_result = super
return true if super_result && !::Gitlab::Geo.enabled?
if super_result && ::Gitlab::Geo.enabled?
return true if !::Gitlab::Geo.primary? && !::Gitlab::Geo.secondary?
return true if ::Gitlab::Geo.secondary? && !::Gitlab::Geo.primary_node_configured?
end
false
end
override :lfs_read_only_message override :lfs_read_only_message
def lfs_read_only_message def lfs_read_only_message
return super unless ::Gitlab::Geo.secondary_with_primary? return super unless ::Gitlab::Geo.secondary_with_primary?
(_('You cannot write to a read-only secondary GitLab Geo instance. Please use %{link_to_primary_node} instead.') % { link_to_primary_node: geo_primary_default_url_to_repo(project) }).html_safe (_('You cannot write to a read-only secondary GitLab Geo instance.
Please use %{link_to_primary_node} instead.') %
{ link_to_primary_node: geo_primary_default_url_to_repo(project) }
).html_safe
end end
end end
end end
......
---
title: 'Geo: HTTP git-lfs push (upload) and locks (verify, lock and unlock) to secondary now redirects to the primary'
merge_request: 6109
author:
type: added
...@@ -5,6 +5,7 @@ describe "Git HTTP requests (Geo)" do ...@@ -5,6 +5,7 @@ describe "Git HTTP requests (Geo)" do
include ::EE::GeoHelpers include ::EE::GeoHelpers
include GitHttpHelpers include GitHttpHelpers
include WorkhorseHelpers include WorkhorseHelpers
using RSpec::Parameterized::TableSyntax
set(:project) { create(:project, :repository, :private) } set(:project) { create(:project, :repository, :private) }
set(:primary) { create(:geo_node, :primary) } set(:primary) { create(:geo_node, :primary) }
...@@ -116,6 +117,80 @@ describe "Git HTTP requests (Geo)" do ...@@ -116,6 +117,80 @@ describe "Git HTTP requests (Geo)" do
end end
end end
context 'git-lfs' do
context 'API' do
describe 'POST batch' do
def make_request
post url, {}, env.merge(extra_env)
end
let(:extra_env) { {} }
let(:incorrect_version_regex) { /You need git-lfs version 2.4.2/ }
let(:url) { "/#{project.full_path}.git/info/lfs/objects/batch" }
subject do
make_request
response
end
context 'with the correct git-lfs version' do
let(:extra_env) { { 'User-Agent' => 'git-lfs/2.4.2 (GitHub; darwin amd64; go 1.10.2)' } }
it 'redirects to the primary' do
is_expected.to have_gitlab_http_status(:redirect)
redirect_location = "#{primary.url.chomp('/')}#{url}"
expect(subject.header['Location']).to eq(redirect_location)
end
end
where(:description, :version) do
'outdated' | 'git-lfs/2.4.1'
'unknown' | 'git-lfs'
end
with_them do
context "with an #{description} git-lfs version" do
let(:extra_env) { { 'User-Agent' => "#{version} (GitHub; darwin amd64; go 1.10.2)" } }
it 'errors out' do
is_expected.to have_gitlab_http_status(:forbidden)
expect(json_response['message']).to match(incorrect_version_regex)
end
end
end
end
end
context 'Locks API' do
where(:description, :path, :args) do
'create' | 'info/lfs/locks' | {}
'verify' | 'info/lfs/locks/verify' | {}
'unlock' | 'info/lfs/locks/1/unlock' | { id: 1 }
end
with_them do
describe "POST #{description}" do
def make_request
post url, args, env
end
let(:url) { "/#{project.full_path}.git/#{path}" }
subject do
make_request
response
end
it 'redirects to the primary' do
is_expected.to have_gitlab_http_status(:redirect)
redirect_location = "#{primary.url.chomp('/')}#{url}"
expect(subject.header['Location']).to eq(redirect_location)
end
end
end
end
end
def valid_geo_env def valid_geo_env
geo_env(auth_token) geo_env(auth_token)
end end
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment