Commit 5f12a6a4 authored by Ash McKenzie's avatar Ash McKenzie Committed by Nick Thomas

Display helpful feedback when proxying an SSH git push to secondary request (v2)

parent dd54651b
...@@ -22,20 +22,15 @@ module Action ...@@ -22,20 +22,15 @@ module Action
def execute def execute
validate! validate!
result = process_api_endpoints inform_client(info_message) if info_message
process_api_endpoints!
if result && HTTP_SUCCESS_CODES.include?(result.code)
result
else
raise_unsuccessful!(result)
end
end end
private private
attr_reader :gl_id, :payload attr_reader :gl_id, :payload
def process_api_endpoints def process_api_endpoints!
output = '' output = ''
resp = nil resp = nil
...@@ -46,7 +41,14 @@ module Action ...@@ -46,7 +41,14 @@ module Action
json = { 'data' => data_with_gl_id, 'output' => output } json = { 'data' => data_with_gl_id, 'output' => output }
resp = post(url, {}, headers: DEFAULT_HEADERS, options: { json: json }) resp = post(url, {}, headers: DEFAULT_HEADERS, options: { json: json })
return resp unless HTTP_SUCCESS_CODES.include?(resp.code)
# Net::HTTPSuccess is the parent of Net::HTTPOK, Net::HTTPCreated etc.
case resp
when Net::HTTPSuccess, Net::HTTPMultipleChoices
true
else
raise_unsuccessful!(resp)
end
begin begin
body = JSON.parse(resp.body) body = JSON.parse(resp.body)
...@@ -54,8 +56,6 @@ module Action ...@@ -54,8 +56,6 @@ module Action
raise UnsuccessfulError, 'Response was not valid JSON' raise UnsuccessfulError, 'Response was not valid JSON'
end end
inform_client(body['message']) if body['message']
print_flush(body['result']) print_flush(body['result'])
# In the context of the git push sequence of events, it's necessary to read # In the context of the git push sequence of events, it's necessary to read
...@@ -78,6 +78,10 @@ module Action ...@@ -78,6 +78,10 @@ module Action
data['api_endpoints'] data['api_endpoints']
end end
def info_message
data['info_message']
end
def config def config
@config ||= GitlabConfig.new @config ||= GitlabConfig.new
end end
...@@ -97,7 +101,11 @@ module Action ...@@ -97,7 +101,11 @@ module Action
end end
def inform_client(str) def inform_client(str)
$stderr.puts(str) $stderr.puts(format_gitlab_output(str))
end
def format_gitlab_output(str)
str.split("\n").map { |line| "> GitLab: #{line}" }.join("\n")
end end
def validate! def validate!
...@@ -120,16 +128,17 @@ module Action ...@@ -120,16 +128,17 @@ module Action
end end
def raise_unsuccessful!(result) def raise_unsuccessful!(result)
message = begin message = "#{exception_message_for(result.body)} (#{result.code})"
body = JSON.parse(result.body) raise UnsuccessfulError, format_gitlab_output(message)
message = body['message']
message = Base64.decode64(body['result']) if !message && body['result'] && !body['result'].empty?
message ? message : NO_MESSAGE_TEXT
rescue JSON::ParserError
NO_MESSAGE_TEXT
end end
raise UnsuccessfulError, "#{message} (#{result.code})" def exception_message_for(body)
body = JSON.parse(body)
return body['message'] unless body['message'].to_s.empty?
body['result'].to_s.empty? ? NO_MESSAGE_TEXT : Base64.decode64(body['result'])
rescue JSON::ParserError
NO_MESSAGE_TEXT
end end
end end
end end
require 'json' require 'json'
require_relative 'http_codes'
class GitAccessStatus class GitAccessStatus
include HTTPCodes HTTP_MULTIPLE_CHOICES = '300'.freeze
attr_reader :message, :gl_repository, :gl_id, :gl_username, :gitaly, :git_protocol, :git_config_options, :payload attr_reader :message, :gl_repository, :gl_id, :gl_username, :gitaly, :git_protocol, :git_config_options, :payload
......
...@@ -95,8 +95,9 @@ class GitlabShell # rubocop:disable Metrics/ClassLength ...@@ -95,8 +95,9 @@ class GitlabShell # rubocop:disable Metrics/ClassLength
$stderr.puts "GitLab: Invalid repository path" $stderr.puts "GitLab: Invalid repository path"
false false
rescue Action::Custom::BaseError => ex rescue Action::Custom::BaseError => ex
$logger.warn('Custom action error', command: origin_cmd, user: log_username) $logger.warn('Custom action error', exception: ex.class, message: ex.message,
$stderr.puts "GitLab: #{ex.message}" command: origin_cmd, user: log_username)
$stderr.puts ex.message
false false
end end
......
module HTTPCodes
HTTP_SUCCESS = '200'.freeze
HTTP_CREATED = '201'.freeze
HTTP_MULTIPLE_CHOICES = '300'.freeze
HTTP_UNAUTHORIZED = '401'.freeze
HTTP_NOT_FOUND = '404'.freeze
HTTP_SUCCESS_CODES = [HTTP_SUCCESS, HTTP_CREATED, HTTP_MULTIPLE_CHOICES].freeze
end
require_relative 'http_codes'
require_relative 'httpunix' require_relative 'httpunix'
require_relative 'gitlab_logger' require_relative 'gitlab_logger'
module HTTPHelper module HTTPHelper
include HTTPCodes
READ_TIMEOUT = 300 READ_TIMEOUT = 300
CONTENT_TYPE_JSON = 'application/json'.freeze CONTENT_TYPE_JSON = 'application/json'.freeze
......
...@@ -26,22 +26,12 @@ describe Action::Custom do ...@@ -26,22 +26,12 @@ describe Action::Custom do
end end
context 'that are valid' do context 'that are valid' do
where(:primary_repo_data) do
[
[ 'http://localhost:3001/user1/repo1.git' ],
[{ 'http' => 'http://localhost:3001/user1/repo1.git' }],
[{ 'http' => 'http://localhost:3001/user1/repo1.git', 'ssh' => 'ssh://user@localhost:3002/user1/repo1.git' }]
]
end
with_them do
let(:payload) do let(:payload) do
{ {
'action' => 'geo_proxy_to_primary', 'action' => 'geo_proxy_to_primary',
'data' => { 'data' => {
'api_endpoints' => %w{/api/v4/fake/info_refs /api/v4/fake/push}, 'api_endpoints' => %w{/api/v4/fake/info_refs /api/v4/fake/push},
'gl_username' => 'user1', 'primary_repo' => 'http://localhost:3001/user1/repo1.git'
'primary_repo' => primary_repo_data
} }
} }
end end
...@@ -61,15 +51,21 @@ describe Action::Custom do ...@@ -61,15 +51,21 @@ describe Action::Custom do
allow($stdout).to receive(:print).with('push-result') allow($stdout).to receive(:print).with('push-result')
end end
it 'prints a message to $stderr' do it 'returns an instance of Net::HTTPCreated' do
VCR.use_cassette("custom-action-ok-with-message") do VCR.use_cassette("custom-action-ok") do
expect { subject.execute }.to output(/NOTE: Message here/).to_stderr expect(subject.execute).to be_instance_of(Net::HTTPCreated)
end end
end end
it 'returns an instance of Net::HTTPCreated' do context 'and with an information message provided' do
VCR.use_cassette("custom-action-ok") do before do
expect(subject.execute ).to be_instance_of(Net::HTTPCreated) payload['data']['info_message'] = 'Important message here.'
end
it 'prints said informational message to $stderr' do
VCR.use_cassette("custom-action-ok-with-message") do
expect { subject.execute }.to output(/Important message here./).to_stderr
end
end end
end end
end end
...@@ -78,10 +74,9 @@ describe Action::Custom do ...@@ -78,10 +74,9 @@ describe Action::Custom do
context 'but responds incorrectly' do context 'but responds incorrectly' do
it 'raises an UnsuccessfulError exception' do it 'raises an UnsuccessfulError exception' do
VCR.use_cassette("custom-action-ok-not-json") do VCR.use_cassette("custom-action-ok-not-json") do
expect { expect do
subject.execute subject.execute
}.to raise_error(Action::Custom::UnsuccessfulError, 'Response was not valid JSON') end.to raise_error(Action::Custom::UnsuccessfulError, 'Response was not valid JSON')
end
end end
end end
end end
...@@ -93,7 +88,6 @@ describe Action::Custom do ...@@ -93,7 +88,6 @@ describe Action::Custom do
{ {
'action' => 'geo_proxy_to_primary', 'action' => 'geo_proxy_to_primary',
'data' => { 'data' => {
'gl_username' => 'user1',
'primary_repo' => 'http://localhost:3001/user1/repo1.git' 'primary_repo' => 'http://localhost:3001/user1/repo1.git'
} }
} }
...@@ -110,7 +104,6 @@ describe Action::Custom do ...@@ -110,7 +104,6 @@ describe Action::Custom do
'action' => 'geo_proxy_to_primary', 'action' => 'geo_proxy_to_primary',
'data' => { 'data' => {
'api_endpoints' => [], 'api_endpoints' => [],
'gl_username' => 'user1',
'primary_repo' => 'http://localhost:3001/user1/repo1.git' 'primary_repo' => 'http://localhost:3001/user1/repo1.git'
} }
} }
...@@ -135,7 +128,6 @@ describe Action::Custom do ...@@ -135,7 +128,6 @@ describe Action::Custom do
'action' => 'geo_proxy_to_primary', 'action' => 'geo_proxy_to_primary',
'data' => { 'data' => {
'api_endpoints' => %w{/api/v4/fake/info_refs_bad /api/v4/fake/push_bad}, 'api_endpoints' => %w{/api/v4/fake/info_refs_bad /api/v4/fake/push_bad},
'gl_username' => 'user1',
'primary_repo' => 'http://localhost:3001/user1/repo1.git' 'primary_repo' => 'http://localhost:3001/user1/repo1.git'
} }
} }
...@@ -144,9 +136,9 @@ describe Action::Custom do ...@@ -144,9 +136,9 @@ describe Action::Custom do
context 'and response is JSON' do context 'and response is JSON' do
it 'raises an UnsuccessfulError exception' do it 'raises an UnsuccessfulError exception' do
VCR.use_cassette("custom-action-not-ok-json") do VCR.use_cassette("custom-action-not-ok-json") do
expect { expect do
subject.execute subject.execute
}.to raise_error(Action::Custom::UnsuccessfulError, 'You cannot perform write operations on a read-only instance (403)') end.to raise_error(Action::Custom::UnsuccessfulError, '> GitLab: You cannot perform write operations on a read-only instance (403)')
end end
end end
end end
...@@ -154,9 +146,9 @@ describe Action::Custom do ...@@ -154,9 +146,9 @@ describe Action::Custom do
context 'and response is not JSON' do context 'and response is not JSON' do
it 'raises an UnsuccessfulError exception' do it 'raises an UnsuccessfulError exception' do
VCR.use_cassette("custom-action-not-ok-not-json") do VCR.use_cassette("custom-action-not-ok-not-json") do
expect { expect do
subject.execute subject.execute
}.to raise_error(Action::Custom::UnsuccessfulError, 'No message (403)') end.to raise_error(Action::Custom::UnsuccessfulError, '> GitLab: No message (403)')
end end
end end
end end
......
...@@ -8,7 +8,7 @@ describe GitlabAccess do ...@@ -8,7 +8,7 @@ describe GitlabAccess do
let(:api) do let(:api) do
double(GitlabNet).tap do |api| double(GitlabNet).tap do |api|
allow(api).to receive(:check_access).and_return(GitAccessStatus.new(true, allow(api).to receive(:check_access).and_return(GitAccessStatus.new(true,
HTTPCodes::HTTP_SUCCESS, '200',
'ok', 'ok',
gl_repository: 'project-1', gl_repository: 'project-1',
gl_id: 'user-123', gl_id: 'user-123',
...@@ -46,7 +46,7 @@ describe GitlabAccess do ...@@ -46,7 +46,7 @@ describe GitlabAccess do
before do before do
allow(api).to receive(:check_access).and_return(GitAccessStatus.new( allow(api).to receive(:check_access).and_return(GitAccessStatus.new(
false, false,
HTTPCodes::HTTP_UNAUTHORIZED, '401',
'denied', 'denied',
gl_repository: nil, gl_repository: nil,
gl_id: nil, gl_id: nil,
......
...@@ -25,7 +25,7 @@ describe GitlabShell do ...@@ -25,7 +25,7 @@ describe GitlabShell do
let(:gitaly_check_access) do let(:gitaly_check_access) do
GitAccessStatus.new( GitAccessStatus.new(
true, true,
HTTPCodes::HTTP_SUCCESS, '200',
'ok', 'ok',
gl_repository: gl_repository, gl_repository: gl_repository,
gl_id: gl_id, gl_id: gl_id,
...@@ -41,7 +41,7 @@ describe GitlabShell do ...@@ -41,7 +41,7 @@ describe GitlabShell do
allow(api).to receive(:discover).and_return({ 'name' => 'John Doe', 'username' => 'testuser' }) allow(api).to receive(:discover).and_return({ 'name' => 'John Doe', 'username' => 'testuser' })
allow(api).to receive(:check_access).and_return(GitAccessStatus.new( allow(api).to receive(:check_access).and_return(GitAccessStatus.new(
true, true,
HTTPCodes::HTTP_SUCCESS, '200',
'ok', 'ok',
gl_repository: gl_repository, gl_repository: gl_repository,
gl_id: gl_id, gl_id: gl_id,
...@@ -262,7 +262,7 @@ describe GitlabShell do ...@@ -262,7 +262,7 @@ describe GitlabShell do
let(:custom_action_gitlab_access_status) do let(:custom_action_gitlab_access_status) do
GitAccessStatus.new( GitAccessStatus.new(
true, true,
HTTPCodes::HTTP_MULTIPLE_CHOICES, '300',
'Multiple Choices', 'Multiple Choices',
payload: fake_payload payload: fake_payload
) )
......
...@@ -46,7 +46,7 @@ http_interactions: ...@@ -46,7 +46,7 @@ http_interactions:
- '1.436040' - '1.436040'
body: body:
encoding: UTF-8 encoding: UTF-8
string: '{"result":"aW5mb19yZWZzLXJlc3VsdA==\n", "message":"NOTE: Message here"}' string: '{"result":"aW5mb19yZWZzLXJlc3VsdA==\n"}'
http_version: http_version:
recorded_at: Fri, 20 Jul 2018 06:18:58 GMT recorded_at: Fri, 20 Jul 2018 06:18:58 GMT
- request: - request:
......
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