Commit f6e1f969 authored by Steve Abrams's avatar Steve Abrams Committed by Rémy Coutable

Conan file upload endpoints

Add endpoints to consume the workhorse /authorize call
and the following file upload endpoint containing the
workhorse file data.

Adds create_package and create_package_File services for
conan.
parent ca279f16
# frozen_string_literal: true
module Packages
module Conan
class CreatePackageFileService
attr_reader :package, :file, :params
def initialize(package, file, params)
@package = package
@file = file
@params = params
end
def execute
package.package_files.create!(
file: file,
size: params['file.size'],
file_name: params[:file_name],
file_type: params['file.type'],
file_sha1: params['file.sha1'],
file_md5: params['file.md5'],
conan_file_metadatum_attributes: {
recipe_revision: params[:recipe_revision],
package_revision: params[:package_revision],
conan_package_reference: params[:conan_package_reference],
conan_file_type: params[:conan_file_type]
}
)
end
end
end
end
# frozen_string_literal: true
module Packages
module Conan
class CreatePackageService < BaseService
def execute
project.packages.create!(
name: params[:package_name],
version: params[:package_version],
package_type: :conan,
conan_metadatum_attributes: {
package_username: params[:package_username],
package_channel: params[:package_channel]
}
)
end
end
end
end
...@@ -5,6 +5,8 @@ class Packages::PackageFileUploader < GitlabUploader ...@@ -5,6 +5,8 @@ class Packages::PackageFileUploader < GitlabUploader
storage_options Gitlab.config.packages storage_options Gitlab.config.packages
after :store, :schedule_background_upload
alias_method :upload, :model alias_method :upload, :model
def filename def filename
......
...@@ -24,6 +24,7 @@ module API ...@@ -24,6 +24,7 @@ module API
}.freeze }.freeze
PACKAGE_COMPONENT_REGEX = Gitlab::Regex.conan_recipe_component_regex PACKAGE_COMPONENT_REGEX = Gitlab::Regex.conan_recipe_component_regex
CONAN_REVISION_REGEX = Gitlab::Regex.conan_revision_regex
before do before do
not_found! unless Feature.enabled?(:conan_package_registry) not_found! unless Feature.enabled?(:conan_package_registry)
...@@ -195,7 +196,7 @@ module API ...@@ -195,7 +196,7 @@ module API
requires :package_version, type: String, regexp: PACKAGE_COMPONENT_REGEX, desc: 'Package version' requires :package_version, type: String, regexp: PACKAGE_COMPONENT_REGEX, desc: 'Package version'
requires :package_username, type: String, regexp: PACKAGE_COMPONENT_REGEX, desc: 'Package username' requires :package_username, type: String, regexp: PACKAGE_COMPONENT_REGEX, desc: 'Package username'
requires :package_channel, type: String, regexp: PACKAGE_COMPONENT_REGEX, desc: 'Package channel' requires :package_channel, type: String, regexp: PACKAGE_COMPONENT_REGEX, desc: 'Package channel'
requires :recipe_revision, type: String, desc: 'Conan Recipe Revision' requires :recipe_revision, type: String, regexp: CONAN_REVISION_REGEX, desc: 'Conan Recipe Revision'
end end
namespace 'files/:package_name/:package_version/:package_username/:package_channel/:recipe_revision', requirements: PACKAGE_REQUIREMENTS do namespace 'files/:package_name/:package_version/:package_username/:package_channel/:recipe_revision', requirements: PACKAGE_REQUIREMENTS do
before do before do
...@@ -203,21 +204,44 @@ module API ...@@ -203,21 +204,44 @@ module API
end end
params do params do
requires :file_name, type: String, desc: 'Package file name' requires :file_name, type: String, desc: 'Package file name', regexp: Gitlab::Regex.conan_file_name_regex
end end
namespace 'export/:file_name', requirements: FILE_NAME_REQUIREMENTS do namespace 'export/:file_name', requirements: FILE_NAME_REQUIREMENTS do
desc 'Download recipe files' do desc 'Download recipe files' do
detail 'This feature was introduced in GitLab 12.5' detail 'This feature was introduced in GitLab 12.6'
end end
get do get do
download_package_file(:recipe_file) download_package_file(:recipe_file)
end end
desc 'Upload recipe package files' do
detail 'This feature was introduced in GitLab 12.6'
end
params do
optional 'file.path', type: String, desc: 'Path to locally stored body (generated by Workhorse)'
optional 'file.name', type: String, desc: 'Real filename as send in Content-Disposition (generated by Workhorse)'
optional 'file.type', type: String, desc: 'Real content type as send in Content-Type (generated by Workhorse)'
optional 'file.size', type: Integer, desc: 'Real size of file (generated by Workhorse)'
optional 'file.md5', type: String, desc: 'MD5 checksum of the file (generated by Workhorse)'
optional 'file.sha1', type: String, desc: 'SHA1 checksum of the file (generated by Workhorse)'
optional 'file.sha256', type: String, desc: 'SHA256 checksum of the file (generated by Workhorse)'
end
put do
upload_package_file(:recipe_file)
end
desc 'Workhorse authorize the conan recipe file' do
detail 'This feature was introduced in GitLab 12.6'
end
put 'authorize' do
authorize_workhorse
end
end end
params do params do
requires :conan_package_reference, type: String, desc: 'Conan Package ID' requires :conan_package_reference, type: String, desc: 'Conan Package ID'
requires :package_revision, type: String, desc: 'Conan Package Revision' requires :package_revision, type: String, desc: 'Conan Package Revision'
requires :file_name, type: String, desc: 'Package file name' requires :file_name, type: String, desc: 'Package file name', regexp: Gitlab::Regex.conan_file_name_regex
end end
namespace 'package/:conan_package_reference/:package_revision/:file_name', requirements: FILE_NAME_REQUIREMENTS do namespace 'package/:conan_package_reference/:package_revision/:file_name', requirements: FILE_NAME_REQUIREMENTS do
desc 'Download package files' do desc 'Download package files' do
...@@ -226,6 +250,29 @@ module API ...@@ -226,6 +250,29 @@ module API
get do get do
download_package_file(:package_file) download_package_file(:package_file)
end end
desc 'Workhorse authorize the conan package file' do
detail 'This feature was introduced in GitLab 12.6'
end
put 'authorize' do
authorize_workhorse
end
desc 'Upload package files' do
detail 'This feature was introduced in GitLab 12.6'
end
params do
optional 'file.path', type: String, desc: 'Path to locally stored body (generated by Workhorse)'
optional 'file.name', type: String, desc: 'Real filename as send in Content-Disposition (generated by Workhorse)'
optional 'file.type', type: String, desc: 'Real content type as send in Content-Type (generated by Workhorse)'
optional 'file.size', type: Integer, desc: 'Real size of file (generated by Workhorse)'
optional 'file.md5', type: String, desc: 'MD5 checksum of the file (generated by Workhorse)'
optional 'file.sha1', type: String, desc: 'SHA1 checksum of the file (generated by Workhorse)'
optional 'file.sha256', type: String, desc: 'SHA256 checksum of the file (generated by Workhorse)'
end
put do
upload_package_file(:package_file)
end
end end
end end
end end
...@@ -329,6 +376,37 @@ module API ...@@ -329,6 +376,37 @@ module API
present_carrierwave_file!(package_file.file) present_carrierwave_file!(package_file.file)
end end
def upload_package_file(file_type)
authorize_upload
uploaded_file = UploadedFile.from_params(params, :file, ::Packages::PackageFileUploader.workhorse_local_upload_path)
bad_request!('Missing package file!') unless uploaded_file
current_package = package || ::Packages::Conan::CreatePackageService.new(project, current_user, params).execute
# conan sends two upload requests, the first has no file, so we skip record creation if file.size == 0
::Packages::Conan::CreatePackageFileService.new(current_package, uploaded_file, params.merge(conan_file_type: file_type)).execute unless params['file.size'] == 0
rescue ObjectStorage::RemoteStoreError => e
Gitlab::Sentry.track_acceptable_exception(e, extra: { file_name: params[:file_name], project_id: project.id })
forbidden!
end
def authorize_workhorse
authorize_upload
Gitlab::Workhorse.verify_api_request!(headers)
status 200
content_type Gitlab::Workhorse::INTERNAL_API_CONTENT_TYPE
::Packages::PackageFileUploader.workhorse_authorize(has_length: true)
end
def authorize_upload
authorize!(:create_package, project)
require_gitlab_workhorse!
end
def find_personal_access_token def find_personal_access_token
personal_access_token = find_personal_access_token_from_conan_jwt || personal_access_token = find_personal_access_token_from_conan_jwt ||
find_personal_access_token_from_conan_http_basic_auth find_personal_access_token_from_conan_http_basic_auth
......
...@@ -110,4 +110,14 @@ RSpec.describe Packages::Package, type: :model do ...@@ -110,4 +110,14 @@ RSpec.describe Packages::Package, type: :model do
end end
end end
end end
describe '.with_conan_channel' do
let!(:package) { create(:conan_package) }
subject { described_class.with_conan_channel('stable') }
it 'includes only packages with specified version' do
is_expected.to eq([package])
end
end
end end
...@@ -2,6 +2,8 @@ ...@@ -2,6 +2,8 @@
require 'spec_helper' require 'spec_helper'
describe API::ConanPackages do describe API::ConanPackages do
include WorkhorseHelpers
let(:package) { create(:conan_package) } let(:package) { create(:conan_package) }
let_it_be(:personal_access_token) { create(:personal_access_token) } let_it_be(:personal_access_token) { create(:personal_access_token) }
let_it_be(:user) { personal_access_token.user } let_it_be(:user) { personal_access_token.user }
...@@ -37,7 +39,7 @@ describe API::ConanPackages do ...@@ -37,7 +39,7 @@ describe API::ConanPackages do
it 'responds with 404 Not Found' do it 'responds with 404 Not Found' do
get api('/packages/conan/v1/ping') get api('/packages/conan/v1/ping')
expect(response).to have_gitlab_http_status(404) expect(response).to have_gitlab_http_status(:not_found)
end end
end end
...@@ -45,14 +47,14 @@ describe API::ConanPackages do ...@@ -45,14 +47,14 @@ describe API::ConanPackages do
it 'responds with 401 Unauthorized when no token provided' do it 'responds with 401 Unauthorized when no token provided' do
get api('/packages/conan/v1/ping') get api('/packages/conan/v1/ping')
expect(response).to have_gitlab_http_status(401) expect(response).to have_gitlab_http_status(:unauthorized)
end end
it 'responds with 200 OK when valid token is provided' do it 'responds with 200 OK when valid token is provided' do
jwt = build_jwt(personal_access_token) jwt = build_jwt(personal_access_token)
get api('/packages/conan/v1/ping'), headers: build_auth_headers(jwt.encoded) get api('/packages/conan/v1/ping'), headers: build_auth_headers(jwt.encoded)
expect(response).to have_gitlab_http_status(200) expect(response).to have_gitlab_http_status(:ok)
expect(response.headers['X-Conan-Server-Capabilities']).to eq("") expect(response.headers['X-Conan-Server-Capabilities']).to eq("")
end end
...@@ -60,27 +62,27 @@ describe API::ConanPackages do ...@@ -60,27 +62,27 @@ describe API::ConanPackages do
jwt = build_jwt(double(id: 12345), user_id: personal_access_token.user_id) jwt = build_jwt(double(id: 12345), user_id: personal_access_token.user_id)
get api('/packages/conan/v1/ping'), headers: build_auth_headers(jwt.encoded) get api('/packages/conan/v1/ping'), headers: build_auth_headers(jwt.encoded)
expect(response).to have_gitlab_http_status(401) expect(response).to have_gitlab_http_status(:unauthorized)
end end
it 'responds with 401 Unauthorized when invalid user is provided' do it 'responds with 401 Unauthorized when invalid user is provided' do
jwt = build_jwt(personal_access_token, user_id: 12345) jwt = build_jwt(personal_access_token, user_id: 12345)
get api('/packages/conan/v1/ping'), headers: build_auth_headers(jwt.encoded) get api('/packages/conan/v1/ping'), headers: build_auth_headers(jwt.encoded)
expect(response).to have_gitlab_http_status(401) expect(response).to have_gitlab_http_status(:unauthorized)
end end
it 'responds with 401 Unauthorized when the provided JWT is signed with different secret' do it 'responds with 401 Unauthorized when the provided JWT is signed with different secret' do
jwt = build_jwt(personal_access_token, secret: SecureRandom.base64(32)) jwt = build_jwt(personal_access_token, secret: SecureRandom.base64(32))
get api('/packages/conan/v1/ping'), headers: build_auth_headers(jwt.encoded) get api('/packages/conan/v1/ping'), headers: build_auth_headers(jwt.encoded)
expect(response).to have_gitlab_http_status(401) expect(response).to have_gitlab_http_status(:unauthorized)
end end
it 'responds with 401 Unauthorized when invalid JWT is provided' do it 'responds with 401 Unauthorized when invalid JWT is provided' do
get api('/packages/conan/v1/ping'), headers: build_auth_headers('invalid-jwt') get api('/packages/conan/v1/ping'), headers: build_auth_headers('invalid-jwt')
expect(response).to have_gitlab_http_status(401) expect(response).to have_gitlab_http_status(:unauthorized)
end end
context 'packages feature disabled' do context 'packages feature disabled' do
...@@ -88,7 +90,7 @@ describe API::ConanPackages do ...@@ -88,7 +90,7 @@ describe API::ConanPackages do
stub_packages_setting(enabled: false) stub_packages_setting(enabled: false)
get api('/packages/conan/v1/ping') get api('/packages/conan/v1/ping')
expect(response).to have_gitlab_http_status(404) expect(response).to have_gitlab_http_status(:not_found)
end end
end end
end end
...@@ -131,7 +133,7 @@ describe API::ConanPackages do ...@@ -131,7 +133,7 @@ describe API::ConanPackages do
it 'responds with 401' do it 'responds with 401' do
subject subject
expect(response).to have_gitlab_http_status(401) expect(response).to have_gitlab_http_status(:unauthorized)
end end
end end
...@@ -139,7 +141,7 @@ describe API::ConanPackages do ...@@ -139,7 +141,7 @@ describe API::ConanPackages do
it 'responds with 200' do it 'responds with 200' do
subject subject
expect(response).to have_gitlab_http_status(200) expect(response).to have_gitlab_http_status(:ok)
end end
it 'token has valid validity time' do it 'token has valid validity time' do
...@@ -162,13 +164,13 @@ describe API::ConanPackages do ...@@ -162,13 +164,13 @@ describe API::ConanPackages do
it 'responds with a 200 OK' do it 'responds with a 200 OK' do
get api('/packages/conan/v1/users/check_credentials'), headers: headers get api('/packages/conan/v1/users/check_credentials'), headers: headers
expect(response).to have_gitlab_http_status(200) expect(response).to have_gitlab_http_status(:ok)
end end
it 'responds with a 401 Unauthorized when an invalid token is used' do it 'responds with a 401 Unauthorized when an invalid token is used' do
get api('/packages/conan/v1/users/check_credentials'), headers: build_auth_headers('invalid-token') get api('/packages/conan/v1/users/check_credentials'), headers: build_auth_headers('invalid-token')
expect(response).to have_gitlab_http_status(401) expect(response).to have_gitlab_http_status(:unauthorized)
end end
end end
...@@ -179,7 +181,7 @@ describe API::ConanPackages do ...@@ -179,7 +181,7 @@ describe API::ConanPackages do
it 'returns 400' do it 'returns 400' do
subject subject
expect(response).to have_gitlab_http_status(400) expect(response).to have_gitlab_http_status(:bad_request)
end end
end end
end end
...@@ -424,7 +426,7 @@ describe API::ConanPackages do ...@@ -424,7 +426,7 @@ describe API::ConanPackages do
it 'returns unauthorized for users without valid permission' do it 'returns unauthorized for users without valid permission' do
subject subject
expect(response).to have_gitlab_http_status(403) expect(response).to have_gitlab_http_status(:forbidden)
end end
context 'with delete permissions' do context 'with delete permissions' do
...@@ -451,7 +453,7 @@ describe API::ConanPackages do ...@@ -451,7 +453,7 @@ describe API::ConanPackages do
it 'returns 400' do it 'returns 400' do
subject subject
expect(response).to have_gitlab_http_status(401) expect(response).to have_gitlab_http_status(:unauthorized)
end end
end end
end end
...@@ -460,7 +462,7 @@ describe API::ConanPackages do ...@@ -460,7 +462,7 @@ describe API::ConanPackages do
it 'returns the file' do it 'returns the file' do
subject subject
expect(response).to have_gitlab_http_status(200) expect(response).to have_gitlab_http_status(:ok)
expect(response.content_type.to_s).to eq('application/octet-stream') expect(response.content_type.to_s).to eq('application/octet-stream')
end end
end end
...@@ -476,7 +478,7 @@ describe API::ConanPackages do ...@@ -476,7 +478,7 @@ describe API::ConanPackages do
it 'returns the file' do it 'returns the file' do
subject subject
expect(response).to have_gitlab_http_status(200) expect(response).to have_gitlab_http_status(:ok)
expect(response.content_type.to_s).to eq('application/octet-stream') expect(response.content_type.to_s).to eq('application/octet-stream')
end end
end end
...@@ -491,7 +493,7 @@ describe API::ConanPackages do ...@@ -491,7 +493,7 @@ describe API::ConanPackages do
it 'returns the file' do it 'returns the file' do
subject subject
expect(response).to have_gitlab_http_status(200) expect(response).to have_gitlab_http_status(:ok)
expect(response.content_type.to_s).to eq('application/octet-stream') expect(response.content_type.to_s).to eq('application/octet-stream')
end end
...@@ -500,7 +502,7 @@ describe API::ConanPackages do ...@@ -500,7 +502,7 @@ describe API::ConanPackages do
subject subject
expect(response).to have_gitlab_http_status(403) expect(response).to have_gitlab_http_status(:forbidden)
end end
end end
...@@ -510,7 +512,7 @@ describe API::ConanPackages do ...@@ -510,7 +512,7 @@ describe API::ConanPackages do
it 'returns forbidden' do it 'returns forbidden' do
subject subject
expect(response).to have_gitlab_http_status(403) expect(response).to have_gitlab_http_status(:forbidden)
end end
end end
...@@ -547,6 +549,253 @@ describe API::ConanPackages do ...@@ -547,6 +549,253 @@ describe API::ConanPackages do
end end
end end
context 'file uploads' do
let(:jwt) { build_jwt(personal_access_token) }
let(:workhorse_token) { JWT.encode({ 'iss' => 'gitlab-workhorse' }, Gitlab::Workhorse.secret, 'HS256') }
let(:workhorse_header) { { 'GitLab-Workhorse' => '1.0', Gitlab::Workhorse::INTERNAL_API_REQUEST_HEADER => workhorse_token } }
let(:headers_with_token) { build_auth_headers(jwt.encoded).merge(workhorse_header) }
let(:recipe_path) { "foo/bar/#{project.full_path.tr('/', '+')}/baz"}
shared_examples 'uploads a package file' do
context 'with object storage disabled' do
context 'without a file from workhorse' do
let(:params) { { file: nil } }
it 'rejects the request' do
subject
expect(response).to have_gitlab_http_status(:bad_request)
end
end
context 'without a token' do
it 'rejects request without a token' do
headers_with_token.delete('HTTP_AUTHORIZATION')
subject
expect(response).to have_gitlab_http_status(:unauthorized)
end
end
context 'when params from workhorse are correct' do
it 'creates package and stores package file' do
expect { subject }
.to change { project.packages.count }.by(1)
.and change { Packages::PackageFile.count }.by(1)
expect(response).to have_gitlab_http_status(:ok)
package_file = project.packages.last.package_files.reload.last
expect(package_file.file_name).to eq(params[:file].original_filename)
end
it "doesn't attempt to migrate file to object storage" do
expect(ObjectStorage::BackgroundMoveWorker).not_to receive(:perform_async)
subject
end
end
end
context 'with object storage enabled' do
context 'and direct upload enabled' do
let!(:fog_connection) do
stub_package_file_object_storage(direct_upload: true)
end
let(:tmp_object) do
fog_connection.directories.new(key: 'packages').files.create(
key: "tmp/uploads/#{file_name}",
body: 'content'
)
end
let(:fog_file) { fog_to_uploaded_file(tmp_object) }
['123123', '../../123123'].each do |remote_id|
context "with invalid remote_id: #{remote_id}" do
let(:params) do
{
file: fog_file,
'file.remote_id' => remote_id
}
end
it 'responds with status 403' do
subject
expect(response).to have_gitlab_http_status(:forbidden)
end
end
end
context 'with valid remote_id' do
let(:params) do
{
file: fog_file,
'file.remote_id' => file_name
}
end
it 'creates package and stores package file' do
expect { subject }
.to change { project.packages.count }.by(1)
.and change { Packages::PackageFile.count }.by(1)
expect(response).to have_gitlab_http_status(:ok)
package_file = project.packages.last.package_files.reload.last
expect(package_file.file_name).to eq(params[:file].original_filename)
expect(package_file.file.read).to eq('content')
end
end
end
context 'and background upload enabled' do
before do
stub_package_file_object_storage(background_upload: true)
end
it 'schedules migration of file to object storage' do
expect(ObjectStorage::BackgroundMoveWorker).to receive(:perform_async).with('Packages::PackageFileUploader', 'Packages::PackageFile', :file, kind_of(Numeric))
subject
end
end
end
end
shared_examples 'workhorse authorization' do
it 'authorizes posting package with a valid token' do
subject
expect(response).to have_gitlab_http_status(:ok)
expect(response.content_type.to_s).to eq(Gitlab::Workhorse::INTERNAL_API_CONTENT_TYPE)
end
it 'rejects request without a valid token' do
headers_with_token['HTTP_AUTHORIZATION'] = 'foo'
subject
expect(response).to have_gitlab_http_status(:unauthorized)
end
it 'rejects request without a valid permission' do
project.add_guest(user)
subject
expect(response).to have_gitlab_http_status(:forbidden)
end
it 'rejects requests that bypassed gitlab-workhorse' do
headers_with_token.delete(Gitlab::Workhorse::INTERNAL_API_REQUEST_HEADER)
subject
expect(response).to have_gitlab_http_status(:error)
end
context 'when using remote storage' do
context 'when direct upload is enabled' do
before do
stub_package_file_object_storage(enabled: true, direct_upload: true)
end
it 'responds with status 200, location of package remote store and object details' do
subject
expect(response).to have_gitlab_http_status(:ok)
expect(response.content_type.to_s).to eq(Gitlab::Workhorse::INTERNAL_API_CONTENT_TYPE)
expect(json_response).not_to have_key('TempPath')
expect(json_response['RemoteObject']).to have_key('ID')
expect(json_response['RemoteObject']).to have_key('GetURL')
expect(json_response['RemoteObject']).to have_key('StoreURL')
expect(json_response['RemoteObject']).to have_key('DeleteURL')
expect(json_response['RemoteObject']).not_to have_key('MultipartUpload')
end
end
context 'when direct upload is disabled' do
before do
stub_package_file_object_storage(enabled: true, direct_upload: false)
end
it 'handles as a local file' do
subject
expect(response).to have_gitlab_http_status(:ok)
expect(response.content_type.to_s).to eq(Gitlab::Workhorse::INTERNAL_API_CONTENT_TYPE)
expect(json_response['TempPath']).to eq(::Packages::PackageFileUploader.workhorse_local_upload_path)
expect(json_response['RemoteObject']).to be_nil
end
end
end
end
describe 'PUT /api/v4/packages/conan/v1/files/:package_name/package_version/:package_username/:package_channel/:recipe_revision/export/:file_name/authorize' do
subject { put api("/packages/conan/v1/files/#{recipe_path}/0/export/conanfile.py/authorize"), headers: headers_with_token }
it_behaves_like 'rejects invalid recipe'
it_behaves_like 'workhorse authorization'
end
describe 'PUT /api/v4/packages/conan/v1/files/:package_name/package_version/:package_username/:package_channel/:recipe_revision/export/:conan_package_reference/:package_revision/:file_name/authorize' do
subject { put api("/packages/conan/v1/files/#{recipe_path}/0/package/123456789/0/conaninfo.txt/authorize"), headers: headers_with_token }
it_behaves_like 'rejects invalid recipe'
it_behaves_like 'workhorse authorization'
end
describe 'PUT /api/v4/packages/conan/v1/files/:package_name/package_version/:package_username/:package_channel/:recipe_revision/export/:file_name' do
let(:file_name) { 'conanfile.py' }
let(:params) { { file: temp_file(file_name) } }
subject do
workhorse_finalize(
"/api/v4/packages/conan/v1/files/#{recipe_path}/0/export/#{file_name}",
method: :put,
file_key: :file,
params: params,
headers: headers_with_token
)
end
it_behaves_like 'rejects invalid recipe'
it_behaves_like 'uploads a package file'
end
describe 'PUT /api/v4/packages/conan/v1/files/:package_name/package_version/:package_username/:package_channel/:recipe_revision/export/:conan_package_reference/:package_revision/:file_name' do
let(:file_name) { 'conaninfo.txt' }
let(:params) { { file: temp_file(file_name) } }
subject do
workhorse_finalize(
"/api/v4/packages/conan/v1/files/#{recipe_path}/0/package/123456789/0/#{file_name}",
method: :put,
file_key: :file,
params: params,
headers: headers_with_token
)
end
it_behaves_like 'rejects invalid recipe'
it_behaves_like 'uploads a package file'
end
def temp_file(package_tmp)
upload_path = ::Packages::PackageFileUploader.workhorse_local_upload_path
file_path = "#{upload_path}/#{package_tmp}"
FileUtils.mkdir_p(upload_path)
File.write(file_path, 'test')
UploadedFile.new(file_path, filename: File.basename(file_path))
end
end
def build_jwt(personal_access_token, secret: jwt_secret, user_id: nil) def build_jwt(personal_access_token, secret: jwt_secret, user_id: nil)
JSONWebToken::HMACToken.new(secret).tap do |jwt| JSONWebToken::HMACToken.new(secret).tap do |jwt|
jwt['pat'] = personal_access_token.id jwt['pat'] = personal_access_token.id
......
# frozen_string_literal: true
require 'spec_helper'
describe Packages::Conan::CreatePackageFileService do
include WorkhorseHelpers
let_it_be(:package) { create(:conan_package) }
describe '#execute' do
let(:file_name) { 'foo.tgz' }
subject { described_class.new(package, file, params) }
shared_examples 'a valid package_file' do
let(:params) do
{
file_name: file_name,
'file.md5': '12345',
'file.sha1': '54321',
'file.size': '128',
'file.type': 'txt',
recipe_revision: '0',
package_revision: '0',
conan_package_reference: '123456789',
conan_file_type: :package_file
}.with_indifferent_access
end
it 'creates a new package file' do
package_file = subject.execute
expect(package_file).to be_valid
expect(package_file.file_name).to eq(file_name)
expect(package_file.file_md5).to eq('12345')
expect(package_file.size).to eq(128)
expect(package_file.conan_file_metadatum).to be_valid
expect(package_file.conan_file_metadatum.recipe_revision).to eq('0')
expect(package_file.conan_file_metadatum.package_revision).to eq('0')
expect(package_file.conan_file_metadatum.conan_package_reference).to eq('123456789')
expect(package_file.conan_file_metadatum.conan_file_type).to eq('package_file')
expect(package_file.file.read).to eq('content')
end
end
shared_examples 'a valid recipe_file' do
let(:params) do
{
file_name: file_name,
'file.md5': '12345',
'file.sha1': '54321',
'file.size': '128',
'file.type': 'txt',
recipe_revision: '0',
conan_file_type: :recipe_file
}.with_indifferent_access
end
it 'creates a new recipe file' do
package_file = subject.execute
expect(package_file).to be_valid
expect(package_file.file_name).to eq(file_name)
expect(package_file.file_md5).to eq('12345')
expect(package_file.size).to eq(128)
expect(package_file.conan_file_metadatum).to be_valid
expect(package_file.conan_file_metadatum.recipe_revision).to eq('0')
expect(package_file.conan_file_metadatum.package_revision).to be_nil
expect(package_file.conan_file_metadatum.conan_package_reference).to be_nil
expect(package_file.conan_file_metadatum.conan_file_type).to eq('recipe_file')
expect(package_file.file.read).to eq('content')
end
end
context 'with temp file' do
let!(:file) do
upload_path = ::Packages::PackageFileUploader.workhorse_local_upload_path
file_path = upload_path + '/' + file_name
FileUtils.mkdir_p(upload_path)
File.write(file_path, 'content')
UploadedFile.new(file_path, filename: File.basename(file_path))
end
it_behaves_like 'a valid package_file'
it_behaves_like 'a valid recipe_file'
end
context 'with remote file' do
let!(:fog_connection) do
stub_package_file_object_storage(direct_upload: true)
end
let(:tmp_object) do
fog_connection.directories.new(key: 'packages').files.create(
key: "tmp/uploads/#{file_name}",
body: 'content'
)
end
let(:file) { fog_to_uploaded_file(tmp_object) }
it_behaves_like 'a valid package_file'
it_behaves_like 'a valid recipe_file'
end
context 'file is missing' do
let(:file) { nil }
let(:params) do
{
file_name: file_name,
recipe_revision: '0',
conan_file_type: :recipe_file
}
end
it 'raises an error' do
expect { subject.execute }.to raise_error(ActiveRecord::RecordInvalid)
end
end
end
end
# frozen_string_literal: true
require 'spec_helper'
describe Packages::Conan::CreatePackageService do
let_it_be(:project) { create(:project) }
let_it_be(:user) { create(:user) }
subject { described_class.new(project, user, params) }
describe '#execute' do
context 'valid params' do
let(:params) do
{
package_name: 'my-pkg',
package_version: '1.0.0',
package_username: ::Packages::ConanMetadatum.package_username_from(full_path: project.full_path),
package_channel: 'stable'
}
end
it 'creates a new package' do
package = subject.execute
expect(package).to be_valid
expect(package.name).to eq(params[:package_name])
expect(package.version).to eq(params[:package_version])
expect(package.package_type).to eq('conan')
expect(package.conan_metadatum.package_username).to eq(params[:package_username])
expect(package.conan_metadatum.package_channel).to eq(params[:package_channel])
end
end
context 'invalid params' do
let(:params) do
{
package_name: 'my-pkg',
package_version: '1.0.0',
package_username: 'foo/bar',
package_channel: 'stable'
}
end
it 'fails' do
expect { subject.execute }.to raise_exception(ActiveRecord::RecordInvalid)
end
end
end
end
...@@ -56,6 +56,13 @@ module StubObjectStorage ...@@ -56,6 +56,13 @@ module StubObjectStorage
**params) **params)
end end
def stub_package_file_object_storage(**params)
stub_object_storage_uploader(config: Gitlab.config.packages.object_store,
uploader: ::Packages::PackageFileUploader,
remote_directory: 'packages',
**params)
end
def stub_uploads_object_storage(uploader = described_class, **params) def stub_uploads_object_storage(uploader = described_class, **params)
stub_object_storage_uploader(config: Gitlab.config.uploads.object_store, stub_object_storage_uploader(config: Gitlab.config.uploads.object_store,
uploader: uploader, uploader: uploader,
......
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