Commit f8858c8f authored by Steve Abrams's avatar Steve Abrams

RubyGems create package service and upload routes

Adds the create service to create the temporary
package record for an uploaded rubygems package.

Implements the two rubygems upload routes.
parent 0bf9d165
# frozen_string_literal: true # frozen_string_literal: true
module Packages module Packages
module Nuget module Nuget
TEMPORARY_PACKAGE_NAME = 'NuGet.Temporary.Package'
def self.table_name_prefix def self.table_name_prefix
'packages_nuget_' 'packages_nuget_'
end end
......
...@@ -98,12 +98,12 @@ class Packages::Package < ApplicationRecord ...@@ -98,12 +98,12 @@ class Packages::Package < ApplicationRecord
end end
scope :preload_composer, -> { preload(:composer_metadatum) } scope :preload_composer, -> { preload(:composer_metadatum) }
scope :without_nuget_temporary_name, -> { where.not(name: Packages::Nuget::CreatePackageService::TEMPORARY_PACKAGE_NAME) } scope :without_nuget_temporary_name, -> { where.not(name: Packages::Nuget::TEMPORARY_PACKAGE_NAME) }
scope :has_version, -> { where.not(version: nil) } scope :has_version, -> { where.not(version: nil) }
scope :processed, -> do scope :processed, -> do
where.not(package_type: :nuget).or( where.not(package_type: :nuget).or(
where.not(name: Packages::Nuget::CreatePackageService::TEMPORARY_PACKAGE_NAME) where.not(name: Packages::Nuget::TEMPORARY_PACKAGE_NAME)
) )
end end
scope :preload_files, -> { preload(:package_files) } scope :preload_files, -> { preload(:package_files) }
......
# frozen_string_literal: true
module Packages
module Rubygems
TEMPORARY_PACKAGE_NAME = 'Gem.Temporary.Package'
def self.table_name_prefix
'packages_rubygems_'
end
end
end
...@@ -3,7 +3,6 @@ ...@@ -3,7 +3,6 @@
module Packages module Packages
module Rubygems module Rubygems
class Metadatum < ApplicationRecord class Metadatum < ApplicationRecord
self.table_name = 'packages_rubygems_metadata'
self.primary_key = :package_id self.primary_key = :package_id
belongs_to :package, -> { where(package_type: :rubygems) }, inverse_of: :rubygems_metadatum belongs_to :package, -> { where(package_type: :rubygems) }, inverse_of: :rubygems_metadatum
......
# frozen_string_literal: true
module Packages
class CreateTemporaryPackageService < ::Packages::CreatePackageService
PACKAGE_VERSION = '0.0.0'
def execute(package_type, name: 'Temporary.Package')
create_package!(package_type,
name: name,
version: "#{PACKAGE_VERSION}-#{uuid}",
status: 'processing'
)
end
private
def uuid
SecureRandom.uuid
end
end
end
# frozen_string_literal: true
module Packages
module Nuget
class CreatePackageService < ::Packages::CreatePackageService
TEMPORARY_PACKAGE_NAME = 'NuGet.Temporary.Package'
PACKAGE_VERSION = '0.0.0'
def execute
create_package!(:nuget,
name: TEMPORARY_PACKAGE_NAME,
version: "#{PACKAGE_VERSION}-#{uuid}"
)
end
private
def uuid
SecureRandom.uuid
end
end
end
end
...@@ -68,7 +68,8 @@ module Packages ...@@ -68,7 +68,8 @@ module Packages
def update_linked_package def update_linked_package
@package_file.package.update!( @package_file.package.update!(
name: package_name, name: package_name,
version: package_version version: package_version,
status: :default
) )
::Packages::Nuget::CreateDependencyService.new(@package_file.package, package_dependencies) ::Packages::Nuget::CreateDependencyService.new(@package_file.package, package_dependencies)
......
...@@ -103,8 +103,10 @@ class Gitlab::Seeder::Packages ...@@ -103,8 +103,10 @@ class Gitlab::Seeder::Packages
name = "MyNugetApp.Package#{i}" name = "MyNugetApp.Package#{i}"
version = "4.2.#{i}" version = "4.2.#{i}"
pkg = ::Packages::Nuget::CreatePackageService.new(project, project.creator, {}).execute pkg = ::Packages::CreateTemporaryPackageService.new(
# when using ::Packages::Nuget::CreatePackageService, packages have a fixed name and a fixed version. project, project.creator, {}
).execute(:nuget, name: Packages::Nuget::TEMPORARY_PACKAGE_NAME)
# when using ::Packages::CreateTemporaryPackageService, packages have a fixed name and a fixed version.
pkg.update!(name: name, version: version) pkg.update!(name: name, version: version)
filename = 'package.nupkg' filename = 'package.nupkg'
......
...@@ -62,8 +62,9 @@ module API ...@@ -62,8 +62,9 @@ module API
file_name: PACKAGE_FILENAME file_name: PACKAGE_FILENAME
) )
package = ::Packages::Nuget::CreatePackageService.new(project_or_group, current_user, declared_params.merge(build: current_authenticated_job)) package = ::Packages::CreateTemporaryPackageService.new(
.execute project_or_group, current_user, declared_params.merge(build: current_authenticated_job)
).execute(:nuget, name: ::Packages::Nuget::TEMPORARY_PACKAGE_NAME)
package_file = ::Packages::CreatePackageFileService.new(package, file_params.merge(build: current_authenticated_job)) package_file = ::Packages::CreatePackageFileService.new(package, file_params.merge(build: current_authenticated_job))
.execute .execute
......
...@@ -12,7 +12,7 @@ module API ...@@ -12,7 +12,7 @@ module API
# The Marshal version can be found by "#{Marshal::MAJOR_VERSION}.#{Marshal::MINOR_VERSION}" # The Marshal version can be found by "#{Marshal::MAJOR_VERSION}.#{Marshal::MINOR_VERSION}"
# Updating the version should require a GitLab API version change. # Updating the version should require a GitLab API version change.
MARSHAL_VERSION = '4.8' MARSHAL_VERSION = '4.8'
PACKAGE_FILENAME = 'package.gem'
FILE_NAME_REQUIREMENTS = { FILE_NAME_REQUIREMENTS = {
file_name: API::NO_SLASH_URL_PART_REGEX file_name: API::NO_SLASH_URL_PART_REGEX
}.freeze }.freeze
...@@ -73,16 +73,45 @@ module API ...@@ -73,16 +73,45 @@ module API
detail 'This feature was introduced in GitLab 13.9' detail 'This feature was introduced in GitLab 13.9'
end end
post 'gems/authorize' do post 'gems/authorize' do
# To be implemented in https://gitlab.com/gitlab-org/gitlab/-/issues/299263 authorize_workhorse!(
not_found! subject: user_project,
has_length: false,
maximum_size: user_project.actual_limits.rubygems_max_file_size
)
end end
desc 'Upload a gem' do desc 'Upload a gem' do
detail 'This feature was introduced in GitLab 13.9' detail 'This feature was introduced in GitLab 13.9'
end end
params do
requires :file, type: ::API::Validations::Types::WorkhorseFile, desc: 'The package file to be published (generated by Multipart middleware)'
end
post 'gems' do post 'gems' do
# To be implemented in https://gitlab.com/gitlab-org/gitlab/-/issues/299263 authorize_upload!(user_project)
not_found! bad_request!('File is too large') if user_project.actual_limits.exceeded?(:rubygems_max_file_size, params[:file].size)
track_package_event('push_package', :rubygems)
ActiveRecord::Base.transaction do
package = ::Packages::CreateTemporaryPackageService.new(
user_project, current_user, declared_params.merge(build: current_authenticated_job)
).execute(:rubygems, name: ::Packages::Rubygems::TEMPORARY_PACKAGE_NAME)
file_params = {
file: params[:file],
file_name: PACKAGE_FILENAME
}
::Packages::CreatePackageFileService.new(
package, file_params.merge(build: current_authenticated_job)
).execute
end
created!
rescue ObjectStorage::RemoteStoreError => e
Gitlab::ErrorTracking.track_exception(e, extra: { file_name: params[:file_name], project_id: user_project.id })
forbidden!
end end
desc 'Fetch a list of dependencies' do desc 'Fetch a list of dependencies' do
......
...@@ -41,6 +41,9 @@ ...@@ -41,6 +41,9 @@
- i_package_pypi_delete_package - i_package_pypi_delete_package
- i_package_pypi_pull_package - i_package_pypi_pull_package
- i_package_pypi_push_package - i_package_pypi_push_package
- i_package_rubygems_delete_package
- i_package_rubygems_pull_package
- i_package_rubygems_push_package
- i_package_tag_delete_package - i_package_tag_delete_package
- i_package_tag_pull_package - i_package_tag_pull_package
- i_package_tag_push_package - i_package_tag_push_package
...@@ -99,6 +99,16 @@ ...@@ -99,6 +99,16 @@
aggregation: weekly aggregation: weekly
redis_slot: package redis_slot: package
feature_flag: collect_package_events_redis feature_flag: collect_package_events_redis
- name: i_package_rubygems_deploy_token
category: deploy_token_packages
aggregation: weekly
redis_slot: package
feature_flag: collect_package_events_redis
- name: i_package_rubygems_user
category: user_packages
aggregation: weekly
redis_slot: package
feature_flag: collect_package_events_redis
- name: i_package_tag_deploy_token - name: i_package_tag_deploy_token
category: deploy_token_packages category: deploy_token_packages
aggregation: weekly aggregation: weekly
......
...@@ -122,7 +122,7 @@ RSpec.describe Packages::GroupPackagesFinder do ...@@ -122,7 +122,7 @@ RSpec.describe Packages::GroupPackagesFinder do
end end
context 'when there are processing packages' do context 'when there are processing packages' do
let_it_be(:package4) { create(:nuget_package, project: project, name: Packages::Nuget::CreatePackageService::TEMPORARY_PACKAGE_NAME) } let_it_be(:package4) { create(:nuget_package, project: project, name: Packages::Nuget::TEMPORARY_PACKAGE_NAME) }
it { is_expected.to match_array([package1, package2]) } it { is_expected.to match_array([package1, package2]) }
end end
......
...@@ -14,7 +14,7 @@ RSpec.describe ::Packages::PackageFinder do ...@@ -14,7 +14,7 @@ RSpec.describe ::Packages::PackageFinder do
it { is_expected.to eq(maven_package) } it { is_expected.to eq(maven_package) }
context 'processing packages' do context 'processing packages' do
let_it_be(:nuget_package) { create(:nuget_package, project: project, name: Packages::Nuget::CreatePackageService::TEMPORARY_PACKAGE_NAME) } let_it_be(:nuget_package) { create(:nuget_package, project: project, name: Packages::Nuget::TEMPORARY_PACKAGE_NAME) }
let(:package_id) { nuget_package.id } let(:package_id) { nuget_package.id }
it 'are not returned' do it 'are not returned' do
......
...@@ -76,7 +76,7 @@ RSpec.describe ::Packages::PackagesFinder do ...@@ -76,7 +76,7 @@ RSpec.describe ::Packages::PackagesFinder do
end end
context 'with processing packages' do context 'with processing packages' do
let_it_be(:nuget_package) { create(:nuget_package, project: project, name: Packages::Nuget::CreatePackageService::TEMPORARY_PACKAGE_NAME) } let_it_be(:nuget_package) { create(:nuget_package, project: project, name: Packages::Nuget::TEMPORARY_PACKAGE_NAME) }
it { is_expected.to match_array([conan_package, maven_package]) } it { is_expected.to match_array([conan_package, maven_package]) }
end end
......
...@@ -14,7 +14,7 @@ RSpec.describe Gitlab::UsageDataCounters::PackageEventCounter, :clean_gitlab_red ...@@ -14,7 +14,7 @@ RSpec.describe Gitlab::UsageDataCounters::PackageEventCounter, :clean_gitlab_red
end end
it 'includes the right events' do it 'includes the right events' do
expect(described_class::KNOWN_EVENTS.size).to eq 45 expect(described_class::KNOWN_EVENTS.size).to eq 48
end end
described_class::KNOWN_EVENTS.each do |event| described_class::KNOWN_EVENTS.each do |event|
......
...@@ -511,7 +511,7 @@ RSpec.describe Packages::Package, type: :model do ...@@ -511,7 +511,7 @@ RSpec.describe Packages::Package, type: :model do
describe '.without_nuget_temporary_name' do describe '.without_nuget_temporary_name' do
let!(:package1) { create(:nuget_package) } let!(:package1) { create(:nuget_package) }
let!(:package2) { create(:nuget_package, name: Packages::Nuget::CreatePackageService::TEMPORARY_PACKAGE_NAME) } let!(:package2) { create(:nuget_package, name: Packages::Nuget::TEMPORARY_PACKAGE_NAME) }
subject { described_class.without_nuget_temporary_name } subject { described_class.without_nuget_temporary_name }
...@@ -530,7 +530,7 @@ RSpec.describe Packages::Package, type: :model do ...@@ -530,7 +530,7 @@ RSpec.describe Packages::Package, type: :model do
it { is_expected.to match_array([package1, package2, package3]) } it { is_expected.to match_array([package1, package2, package3]) }
context 'with temporary packages' do context 'with temporary packages' do
let!(:package1) { create(:nuget_package, name: Packages::Nuget::CreatePackageService::TEMPORARY_PACKAGE_NAME) } let!(:package1) { create(:nuget_package, name: Packages::Nuget::TEMPORARY_PACKAGE_NAME) }
it { is_expected.to match_array([package2, package3]) } it { is_expected.to match_array([package2, package3]) }
end end
......
...@@ -3,9 +3,11 @@ ...@@ -3,9 +3,11 @@
require 'spec_helper' require 'spec_helper'
RSpec.describe API::RubygemPackages do RSpec.describe API::RubygemPackages do
include PackagesManagerApiSpecHelpers
include WorkhorseHelpers
using RSpec::Parameterized::TableSyntax using RSpec::Parameterized::TableSyntax
let_it_be(:project) { create(:project) } let_it_be_with_reload(:project) { create(:project) }
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 }
let_it_be(:job) { create(:ci_build, :running, user: user) } let_it_be(:job) { create(:ci_build, :running, user: user) }
...@@ -13,6 +15,14 @@ RSpec.describe API::RubygemPackages do ...@@ -13,6 +15,14 @@ RSpec.describe API::RubygemPackages do
let_it_be(:project_deploy_token) { create(:project_deploy_token, deploy_token: deploy_token, project: project) } let_it_be(:project_deploy_token) { create(:project_deploy_token, deploy_token: deploy_token, project: project) }
let_it_be(:headers) { {} } let_it_be(:headers) { {} }
let(:tokens) do
{
personal_access_token: personal_access_token.token,
deploy_token: deploy_token.token,
job_token: job.token
}
end
shared_examples 'when feature flag is disabled' do shared_examples 'when feature flag is disabled' do
let(:headers) do let(:headers) do
{ 'HTTP_AUTHORIZATION' => personal_access_token.token } { 'HTTP_AUTHORIZATION' => personal_access_token.token }
...@@ -42,14 +52,6 @@ RSpec.describe API::RubygemPackages do ...@@ -42,14 +52,6 @@ RSpec.describe API::RubygemPackages do
{ 'HTTP_AUTHORIZATION' => token } { 'HTTP_AUTHORIZATION' => token }
end end
let(:tokens) do
{
personal_access_token: personal_access_token.token,
deploy_token: deploy_token.token,
job_token: job.token
}
end
where(:user_role, :token_type, :valid_token, :status) do where(:user_role, :token_type, :valid_token, :status) do
:guest | :personal_access_token | true | :not_found :guest | :personal_access_token | true | :not_found
:guest | :personal_access_token | false | :unauthorized :guest | :personal_access_token | false | :unauthorized
...@@ -114,19 +116,163 @@ RSpec.describe API::RubygemPackages do ...@@ -114,19 +116,163 @@ RSpec.describe API::RubygemPackages do
end end
describe 'POST /api/v4/projects/:project_id/packages/rubygems/api/v1/gems/authorize' do describe 'POST /api/v4/projects/:project_id/packages/rubygems/api/v1/gems/authorize' do
include_context 'workhorse headers'
let(:url) { api("/projects/#{project.id}/packages/rubygems/api/v1/gems/authorize") } let(:url) { api("/projects/#{project.id}/packages/rubygems/api/v1/gems/authorize") }
let(:headers) { {} }
subject { post(url, headers: headers) } subject { post(url, headers: headers) }
it_behaves_like 'an unimplemented route' context 'with valid project' do
where(:visibility, :user_role, :member, :token_type, :valid_token, :shared_examples_name, :expected_status) do
:public | :developer | true | :personal_access_token | true | 'process rubygems workhorse authorization' | :success
:public | :guest | true | :personal_access_token | true | 'rejects rubygems packages access' | :forbidden
:public | :developer | true | :personal_access_token | false | 'rejects rubygems packages access' | :unauthorized
:public | :guest | true | :personal_access_token | false | 'rejects rubygems packages access' | :unauthorized
:public | :developer | false | :personal_access_token | true | 'rejects rubygems packages access' | :forbidden
:public | :guest | false | :personal_access_token | true | 'rejects rubygems packages access' | :forbidden
:public | :developer | false | :personal_access_token | false | 'rejects rubygems packages access' | :unauthorized
:public | :guest | false | :personal_access_token | false | 'rejects rubygems packages access' | :unauthorized
:public | :anonymous | false | :personal_access_token | true | 'rejects rubygems packages access' | :unauthorized
:private | :developer | true | :personal_access_token | true | 'process rubygems workhorse authorization' | :success
:private | :guest | true | :personal_access_token | true | 'rejects rubygems packages access' | :forbidden
:private | :developer | true | :personal_access_token | false | 'rejects rubygems packages access' | :unauthorized
:private | :guest | true | :personal_access_token | false | 'rejects rubygems packages access' | :unauthorized
:private | :developer | false | :personal_access_token | true | 'rejects rubygems packages access' | :not_found
:private | :guest | false | :personal_access_token | true | 'rejects rubygems packages access' | :not_found
:private | :developer | false | :personal_access_token | false | 'rejects rubygems packages access' | :unauthorized
:private | :guest | false | :personal_access_token | false | 'rejects rubygems packages access' | :unauthorized
:private | :anonymous | false | :personal_access_token | true | 'rejects rubygems packages access' | :unauthorized
:public | :developer | true | :job_token | true | 'process rubygems workhorse authorization' | :success
:public | :guest | true | :job_token | true | 'rejects rubygems packages access' | :forbidden
:public | :developer | true | :job_token | false | 'rejects rubygems packages access' | :unauthorized
:public | :guest | true | :job_token | false | 'rejects rubygems packages access' | :unauthorized
:public | :developer | false | :job_token | true | 'rejects rubygems packages access' | :forbidden
:public | :guest | false | :job_token | true | 'rejects rubygems packages access' | :forbidden
:public | :developer | false | :job_token | false | 'rejects rubygems packages access' | :unauthorized
:public | :guest | false | :job_token | false | 'rejects rubygems packages access' | :unauthorized
:private | :developer | true | :job_token | true | 'process rubygems workhorse authorization' | :success
:private | :guest | true | :job_token | true | 'rejects rubygems packages access' | :forbidden
:private | :developer | true | :job_token | false | 'rejects rubygems packages access' | :unauthorized
:private | :guest | true | :job_token | false | 'rejects rubygems packages access' | :unauthorized
:private | :developer | false | :job_token | true | 'rejects rubygems packages access' | :not_found
:private | :guest | false | :job_token | true | 'rejects rubygems packages access' | :not_found
:private | :developer | false | :job_token | false | 'rejects rubygems packages access' | :unauthorized
:private | :guest | false | :job_token | false | 'rejects rubygems packages access' | :unauthorized
:public | :developer | true | :deploy_token | true | 'process rubygems workhorse authorization' | :success
:public | :developer | true | :deploy_token | false | 'rejects rubygems packages access' | :unauthorized
:private | :developer | true | :deploy_token | true | 'process rubygems workhorse authorization' | :success
:private | :developer | true | :deploy_token | false | 'rejects rubygems packages access' | :unauthorized
end
with_them do
let(:token) { valid_token ? tokens[token_type] : 'invalid-token123' }
let(:user_headers) { user_role == :anonymous ? {} : { 'HTTP_AUTHORIZATION' => token } }
let(:headers) { user_headers.merge(workhorse_headers) }
before do
project.update!(visibility: visibility.to_s)
end
it_behaves_like params[:shared_examples_name], params[:user_role], params[:expected_status], params[:member]
end
end
end end
describe 'POST /api/v4/projects/:project_id/packages/rubygems/api/v1/gems' do describe 'POST /api/v4/projects/:project_id/packages/rubygems/api/v1/gems' do
let(:url) { api("/projects/#{project.id}/packages/rubygems/api/v1/gems") } include_context 'workhorse headers'
let(:url) { "/projects/#{project.id}/packages/rubygems/api/v1/gems" }
let_it_be(:file_name) { 'package.gem' }
let(:headers) { {} }
let(:params) { { file: temp_file(file_name) } }
let(:file_key) { :file }
let(:send_rewritten_field) { true }
subject do
workhorse_finalize(
api(url),
method: :post,
file_key: file_key,
params: params,
headers: headers,
send_rewritten_field: send_rewritten_field
)
end
subject { post(url, headers: headers) } context 'with valid project' do
where(:visibility, :user_role, :member, :token_type, :valid_token, :shared_examples_name, :expected_status) do
:public | :developer | true | :personal_access_token | true | 'process rubygems upload' | :created
:public | :guest | true | :personal_access_token | true | 'rejects rubygems packages access' | :forbidden
:public | :developer | true | :personal_access_token | false | 'rejects rubygems packages access' | :unauthorized
:public | :guest | true | :personal_access_token | false | 'rejects rubygems packages access' | :unauthorized
:public | :developer | false | :personal_access_token | true | 'rejects rubygems packages access' | :forbidden
:public | :guest | false | :personal_access_token | true | 'rejects rubygems packages access' | :forbidden
:public | :developer | false | :personal_access_token | false | 'rejects rubygems packages access' | :unauthorized
:public | :guest | false | :personal_access_token | false | 'rejects rubygems packages access' | :unauthorized
:public | :anonymous | false | :personal_access_token | true | 'rejects rubygems packages access' | :unauthorized
:private | :developer | true | :personal_access_token | true | 'process rubygems upload' | :created
:private | :guest | true | :personal_access_token | true | 'rejects rubygems packages access' | :forbidden
:private | :developer | true | :personal_access_token | false | 'rejects rubygems packages access' | :unauthorized
:private | :guest | true | :personal_access_token | false | 'rejects rubygems packages access' | :unauthorized
:private | :developer | false | :personal_access_token | true | 'rejects rubygems packages access' | :not_found
:private | :guest | false | :personal_access_token | true | 'rejects rubygems packages access' | :not_found
:private | :developer | false | :personal_access_token | false | 'rejects rubygems packages access' | :unauthorized
:private | :guest | false | :personal_access_token | false | 'rejects rubygems packages access' | :unauthorized
:private | :anonymous | false | :personal_access_token | true | 'rejects rubygems packages access' | :unauthorized
:public | :developer | true | :job_token | true | 'process rubygems upload' | :created
:public | :guest | true | :job_token | true | 'rejects rubygems packages access' | :forbidden
:public | :developer | true | :job_token | false | 'rejects rubygems packages access' | :unauthorized
:public | :guest | true | :job_token | false | 'rejects rubygems packages access' | :unauthorized
:public | :developer | false | :job_token | true | 'rejects rubygems packages access' | :forbidden
:public | :guest | false | :job_token | true | 'rejects rubygems packages access' | :forbidden
:public | :developer | false | :job_token | false | 'rejects rubygems packages access' | :unauthorized
:public | :guest | false | :job_token | false | 'rejects rubygems packages access' | :unauthorized
:private | :developer | true | :job_token | true | 'process rubygems upload' | :created
:private | :guest | true | :job_token | true | 'rejects rubygems packages access' | :forbidden
:private | :developer | true | :job_token | false | 'rejects rubygems packages access' | :unauthorized
:private | :guest | true | :job_token | false | 'rejects rubygems packages access' | :unauthorized
:private | :developer | false | :job_token | true | 'rejects rubygems packages access' | :not_found
:private | :guest | false | :job_token | true | 'rejects rubygems packages access' | :not_found
:private | :developer | false | :job_token | false | 'rejects rubygems packages access' | :unauthorized
:private | :guest | false | :job_token | false | 'rejects rubygems packages access' | :unauthorized
:public | :developer | true | :deploy_token | true | 'process rubygems upload' | :created
:public | :developer | true | :deploy_token | false | 'rejects rubygems packages access' | :unauthorized
:private | :developer | true | :deploy_token | true | 'process rubygems upload' | :created
:private | :developer | true | :deploy_token | false | 'rejects rubygems packages access' | :unauthorized
end
it_behaves_like 'an unimplemented route' with_them do
let(:token) { valid_token ? tokens[token_type] : 'invalid-token123' }
let(:user_headers) { user_role == :anonymous ? {} : { 'HTTP_AUTHORIZATION' => token } }
let(:headers) { user_headers.merge(workhorse_headers) }
before do
project.update!(visibility: visibility.to_s)
end
it_behaves_like params[:shared_examples_name], params[:user_role], params[:expected_status], params[:member]
end
context 'failed package file save' do
let(:user_headers) { { 'HTTP_AUTHORIZATION' => personal_access_token.token } }
let(:headers) { user_headers.merge(workhorse_headers) }
before do
project.add_developer(user)
end
it 'does not create package record', :aggregate_failures do
allow(Packages::CreatePackageFileService).to receive(:new).and_raise(StandardError)
expect { subject }
.to change { project.packages.count }.by(0)
.and change { Packages::PackageFile.count }.by(0)
expect(response).to have_gitlab_http_status(:error)
end
end
end
end end
describe 'GET /api/v4/projects/:project_id/packages/rubygems/api/v1/dependencies' do describe 'GET /api/v4/projects/:project_id/packages/rubygems/api/v1/dependencies' do
......
# frozen_string_literal: true # frozen_string_literal: true
require 'spec_helper' require 'spec_helper'
RSpec.describe Packages::Nuget::CreatePackageService do RSpec.describe Packages::CreateTemporaryPackageService do
let_it_be(:project) { create(:project) } let_it_be(:project) { create(:project) }
let_it_be(:user) { create(:user) } let_it_be(:user) { create(:user) }
let_it_be(:params) { {} } let_it_be(:params) { {} }
let_it_be(:package_name) { 'my-package' }
let_it_be(:package_type) { 'rubygems' }
describe '#execute' do describe '#execute' do
subject { described_class.new(project, user, params).execute } subject { described_class.new(project, user, params).execute(package_type, name: package_name) }
let(:package) { Packages::Package.last } let(:package) { Packages::Package.last }
it 'creates the package' do it 'creates the package', :aggregate_failures do
expect { subject }.to change { Packages::Package.count }.by(1) expect { subject }.to change { Packages::Package.count }.by(1)
expect(package).to be_valid expect(package).to be_valid
expect(package.name).to eq(Packages::Nuget::CreatePackageService::TEMPORARY_PACKAGE_NAME) expect(package).to be_processing
expect(package.version).to start_with(Packages::Nuget::CreatePackageService::PACKAGE_VERSION) expect(package.name).to eq(package_name)
expect(package.package_type).to eq('nuget') expect(package.version).to start_with(described_class::PACKAGE_VERSION)
expect(package.package_type).to eq(package_type)
end end
it 'can create two packages in a row' do it 'can create two packages in a row', :aggregate_failures do
expect { subject }.to change { Packages::Package.count }.by(1) expect { subject }.to change { Packages::Package.count }.by(1)
expect { described_class.new(project, user, params).execute }.to change { Packages::Package.count }.by(1)
expect do
described_class.new(project, user, params).execute(package_type, name: package_name)
end.to change { Packages::Package.count }.by(1)
expect(package).to be_valid expect(package).to be_valid
expect(package.name).to eq(Packages::Nuget::CreatePackageService::TEMPORARY_PACKAGE_NAME) expect(package).to be_processing
expect(package.version).to start_with(Packages::Nuget::CreatePackageService::PACKAGE_VERSION) expect(package.name).to eq(package_name)
expect(package.package_type).to eq('nuget') expect(package.version).to start_with(described_class::PACKAGE_VERSION)
expect(package.package_type).to eq(package_type)
end end
it_behaves_like 'assigns the package creator' it_behaves_like 'assigns the package creator'
it_behaves_like 'assigns build to package' it_behaves_like 'assigns build to package'
it_behaves_like 'assigns status to package'
end end
end end
...@@ -5,7 +5,7 @@ require 'spec_helper' ...@@ -5,7 +5,7 @@ require 'spec_helper'
RSpec.describe Packages::Nuget::UpdatePackageFromMetadataService, :clean_gitlab_redis_shared_state do RSpec.describe Packages::Nuget::UpdatePackageFromMetadataService, :clean_gitlab_redis_shared_state do
include ExclusiveLeaseHelpers include ExclusiveLeaseHelpers
let(:package) { create(:nuget_package) } let(:package) { create(:nuget_package, :processing) }
let(:package_file) { package.package_files.first } let(:package_file) { package.package_files.first }
let(:service) { described_class.new(package_file) } let(:service) { described_class.new(package_file) }
let(:package_name) { 'DummyProject.DummyPackage' } let(:package_name) { 'DummyProject.DummyPackage' }
...@@ -60,6 +60,7 @@ RSpec.describe Packages::Nuget::UpdatePackageFromMetadataService, :clean_gitlab_ ...@@ -60,6 +60,7 @@ RSpec.describe Packages::Nuget::UpdatePackageFromMetadataService, :clean_gitlab_
.to change { ::Packages::Package.count }.by(0) .to change { ::Packages::Package.count }.by(0)
.and change { Packages::DependencyLink.count }.by(0) .and change { Packages::DependencyLink.count }.by(0)
expect(package_file.reload.file_name).not_to eq(package_file_name) expect(package_file.reload.file_name).not_to eq(package_file_name)
expect(package_file.package).to be_processing
expect(package_file.package.reload.name).not_to eq(package_name) expect(package_file.package.reload.name).not_to eq(package_name)
expect(package_file.package.version).not_to eq(package_version) expect(package_file.package.version).not_to eq(package_version)
end end
...@@ -78,6 +79,7 @@ RSpec.describe Packages::Nuget::UpdatePackageFromMetadataService, :clean_gitlab_ ...@@ -78,6 +79,7 @@ RSpec.describe Packages::Nuget::UpdatePackageFromMetadataService, :clean_gitlab_
expect(package.reload.name).to eq(package_name) expect(package.reload.name).to eq(package_name)
expect(package.version).to eq(package_version) expect(package.version).to eq(package_version)
expect(package).to be_default
expect(package_file.reload.file_name).to eq(package_file_name) expect(package_file.reload.file_name).to eq(package_file_name)
# hard reset needed to properly reload package_file.file # hard reset needed to properly reload package_file.file
expect(Packages::PackageFile.find(package_file.id).file.size).not_to eq 0 expect(Packages::PackageFile.find(package_file.id).file.size).not_to eq 0
...@@ -184,6 +186,7 @@ RSpec.describe Packages::Nuget::UpdatePackageFromMetadataService, :clean_gitlab_ ...@@ -184,6 +186,7 @@ RSpec.describe Packages::Nuget::UpdatePackageFromMetadataService, :clean_gitlab_
expect(package.reload.name).to eq(package_name) expect(package.reload.name).to eq(package_name)
expect(package.version).to eq(package_version) expect(package.version).to eq(package_version)
expect(package).to be_default
expect(package_file.reload.file_name).to eq(package_file_name) expect(package_file.reload.file_name).to eq(package_file_name)
# hard reset needed to properly reload package_file.file # hard reset needed to properly reload package_file.file
expect(Packages::PackageFile.find(package_file.id).file.size).not_to eq 0 expect(Packages::PackageFile.find(package_file.id).file.size).not_to eq 0
......
# frozen_string_literal: true
RSpec.shared_examples 'rejects rubygems packages access' do |user_type, status, add_member = true|
context "for user type #{user_type}" do
before do
project.send("add_#{user_type}", user) if add_member && user_type != :anonymous
end
it_behaves_like 'returning response status', status
end
end
RSpec.shared_examples 'process rubygems workhorse authorization' do |user_type, status, add_member = true|
context "for user type #{user_type}" do
before do
project.send("add_#{user_type}", user) if add_member && user_type != :anonymous
end
it_behaves_like 'returning response status', status
it 'has the proper content type' do
subject
expect(response.media_type).to eq(Gitlab::Workhorse::INTERNAL_API_CONTENT_TYPE)
end
context 'with a request that bypassed gitlab-workhorse' do
let(:headers) do
{ 'HTTP_AUTHORIZATION' => personal_access_token.token }
.merge(workhorse_headers)
.tap { |h| h.delete(Gitlab::Workhorse::INTERNAL_API_REQUEST_HEADER) }
end
before do
project.add_maintainer(user)
end
it_behaves_like 'returning response status', :forbidden
end
end
end
RSpec.shared_examples 'process rubygems upload' do |user_type, status, add_member = true|
RSpec.shared_examples 'creates rubygems package files' do
it 'creates package files', :aggregate_failures do
expect { subject }
.to change { project.packages.count }.by(1)
.and change { Packages::PackageFile.count }.by(1)
expect(response).to have_gitlab_http_status(status)
package_file = project.packages.last.package_files.reload.last
expect(package_file.file_name).to eq('package.gem')
end
end
context "for user type #{user_type}" do
before do
project.send("add_#{user_type}", user) if add_member && user_type != :anonymous
end
context 'with object storage disabled' do
before do
stub_package_file_object_storage(enabled: false)
end
context 'without a file from workhorse' do
let(:send_rewritten_field) { false }
it_behaves_like 'returning response status', :bad_request
end
context 'with correct params' do
it_behaves_like 'package workhorse uploads'
it_behaves_like 'creates rubygems package files'
it_behaves_like 'a package tracking event', 'API::RubygemPackages', 'push_package'
end
end
context 'with object storage enabled' do
let(:tmp_object) do
fog_connection.directories.new(key: 'packages').files.create( # rubocop:disable Rails/SaveBang
key: "tmp/uploads/#{file_name}",
body: 'content'
)
end
let(:fog_file) { fog_to_uploaded_file(tmp_object) }
let(:params) { { file: fog_file, 'file.remote_id' => file_name } }
context 'and direct upload enabled' do
let(:fog_connection) do
stub_package_file_object_storage(direct_upload: true)
end
it_behaves_like 'creates rubygems package files'
['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_behaves_like 'returning response status', :forbidden
end
end
end
context 'and direct upload disabled' do
context 'and background upload disabled' do
let(:fog_connection) do
stub_package_file_object_storage(direct_upload: false, background_upload: false)
end
it_behaves_like 'creates rubygems package files'
end
context 'and background upload enabled' do
let(:fog_connection) do
stub_package_file_object_storage(direct_upload: false, background_upload: true)
end
it_behaves_like 'creates rubygems package files'
end
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