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
storage_options Gitlab.config.packages
after :store, :schedule_background_upload
alias_method :upload, :model
def filename
......
......@@ -24,6 +24,7 @@ module API
}.freeze
PACKAGE_COMPONENT_REGEX = Gitlab::Regex.conan_recipe_component_regex
CONAN_REVISION_REGEX = Gitlab::Regex.conan_revision_regex
before do
not_found! unless Feature.enabled?(:conan_package_registry)
......@@ -195,7 +196,7 @@ module API
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_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
namespace 'files/:package_name/:package_version/:package_username/:package_channel/:recipe_revision', requirements: PACKAGE_REQUIREMENTS do
before do
......@@ -203,21 +204,44 @@ module API
end
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
namespace 'export/:file_name', requirements: FILE_NAME_REQUIREMENTS 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
get do
download_package_file(:recipe_file)
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
params do
requires :conan_package_reference, type: String, desc: 'Conan Package ID'
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
namespace 'package/:conan_package_reference/:package_revision/:file_name', requirements: FILE_NAME_REQUIREMENTS do
desc 'Download package files' do
......@@ -226,6 +250,29 @@ module API
get do
download_package_file(:package_file)
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
......@@ -329,6 +376,37 @@ module API
present_carrierwave_file!(package_file.file)
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
personal_access_token = find_personal_access_token_from_conan_jwt ||
find_personal_access_token_from_conan_http_basic_auth
......
......@@ -110,4 +110,14 @@ RSpec.describe Packages::Package, type: :model do
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
......@@ -2,6 +2,8 @@
require 'spec_helper'
describe API::ConanPackages do
include WorkhorseHelpers
let(:package) { create(:conan_package) }
let_it_be(:personal_access_token) { create(:personal_access_token) }
let_it_be(:user) { personal_access_token.user }
......@@ -37,7 +39,7 @@ describe API::ConanPackages do
it 'responds with 404 Not Found' do
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
......@@ -45,14 +47,14 @@ describe API::ConanPackages do
it 'responds with 401 Unauthorized when no token provided' do
get api('/packages/conan/v1/ping')
expect(response).to have_gitlab_http_status(401)
expect(response).to have_gitlab_http_status(:unauthorized)
end
it 'responds with 200 OK when valid token is provided' do
jwt = build_jwt(personal_access_token)
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("")
end
......@@ -60,27 +62,27 @@ describe API::ConanPackages do
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)
expect(response).to have_gitlab_http_status(401)
expect(response).to have_gitlab_http_status(:unauthorized)
end
it 'responds with 401 Unauthorized when invalid user is provided' do
jwt = build_jwt(personal_access_token, user_id: 12345)
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
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))
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
it 'responds with 401 Unauthorized when invalid JWT is provided' do
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
context 'packages feature disabled' do
......@@ -88,7 +90,7 @@ describe API::ConanPackages do
stub_packages_setting(enabled: false)
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
......@@ -131,7 +133,7 @@ describe API::ConanPackages do
it 'responds with 401' do
subject
expect(response).to have_gitlab_http_status(401)
expect(response).to have_gitlab_http_status(:unauthorized)
end
end
......@@ -139,7 +141,7 @@ describe API::ConanPackages do
it 'responds with 200' do
subject
expect(response).to have_gitlab_http_status(200)
expect(response).to have_gitlab_http_status(:ok)
end
it 'token has valid validity time' do
......@@ -162,13 +164,13 @@ describe API::ConanPackages do
it 'responds with a 200 OK' do
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
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')
expect(response).to have_gitlab_http_status(401)
expect(response).to have_gitlab_http_status(:unauthorized)
end
end
......@@ -179,7 +181,7 @@ describe API::ConanPackages do
it 'returns 400' do
subject
expect(response).to have_gitlab_http_status(400)
expect(response).to have_gitlab_http_status(:bad_request)
end
end
end
......@@ -424,7 +426,7 @@ describe API::ConanPackages do
it 'returns unauthorized for users without valid permission' do
subject
expect(response).to have_gitlab_http_status(403)
expect(response).to have_gitlab_http_status(:forbidden)
end
context 'with delete permissions' do
......@@ -451,7 +453,7 @@ describe API::ConanPackages do
it 'returns 400' do
subject
expect(response).to have_gitlab_http_status(401)
expect(response).to have_gitlab_http_status(:unauthorized)
end
end
end
......@@ -460,7 +462,7 @@ describe API::ConanPackages do
it 'returns the file' do
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')
end
end
......@@ -476,7 +478,7 @@ describe API::ConanPackages do
it 'returns the file' do
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')
end
end
......@@ -491,7 +493,7 @@ describe API::ConanPackages do
it 'returns the file' do
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')
end
......@@ -500,7 +502,7 @@ describe API::ConanPackages do
subject
expect(response).to have_gitlab_http_status(403)
expect(response).to have_gitlab_http_status(:forbidden)
end
end
......@@ -510,7 +512,7 @@ describe API::ConanPackages do
it 'returns forbidden' do
subject
expect(response).to have_gitlab_http_status(403)
expect(response).to have_gitlab_http_status(:forbidden)
end
end
......@@ -547,6 +549,253 @@ describe API::ConanPackages do
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)
JSONWebToken::HMACToken.new(secret).tap do |jwt|
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
**params)
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)
stub_object_storage_uploader(config: Gitlab.config.uploads.object_store,
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