Commit 1863d47a authored by Steve Abrams's avatar Steve Abrams Committed by Mayra Cabrera

Conan project-level API endpoints

Adds a new set of Grape routes for the Conan
package registry that is within the project
resource.
parent 8ee02e75
...@@ -3,6 +3,9 @@ ...@@ -3,6 +3,9 @@
class Packages::Conan::FileMetadatum < ApplicationRecord class Packages::Conan::FileMetadatum < ApplicationRecord
belongs_to :package_file, inverse_of: :conan_file_metadatum belongs_to :package_file, inverse_of: :conan_file_metadatum
DEFAULT_PACKAGE_REVISION = '0'.freeze
DEFAULT_RECIPE_REVISION = '0'.freeze
validates :package_file, presence: true validates :package_file, presence: true
validates :recipe_revision, validates :recipe_revision,
......
...@@ -3,6 +3,7 @@ ...@@ -3,6 +3,7 @@
module Packages module Packages
module Conan module Conan
class PackagePresenter class PackagePresenter
include API::Helpers::Packages::Conan::ApiHelpers
include API::Helpers::RelatedResourcesHelpers include API::Helpers::RelatedResourcesHelpers
include Gitlab::Utils::StrongMemoize include Gitlab::Utils::StrongMemoize
...@@ -17,7 +18,10 @@ module Packages ...@@ -17,7 +18,10 @@ module Packages
def recipe_urls def recipe_urls
map_package_files do |package_file| map_package_files do |package_file|
build_recipe_file_url(package_file) if package_file.conan_file_metadatum.recipe_file? next unless package_file.conan_file_metadatum.recipe_file?
options = url_options(package_file)
recipe_file_url(options)
end end
end end
...@@ -31,7 +35,12 @@ module Packages ...@@ -31,7 +35,12 @@ module Packages
map_package_files do |package_file| map_package_files do |package_file|
next unless package_file.conan_file_metadatum.package_file? && matching_reference?(package_file) next unless package_file.conan_file_metadatum.package_file? && matching_reference?(package_file)
build_package_file_url(package_file) options = url_options(package_file).merge(
conan_package_reference: package_file.conan_file_metadatum.conan_package_reference,
package_revision: package_file.conan_file_metadatum.package_revision
)
package_file_url(options)
end end
end end
...@@ -45,36 +54,21 @@ module Packages ...@@ -45,36 +54,21 @@ module Packages
private private
def build_recipe_file_url(package_file) def url_options(package_file)
expose_url( {
api_v4_packages_conan_v1_files_export_path(
package_name: @package.name,
package_version: @package.version,
package_username: @package.conan_metadatum.package_username,
package_channel: @package.conan_metadatum.package_channel,
recipe_revision: package_file.conan_file_metadatum.recipe_revision,
file_name: package_file.file_name
)
)
end
def build_package_file_url(package_file)
expose_url(
api_v4_packages_conan_v1_files_package_path(
package_name: @package.name, package_name: @package.name,
package_version: @package.version, package_version: @package.version,
package_username: @package.conan_metadatum.package_username, package_username: @package.conan_metadatum.package_username,
package_channel: @package.conan_metadatum.package_channel, package_channel: @package.conan_metadatum.package_channel,
recipe_revision: package_file.conan_file_metadatum.recipe_revision, file_name: package_file.file_name,
conan_package_reference: package_file.conan_file_metadatum.conan_package_reference, recipe_revision: package_file.conan_file_metadatum.recipe_revision.presence || ::Packages::Conan::FileMetadatum::DEFAULT_RECIPE_REVISION
package_revision: package_file.conan_file_metadatum.package_revision, }
file_name: package_file.file_name
)
)
end end
def map_package_files def map_package_files
package_files.to_a.map do |package_file| package_files.to_a.map do |package_file|
next unless package_file.conan_file_metadatum
key = package_file.file_name key = package_file.file_name
value = yield(package_file) value = yield(package_file)
next unless key && value next unless key && value
......
---
title: Allow Conan packages to be scoped to project-level
merge_request: 39541
author:
type: added
...@@ -67,7 +67,7 @@ The current state of existing package registries availability is: ...@@ -67,7 +67,7 @@ The current state of existing package registries availability is:
| Repository Type | Project Level | Group Level | Instance Level | | Repository Type | Project Level | Group Level | Instance Level |
|-----------------|---------------|-------------|----------------| |-----------------|---------------|-------------|----------------|
| Maven | Yes | Yes | Yes | | Maven | Yes | Yes | Yes |
| Conan | No - [open issue](https://gitlab.com/gitlab-org/gitlab/-/issues/11679) | No - [open issue](https://gitlab.com/gitlab-org/gitlab/-/issues/11679) | Yes | | Conan | Yes | No - [open issue](https://gitlab.com/gitlab-org/gitlab/-/issues/11679) | Yes |
| NPM | No - [open issue](https://gitlab.com/gitlab-org/gitlab/-/issues/36853) | Yes | No - [open issue](https://gitlab.com/gitlab-org/gitlab/-/issues/36853) | | NPM | No - [open issue](https://gitlab.com/gitlab-org/gitlab/-/issues/36853) | Yes | No - [open issue](https://gitlab.com/gitlab-org/gitlab/-/issues/36853) |
| NuGet | Yes | No - [open issue](https://gitlab.com/gitlab-org/gitlab/-/issues/36423) | No | | NuGet | Yes | No - [open issue](https://gitlab.com/gitlab-org/gitlab/-/issues/36423) | No |
| PyPI | Yes | No | No | | PyPI | Yes | No | No |
...@@ -87,7 +87,7 @@ Composer package naming scope is Instance Level. ...@@ -87,7 +87,7 @@ Composer package naming scope is Instance Level.
To avoid name conflict for instance-level endpoints you will need to define a package naming convention To avoid name conflict for instance-level endpoints you will need to define a package naming convention
that gives a way to identify the project that the package belongs to. This generally involves using the project that gives a way to identify the project that the package belongs to. This generally involves using the project
ID or full project path in the package name. See ID or full project path in the package name. See
[Conan's naming convention](../user/packages/conan_repository/index.md#package-recipe-naming-convention) as an example. [Conan's naming convention](../user/packages/conan_repository/index.md#package-recipe-naming-convention-for-instance-level-remote) as an example.
For group and project-level endpoints, naming can be less constrained and it will be up to the group and project For group and project-level endpoints, naming can be less constrained and it will be up to the group and project
members to be certain that there is no conflict between two package names. However, the system should prevent members to be certain that there is no conflict between two package names. However, the system should prevent
......
...@@ -83,15 +83,13 @@ conan new Hello/0.1 -t ...@@ -83,15 +83,13 @@ conan new Hello/0.1 -t
Next, create a package for that recipe by running `conan create` providing the Conan user and channel: Next, create a package for that recipe by running `conan create` providing the Conan user and channel:
```shell ```shell
conan create . my-org+my-group+my-project/beta conan create . mycompany/beta
``` ```
NOTE: **Note:** NOTE: **Note:**
Current [naming restrictions](#package-recipe-naming-convention) require you to name the `user` value as the `+` separated path of your project on GitLab. If you are using the [instance level remote](#instance-level-remote), a specific [naming convention](#package-recipe-naming-convention-for-instance-level-remote) must be followed.
The example above would create a package belonging to this project: `https://gitlab.com/my-org/my-group/my-project` with a channel of `beta`. These two example commands generate a final package with the recipe `Hello/0.1@mycompany/beta`.
These two example commands generate a final package with the recipe `Hello/0.1@my-org+my-group+my-project/beta`.
For more advanced details on creating and managing your packages, refer to the [Conan docs](https://docs.conan.io/en/latest/creating_packages.html). For more advanced details on creating and managing your packages, refer to the [Conan docs](https://docs.conan.io/en/latest/creating_packages.html).
...@@ -99,6 +97,38 @@ You are now ready to upload your package to the GitLab registry. To get started, ...@@ -99,6 +97,38 @@ You are now ready to upload your package to the GitLab registry. To get started,
## Adding the GitLab Package Registry as a Conan remote ## Adding the GitLab Package Registry as a Conan remote
You can add the GitLab Package Registry as a Conan remote at the project or instance level.
### Project level remote
> [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/11679) in GitLab 13.4.
The project level remote allows you to work with packages within a given project.
The advantage of using the project level remote is there are no restrictions to your
package name, however all GitLab Conan packages require a full recipe
with the user and channel (`package_name/version@user/channel`).
To add the remote:
```shell
conan remote add gitlab https://gitlab.example.com/api/v4/projects/<project_id>/packages/conan
```
Once the remote is set, you can use the remote when running Conan commands by adding `--remote=gitlab` to the end of your commands.
For example:
```shell
conan search Hello* --all --remote=gitlab
```
### Instance level remote
The instance level remote allows you to use a single remote to access packages accross your entire
GitLab instance. However, when using this remote, there are certain
[package naming restrictions](#package-recipe-naming-convention-for-instance-level-remote)
that must be followed.
Add a new remote to your Conan configuration: Add a new remote to your Conan configuration:
```shell ```shell
...@@ -113,6 +143,25 @@ For example: ...@@ -113,6 +143,25 @@ For example:
conan search 'Hello*' --remote=gitlab conan search 'Hello*' --remote=gitlab
``` ```
#### Package recipe naming convention for instance level remote
The standard Conan recipe convention looks like `package_name/version@user/channel`,
but if you're using the [instance level remote](#instance-level-remote), the recipe
`user` must be the plus sign (`+`) separated project path.
The following table shows some example recipes you can give your package based on
the project name and path.
| Project | Package | Supported |
| ---------------------------------- | ----------------------------------------------- | --------- |
| `foo/bar` | `my-package/1.0.0@foo+bar/stable` | Yes |
| `foo/bar-baz/buz` | `my-package/1.0.0@foo+bar-baz+buz/stable` | Yes |
| `gitlab-org/gitlab-ce` | `my-package/1.0.0@gitlab-org+gitlab-ce/stable` | Yes |
| `gitlab-org/gitlab-ce` | `my-package/1.0.0@foo/stable` | No |
NOTE: **Note:**
[Project level remotes](#project-level-remote) allow for more flexible package names.
## Authenticating to the GitLab Conan Repository ## Authenticating to the GitLab Conan Repository
You need a personal access token or deploy token. You need a personal access token or deploy token.
...@@ -142,7 +191,7 @@ Alternatively, you could explicitly include your credentials in any given comman ...@@ -142,7 +191,7 @@ Alternatively, you could explicitly include your credentials in any given comman
For example: For example:
```shell ```shell
CONAN_LOGIN_USERNAME=<gitlab_username or deploy_token_username> CONAN_PASSWORD=<personal_access_token or deploy_token> conan upload Hello/0.1@my-group+my-project/beta --all --remote=gitlab CONAN_LOGIN_USERNAME=<gitlab_username or deploy_token_username> CONAN_PASSWORD=<personal_access_token or deploy_token> conan upload Hello/0.1@mycompany/beta --all --remote=gitlab
``` ```
### Setting a default remote to your project (optional) ### Setting a default remote to your project (optional)
...@@ -150,7 +199,7 @@ CONAN_LOGIN_USERNAME=<gitlab_username or deploy_token_username> CONAN_PASSWORD=< ...@@ -150,7 +199,7 @@ CONAN_LOGIN_USERNAME=<gitlab_username or deploy_token_username> CONAN_PASSWORD=<
If you'd like Conan to always use GitLab as the registry for your package, you can tell Conan to always reference the GitLab remote for a given package recipe: If you'd like Conan to always use GitLab as the registry for your package, you can tell Conan to always reference the GitLab remote for a given package recipe:
```shell ```shell
conan remote add_ref Hello/0.1@my-group+my-project/beta gitlab conan remote add_ref Hello/0.1@mycompany/beta gitlab
``` ```
NOTE: **Note:** NOTE: **Note:**
...@@ -165,34 +214,19 @@ The rest of the example commands in this documentation assume that you've added ...@@ -165,34 +214,19 @@ The rest of the example commands in this documentation assume that you've added
## Uploading a package ## Uploading a package
First you need to [create your Conan package locally](https://docs.conan.io/en/latest/creating_packages/getting_started.html). In order to work with the GitLab Package Registry, a specific [naming convention](#package-recipe-naming-convention) must be followed. First you need to [create your Conan package locally](https://docs.conan.io/en/latest/creating_packages/getting_started.html).
NOTE: **Note:**
If you are using the [instance level remote](#instance-level-remote), a specific [naming convention](#package-recipe-naming-convention-for-instance-level-remote) must be followed.
Ensure you have a project created on GitLab and that the personal access token you're using has the correct permissions for write access to the container registry by selecting the `api` [scope](../../../user/profile/personal_access_tokens.md#limiting-scopes-of-a-personal-access-token). Ensure you have a project created on GitLab and that the personal access token you're using has the correct permissions for write access to the container registry by selecting the `api` [scope](../../../user/profile/personal_access_tokens.md#limiting-scopes-of-a-personal-access-token).
You can upload your package to the GitLab Package Registry using the `conan upload` command: You can upload your package to the GitLab Package Registry using the `conan upload` command:
```shell ```shell
conan upload Hello/0.1@my-group+my-project/beta --all conan upload Hello/0.1@mycompany/beta --all
``` ```
### Package recipe naming convention
Standard Conan recipe convention looks like `package_name/version@user/channel`.
**The recipe user must be the `+` separated project path**. The package
name may be anything, but it is preferred that the project name be used unless
it's not possible due to a naming collision. For example:
| Project | Package | Supported |
| ---------------------------------- | ----------------------------------------------- | --------- |
| `foo/bar` | `my-package/1.0.0@foo+bar/stable` | Yes |
| `foo/bar-baz/buz` | `my-package/1.0.0@foo+bar-baz+buz/stable` | Yes |
| `gitlab-org/gitlab-ce` | `my-package/1.0.0@gitlab-org+gitlab-ce/stable` | Yes |
| `gitlab-org/gitlab-ce` | `my-package/1.0.0@foo/stable` | No |
NOTE: **Note:**
A future iteration will extend support to [project and group level](https://gitlab.com/gitlab-org/gitlab/-/issues/11679) remotes which allows for more flexible naming conventions.
## Installing a package ## Installing a package
Conan packages are commonly installed as dependencies using the `conanfile.txt` file. Conan packages are commonly installed as dependencies using the `conanfile.txt` file.
...@@ -204,7 +238,7 @@ Add the Conan recipe to the `[requires]` section of the file: ...@@ -204,7 +238,7 @@ Add the Conan recipe to the `[requires]` section of the file:
```ini ```ini
[requires] [requires]
Hello/0.1@my-group+my-project/beta Hello/0.1@mycompany/beta
[generators] [generators]
cmake cmake
...@@ -253,7 +287,7 @@ To search using a partial name, use the wildcard symbol `*`, which should be pla ...@@ -253,7 +287,7 @@ To search using a partial name, use the wildcard symbol `*`, which should be pla
```shell ```shell
conan search Hello --all --remote=gitlab conan search Hello --all --remote=gitlab
conan search He* --all --remote=gitlab conan search He* --all --remote=gitlab
conan search Hello/0.1@my-group+my-project/beta --all --remote=gitlab conan search Hello/0.1@mycompany/beta --all --remote=gitlab
``` ```
The scope of your search includes all projects you have permission to access, this includes your private projects as well as all public projects. The scope of your search includes all projects you have permission to access, this includes your private projects as well as all public projects.
...@@ -263,7 +297,7 @@ The scope of your search includes all projects you have permission to access, th ...@@ -263,7 +297,7 @@ The scope of your search includes all projects you have permission to access, th
The `conan info` command returns information about a given package: The `conan info` command returns information about a given package:
```shell ```shell
conan info Hello/0.1@my-group+my-project/beta conan info Hello/0.1@mycompany/beta
``` ```
## List of supported CLI commands ## List of supported CLI commands
......
...@@ -194,7 +194,8 @@ module API ...@@ -194,7 +194,8 @@ module API
mount ::API::NugetPackages mount ::API::NugetPackages
mount ::API::PypiPackages mount ::API::PypiPackages
mount ::API::ComposerPackages mount ::API::ComposerPackages
mount ::API::ConanPackages mount ::API::ConanProjectPackages
mount ::API::ConanInstancePackages
mount ::API::MavenPackages mount ::API::MavenPackages
mount ::API::NpmPackages mount ::API::NpmPackages
mount ::API::GenericPackages mount ::API::GenericPackages
......
# frozen_string_literal: true
# Conan Instance-Level Package Manager Client API
module API
class ConanInstancePackages < Grape::API::Instance
namespace 'packages/conan/v1' do
include ConanPackageEndpoints
end
end
end
...@@ -9,8 +9,8 @@ ...@@ -9,8 +9,8 @@
# #
# Technical debt: https://gitlab.com/gitlab-org/gitlab/issues/35798 # Technical debt: https://gitlab.com/gitlab-org/gitlab/issues/35798
module API module API
class ConanPackages < Grape::API::Instance module ConanPackageEndpoints
helpers ::API::Helpers::PackagesManagerClientsHelpers extend ActiveSupport::Concern
PACKAGE_REQUIREMENTS = { PACKAGE_REQUIREMENTS = {
package_name: API::NO_SLASH_URL_PART_REGEX, package_name: API::NO_SLASH_URL_PART_REGEX,
...@@ -28,6 +28,11 @@ module API ...@@ -28,6 +28,11 @@ module API
CONAN_FILES = (Gitlab::Regex::Packages::CONAN_RECIPE_FILES + Gitlab::Regex::Packages::CONAN_PACKAGE_FILES).freeze CONAN_FILES = (Gitlab::Regex::Packages::CONAN_RECIPE_FILES + Gitlab::Regex::Packages::CONAN_PACKAGE_FILES).freeze
included do
helpers ::API::Helpers::PackagesManagerClientsHelpers
helpers ::API::Helpers::Packages::Conan::ApiHelpers
helpers ::API::Helpers::RelatedResourcesHelpers
before do before do
require_packages_enabled! require_packages_enabled!
...@@ -36,7 +41,6 @@ module API ...@@ -36,7 +41,6 @@ module API
authenticate! authenticate!
end end
namespace 'packages/conan/v1' do
desc 'Ping the Conan API' do desc 'Ping the Conan API' do
detail 'This feature was introduced in GitLab 12.2' detail 'This feature was introduced in GitLab 12.2'
end end
...@@ -242,7 +246,7 @@ module API ...@@ -242,7 +246,7 @@ module API
delete do delete do
authorize!(:destroy_package, project) authorize!(:destroy_package, project)
package_event('delete_package') package_event('delete_package', category: 'API::ConanPackages')
package.destroy package.destroy
end end
...@@ -341,11 +345,5 @@ module API ...@@ -341,11 +345,5 @@ module API
end end
end end
end end
helpers do
include Gitlab::Utils::StrongMemoize
include ::API::Helpers::RelatedResourcesHelpers
include ::API::Helpers::Packages::Conan::ApiHelpers
end
end end
end end
# frozen_string_literal: true
# Conan Project-Level Package Manager Client API
module API
class ConanProjectPackages < Grape::API::Instance
params do
requires :id, type: Integer, desc: 'The ID of a project', regexp: %r{\A[1-9]\d*\z}
end
resource :projects, requirements: API::NAMESPACE_OR_PROJECT_REQUIREMENTS do
namespace ':id/packages/conan/v1' do
include ConanPackageEndpoints
end
end
end
end
...@@ -5,6 +5,8 @@ module API ...@@ -5,6 +5,8 @@ module API
module Packages module Packages
module Conan module Conan
module ApiHelpers module ApiHelpers
include Gitlab::Utils::StrongMemoize
def present_download_urls(entity) def present_download_urls(entity)
authorize!(:read_package, project) authorize!(:read_package, project)
...@@ -31,7 +33,7 @@ module API ...@@ -31,7 +33,7 @@ module API
def recipe_upload_urls def recipe_upload_urls
{ upload_urls: Hash[ { upload_urls: Hash[
file_names.select(&method(:recipe_file?)).map do |file_name| file_names.select(&method(:recipe_file?)).map do |file_name|
[file_name, recipe_file_upload_url(file_name)] [file_name, build_recipe_file_upload_url(file_name)]
end end
] } ] }
end end
...@@ -39,7 +41,7 @@ module API ...@@ -39,7 +41,7 @@ module API
def package_upload_urls def package_upload_urls
{ upload_urls: Hash[ { upload_urls: Hash[
file_names.select(&method(:package_file?)).map do |file_name| file_names.select(&method(:package_file?)).map do |file_name|
[file_name, package_file_upload_url(file_name)] [file_name, build_package_file_upload_url(file_name)]
end end
] } ] }
end end
...@@ -52,32 +54,58 @@ module API ...@@ -52,32 +54,58 @@ module API
file_name.in?(::Packages::Conan::FileMetadatum::PACKAGE_FILES) file_name.in?(::Packages::Conan::FileMetadatum::PACKAGE_FILES)
end end
def package_file_upload_url(file_name) def build_package_file_upload_url(file_name)
expose_url( options = url_options(file_name).merge(
api_v4_packages_conan_v1_files_package_path( conan_package_reference: params[:conan_package_reference],
package_revision: ::Packages::Conan::FileMetadatum::DEFAULT_PACKAGE_REVISION
)
package_file_url(options)
end
def build_recipe_file_upload_url(file_name)
recipe_file_url(url_options(file_name))
end
def url_options(file_name)
{
package_name: params[:package_name], package_name: params[:package_name],
package_version: params[:package_version], package_version: params[:package_version],
package_username: params[:package_username], package_username: params[:package_username],
package_channel: params[:package_channel], package_channel: params[:package_channel],
recipe_revision: '0', file_name: file_name,
conan_package_reference: params[:conan_package_reference], recipe_revision: ::Packages::Conan::FileMetadatum::DEFAULT_RECIPE_REVISION
package_revision: '0', }
file_name: file_name end
def package_file_url(options)
case package_scope
when :project
expose_url(
api_v4_projects_packages_conan_v1_files_package_path(
options.merge(id: project.id)
) )
) )
when :instance
expose_url(
api_v4_packages_conan_v1_files_package_path(options)
)
end
end end
def recipe_file_upload_url(file_name) def recipe_file_url(options)
case package_scope
when :project
expose_url( expose_url(
api_v4_packages_conan_v1_files_export_path( api_v4_projects_packages_conan_v1_files_export_path(
package_name: params[:package_name], options.merge(id: project.id)
package_version: params[:package_version],
package_username: params[:package_username],
package_channel: params[:package_channel],
recipe_revision: '0',
file_name: file_name
) )
) )
when :instance
expose_url(
api_v4_packages_conan_v1_files_export_path(options)
)
end
end end
def recipe def recipe
...@@ -86,8 +114,13 @@ module API ...@@ -86,8 +114,13 @@ module API
def project def project
strong_memoize(:project) do strong_memoize(:project) do
case package_scope
when :project
find_project!(params[:id])
when :instance
full_path = ::Packages::Conan::Metadatum.full_path_from(package_username: params[:package_username]) full_path = ::Packages::Conan::Metadatum.full_path_from(package_username: params[:package_username])
Project.find_by_full_path(full_path) find_project!(full_path)
end
end end
end end
...@@ -97,6 +130,7 @@ module API ...@@ -97,6 +130,7 @@ module API
.conan .conan
.with_name(params[:package_name]) .with_name(params[:package_name])
.with_version(params[:package_version]) .with_version(params[:package_version])
.with_conan_username(params[:package_username])
.with_conan_channel(params[:package_channel]) .with_conan_channel(params[:package_channel])
.order_created .order_created
.last .last
...@@ -124,7 +158,7 @@ module API ...@@ -124,7 +158,7 @@ module API
conan_package_reference: params[:conan_package_reference] conan_package_reference: params[:conan_package_reference]
).execute! ).execute!
package_event('pull_package') if params[:file_name] == ::Packages::Conan::FileMetadatum::PACKAGE_BINARY package_event('pull_package', category: 'API::ConanPackages') if params[:file_name] == ::Packages::Conan::FileMetadatum::PACKAGE_BINARY
present_carrierwave_file!(package_file.file) present_carrierwave_file!(package_file.file)
end end
...@@ -135,7 +169,7 @@ module API ...@@ -135,7 +169,7 @@ module API
def track_push_package_event def track_push_package_event
if params[:file_name] == ::Packages::Conan::FileMetadatum::PACKAGE_BINARY && params[:file].size > 0 # rubocop: disable Style/ZeroLengthPredicate if params[:file_name] == ::Packages::Conan::FileMetadatum::PACKAGE_BINARY && params[:file].size > 0 # rubocop: disable Style/ZeroLengthPredicate
package_event('push_package') package_event('push_package', category: 'API::ConanPackages')
end end
end end
...@@ -236,6 +270,10 @@ module API ...@@ -236,6 +270,10 @@ module API
token token
end end
def package_scope
params[:id].present? ? :project : :instance
end
end end
end end
end end
......
...@@ -7,15 +7,34 @@ RSpec.describe ::Packages::Conan::PackagePresenter do ...@@ -7,15 +7,34 @@ RSpec.describe ::Packages::Conan::PackagePresenter do
let_it_be(:package) { create(:conan_package) } let_it_be(:package) { create(:conan_package) }
let_it_be(:project) { package.project } let_it_be(:project) { package.project }
let_it_be(:conan_package_reference) { '123456789'} let_it_be(:conan_package_reference) { '123456789'}
let(:params) { { package_scope: :instance } }
describe '#recipe_urls' do shared_examples 'no existing package' do
subject { described_class.new(package, user, project).recipe_urls } context 'when package does not exist' do
context 'no existing package' do
let(:package) { nil } let(:package) { nil }
it { is_expected.to be_empty } it { is_expected.to be_empty }
end end
end
shared_examples 'conan_file_metadatum is not found' do
context 'when no conan_file_metadatum exists' do
before do
package.package_files.each do |file|
file.conan_file_metadatum.delete
file.reload
end
end
it { is_expected.to be_empty }
end
end
describe '#recipe_urls' do
subject { described_class.new(package, user, project, params).recipe_urls }
it_behaves_like 'no existing package'
it_behaves_like 'conan_file_metadatum is not found'
context 'existing package' do context 'existing package' do
let(:expected_result) do let(:expected_result) do
...@@ -33,17 +52,28 @@ RSpec.describe ::Packages::Conan::PackagePresenter do ...@@ -33,17 +52,28 @@ RSpec.describe ::Packages::Conan::PackagePresenter do
it { is_expected.to eq(expected_result) } it { is_expected.to eq(expected_result) }
end end
context 'with package_scope of project' do
# #recipe_file_url checks for params[:id]
let(:params) { { id: project.id } }
let(:expected_result) do
{
"conanfile.py" => "#{Settings.build_base_gitlab_url}/api/v4/projects/#{project.id}/packages/conan/v1/files/#{package.conan_recipe_path}/0/export/conanfile.py",
"conanmanifest.txt" => "#{Settings.build_base_gitlab_url}/api/v4/projects/#{project.id}/packages/conan/v1/files/#{package.conan_recipe_path}/0/export/conanmanifest.txt"
}
end
it { is_expected.to eq(expected_result) }
end
end end
end end
describe '#recipe_snapshot' do describe '#recipe_snapshot' do
subject { described_class.new(package, user, project).recipe_snapshot } subject { described_class.new(package, user, project).recipe_snapshot }
context 'no existing package' do it_behaves_like 'no existing package'
let(:package) { nil } it_behaves_like 'conan_file_metadatum is not found'
it { is_expected.to be_empty }
end
context 'existing package' do context 'existing package' do
let(:expected_result) do let(:expected_result) do
...@@ -60,17 +90,21 @@ RSpec.describe ::Packages::Conan::PackagePresenter do ...@@ -60,17 +90,21 @@ RSpec.describe ::Packages::Conan::PackagePresenter do
describe '#package_urls' do describe '#package_urls' do
let(:reference) { conan_package_reference } let(:reference) { conan_package_reference }
let(:params) do
{
conan_package_reference: reference,
package_scope: :instance
}
end
subject do subject do
described_class.new( described_class.new(
package, user, project, conan_package_reference: reference package, user, project, params
).package_urls ).package_urls
end end
context 'no existing package' do it_behaves_like 'no existing package'
let(:package) { nil } it_behaves_like 'conan_file_metadatum is not found'
it { is_expected.to be_empty }
end
context 'existing package' do context 'existing package' do
let(:expected_result) do let(:expected_result) do
...@@ -83,6 +117,26 @@ RSpec.describe ::Packages::Conan::PackagePresenter do ...@@ -83,6 +117,26 @@ RSpec.describe ::Packages::Conan::PackagePresenter do
it { is_expected.to eq(expected_result) } it { is_expected.to eq(expected_result) }
context 'with package_scope of project' do
# #package_file_url checks for params[:id]
let(:params) do
{
conan_package_reference: reference,
id: project.id
}
end
let(:expected_result) do
{
"conaninfo.txt" => "#{Settings.build_base_gitlab_url}/api/v4/projects/#{project.id}/packages/conan/v1/files/#{package.conan_recipe_path}/0/package/#{conan_package_reference}/0/conaninfo.txt",
"conanmanifest.txt" => "#{Settings.build_base_gitlab_url}/api/v4/projects/#{project.id}/packages/conan/v1/files/#{package.conan_recipe_path}/0/package/#{conan_package_reference}/0/conanmanifest.txt",
"conan_package.tgz" => "#{Settings.build_base_gitlab_url}/api/v4/projects/#{project.id}/packages/conan/v1/files/#{package.conan_recipe_path}/0/package/#{conan_package_reference}/0/conan_package.tgz"
}
end
it { is_expected.to eq(expected_result) }
end
context 'multiple packages with different references' do context 'multiple packages with different references' do
let(:info_file) { create(:conan_package_file, :conan_package_info, package: package) } let(:info_file) { create(:conan_package_file, :conan_package_info, package: package) }
let(:manifest_file) { create(:conan_package_file, :conan_package_manifest, package: package) } let(:manifest_file) { create(:conan_package_file, :conan_package_manifest, package: package) }
...@@ -132,11 +186,8 @@ RSpec.describe ::Packages::Conan::PackagePresenter do ...@@ -132,11 +186,8 @@ RSpec.describe ::Packages::Conan::PackagePresenter do
).package_snapshot ).package_snapshot
end end
context 'no existing package' do it_behaves_like 'no existing package'
let(:package) { nil } it_behaves_like 'conan_file_metadatum is not found'
it { is_expected.to be_empty }
end
context 'existing package' do context 'existing package' do
let(:expected_result) do let(:expected_result) do
......
# frozen_string_literal: true
require 'spec_helper'
RSpec.describe API::ConanInstancePackages do
include_context 'conan api setup'
describe 'GET /api/v4/packages/conan/v1/ping' do
let_it_be(:url) { '/packages/conan/v1/ping' }
it_behaves_like 'conan ping endpoint'
end
describe 'GET /api/v4/packages/conan/v1/conans/search' do
let_it_be(:url) { '/packages/conan/v1/conans/search' }
it_behaves_like 'conan search endpoint'
end
describe 'GET /api/v4/packages/conan/v1/users/authenticate' do
let_it_be(:url) { '/packages/conan/v1/users/authenticate' }
it_behaves_like 'conan authenticate endpoint'
end
describe 'GET /api/v4/packages/conan/v1/users/check_credentials' do
let_it_be(:url) { "/packages/conan/v1/users/check_credentials" }
it_behaves_like 'conan check_credentials endpoint'
end
context 'recipe endpoints' do
include_context 'conan recipe endpoints'
let(:project_id) { 9999 }
let(:url_prefix) { "#{Settings.gitlab.base_url}/api/v4" }
describe 'GET /api/v4/packages/conan/v1/conans/:package_name/package_version/:package_username/:package_channel' do
let(:recipe_path) { package.conan_recipe_path }
let(:url) { "/packages/conan/v1/conans/#{recipe_path}" }
it_behaves_like 'recipe snapshot endpoint'
end
describe 'GET /api/v4/packages/conan/v1/conans/:package_name/package_version/:package_username/:package_channel/packages/:conan_package_reference' do
let(:recipe_path) { package.conan_recipe_path }
let(:url) { "/packages/conan/v1/conans/#{recipe_path}/packages/#{conan_package_reference}" }
it_behaves_like 'package snapshot endpoint'
end
describe 'GET /api/v4/packages/conan/v1/conans/:package_name/package_version/:package_username/:package_channel/digest' do
subject { get api("/packages/conan/v1/conans/#{recipe_path}/digest"), headers: headers }
it_behaves_like 'recipe download_urls endpoint'
end
describe 'GET /api/v4/packages/conan/v1/conans/:package_name/package_version/:package_username/:package_channel/packages/:conan_package_reference/download_urls' do
subject { get api("/packages/conan/v1/conans/#{recipe_path}/packages/#{conan_package_reference}/download_urls"), headers: headers }
it_behaves_like 'package download_urls endpoint'
end
describe 'GET /api/v4/packages/conan/v1/conans/:package_name/package_version/:package_username/:package_channel/download_urls' do
subject { get api("/packages/conan/v1/conans/#{recipe_path}/download_urls"), headers: headers }
it_behaves_like 'recipe download_urls endpoint'
end
describe 'GET /api/v4/packages/conan/v1/conans/:package_name/package_version/:package_username/:package_channel/packages/:conan_package_reference/digest' do
subject { get api("/packages/conan/v1/conans/#{recipe_path}/packages/#{conan_package_reference}/digest"), headers: headers }
it_behaves_like 'package download_urls endpoint'
end
describe 'POST /api/v4/packages/conan/v1/conans/:package_name/package_version/:package_username/:package_channel/upload_urls' do
subject { post api("/packages/conan/v1/conans/#{recipe_path}/upload_urls"), params: params.to_json, headers: headers }
it_behaves_like 'recipe upload_urls endpoint'
end
describe 'POST /api/v4/packages/conan/v1/conans/:package_name/package_version/:package_username/:package_channel/packages/:conan_package_reference/upload_urls' do
subject { post api("/packages/conan/v1/conans/#{recipe_path}/packages/123456789/upload_urls"), params: params.to_json, headers: headers }
it_behaves_like 'package upload_urls endpoint'
end
describe 'DELETE /api/v4/packages/conan/v1/conans/:package_name/package_version/:package_username/:package_channel' do
subject { delete api("/packages/conan/v1/conans/#{recipe_path}"), headers: headers}
it_behaves_like 'delete package endpoint'
end
end
context 'file download endpoints' do
include_context 'conan file download endpoints'
describe 'GET /api/v4/packages/conan/v1/files/:package_name/package_version/:package_username/:package_channel/
:recipe_revision/export/:file_name' do
subject do
get api("/packages/conan/v1/files/#{recipe_path}/#{metadata.recipe_revision}/export/#{recipe_file.file_name}"),
headers: headers
end
it_behaves_like 'recipe file download endpoint'
it_behaves_like 'project not found by recipe'
end
describe 'GET /api/v4/packages/conan/v1/files/:package_name/package_version/:package_username/:package_channel/
:recipe_revision/package/:conan_package_reference/:package_revision/:file_name' do
subject do
get api("/packages/conan/v1/files/#{recipe_path}/#{metadata.recipe_revision}/package/#{metadata.conan_package_reference}/#{metadata.package_revision}/#{package_file.file_name}"),
headers: headers
end
it_behaves_like 'package file download endpoint'
it_behaves_like 'project not found by recipe'
end
end
context 'file upload endpoints' do
include_context 'conan file upload endpoints'
describe 'PUT /api/v4/packages/conan/v1/files/:package_name/package_version/:package_username/:package_channel/:recipe_revision/export/:file_name/authorize' do
let(:file_name) { 'conanfile.py' }
subject { put api("/packages/conan/v1/files/#{recipe_path}/0/export/#{file_name}/authorize"), headers: headers_with_token }
it_behaves_like 'workhorse authorize endpoint'
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
let(:file_name) { 'conaninfo.txt' }
subject { put api("/packages/conan/v1/files/#{recipe_path}/0/package/123456789/0/#{file_name}/authorize"), headers: headers_with_token }
it_behaves_like 'workhorse authorize endpoint'
end
describe 'PUT /api/v4/packages/conan/v1/files/:package_name/package_version/:package_username/:package_channel/:recipe_revision/export/:file_name' do
let(:url) { "/api/v4/packages/conan/v1/files/#{recipe_path}/0/export/#{file_name}" }
it_behaves_like 'workhorse recipe file upload endpoint'
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(:url) { "/api/v4/packages/conan/v1/files/#{recipe_path}/0/package/123456789/0/#{file_name}" }
it_behaves_like 'workhorse package file upload endpoint'
end
end
end
This diff is collapsed.
# frozen_string_literal: true
require 'spec_helper'
RSpec.describe API::ConanProjectPackages do
include_context 'conan api setup'
let(:project_id) { project.id }
describe 'GET /api/v4/projects/:id/packages/conan/v1/ping' do
let(:url) { "/projects/#{project.id}/packages/conan/v1/ping" }
it_behaves_like 'conan ping endpoint'
end
describe 'GET /api/v4/projects/:id/packages/conan/v1/conans/search' do
let(:url) { "/projects/#{project.id}/packages/conan/v1/conans/search" }
it_behaves_like 'conan search endpoint'
end
describe 'GET /api/v4/projects/:id/packages/conan/v1/users/authenticate' do
let(:url) { "/projects/#{project.id}/packages/conan/v1/users/authenticate" }
it_behaves_like 'conan authenticate endpoint'
end
describe 'GET /api/v4/projects/:id/packages/conan/v1/users/check_credentials' do
let(:url) { "/projects/#{project.id}/packages/conan/v1/users/check_credentials" }
it_behaves_like 'conan check_credentials endpoint'
end
context 'recipe endpoints' do
include_context 'conan recipe endpoints'
let(:url_prefix) { "#{Settings.gitlab.base_url}/api/v4/projects/#{project_id}" }
describe 'GET /api/v4/projects/:id/packages/conan/v1/conans/:package_name/package_version/:package_username/:package_channel' do
let(:recipe_path) { package.conan_recipe_path }
let(:url) { "/projects/#{project_id}/packages/conan/v1/conans/#{recipe_path}" }
it_behaves_like 'recipe snapshot endpoint'
end
describe 'GET /api/v4/projects/:id/packages/conan/v1/conans/:package_name/package_version/:package_username/:package_channel/packages/:conan_package_reference' do
let(:recipe_path) { package.conan_recipe_path }
let(:url) { "/projects/#{project_id}/packages/conan/v1/conans/#{recipe_path}/packages/#{conan_package_reference}" }
it_behaves_like 'package snapshot endpoint'
end
describe 'GET /api/v4/projects/:id/packages/conan/v1/conans/:package_name/package_version/:package_username/:package_channel/digest' do
subject { get api("/projects/#{project_id}/packages/conan/v1/conans/#{recipe_path}/digest"), headers: headers }
it_behaves_like 'recipe download_urls endpoint'
end
describe 'GET /api/v4/projects/:id/packages/conan/v1/conans/:package_name/package_version/:package_username/:package_channel/packages/:conan_package_reference/download_urls' do
subject { get api("/projects/#{project_id}/packages/conan/v1/conans/#{recipe_path}/packages/#{conan_package_reference}/download_urls"), headers: headers }
it_behaves_like 'package download_urls endpoint'
end
describe 'GET /api/v4/projects/:id/packages/conan/v1/conans/:package_name/package_version/:package_username/:package_channel/download_urls' do
subject { get api("/projects/#{project_id}/packages/conan/v1/conans/#{recipe_path}/download_urls"), headers: headers }
it_behaves_like 'recipe download_urls endpoint'
end
describe 'GET /api/v4/projects/:id/packages/conan/v1/conans/:package_name/package_version/:package_username/:package_channel/packages/:conan_package_reference/digest' do
subject { get api("/projects/#{project_id}/packages/conan/v1/conans/#{recipe_path}/packages/#{conan_package_reference}/digest"), headers: headers }
it_behaves_like 'package download_urls endpoint'
end
describe 'POST /api/v4/projects/:id/packages/conan/v1/conans/:package_name/package_version/:package_username/:package_channel/upload_urls' do
subject { post api("/projects/#{project_id}/packages/conan/v1/conans/#{recipe_path}/upload_urls"), params: params.to_json, headers: headers }
it_behaves_like 'recipe upload_urls endpoint'
end
describe 'POST /api/v4/projects/:id/packages/conan/v1/conans/:package_name/package_version/:package_username/:package_channel/packages/:conan_package_reference/upload_urls' do
subject { post api("/projects/#{project_id}/packages/conan/v1/conans/#{recipe_path}/packages/123456789/upload_urls"), params: params.to_json, headers: headers }
it_behaves_like 'package upload_urls endpoint'
end
describe 'DELETE /api/v4/projects/:id/packages/conan/v1/conans/:package_name/package_version/:package_username/:package_channel' do
subject { delete api("/projects/#{project_id}/packages/conan/v1/conans/#{recipe_path}"), headers: headers}
it_behaves_like 'delete package endpoint'
end
end
context 'file download endpoints' do
include_context 'conan file download endpoints'
describe 'GET /api/v4/projects/:id/packages/conan/v1/files/:package_name/package_version/:package_username/:package_channel/
:recipe_revision/export/:file_name' do
subject do
get api("/projects/#{project_id}/packages/conan/v1/files/#{recipe_path}/#{metadata.recipe_revision}/export/#{recipe_file.file_name}"),
headers: headers
end
it_behaves_like 'recipe file download endpoint'
it_behaves_like 'project not found by project id'
end
describe 'GET /api/v4/projects/:id/packages/conan/v1/files/:package_name/package_version/:package_username/:package_channel/
:recipe_revision/package/:conan_package_reference/:package_revision/:file_name' do
subject do
get api("/projects/#{project_id}/packages/conan/v1/files/#{recipe_path}/#{metadata.recipe_revision}/package/#{metadata.conan_package_reference}/#{metadata.package_revision}/#{package_file.file_name}"),
headers: headers
end
it_behaves_like 'package file download endpoint'
it_behaves_like 'project not found by project id'
end
end
context 'file upload endpoints' do
include_context 'conan file upload endpoints'
describe 'PUT /api/v4/projects/:id/packages/conan/v1/files/:package_name/package_version/:package_username/:package_channel/:recipe_revision/export/:file_name/authorize' do
let(:file_name) { 'conanfile.py' }
subject { put api("/projects/#{project_id}/packages/conan/v1/files/#{recipe_path}/0/export/#{file_name}/authorize"), headers: headers_with_token }
it_behaves_like 'workhorse authorize endpoint'
end
describe 'PUT /api/v4/projects/:id/packages/conan/v1/files/:package_name/package_version/:package_username/:package_channel/:recipe_revision/export/:conan_package_reference/:package_revision/:file_name/authorize' do
let(:file_name) { 'conaninfo.txt' }
subject { put api("/projects/#{project_id}/packages/conan/v1/files/#{recipe_path}/0/package/123456789/0/#{file_name}/authorize"), headers: headers_with_token }
it_behaves_like 'workhorse authorize endpoint'
end
describe 'PUT /api/v4/projects/:id/packages/conan/v1/files/:package_name/package_version/:package_username/:package_channel/:recipe_revision/export/:file_name' do
let(:url) { "/api/v4/projects/#{project_id}/packages/conan/v1/files/#{recipe_path}/0/export/#{file_name}" }
it_behaves_like 'workhorse recipe file upload endpoint'
end
describe 'PUT /api/v4/projects/:id/packages/conan/v1/files/:package_name/package_version/:package_username/:package_channel/:recipe_revision/export/:conan_package_reference/:package_revision/:file_name' do
let(:url) { "/api/v4/projects/#{project_id}/packages/conan/v1/files/#{recipe_path}/0/package/123456789/0/#{file_name}" }
it_behaves_like 'workhorse package file upload endpoint'
end
end
end
# frozen_string_literal: true
RSpec.shared_context 'conan api setup' do
include PackagesManagerApiSpecHelpers
include HttpBasicAuthHelpers
let(:package) { create(:conan_package) }
let_it_be(:personal_access_token) { create(:personal_access_token) }
let_it_be(:user) { personal_access_token.user }
let_it_be(:base_secret) { SecureRandom.base64(64) }
let_it_be(:job) { create(:ci_build, :running, user: user) }
let_it_be(:job_token) { job.token }
let_it_be(:deploy_token) { create(:deploy_token, read_package_registry: true, write_package_registry: true) }
let(:project) { package.project }
let(:auth_token) { personal_access_token.token }
let(:project_deploy_token) { create(:project_deploy_token, deploy_token: deploy_token, project: project) }
let(:headers) do
{ 'HTTP_AUTHORIZATION' => ActionController::HttpAuthentication::Basic.encode_credentials('foo', auth_token) }
end
let(:jwt_secret) do
OpenSSL::HMAC.hexdigest(
OpenSSL::Digest::SHA256.new,
base_secret,
Gitlab::ConanToken::HMAC_KEY
)
end
before do
project.add_developer(user)
allow(Settings).to receive(:attr_encrypted_db_key_base).and_return(base_secret)
end
end
RSpec.shared_context 'conan recipe endpoints' do
include PackagesManagerApiSpecHelpers
include HttpBasicAuthHelpers
let(:jwt) { build_jwt(personal_access_token) }
let(:headers) { build_token_auth_header(jwt.encoded) }
let(:conan_package_reference) { '123456789' }
let(:presenter) { double('::Packages::Conan::PackagePresenter') }
before do
allow(::Packages::Conan::PackagePresenter).to receive(:new)
.with(package, user, package.project, any_args)
.and_return(presenter)
end
end
RSpec.shared_context 'conan file download endpoints' do
include PackagesManagerApiSpecHelpers
include HttpBasicAuthHelpers
let(:jwt) { build_jwt(personal_access_token) }
let(:headers) { build_token_auth_header(jwt.encoded) }
let(:recipe_path) { package.conan_recipe_path }
let(:package_file) { package.package_files.find_by(file_name: 'conaninfo.txt') }
let(:recipe_file) { package.package_files.find_by(file_name: 'conanfile.py') }
let(:metadata) { package_file.conan_file_metadatum }
end
RSpec.shared_context 'conan file upload endpoints' do
include PackagesManagerApiSpecHelpers
include WorkhorseHelpers
include HttpBasicAuthHelpers
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_token_auth_header(jwt.encoded).merge(workhorse_header) }
let(:recipe_path) { "foo/bar/#{project.full_path.tr('/', '+')}/baz"}
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