Commit 2a9675e9 authored by Heinrich Lee Yu's avatar Heinrich Lee Yu

Merge branch '235490-generic-packages/download' into 'master'

Implement generic packages download

See merge request gitlab-org/gitlab!43503
parents fcfcd6b2 00da580d
# frozen_string_literal: true
module Packages
module Generic
class PackageFinder
def initialize(project)
@project = project
end
def execute!(package_name, package_version)
project
.packages
.generic
.by_name_and_version!(package_name, package_version)
end
private
attr_reader :project
end
end
end
...@@ -26,7 +26,7 @@ class Packages::Package < ApplicationRecord ...@@ -26,7 +26,7 @@ class Packages::Package < ApplicationRecord
validates :project, presence: true validates :project, presence: true
validates :name, presence: true validates :name, presence: true
validates :name, format: { with: Gitlab::Regex.package_name_regex }, unless: :conan? validates :name, format: { with: Gitlab::Regex.package_name_regex }, unless: -> { conan? || generic? }
validates :name, validates :name,
uniqueness: { scope: %i[project_id version package_type] }, unless: :conan? uniqueness: { scope: %i[project_id version package_type] }, unless: :conan?
...@@ -35,8 +35,9 @@ class Packages::Package < ApplicationRecord ...@@ -35,8 +35,9 @@ class Packages::Package < ApplicationRecord
validate :valid_npm_package_name, if: :npm? validate :valid_npm_package_name, if: :npm?
validate :valid_composer_global_name, if: :composer? validate :valid_composer_global_name, if: :composer?
validate :package_already_taken, if: :npm? validate :package_already_taken, if: :npm?
validates :version, format: { with: Gitlab::Regex.semver_regex }, if: -> { npm? || nuget? }
validates :name, format: { with: Gitlab::Regex.conan_recipe_component_regex }, if: :conan? validates :name, format: { with: Gitlab::Regex.conan_recipe_component_regex }, if: :conan?
validates :name, format: { with: Gitlab::Regex.generic_package_name_regex }, if: :generic?
validates :version, format: { with: Gitlab::Regex.semver_regex }, if: -> { npm? || nuget? }
validates :version, format: { with: Gitlab::Regex.conan_recipe_component_regex }, if: :conan? validates :version, format: { with: Gitlab::Regex.conan_recipe_component_regex }, if: :conan?
validates :version, format: { with: Gitlab::Regex.maven_version_regex }, if: -> { version? && maven? } validates :version, format: { with: Gitlab::Regex.maven_version_regex }, if: -> { version? && maven? }
validates :version, format: { with: Gitlab::Regex.pypi_version_regex }, if: :pypi? validates :version, format: { with: Gitlab::Regex.pypi_version_regex }, if: :pypi?
...@@ -120,6 +121,10 @@ class Packages::Package < ApplicationRecord ...@@ -120,6 +121,10 @@ class Packages::Package < ApplicationRecord
.where(packages_package_files: { file_name: file_name, file_sha256: sha256 }).last! .where(packages_package_files: { file_name: file_name, file_sha256: sha256 }).last!
end end
def self.by_name_and_version!(name, version)
find_by!(name: name, version: version)
end
def self.pluck_names def self.pluck_names
pluck(:name) pluck(:name)
end end
......
...@@ -30,7 +30,7 @@ module API ...@@ -30,7 +30,7 @@ module API
route_setting :authentication, job_token_allowed: true route_setting :authentication, job_token_allowed: true
params do params do
requires :package_name, type: String, desc: 'Package name' requires :package_name, type: String, desc: 'Package name', regexp: Gitlab::Regex.generic_package_name_regex, file_path: true
requires :package_version, type: String, desc: 'Package version', regexp: Gitlab::Regex.generic_package_version_regex requires :package_version, type: String, desc: 'Package version', regexp: Gitlab::Regex.generic_package_version_regex
requires :file_name, type: String, desc: 'Package file name', regexp: Gitlab::Regex.generic_package_file_name_regex, file_path: true requires :file_name, type: String, desc: 'Package file name', regexp: Gitlab::Regex.generic_package_file_name_regex, file_path: true
end end
...@@ -44,7 +44,7 @@ module API ...@@ -44,7 +44,7 @@ module API
end end
params do params do
requires :package_name, type: String, desc: 'Package name' requires :package_name, type: String, desc: 'Package name', regexp: Gitlab::Regex.generic_package_name_regex, file_path: true
requires :package_version, type: String, desc: 'Package version', regexp: Gitlab::Regex.generic_package_version_regex requires :package_version, type: String, desc: 'Package version', regexp: Gitlab::Regex.generic_package_version_regex
requires :file_name, type: String, desc: 'Package file name', regexp: Gitlab::Regex.generic_package_file_name_regex, file_path: true requires :file_name, type: String, desc: 'Package file name', regexp: Gitlab::Regex.generic_package_file_name_regex, file_path: true
requires :file, type: ::API::Validations::Types::WorkhorseFile, desc: 'The package file to be published (generated by Multipart middleware)' requires :file, type: ::API::Validations::Types::WorkhorseFile, desc: 'The package file to be published (generated by Multipart middleware)'
...@@ -69,6 +69,29 @@ module API ...@@ -69,6 +69,29 @@ module API
forbidden! forbidden!
end end
desc 'Download package file' do
detail 'This feature was introduced in GitLab 13.5'
end
params do
requires :package_name, type: String, desc: 'Package name', regexp: Gitlab::Regex.generic_package_name_regex, file_path: true
requires :package_version, type: String, desc: 'Package version', regexp: Gitlab::Regex.generic_package_version_regex
requires :file_name, type: String, desc: 'Package file name', regexp: Gitlab::Regex.generic_package_file_name_regex, file_path: true
end
route_setting :authentication, job_token_allowed: true
get do
authorize_read_package!(project)
package = ::Packages::Generic::PackageFinder.new(project).execute!(params[:package_name], params[:package_version])
package_file = ::Packages::PackageFileFinder.new(package, params[:file_name]).execute!
track_event('pull_package')
present_carrierwave_file!(package_file.file)
end
end end
end end
end end
......
...@@ -142,9 +142,13 @@ module Gitlab ...@@ -142,9 +142,13 @@ module Gitlab
/\A\d+\.\d+\.\d+\z/ /\A\d+\.\d+\.\d+\z/
end end
def generic_package_file_name_regex def generic_package_name_regex
maven_file_name_regex maven_file_name_regex
end end
def generic_package_file_name_regex
generic_package_name_regex
end
end end
extend self extend self
......
...@@ -140,6 +140,14 @@ FactoryBot.define do ...@@ -140,6 +140,14 @@ FactoryBot.define do
size { 1149.bytes } size { 1149.bytes }
end end
trait(:generic) do
package
file_fixture { 'spec/fixtures/packages/generic/myfile.tar.gz' }
file_name { "#{package.name}.tar.gz" }
file_sha256 { '440e5e148a25331bbd7991575f7d54933c0ebf6cc735a18ee5066ac1381bb590' }
size { 1149.bytes }
end
trait(:object_storage) do trait(:object_storage) do
file_store { Packages::PackageFileUploader::Store::REMOTE } file_store { Packages::PackageFileUploader::Store::REMOTE }
end end
......
# frozen_string_literal: true
require 'spec_helper'
RSpec.describe ::Packages::Generic::PackageFinder do
let_it_be(:project) { create(:project) }
let_it_be(:package) { create(:generic_package, project: project) }
describe '#execute!' do
subject(:finder) { described_class.new(project) }
it 'finds package by name and version' do
found_package = finder.execute!(package.name, package.version)
expect(found_package).to eq(package)
end
it 'ignores packages with same name but different version' do
create(:generic_package, project: project, name: package.name, version: '3.1.4')
found_package = finder.execute!(package.name, package.version)
expect(found_package).to eq(package)
end
it 'raises ActiveRecord::RecordNotFound if package is not found' do
expect { finder.execute!(package.name, '3.1.4') }
.to raise_error(ActiveRecord::RecordNotFound)
end
end
end
...@@ -599,6 +599,20 @@ RSpec.describe Gitlab::Regex do ...@@ -599,6 +599,20 @@ RSpec.describe Gitlab::Regex do
it { is_expected.not_to match('') } it { is_expected.not_to match('') }
end end
describe '.generic_package_name_regex' do
subject { described_class.generic_package_name_regex }
it { is_expected.to match('123') }
it { is_expected.to match('foo') }
it { is_expected.to match('foo.bar.baz-2.0-20190901.47283-1') }
it { is_expected.not_to match('../../foo') }
it { is_expected.not_to match('..\..\foo') }
it { is_expected.not_to match('%2f%2e%2e%2f%2essh%2fauthorized_keys') }
it { is_expected.not_to match('$foo/bar') }
it { is_expected.not_to match('my file name') }
it { is_expected.not_to match('!!()()') }
end
describe '.generic_package_file_name_regex' do describe '.generic_package_file_name_regex' do
subject { described_class.generic_package_file_name_regex } subject { described_class.generic_package_file_name_regex }
......
...@@ -108,6 +108,20 @@ RSpec.describe Packages::Package, type: :model do ...@@ -108,6 +108,20 @@ RSpec.describe Packages::Package, type: :model do
it { is_expected.not_to allow_value('.foobar').for(:name) } it { is_expected.not_to allow_value('.foobar').for(:name) }
it { is_expected.not_to allow_value('%foo%bar').for(:name) } it { is_expected.not_to allow_value('%foo%bar').for(:name) }
end end
context 'generic package' do
subject { build_stubbed(:generic_package) }
it { is_expected.to allow_value('123').for(:name) }
it { is_expected.to allow_value('foo').for(:name) }
it { is_expected.to allow_value('foo.bar.baz-2.0-20190901.47283-1').for(:name) }
it { is_expected.not_to allow_value('../../foo').for(:name) }
it { is_expected.not_to allow_value('..\..\foo').for(:name) }
it { is_expected.not_to allow_value('%2f%2e%2e%2f%2essh%2fauthorized_keys').for(:name) }
it { is_expected.not_to allow_value('$foo/bar').for(:name) }
it { is_expected.not_to allow_value('my file name').for(:name) }
it { is_expected.not_to allow_value('!!().for(:name)().for(:name)').for(:name) }
end
end end
describe '#version' do describe '#version' do
......
This diff is collapsed.
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