Commit 107311e3 authored by Giorgenes Gelatti's avatar Giorgenes Gelatti Committed by Kerri Miller

Support composer v2 metadata-url

Adds metadata-url field and additional
api to support composer v2.
parent 30d2d8bf
...@@ -5,25 +5,35 @@ module Packages ...@@ -5,25 +5,35 @@ module Packages
class PackagesPresenter class PackagesPresenter
include API::Helpers::RelatedResourcesHelpers include API::Helpers::RelatedResourcesHelpers
def initialize(group, packages) def initialize(group, packages, is_v2 = false)
@group = group @group = group
@packages = packages @packages = packages
@is_v2 = is_v2
end end
def root def root
v1_path = expose_path(api_v4_group___packages_composer_package_name_path({ id: @group.id, package_name: '%package%$%hash%', format: '.json' }, true))
v2_path = expose_path(api_v4_group___packages_composer_p2_package_name_path({ id: @group.id, package_name: '%package%', format: '.json' }, true)) v2_path = expose_path(api_v4_group___packages_composer_p2_package_name_path({ id: @group.id, package_name: '%package%', format: '.json' }, true))
{ index = {
'packages' => [], 'packages' => [],
'metadata-url' => v2_path
}
# if the client is composer v2 then we don't want to
# include the provider_sha since it is computationally expensive
# to compute.
return index if @is_v2
v1_path = expose_path(api_v4_group___packages_composer_package_name_path({ id: @group.id, package_name: '%package%$%hash%', format: '.json' }, true))
index.merge!(
'provider-includes' => { 'provider-includes' => {
'p/%hash%.json' => { 'p/%hash%.json' => {
'sha256' => provider_sha 'sha256' => provider_sha
} }
}, },
'providers-url' => v1_path, 'providers-url' => v1_path
'metadata-url' => v2_path )
}
end end
def provider def provider
......
---
title: Improve performance for composer v2 clients
merge_request: 55169
author:
type: added
...@@ -47,8 +47,12 @@ module API ...@@ -47,8 +47,12 @@ module API
end end
end end
def composer_v2?
headers['User-Agent'].to_s.include?('Composer/2')
end
def presenter def presenter
@presenter ||= ::Packages::Composer::PackagesPresenter.new(user_group, packages) @presenter ||= ::Packages::Composer::PackagesPresenter.new(user_group, packages, composer_v2?)
end end
end end
...@@ -66,33 +70,25 @@ module API ...@@ -66,33 +70,25 @@ module API
end end
desc 'Composer packages endpoint at group level' desc 'Composer packages endpoint at group level'
route_setting :authentication, job_token_allowed: true, basic_auth_personal_access_token: true route_setting :authentication, job_token_allowed: true, basic_auth_personal_access_token: true
get ':id/-/packages/composer/packages' do get ':id/-/packages/composer/packages' do
presenter.root presenter.root
end end
desc 'Composer packages endpoint at group level for packages list' desc 'Composer packages endpoint at group level for packages list'
params do params do
requires :sha, type: String, desc: 'Shasum of current json' requires :sha, type: String, desc: 'Shasum of current json'
end end
route_setting :authentication, job_token_allowed: true, basic_auth_personal_access_token: true route_setting :authentication, job_token_allowed: true, basic_auth_personal_access_token: true
get ':id/-/packages/composer/p/:sha' do get ':id/-/packages/composer/p/:sha' do
presenter.provider presenter.provider
end end
desc 'Composer packages endpoint at group level for package versions metadata' desc 'Composer v2 packages p2 endpoint at group level for package versions metadata'
params do params do
requires :package_name, type: String, file_path: true, desc: 'The Composer package name' requires :package_name, type: String, file_path: true, desc: 'The Composer package name'
end end
route_setting :authentication, job_token_allowed: true, basic_auth_personal_access_token: true route_setting :authentication, job_token_allowed: true, basic_auth_personal_access_token: true
get ':id/-/packages/composer/p2/*package_name', requirements: COMPOSER_ENDPOINT_REQUIREMENTS, file_path: true do get ':id/-/packages/composer/p2/*package_name', requirements: COMPOSER_ENDPOINT_REQUIREMENTS, file_path: true do
not_found! if packages.empty? not_found! if packages.empty?
...@@ -100,13 +96,10 @@ module API ...@@ -100,13 +96,10 @@ module API
end end
desc 'Composer packages endpoint at group level for package versions metadata' desc 'Composer packages endpoint at group level for package versions metadata'
params do params do
requires :package_name, type: String, file_path: true, desc: 'The Composer package name' requires :package_name, type: String, file_path: true, desc: 'The Composer package name'
end end
route_setting :authentication, job_token_allowed: true, basic_auth_personal_access_token: true route_setting :authentication, job_token_allowed: true, basic_auth_personal_access_token: true
get ':id/-/packages/composer/*package_name', requirements: COMPOSER_ENDPOINT_REQUIREMENTS, file_path: true do get ':id/-/packages/composer/*package_name', requirements: COMPOSER_ENDPOINT_REQUIREMENTS, file_path: true do
not_found! if packages.empty? not_found! if packages.empty?
not_found! if params[:sha].blank? not_found! if params[:sha].blank?
...@@ -125,7 +118,6 @@ module API ...@@ -125,7 +118,6 @@ module API
end end
desc 'Composer packages endpoint for registering packages' desc 'Composer packages endpoint for registering packages'
namespace ':id/packages/composer' do namespace ':id/packages/composer' do
route_setting :authentication, job_token_allowed: true, basic_auth_personal_access_token: true route_setting :authentication, job_token_allowed: true, basic_auth_personal_access_token: true
...@@ -134,7 +126,6 @@ module API ...@@ -134,7 +126,6 @@ module API
optional :tag, type: String, desc: 'The name of the tag' optional :tag, type: String, desc: 'The name of the tag'
exactly_one_of :tag, :branch exactly_one_of :tag, :branch
end end
post do post do
authorize_create_package!(authorized_user_project) authorize_create_package!(authorized_user_project)
...@@ -159,7 +150,6 @@ module API ...@@ -159,7 +150,6 @@ module API
requires :sha, type: String, desc: 'Shasum of current json' requires :sha, type: String, desc: 'Shasum of current json'
requires :package_name, type: String, file_path: true, desc: 'The Composer package name' requires :package_name, type: String, file_path: true, desc: 'The Composer package name'
end end
get 'archives/*package_name' do get 'archives/*package_name' do
metadata = unauthorized_user_project metadata = unauthorized_user_project
.packages .packages
......
{
"type": "object",
"required": ["packages", "metadata-url"],
"properties": {
"packages": {
"type": "array",
"items": { "type": "integer" }
},
"metadata-url": {
"type": "string"
}
},
"additionalProperties": false
}
...@@ -15,7 +15,8 @@ RSpec.describe ::Packages::Composer::PackagesPresenter do ...@@ -15,7 +15,8 @@ RSpec.describe ::Packages::Composer::PackagesPresenter do
let(:branch) { project.repository.find_branch('master') } let(:branch) { project.repository.find_branch('master') }
let(:packages) { [package1, package2] } let(:packages) { [package1, package2] }
let(:presenter) { described_class.new(group, packages) } let(:is_v2) { false }
let(:presenter) { described_class.new(group, packages, is_v2) }
describe '#package_versions' do describe '#package_versions' do
subject { presenter.package_versions } subject { presenter.package_versions }
...@@ -84,5 +85,19 @@ RSpec.describe ::Packages::Composer::PackagesPresenter do ...@@ -84,5 +85,19 @@ RSpec.describe ::Packages::Composer::PackagesPresenter do
it 'returns the provider json' do it 'returns the provider json' do
expect(subject).to match(expected_json) expect(subject).to match(expected_json)
end end
context 'with a client version 2' do
let(:is_v2) { true }
let(:expected_json) do
{
'packages' => [],
'metadata-url' => "prefix/api/v4/group/#{group.id}/-/packages/composer/p2/%package%.json"
}
end
it 'returns the provider json' do
expect(subject).to match(expected_json)
end
end
end end
end end
...@@ -7,20 +7,30 @@ RSpec.shared_context 'Composer user type' do |user_type, add_member| ...@@ -7,20 +7,30 @@ RSpec.shared_context 'Composer user type' do |user_type, add_member|
end end
end end
RSpec.shared_examples 'Composer package index with version' do |schema_path|
it 'returns the package index' do
subject
expect(response).to have_gitlab_http_status(status)
if status == :success
expect(response).to match_response_schema(schema_path)
expect(json_response).to eq presenter.root
end
end
end
RSpec.shared_examples 'Composer package index' do |user_type, status, add_member, include_package| RSpec.shared_examples 'Composer package index' do |user_type, status, add_member, include_package|
include_context 'Composer user type', user_type, add_member do include_context 'Composer user type', user_type, add_member do
let(:expected_packages) { include_package == :include_package ? [package] : [] } let(:expected_packages) { include_package == :include_package ? [package] : [] }
let(:presenter) { ::Packages::Composer::PackagesPresenter.new(group, expected_packages ) } let(:presenter) { ::Packages::Composer::PackagesPresenter.new(group, expected_packages ) }
it 'returns the package index' do it_behaves_like 'Composer package index with version', 'public_api/v4/packages/composer/index'
subject
expect(response).to have_gitlab_http_status(status) context 'with version 2' do
let(:headers) { super().merge('User-Agent' => 'Composer/2.0.9 (Darwin; 19.6.0; PHP 7.4.8; cURL 7.71.1)') }
if status == :success it_behaves_like 'Composer package index with version', 'public_api/v4/packages/composer/index_v2'
expect(response).to match_response_schema('public_api/v4/packages/composer/index')
expect(json_response).to eq presenter.root
end
end 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