Commit 8195a94f authored by Dmitriy Zaporozhets's avatar Dmitriy Zaporozhets

Refactor dependency proxy code

Signed-off-by: default avatarDmitriy Zaporozhets <dmitriy.zaporozhets@gmail.com>
parent dc77b6a9
...@@ -6,17 +6,27 @@ class Groups::DependencyProxyForContainersController < Groups::ApplicationContro ...@@ -6,17 +6,27 @@ class Groups::DependencyProxyForContainersController < Groups::ApplicationContro
before_action :ensure_feature_enabled! before_action :ensure_feature_enabled!
before_action :ensure_token_granted! before_action :ensure_token_granted!
attr_reader :token
def manifest def manifest
response = DependencyProxy::PullManifestService.new(image, tag, token).execute result = DependencyProxy::PullManifestService.new(image, tag, token).execute
render status: response[:code], json: response[:body] if result[:status] == :success
render json: result[:manifest]
else
render status: result[:http_status], json: result[:message]
end
end end
def blob def blob
blob = DependencyProxy::FindOrCreateBlobService result = DependencyProxy::FindOrCreateBlobService
.new(group, image, token, params[:sha]).execute .new(group, image, token, params[:sha]).execute
send_upload(blob.file) if result[:status] == :success
send_upload(result[:blob].file)
else
head result[:http_status]
end
end end
private private
...@@ -29,10 +39,6 @@ class Groups::DependencyProxyForContainersController < Groups::ApplicationContro ...@@ -29,10 +39,6 @@ class Groups::DependencyProxyForContainersController < Groups::ApplicationContro
params[:tag] params[:tag]
end end
def token
@token
end
def ensure_feature_enabled! def ensure_feature_enabled!
render_404 unless Gitlab.config.dependency_proxy.enabled && render_404 unless Gitlab.config.dependency_proxy.enabled &&
group.feature_available?(:dependency_proxy) && group.feature_available?(:dependency_proxy) &&
...@@ -40,12 +46,12 @@ class Groups::DependencyProxyForContainersController < Groups::ApplicationContro ...@@ -40,12 +46,12 @@ class Groups::DependencyProxyForContainersController < Groups::ApplicationContro
end end
def ensure_token_granted! def ensure_token_granted!
response = DependencyProxy::RequestTokenService.new(image).execute result = DependencyProxy::RequestTokenService.new(image).execute
if response[:code] == 200 and response[:body].present? if result[:status] == :success
@token = response[:body] @token = result[:token]
else else
render status: response[:code], json: response[:body] render status: result[:http_status], json: result[:message]
end end
end end
end end
# frozen_string_literal: true # frozen_string_literal: true
module DependencyProxy module DependencyProxy
class BaseService class BaseService < ::BaseService
private private
def registry def registry
...@@ -13,12 +13,5 @@ module DependencyProxy ...@@ -13,12 +13,5 @@ module DependencyProxy
Authorization: "Bearer #{@token}" Authorization: "Bearer #{@token}"
} }
end end
def to_response(code, body)
{
code: code,
body: body
}
end
end end
end end
...@@ -2,31 +2,41 @@ ...@@ -2,31 +2,41 @@
module DependencyProxy module DependencyProxy
class DownloadBlobService < DependencyProxy::BaseService class DownloadBlobService < DependencyProxy::BaseService
DownloadError = Class.new(StandardError) class DownloadError < StandardError
attr_reader :http_status
def initialize(image, blob_sha, token, file_path) def initialize(message, http_status)
@http_status = http_status
super(message)
end
end
def initialize(image, blob_sha, token)
@image = image @image = image
@blob_sha = blob_sha @blob_sha = blob_sha
@token = token @token = token
@file_path = file_path @temp_file = Tempfile.new
end end
def execute def execute
File.open(@file_path, "wb") do |file| File.open(@temp_file.path, "wb") do |file|
Gitlab::HTTP.get(blob_url, headers: auth_headers, stream_body: true) do |fragment| Gitlab::HTTP.get(blob_url, headers: auth_headers, stream_body: true) do |fragment|
if [301, 302, 307].include?(fragment.code) if [301, 302, 307].include?(fragment.code)
# do nothing # do nothing
elsif fragment.code == 200 elsif fragment.code == 200
file.write(fragment) file.write(fragment)
else else
raise DownloadError, "Non-success status code while downloading a blob. #{fragment.code}" raise DownloadError.new('Non-success response code on downloading blob fragment', fragment.code)
end end
end end
end end
true success(file: @temp_file)
rescue DownloadError rescue DownloadError => exception
false error(exception.message, exception.http_status)
rescue Timeout::Error => exception
error(exception.message, 599)
end end
private private
......
...@@ -14,19 +14,32 @@ module DependencyProxy ...@@ -14,19 +14,32 @@ module DependencyProxy
blob = @group.dependency_proxy_blobs.find_or_build(file_name) blob = @group.dependency_proxy_blobs.find_or_build(file_name)
unless blob.persisted? unless blob.persisted?
temp_file = Tempfile.new result = DependencyProxy::DownloadBlobService
.new(@image, @blob_sha, @token).execute
success = DependencyProxy::DownloadBlobService if result[:status] == :error
.new(@image, @blob_sha, @token, temp_file.path).execute log_failure(result)
return unless success return error('Failed to download the blob', result[:http_status])
end
blob.file = temp_file blob.file = result[:file]
blob.size = temp_file.size blob.size = result[:file].size
blob.save! blob.save!
end end
blob success(blob: blob)
end
private
def log_failure(result)
log_error(
"Dependency proxy: Failed to download the blob." \
"Blob sha: #{@blob_sha}." \
"Error message: #{result[:message][0, 100]}" \
"HTTP status: #{result[:http_status]}"
)
end end
end end
end end
...@@ -11,9 +11,13 @@ module DependencyProxy ...@@ -11,9 +11,13 @@ module DependencyProxy
def execute def execute
response = Gitlab::HTTP.get(manifest_url, headers: auth_headers) response = Gitlab::HTTP.get(manifest_url, headers: auth_headers)
to_response(response.code, response.body) if response.success?
rescue Net::OpenTimeout, Net::ReadTimeout => exception success(manifest: response.body)
to_response(599, exception.message) else
error(response.body, response.code)
end
rescue Timeout::Error => exception
error(exception.message, 599)
end end
private private
......
...@@ -9,15 +9,15 @@ module DependencyProxy ...@@ -9,15 +9,15 @@ module DependencyProxy
def execute def execute
response = Gitlab::HTTP.get(auth_url) response = Gitlab::HTTP.get(auth_url)
if response.code == 200 if response.success?
to_response(200, JSON.parse(response.body)['token']) success(token: JSON.parse(response.body)['token'])
else else
to_response(response.code, 'Expected 200 response code for an access token') error('Expected 200 response code for an access token', response.code)
end end
rescue Net::OpenTimeout, Net::ReadTimeout => exception rescue Timeout::Error => exception
to_response(599, exception.message) error(exception.message, 599)
rescue JSON::ParserError rescue JSON::ParserError
to_response(500, 'Failed to parse a response body for an access token') error('Failed to parse a response body for an access token', 500)
end end
private private
......
...@@ -4,7 +4,7 @@ require 'spec_helper' ...@@ -4,7 +4,7 @@ require 'spec_helper'
describe Groups::DependencyProxyForContainersController do describe Groups::DependencyProxyForContainersController do
let(:group) { create(:group) } let(:group) { create(:group) }
let(:token_response) { { code: 200, body: 'abcd1234' } } let(:token_response) { { status: :success, token: 'abcd1234' } }
before do before do
allow(Gitlab.config.dependency_proxy) allow(Gitlab.config.dependency_proxy)
...@@ -16,7 +16,7 @@ describe Groups::DependencyProxyForContainersController do ...@@ -16,7 +16,7 @@ describe Groups::DependencyProxyForContainersController do
describe 'GET #manifest' do describe 'GET #manifest' do
let(:manifest) { { foo: 'bar' }.to_json } let(:manifest) { { foo: 'bar' }.to_json }
let(:pull_response) { { code: 200, body: manifest } } let(:pull_response) { { status: :success, manifest: manifest } }
before do before do
allow_any_instance_of(DependencyProxy::PullManifestService) allow_any_instance_of(DependencyProxy::PullManifestService)
...@@ -29,7 +29,13 @@ describe Groups::DependencyProxyForContainersController do ...@@ -29,7 +29,13 @@ describe Groups::DependencyProxyForContainersController do
end end
context 'remote token request fails' do context 'remote token request fails' do
let(:token_response) { { code: 503, body: 'Service Unavailable' } } let(:token_response) do
{
status: :error,
http_status: 503,
message: 'Service Unavailable'
}
end
it 'proxies status from the remote token request' do it 'proxies status from the remote token request' do
get_manifest get_manifest
...@@ -40,7 +46,13 @@ describe Groups::DependencyProxyForContainersController do ...@@ -40,7 +46,13 @@ describe Groups::DependencyProxyForContainersController do
end end
context 'remote manifest request fails' do context 'remote manifest request fails' do
let(:pull_response) { { code: 400, body: '' } } let(:pull_response) do
{
status: :error,
http_status: 400,
message: ''
}
end
it 'proxies status from the remote manifest request' do it 'proxies status from the remote manifest request' do
get_manifest get_manifest
...@@ -72,10 +84,11 @@ describe Groups::DependencyProxyForContainersController do ...@@ -72,10 +84,11 @@ describe Groups::DependencyProxyForContainersController do
describe 'GET #blob' do describe 'GET #blob' do
let(:blob) { create(:dependency_proxy_blob) } let(:blob) { create(:dependency_proxy_blob) }
let(:blob_sha) { blob.file_name.sub('.gz', '') } let(:blob_sha) { blob.file_name.sub('.gz', '') }
let(:blob_response) { { status: :success, blob: blob } }
before do before do
allow_any_instance_of(DependencyProxy::FindOrCreateBlobService) allow_any_instance_of(DependencyProxy::FindOrCreateBlobService)
.to receive(:execute).and_return(blob) .to receive(:execute).and_return(blob_response)
end end
context 'feature enabled' do context 'feature enabled' do
...@@ -83,6 +96,23 @@ describe Groups::DependencyProxyForContainersController do ...@@ -83,6 +96,23 @@ describe Groups::DependencyProxyForContainersController do
enable_dependency_proxy enable_dependency_proxy
end end
context 'remote blob request fails' do
let(:blob_response) do
{
status: :error,
http_status: 400,
message: ''
}
end
it 'proxies status from the remote blob request' do
get_blob
expect(response).to have_gitlab_http_status(400)
expect(response.body).to be_empty
end
end
it 'sends a file' do it 'sends a file' do
expect(controller).to receive(:send_file).with(blob.file.path, {}) expect(controller).to receive(:send_file).with(blob.file.path, {})
......
...@@ -7,15 +7,38 @@ describe DependencyProxy::DownloadBlobService do ...@@ -7,15 +7,38 @@ describe DependencyProxy::DownloadBlobService do
let(:image) { 'alpine' } let(:image) { 'alpine' }
let(:token) { Digest::SHA256.hexdigest('123') } let(:token) { Digest::SHA256.hexdigest('123') }
let(:blob_sha) { Digest::SHA256.hexdigest('ruby:2.3.9') } let(:blob_sha) { Digest::SHA256.hexdigest('ruby:2.3.9') }
let(:file) { Tempfile.new }
subject { described_class.new(image, blob_sha, token, file.path) } subject { described_class.new(image, blob_sha, token).execute }
before do context 'remote request is successful' do
stub_blob_download(image, blob_sha) before do
stub_blob_download(image, blob_sha)
end
it { expect(subject[:status]).to eq(:success) }
it { expect(subject[:file]).to be_a(Tempfile) }
it { expect(subject[:file].size).to eq(6) }
end
context 'remote request is not found' do
before do
stub_blob_download(image, blob_sha, 404)
end
it { expect(subject[:status]).to eq(:error) }
it { expect(subject[:http_status]).to eq(404) }
it { expect(subject[:message]).to eq('Non-success response code on downloading blob fragment') }
end end
it 'downloads blob and writes it into the file' do context 'net timeout exception' do
expect { subject.execute }.to change { file.size }.from(0).to(6) before do
blob_url = DependencyProxy::Registry.blob_url(image, blob_sha)
stub_request(:get, blob_url).to_timeout
end
it { expect(subject[:status]).to eq(:error) }
it { expect(subject[:http_status]).to eq(599) }
it { expect(subject[:message]).to eq('execution expired') }
end end
end end
...@@ -23,8 +23,9 @@ describe DependencyProxy::FindOrCreateBlobService do ...@@ -23,8 +23,9 @@ describe DependencyProxy::FindOrCreateBlobService do
end end
it 'downloads blob from remote registry if there is no cached one' do it 'downloads blob from remote registry if there is no cached one' do
is_expected.to be_a(DependencyProxy::Blob) expect(subject[:status]).to eq(:success)
is_expected.to be_persisted expect(subject[:blob]).to be_a(DependencyProxy::Blob)
expect(subject[:blob]).to be_persisted
end end
end end
...@@ -32,8 +33,9 @@ describe DependencyProxy::FindOrCreateBlobService do ...@@ -32,8 +33,9 @@ describe DependencyProxy::FindOrCreateBlobService do
let(:blob_sha) { blob.file_name.sub('.gz', '') } let(:blob_sha) { blob.file_name.sub('.gz', '') }
it 'uses cached blob instead of downloading one' do it 'uses cached blob instead of downloading one' do
is_expected.to be_a(DependencyProxy::Blob) expect(subject[:status]).to eq(:success)
is_expected.to eq(blob) expect(subject[:blob]).to be_a(DependencyProxy::Blob)
expect(subject[:blob]).to eq(blob)
end end
end end
...@@ -42,6 +44,10 @@ describe DependencyProxy::FindOrCreateBlobService do ...@@ -42,6 +44,10 @@ describe DependencyProxy::FindOrCreateBlobService do
stub_blob_download(image, blob_sha, 404) stub_blob_download(image, blob_sha, 404)
end end
it { is_expected.to be nil } it 'returns error message and http status' do
expect(subject[:status]).to eq(:error)
expect(subject[:message]).to eq('Failed to download the blob')
expect(subject[:http_status]).to eq(404)
end
end end
end end
...@@ -16,8 +16,8 @@ describe DependencyProxy::PullManifestService do ...@@ -16,8 +16,8 @@ describe DependencyProxy::PullManifestService do
stub_manifest_download(image, tag) stub_manifest_download(image, tag)
end end
it { expect(subject[:code]).to eq(200) } it { expect(subject[:status]).to eq(:success) }
it { expect(subject[:body]).to eq(manifest) } it { expect(subject[:manifest]).to eq(manifest) }
end end
context 'remote request is not found' do context 'remote request is not found' do
...@@ -25,8 +25,9 @@ describe DependencyProxy::PullManifestService do ...@@ -25,8 +25,9 @@ describe DependencyProxy::PullManifestService do
stub_manifest_download(image, tag, 404, 'Not found') stub_manifest_download(image, tag, 404, 'Not found')
end end
it { expect(subject[:code]).to eq(404) } it { expect(subject[:status]).to eq(:error) }
it { expect(subject[:body]).to eq('Not found') } it { expect(subject[:http_status]).to eq(404) }
it { expect(subject[:message]).to eq('Not found') }
end end
context 'net timeout exception' do context 'net timeout exception' do
...@@ -36,7 +37,8 @@ describe DependencyProxy::PullManifestService do ...@@ -36,7 +37,8 @@ describe DependencyProxy::PullManifestService do
stub_request(:get, manifest_link).to_timeout stub_request(:get, manifest_link).to_timeout
end end
it { expect(subject[:code]).to eq(599) } it { expect(subject[:status]).to eq(:error) }
it { expect(subject[:body]).to eq('execution expired') } it { expect(subject[:http_status]).to eq(599) }
it { expect(subject[:message]).to eq('execution expired') }
end end
end end
...@@ -14,8 +14,8 @@ describe DependencyProxy::RequestTokenService do ...@@ -14,8 +14,8 @@ describe DependencyProxy::RequestTokenService do
stub_registry_auth(image, token) stub_registry_auth(image, token)
end end
it { expect(subject[:code]).to eq(200) } it { expect(subject[:status]).to eq(:success) }
it { expect(subject[:body]).to eq(token) } it { expect(subject[:token]).to eq(token) }
end end
context 'remote request is not found' do context 'remote request is not found' do
...@@ -23,8 +23,9 @@ describe DependencyProxy::RequestTokenService do ...@@ -23,8 +23,9 @@ describe DependencyProxy::RequestTokenService do
stub_registry_auth(image, token, 404) stub_registry_auth(image, token, 404)
end end
it { expect(subject[:code]).to eq(404) } it { expect(subject[:status]).to eq(:error) }
it { expect(subject[:body]).to eq('Expected 200 response code for an access token') } it { expect(subject[:http_status]).to eq(404) }
it { expect(subject[:message]).to eq('Expected 200 response code for an access token') }
end end
context 'failed to parse response body' do context 'failed to parse response body' do
...@@ -32,8 +33,9 @@ describe DependencyProxy::RequestTokenService do ...@@ -32,8 +33,9 @@ describe DependencyProxy::RequestTokenService do
stub_registry_auth(image, token, 200, 'dasd1321: wow') stub_registry_auth(image, token, 200, 'dasd1321: wow')
end end
it { expect(subject[:code]).to eq(500) } it { expect(subject[:status]).to eq(:error) }
it { expect(subject[:body]).to eq('Failed to parse a response body for an access token') } it { expect(subject[:http_status]).to eq(500) }
it { expect(subject[:message]).to eq('Failed to parse a response body for an access token') }
end end
context 'net timeout exception' do context 'net timeout exception' do
...@@ -43,7 +45,8 @@ describe DependencyProxy::RequestTokenService do ...@@ -43,7 +45,8 @@ describe DependencyProxy::RequestTokenService do
stub_request(:any, auth_link).to_timeout stub_request(:any, auth_link).to_timeout
end end
it { expect(subject[:code]).to eq(599) } it { expect(subject[:status]).to eq(:error) }
it { expect(subject[:body]).to eq('execution expired') } it { expect(subject[:http_status]).to eq(599) }
it { expect(subject[:message]).to eq('execution expired') }
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