Commit 58df68e3 authored by Alejandro Rodríguez's avatar Alejandro Rodríguez

Support new /internal/post-receive API endpoint

parent e358cf25
v5.9.0
- Support new /internal/post-receive API endpoint for post-receive operations
v5.8.1 v5.8.1
- Support old versions of ruby without monotonic clock - Support old versions of ruby without monotonic clock
......
...@@ -11,6 +11,7 @@ require_relative 'httpunix' ...@@ -11,6 +11,7 @@ require_relative 'httpunix'
class GitlabNet class GitlabNet
class ApiUnreachableError < StandardError; end class ApiUnreachableError < StandardError; end
class NotFound < StandardError; end
CHECK_TIMEOUT = 5 CHECK_TIMEOUT = 5
READ_TIMEOUT = 300 READ_TIMEOUT = 300
...@@ -112,6 +113,19 @@ class GitlabNet ...@@ -112,6 +113,19 @@ class GitlabNet
false false
end end
def post_receive(gl_repository, identifier, changes)
params = {
gl_repository: gl_repository,
identifier: identifier,
changes: changes
}
resp = post("#{host}/post_receive", params)
raise NotFound if resp.code == '404'
JSON.parse(resp.body) if resp.code == '200'
end
def redis_client def redis_client
redis_config = config.redis redis_config = config.redis
database = redis_config['database'] || 0 database = redis_config['database'] || 0
......
...@@ -20,29 +20,20 @@ class GitlabPostReceive ...@@ -20,29 +20,20 @@ class GitlabPostReceive
end end
def exec def exec
result = update_redis response = GitlabMetrics.measure("post-receive") do
api.post_receive(gl_repository, @actor, changes)
end
begin return false unless response
broadcast_message = GitlabMetrics.measure("broadcast-message") do
api.broadcast_message
end
if broadcast_message.has_key?("message") print_broadcast_message(response['broadcast_message']) if response['broadcast_message']
puts print_merge_request_links(response['merge_request_urls']) if response['merge_request_urls']
print_broadcast_message(broadcast_message["message"])
end
merge_request_urls = GitlabMetrics.measure("merge-request-urls") do response['reference_counter_decreased']
api.merge_request_urls(@gl_repository, @repo_path, @changes) rescue GitlabNet::ApiUnreachableError
end false
print_merge_request_links(merge_request_urls) rescue GitlabNet::NotFound
fallback_post_receive
api.notify_post_receive(gl_repository, repo_path)
rescue GitlabNet::ApiUnreachableError
nil
end
result && GitlabReferenceCounter.new(repo_path).decrease
end end
protected protected
...@@ -89,6 +80,7 @@ class GitlabPostReceive ...@@ -89,6 +80,7 @@ class GitlabPostReceive
# message.scan returns a nested array of capture groups, so flatten. # message.scan returns a nested array of capture groups, so flatten.
lines = message.scan(/(.{,#{text_width}})(?:\s|$)/)[0...-1].flatten lines = message.scan(/(.{,#{text_width}})(?:\s|$)/)[0...-1].flatten
puts
puts "=" * total_width puts "=" * total_width
puts puts
...@@ -127,4 +119,31 @@ class GitlabPostReceive ...@@ -127,4 +119,31 @@ class GitlabPostReceive
false false
end end
end end
private
def fallback_post_receive
result = update_redis
begin
broadcast_message = GitlabMetrics.measure("broadcast-message") do
api.broadcast_message
end
if broadcast_message.has_key?("message")
print_broadcast_message(broadcast_message["message"])
end
merge_request_urls = GitlabMetrics.measure("merge-request-urls") do
api.merge_request_urls(@gl_repository, @repo_path, @changes)
end
print_merge_request_links(merge_request_urls)
api.notify_post_receive(gl_repository, repo_path)
rescue GitlabNet::ApiUnreachableError
nil
end
result && GitlabReferenceCounter.new(repo_path).decrease
end
end end
...@@ -126,6 +126,46 @@ describe GitlabNet, vcr: true do ...@@ -126,6 +126,46 @@ describe GitlabNet, vcr: true do
end end
end end
describe :post_receive do
let(:gl_repository) { "project-1" }
let(:changes) { "123456 789012 refs/heads/test\n654321 210987 refs/tags/tag" }
let(:params) do
{ gl_repository: gl_repository, identifier: key, changes: changes }
end
let(:merge_request_urls) do
[{
"branch_name" => "test",
"url" => "http://localhost:3000/gitlab-org/gitlab-test/merge_requests/7",
"new_merge_request" => false
}]
end
subject { gitlab_net.post_receive(gl_repository, key, changes) }
it 'sends the correct parameters' do
Net::HTTP::Post.any_instance.should_receive(:set_form_data).with(hash_including(params))
VCR.use_cassette("post-receive") do
subject
end
end
it 'calls /internal/post-receive' do
VCR.use_cassette("post-receive") do
expect(subject['merge_request_urls']).to eq(merge_request_urls)
expect(subject['broadcast_message']).to eq('Message')
expect(subject['reference_counter_decreased']).to eq(true)
end
end
it 'throws a NotFound error when post-receive is not available' do
VCR.use_cassette("post-receive-not-found") do
expect { subject }.to raise_error(GitlabNet::NotFound)
end
end
end
describe :authorized_key do describe :authorized_key do
let (:ssh_key) { "rsa-key" } let (:ssh_key) { "rsa-key" }
......
...@@ -4,173 +4,164 @@ require 'gitlab_post_receive' ...@@ -4,173 +4,164 @@ require 'gitlab_post_receive'
describe GitlabPostReceive do describe GitlabPostReceive do
let(:repository_path) { "/home/git/repositories" } let(:repository_path) { "/home/git/repositories" }
let(:repo_name) { 'dzaporozhets/gitlab-ci' } let(:repo_name) { 'dzaporozhets/gitlab-ci' }
let(:actor) { 'key-123' } let(:actor) { 'key-123' }
let(:changes) { "123456 789012 refs/heads/tést\n654321 210987 refs/tags/tag" } let(:changes) { "123456 789012 refs/heads/tést\n654321 210987 refs/tags/tag" }
let(:wrongly_encoded_changes) { changes.encode("ISO-8859-1").force_encoding("UTF-8") } let(:wrongly_encoded_changes) { changes.encode("ISO-8859-1").force_encoding("UTF-8") }
let(:base64_changes) { Base64.encode64(wrongly_encoded_changes) } let(:base64_changes) { Base64.encode64(wrongly_encoded_changes) }
let(:repo_path) { File.join(repository_path, repo_name) + ".git" } let(:repo_path) { File.join(repository_path, repo_name) + ".git" }
let(:gl_repository) { "project-1" } let(:gl_repository) { "project-1" }
let(:gitlab_post_receive) { GitlabPostReceive.new(gl_repository, repo_path, actor, wrongly_encoded_changes) } let(:gitlab_post_receive) { GitlabPostReceive.new(gl_repository, repo_path, actor, wrongly_encoded_changes) }
let(:message) { "test " * 10 + "message " * 10 } let(:broadcast_message) { "test " * 10 + "message " * 10 }
let(:redis_client) { double('redis_client') } let(:redis_client) { double('redis_client') }
let(:enqueued_at) { Time.new(2016, 6, 23, 6, 59) } let(:enqueued_at) { Time.new(2016, 6, 23, 6, 59) }
let(:new_merge_request_urls) do
[{
'branch_name' => 'new_branch',
'url' => 'http://localhost/dzaporozhets/gitlab-ci/merge_requests/new?merge_request%5Bsource_branch%5D=new_branch',
'new_merge_request' => true
}]
end
let(:existing_merge_request_urls) do
[{
'branch_name' => 'feature_branch',
'url' => 'http://localhost/dzaporozhets/gitlab-ci/merge_requests/1',
'new_merge_request' => false
}]
end
before do before do
$logger = double('logger').as_null_object # Global vars are bad
GitlabConfig.any_instance.stub(repos_path: repository_path) GitlabConfig.any_instance.stub(repos_path: repository_path)
GitlabNet.any_instance.stub(broadcast_message: { })
GitlabNet.any_instance.stub(:merge_request_urls).with(gl_repository, repo_path, wrongly_encoded_changes) { [] }
GitlabNet.any_instance.stub(notify_post_receive: true)
expect(Time).to receive(:now).and_return(enqueued_at)
end end
describe "#exec" do describe "#exec" do
before do context 'when the new post_receive API endpoint is not available' do
allow_any_instance_of(GitlabNet).to receive(:redis_client).and_return(redis_client) before do
allow_any_instance_of(GitlabReferenceCounter).to receive(:redis_client).and_return(redis_client) GitlabNet.any_instance.stub(broadcast_message: { })
allow(redis_client).to receive(:get).and_return(1) GitlabNet.any_instance.stub(:merge_request_urls).with(gl_repository, repo_path, wrongly_encoded_changes) { [] }
allow(redis_client).to receive(:incr).and_return(true) GitlabNet.any_instance.stub(notify_post_receive: true)
allow(redis_client).to receive(:decr).and_return(0)
allow(redis_client).to receive(:rpush).and_return(true) allow_any_instance_of(GitlabNet).to receive(:post_receive).and_raise(GitlabNet::NotFound)
end allow_any_instance_of(GitlabNet).to receive(:redis_client).and_return(redis_client)
allow_any_instance_of(GitlabReferenceCounter).to receive(:redis_client).and_return(redis_client)
allow(redis_client).to receive(:get).and_return(1)
allow(redis_client).to receive(:incr).and_return(true)
allow(redis_client).to receive(:decr).and_return(0)
allow(redis_client).to receive(:rpush).and_return(true)
expect(Time).to receive(:now).and_return(enqueued_at)
end
context 'Without broad cast message' do context 'Without broad cast message' do
context 'pushing new branch' do context 'pushing new branch' do
before do before do
GitlabNet.any_instance.stub(:merge_request_urls).with(gl_repository, repo_path, wrongly_encoded_changes) do GitlabNet.any_instance.stub(:merge_request_urls).with(gl_repository, repo_path, wrongly_encoded_changes) do
[{ new_merge_request_urls
"branch_name" => "new_branch", end
"url" => "http://localhost/dzaporozhets/gitlab-ci/merge_requests/new?merge_request%5Bsource_branch%5D=new_branch", end
"new_merge_request" => true
}] it "prints the new merge request url" do
assert_new_mr_printed(gitlab_post_receive)
gitlab_post_receive.exec
end end
end end
it "prints the new merge request url" do context 'pushing existing branch with merge request created' do
expect(redis_client).to receive(:rpush) before do
GitlabNet.any_instance.stub(:merge_request_urls).with(gl_repository, repo_path, wrongly_encoded_changes) do
existing_merge_request_urls
end
end
expect(gitlab_post_receive).to receive(:puts).ordered it "prints the view merge request url" do
expect(gitlab_post_receive).to receive(:puts).with( assert_existing_mr_printed(gitlab_post_receive)
"To create a merge request for new_branch, visit:"
).ordered
expect(gitlab_post_receive).to receive(:puts).with(
" http://localhost/dzaporozhets/gitlab-ci/merge_requests/new?merge_request%5Bsource_branch%5D=new_branch"
).ordered
expect(gitlab_post_receive).to receive(:puts).ordered
gitlab_post_receive.exec gitlab_post_receive.exec
end
end end
end end
context 'pushing existing branch with merge request created' do context 'show broadcast message and merge request link' do
before do before do
GitlabNet.any_instance.stub(:merge_request_urls).with(gl_repository, repo_path, wrongly_encoded_changes) do GitlabNet.any_instance.stub(:merge_request_urls).with(gl_repository, repo_path, wrongly_encoded_changes) do
[{ new_merge_request_urls
"branch_name" => "feature_branch",
"url" => "http://localhost/dzaporozhets/gitlab-ci/merge_requests/1",
"new_merge_request" => false
}]
end end
GitlabNet.any_instance.stub(broadcast_message: { "message" => broadcast_message })
end end
it "prints the view merge request url" do it 'prints the broadcast message and create new merge request link' do
expect(redis_client).to receive(:rpush) assert_broadcast_message_printed(gitlab_post_receive)
assert_new_mr_printed(gitlab_post_receive)
expect(gitlab_post_receive).to receive(:puts).ordered
expect(gitlab_post_receive).to receive(:puts).with(
"View merge request for feature_branch:"
).ordered
expect(gitlab_post_receive).to receive(:puts).with(
" http://localhost/dzaporozhets/gitlab-ci/merge_requests/1"
).ordered
expect(gitlab_post_receive).to receive(:puts).ordered
gitlab_post_receive.exec gitlab_post_receive.exec
end end
end end
end
context 'show broadcast message and merge request link' do context 'Sidekiq jobs' do
before do it "pushes a Sidekiq job onto the queue" do
GitlabNet.any_instance.stub(:merge_request_urls).with(gl_repository, repo_path, wrongly_encoded_changes) do expect(redis_client).to receive(:rpush).with(
[{ 'resque:gitlab:queue:post_receive',
"branch_name" => "new_branch", %Q/{"class":"PostReceive","args":["#{gl_repository}","#{actor}",#{base64_changes.inspect}],"jid":"#{gitlab_post_receive.jid}","enqueued_at":#{enqueued_at.to_f}}/
"url" => "http://localhost/dzaporozhets/gitlab-ci/merge_requests/new?merge_request%5Bsource_branch%5D=new_branch", ).and_return(true)
"new_merge_request" => true
}]
end
GitlabNet.any_instance.stub(broadcast_message: { "message" => message })
end
it 'prints the broadcast message and create new merge request link' do gitlab_post_receive.exec
expect(redis_client).to receive(:rpush) end
expect(gitlab_post_receive).to receive(:puts).ordered
expect(gitlab_post_receive).to receive(:puts).with(
"========================================================================"
).ordered
expect(gitlab_post_receive).to receive(:puts).ordered
expect(gitlab_post_receive).to receive(:puts).with(
" test test test test test test test test test test message message"
).ordered
expect(gitlab_post_receive).to receive(:puts).with(
" message message message message message message message message"
).ordered
expect(gitlab_post_receive).to receive(:puts).ordered
expect(gitlab_post_receive).to receive(:puts).with(
"========================================================================"
).ordered
expect(gitlab_post_receive).to receive(:puts).ordered
expect(gitlab_post_receive).to receive(:puts).with(
"To create a merge request for new_branch, visit:"
).ordered
expect(gitlab_post_receive).to receive(:puts).with(
" http://localhost/dzaporozhets/gitlab-ci/merge_requests/new?merge_request%5Bsource_branch%5D=new_branch"
).ordered
expect(gitlab_post_receive).to receive(:puts).ordered
gitlab_post_receive.exec
end
end
context 'Sidekiq jobs' do context 'when gl_repository is nil' do
it "pushes a Sidekiq job onto the queue" do let(:gl_repository) { nil }
expect(redis_client).to receive(:rpush).with(
'resque:gitlab:queue:post_receive',
%Q/{"class":"PostReceive","args":["#{gl_repository}","#{actor}",#{base64_changes.inspect}],"jid":"#{gitlab_post_receive.jid}","enqueued_at":#{enqueued_at.to_f}}/
).and_return(true)
gitlab_post_receive.exec it "pushes a Sidekiq job with the repository path" do
end expect(redis_client).to receive(:rpush).with(
'resque:gitlab:queue:post_receive',
%Q/{"class":"PostReceive","args":["#{repo_path}","#{actor}",#{base64_changes.inspect}],"jid":"#{gitlab_post_receive.jid}","enqueued_at":#{enqueued_at.to_f}}/
).and_return(true)
context 'when gl_repository is nil' do gitlab_post_receive.exec
let(:gl_repository) { nil } end
end
end
it "pushes a Sidekiq job with the repository path" do context 'reference counter' do
expect(redis_client).to receive(:rpush).with( it 'decreases the reference counter for the project' do
'resque:gitlab:queue:post_receive', expect_any_instance_of(GitlabReferenceCounter).to receive(:decrease).and_return(true)
%Q/{"class":"PostReceive","args":["#{repo_path}","#{actor}",#{base64_changes.inspect}],"jid":"#{gitlab_post_receive.jid}","enqueued_at":#{enqueued_at.to_f}}/
).and_return(true)
gitlab_post_receive.exec gitlab_post_receive.exec
end end
context "when the redis command succeeds" do
before do
allow(redis_client).to receive(:decr).and_return(0)
end
it "returns true" do
expect(gitlab_post_receive.exec).to eq(true)
end
end
context "when the redis command fails" do
before do
allow(redis_client).to receive(:decr).and_raise('Fail')
end
it "returns false" do
expect(gitlab_post_receive.exec).to eq(false)
end
end
end end
end
context 'reference counter' do context 'post_receive notification' do
it 'decreases the reference counter for the project' do it 'calls the api to notify the execution of the hook' do
expect_any_instance_of(GitlabReferenceCounter).to receive(:decrease).and_return(true) expect_any_instance_of(GitlabNet).to receive(:notify_post_receive).
with(gl_repository, repo_path)
gitlab_post_receive.exec gitlab_post_receive.exec
end
end end
context "when the redis command succeeds" do context "when the redis command succeeds" do
before do before do
allow(redis_client).to receive(:decr).and_return(0) allow(redis_client).to receive(:rpush).and_return(true)
end end
it "returns true" do it "returns true" do
...@@ -180,7 +171,7 @@ describe GitlabPostReceive do ...@@ -180,7 +171,7 @@ describe GitlabPostReceive do
context "when the redis command fails" do context "when the redis command fails" do
before do before do
allow(redis_client).to receive(:decr).and_raise('Fail') allow(redis_client).to receive(:rpush).and_raise('Fail')
end end
it "returns false" do it "returns false" do
...@@ -189,35 +180,76 @@ describe GitlabPostReceive do ...@@ -189,35 +180,76 @@ describe GitlabPostReceive do
end end
end end
context 'post_receive notification' do context 'when the new post_receive API endpoint is available' do
let(:response) { { 'reference_counter_decreased' => true } }
it 'calls the api to notify the execution of the hook' do it 'calls the api to notify the execution of the hook' do
expect_any_instance_of(GitlabNet).to receive(:notify_post_receive). expect_any_instance_of(GitlabNet).to receive(:post_receive).and_return(response)
with(gl_repository, repo_path)
gitlab_post_receive.exec expect(gitlab_post_receive.exec).to eq(true)
end end
end
context "when the redis command succeeds" do context 'merge request urls and broadcast messages' do
let(:response) do
{
'reference_counter_decreased' => true,
'merge_request_urls' => new_merge_request_urls,
'broadcast_message' => broadcast_message
}
end
before do it 'prints the merge request urls and broadcast message' do
allow(redis_client).to receive(:rpush).and_return(true) expect_any_instance_of(GitlabNet).to receive(:post_receive).and_return(response)
end assert_broadcast_message_printed(gitlab_post_receive)
assert_new_mr_printed(gitlab_post_receive)
it "returns true" do expect(gitlab_post_receive.exec).to eq(true)
expect(gitlab_post_receive.exec).to eq(true) end
end end
end end
end
context "when the redis command fails" do private
def assert_new_mr_printed(gitlab_post_receive)
expect(gitlab_post_receive).to receive(:puts).ordered
expect(gitlab_post_receive).to receive(:puts).with(
"To create a merge request for new_branch, visit:"
).ordered
expect(gitlab_post_receive).to receive(:puts).with(
" http://localhost/dzaporozhets/gitlab-ci/merge_requests/new?merge_request%5Bsource_branch%5D=new_branch"
).ordered
expect(gitlab_post_receive).to receive(:puts).ordered
end
before do def assert_existing_mr_printed(gitlab_post_receive)
allow(redis_client).to receive(:rpush).and_raise('Fail') expect(gitlab_post_receive).to receive(:puts).ordered
end expect(gitlab_post_receive).to receive(:puts).with(
"View merge request for feature_branch:"
).ordered
expect(gitlab_post_receive).to receive(:puts).with(
" http://localhost/dzaporozhets/gitlab-ci/merge_requests/1"
).ordered
expect(gitlab_post_receive).to receive(:puts).ordered
end
it "returns false" do def assert_broadcast_message_printed(gitlab_post_receive)
expect(gitlab_post_receive.exec).to eq(false) expect(gitlab_post_receive).to receive(:puts).ordered
end expect(gitlab_post_receive).to receive(:puts).with(
end "========================================================================"
).ordered
expect(gitlab_post_receive).to receive(:puts).ordered
expect(gitlab_post_receive).to receive(:puts).with(
" test test test test test test test test test test message message"
).ordered
expect(gitlab_post_receive).to receive(:puts).with(
" message message message message message message message message"
).ordered
expect(gitlab_post_receive).to receive(:puts).ordered
expect(gitlab_post_receive).to receive(:puts).with(
"========================================================================"
).ordered
end end
end end
---
http_interactions:
- request:
method: post
uri: http://localhost:3000/api/v4/internal/post_receive
body:
encoding: US-ASCII
string: gl_repository=project-1&identifier=key-1&changes=123456+789012+refs%2Fheads%2Ftest%0A654321+210987+refs%2Ftags%2Ftag&secret_token=0a3938d9d95d807e94d937af3a4fbbea%0A
headers:
Accept-Encoding:
- gzip;q=1.0,deflate;q=0.6,identity;q=0.3
Accept:
- "*/*"
User-Agent:
- Ruby
Content-Type:
- application/x-www-form-urlencoded
response:
status:
code: 404
message: Not Found
headers:
Cache-Control:
- no-cache
Content-Length:
- '25'
Content-Type:
- application/json
Date:
- Wed, 30 Aug 2017 22:24:37 GMT
Vary:
- Origin
X-Request-Id:
- bbfdb1ed-99dc-4246-a606-3074ffd5d87b
X-Runtime:
- '0.459681'
body:
encoding: UTF-8
string: '{"error":"404 Not Found"}'
http_version:
recorded_at: Wed, 30 Aug 2017 22:24:37 GMT
recorded_with: VCR 2.4.0
---
http_interactions:
- request:
method: post
uri: http://localhost:3000/api/v4/internal/post_receive
body:
encoding: US-ASCII
string: gl_repository=project-1&identifier=key-1&changes=123456+789012+refs%2Fheads%2Ftest%0A654321+210987+refs%2Ftags%2Ftag&secret_token=0a3938d9d95d807e94d937af3a4fbbea%0A
headers:
Accept-Encoding:
- gzip;q=1.0,deflate;q=0.6,identity;q=0.3
Accept:
- "*/*"
User-Agent:
- Ruby
Content-Type:
- application/x-www-form-urlencoded
response:
status:
code: 200
message: OK
headers:
Cache-Control:
- max-age=0, private, must-revalidate
Content-Length:
- '208'
Content-Type:
- application/json
Date:
- Wed, 30 Aug 2017 22:08:28 GMT
Etag:
- W/"1757d1411091b751684cde10119e0942"
Vary:
- Origin
X-Frame-Options:
- SAMEORIGIN
X-Request-Id:
- f7d422a7-c1a3-49d1-9b81-7fc48c954767
X-Runtime:
- '0.687283'
body:
encoding: UTF-8
string: '{"merge_request_urls":[{"branch_name":"test","url":"http://localhost:3000/gitlab-org/gitlab-test/merge_requests/7","new_merge_request":false}],"broadcast_message":"Message","reference_counter_decreased":true}'
http_version:
recorded_at: Wed, 30 Aug 2017 22:08:28 GMT
recorded_with: VCR 2.4.0
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