Commit 9decb78c authored by Ash McKenzie's avatar Ash McKenzie

New Gitlab::Geo::GitPushSSHProxy

parent 58d7871a
# frozen_string_literal: true
module Gitlab
module Geo
class GitPushSSHProxy
HTTP_READ_TIMEOUT = 10
HTTP_SUCCESS_CODE = '200'.freeze
MustBeASecondaryNode = Class.new(StandardError)
def initialize(data)
@data = data
end
def info_refs
ensure_secondary!
url = "#{primary_repo}/info/refs?service=git-receive-pack"
headers = {
'Content-Type' => 'application/x-git-upload-pack-request'
}
resp = get(url, headers)
return resp unless resp.code == HTTP_SUCCESS_CODE
resp.body = remove_http_service_fragment_from(resp.body)
resp
end
def push(info_refs_response)
ensure_secondary!
url = "#{primary_repo}/git-receive-pack"
headers = {
'Content-Type' => 'application/x-git-receive-pack-request',
'Accept' => 'application/x-git-receive-pack-result'
}
post(url, info_refs_response, headers)
end
private
attr_reader :data
def primary_repo
@primary_repo ||= data['primary_repo']
end
def gl_id
@gl_id ||= data['gl_id']
end
def base_headers
@base_headers ||= {
'Geo-GL-Id' => gl_id,
'Authorization' => Gitlab::Geo::BaseRequest.new.authorization
}
end
def get(url, headers)
request(url, Net::HTTP::Get, headers)
end
def post(url, body, headers)
request(url, Net::HTTP::Post, headers, body: body)
end
def request(url, klass, headers, body: nil)
headers = base_headers.merge(headers)
uri = URI.parse(url)
req = klass.new(uri, headers)
req.body = body if body
http = Net::HTTP.new(uri.hostname, uri.port)
http.read_timeout = HTTP_READ_TIMEOUT
http.use_ssl = true if uri.is_a?(URI::HTTPS)
http.start { http.request(req) }
end
def remove_http_service_fragment_from(body)
# HTTP(S) and SSH responses are very similar, except for the fragment below.
# As we're performing a git HTTP(S) request here, we'll get a HTTP(s)
# suitable git response. However, we're executing in the context of an
# SSH session so we need to make the response suitable for what git over
# SSH expects.
#
# See Downloading Data > HTTP(S) section at:
# https://git-scm.com/book/en/v2/Git-Internals-Transfer-Protocols
body.gsub(/\A001f# service=git-receive-pack\n0000/, '')
end
def ensure_secondary!
raise MustBeASecondaryNode, 'Node is not a secondary' unless Gitlab::Geo.secondary_with_primary?
end
end
end
end
# frozen_string_literal: true
require 'spec_helper'
describe Gitlab::Geo::GitPushSSHProxy, :geo do
include ::EE::GeoHelpers
set(:primary_node) { create(:geo_node, :primary) }
set(:secondary_node) { create(:geo_node) }
let(:current_node) { nil }
let(:project) { create(:project, :repository) }
let(:user) { project.creator }
let(:key) { create(:key, user: user) }
let(:base_request) { double(Gitlab::Geo::BaseRequest.new.authorization) }
let(:info_refs_body_short) do
"008f43ba78b7912f7bf7ef1d7c3b8a0e5ae14a759dfa refs/heads/masterreport-status delete-refs side-band-64k quiet atomic ofs-delta agent=git/2.18.0
0000"
end
let(:base_headers) do
{
'Geo-GL-Id' => "key-#{key.id}",
'Authorization' => 'secret'
}
end
let(:data) do
{
'gl_id' => "key-#{key.id}",
'primary_repo' => "#{primary_node.url}#{project.repository.full_path}.git"
}
end
subject { described_class.new(data) }
before do
stub_current_geo_node(current_node)
allow(Gitlab::Geo::BaseRequest).to receive(:new).and_return(base_request)
allow(base_request).to receive(:authorization).and_return('secret')
end
describe '#info_refs' do
context 'against primary node' do
let(:current_node) { primary_node }
it 'raises an exception' do
expect do
subject.info_refs
end.to raise_error(described_class::MustBeASecondaryNode)
end
end
context 'against secondary node' do
let(:current_node) { secondary_node }
let(:full_info_refs_url) { "#{primary_node.url}#{project.full_path}.git/info/refs?service=git-receive-pack" }
let(:info_refs_headers) { base_headers.merge('Content-Type' => 'application/x-git-upload-pack-request') }
let(:info_refs_http_body_full) do
"001f# service=git-receive-pack
0000#{info_refs_body_short}"
end
before do
stub_request(:get, full_info_refs_url).to_return(status: 200, body: info_refs_http_body_full, headers: info_refs_headers)
end
it 'returns a Net::HTTPOK' do
expect(subject.info_refs).to be_a(Net::HTTPOK)
end
it 'returns a modified body' do
expect(subject.info_refs.body).to eql(info_refs_body_short)
end
end
end
describe '#push' do
context 'against primary node' do
let(:current_node) { primary_node }
it 'raises an exception' do
expect do
subject.push(info_refs_body_short)
end.to raise_error(described_class::MustBeASecondaryNode)
end
end
context 'against secondary node' do
let(:current_node) { secondary_node }
let(:full_git_receive_pack_url) { "#{primary_node.url}#{project.full_path}.git/git-receive-pack" }
let(:push_headers) do
base_headers.merge(
'Content-Type' => 'application/x-git-receive-pack-request',
'Accept' => 'application/x-git-receive-pack-result'
)
end
before do
stub_request(:post, full_git_receive_pack_url).to_return(status: 201, body: info_refs_body_short, headers: push_headers)
end
it 'returns a Net::HTTPCreated' do
expect(subject.push(info_refs_body_short)).to be_a(Net::HTTPCreated)
end
end
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