Commit 1818c1ac authored by Dmitriy Zaporozhets's avatar Dmitriy Zaporozhets

Merge branch '5934-npm-package-management-mvc' into 'master'

Add NPM registry support to GitLab packages

Closes #5934

See merge request gitlab-org/gitlab-ee!8673
parents d8bfaefd 05db2da6
......@@ -128,7 +128,8 @@ created in snippets, wikis, and repos.
- [Default labels](../user/admin_area/labels.html): Create labels that will be automatically added to every new project.
- [Restrict the use of public or internal projects](../public_access/public_access.md#restricting-the-use-of-public-or-internal-projects): Restrict the use of visibility levels for users when they create a project or a snippet.
- [Custom project templates](../user/admin_area/custom_project_templates.md): Configure a set of projects to be used as custom templates when creating a new project. **[PREMIUM ONLY]**
- [Maven Repository](maven_repository.md): Enable Maven Repository within GitLab.
- [Maven Repository](maven_repository.md): Enable Maven Repository within GitLab. **[PREMIUM ONLY]**
- [NPM Registry](npm_registry.md): Enable NPM Registry within GitLab. **[PREMIUM ONLY]**
### Repository settings
......
......@@ -16,153 +16,4 @@ Once enabled, newly created projects will have the Packages feature enabled by
default. Existing projects will need to
[explicitly enabled it](../user/project/packages/maven_repository.md#enabling-the-packages-repository).
To enable the Maven repository:
**Omnibus GitLab installations**
1. Edit `/etc/gitlab/gitlab.rb` and add the following line:
```ruby
gitlab_rails['packages_enabled'] = true
```
1. Save the file and [reconfigure GitLab][] for the changes to take effect.
**Installations from source**
1. After the installation is complete, you will have to configure the `packages`
section in `config/gitlab.yml`. Set to `true` to enable it:
```yaml
packages:
enabled: true
```
1. [Restart GitLab] for the changes to take effect.
## Changing the storage path
By default, the packages are stored locally, but you can change the default
local location or even use object storage.
### Changing the local storage path
The packages for Omnibus GitLab installations are stored under
`/var/opt/gitlab/gitlab-rails/shared/packages/` and for source
installations under `shared/packages/` (relative to the git homedir).
To change the local storage path:
**Omnibus GitLab installations**
1. Edit `/etc/gitlab/gitlab.rb` and add the following line:
```ruby
gitlab_rails['packages_storage_path'] = "/mnt/maven"
```
1. Save the file and [reconfigure GitLab][] for the changes to take effect.
**Installations from source**
1. Edit the `packages` section in `config/gitlab.yml`:
```yaml
packages:
enabled: true
storage_path: shared/packages
```
1. [Restart GitLab] for the changes to take effect.
### Using object storage
Instead of relying on the local storage, you can use an object storage to
upload the maven packages:
**Omnibus GitLab installations**
1. Edit `/etc/gitlab/gitlab.rb` and add the following lines (uncomment where
necessary):
```ruby
gitlab_rails['packages_enabled'] = true
gitlab_rails['packages_storage_path'] = "/var/opt/gitlab/gitlab-rails/shared/packages"
gitlab_rails['packages_object_store_enabled'] = true
gitlab_rails['packages_object_store_remote_directory'] = "packages" # The bucket name.
gitlab_rails['packages_object_store_direct_upload'] = false # Use Object Storage directly for uploads instead of background uploads if enabled (Default: false).
gitlab_rails['packages_object_store_background_upload'] = true # Temporary option to limit automatic upload (Default: true).
gitlab_rails['packages_object_store_proxy_download'] = false # Passthrough all downloads via GitLab instead of using Redirects to Object Storage.
gitlab_rails['packages_object_store_connection'] = {
##
## If the provider is AWS S3, uncomment the following
##
#'provider' => 'AWS',
#'region' => 'eu-west-1',
#'aws_access_key_id' => 'AWS_ACCESS_KEY_ID',
#'aws_secret_access_key' => 'AWS_SECRET_ACCESS_KEY',
##
## If the provider is other than AWS (an S3-compatible one), uncomment the following
##
#'host' => 's3.amazonaws.com',
#'aws_signature_version' => 4 # For creation of signed URLs. Set to 2 if provider does not support v4.
#'endpoint' => 'https://s3.amazonaws.com' # Useful for S3-compliant services such as DigitalOcean Spaces.
#'path_style' => false # If true, use 'host/bucket_name/object' instead of 'bucket_name.host/object'.
}
```
1. Save the file and [reconfigure GitLab][] for the changes to take effect.
**Installations from source**
1. Edit the `packages` section in `config/gitlab.yml` (uncomment where necessary):
```yaml
packages:
enabled: true
##
## The location where build packages are stored (default: shared/packages).
##
#storage_path: shared/packages
object_store:
enabled: false
remote_directory: packages # The bucket name.
#direct_upload: false # Use Object Storage directly for uploads instead of background uploads if enabled (Default: false).
#background_upload: true # Temporary option to limit automatic upload (Default: true).
#proxy_download: false # Passthrough all downloads via GitLab instead of using Redirects to Object Storage.
connection:
##
## If the provider is AWS S3, uncomment the following
##
#provider: AWS
#region: us-east-1
#aws_access_key_id: AWS_ACCESS_KEY_ID
#aws_secret_access_key: AWS_SECRET_ACCESS_KEY
##
## If the provider is other than AWS (an S3-compatible one), uncomment the following
##
#host: 's3.amazonaws.com' # default: s3.amazonaws.com.
#aws_signature_version: 4 # For creation of signed URLs. Set to 2 if provider does not support v4.
#endpoint: 'https://s3.amazonaws.com' # Useful for S3-compliant services such as DigitalOcean Spaces.
#path_style: false # If true, use 'host/bucket_name/object' instead of 'bucket_name.host/object'.
```
1. [Restart GitLab] for the changes to take effect.
### Migrating local packages to object storage
After [configuring the object storage](#using-object-storage), you may use the
following task to migrate existing packages from the local storage to the remote one.
The processing will be done in a background worker and requires **no downtime**.
For Omnibus GitLab:
```sh
sudo gitlab-rake "gitlab:packages:migrate"
```
For installations from source:
```bash
RAILS_ENV=production sudo -u git -H bundle exec rake gitlab:packages:migrate
```
[reconfigure gitlab]: restart_gitlab.md#omnibus-gitlab-reconfigure "How to reconfigure Omnibus GitLab"
[restart gitlab]: restart_gitlab.md#omnibus-gitlab-reconfigure "How to reconfigure Omnibus GitLab"
To enable the Maven repository you need to enable [Packages feature](packages.md)
# GitLab NPM Registry administration **[PREMIUM ONLY]**
>
[Introduced](https://gitlab.com/gitlab-org/gitlab-ee/issues/5934)
in GitLab 11.7. To learn how to use
When the GitLab NPM Registry is enabled, every project in GitLab will have
its own space to store [NPM](https://www.npmjs.com/) packages.
To learn how to use it, see the [user documentation](../user/project/packages/npm_registry.md).
## Enabling the NPM Registry
NOTE: **Note:**
Once enabled, newly created projects will have the Packages feature enabled by
default. Existing projects will need to
[explicitly enabled it](../user/project/packages/npm_registry.md#enabling-the-packages-repository).
To enable the NPM Registry you need to enable [Packages feature](packages.md)
# GitLab Packages administration **[PREMIUM ONLY]**
Package feature provides a support for next products:
* [Maven Repository](maven_repository.md)
* [NPM Registry](npm_registry.md)
## Enabling the Packages feature
To enable the Packages feature:
**Omnibus GitLab installations**
1. Edit `/etc/gitlab/gitlab.rb` and add the following line:
```ruby
gitlab_rails['packages_enabled'] = true
```
1. Save the file and [reconfigure GitLab][] for the changes to take effect.
**Installations from source**
1. After the installation is complete, you will have to configure the `packages`
section in `config/gitlab.yml`. Set to `true` to enable it:
```yaml
packages:
enabled: true
```
1. [Restart GitLab] for the changes to take effect.
## Changing the storage path
By default, the packages are stored locally, but you can change the default
local location or even use object storage.
### Changing the local storage path
The packages for Omnibus GitLab installations are stored under
`/var/opt/gitlab/gitlab-rails/shared/packages/` and for source
installations under `shared/packages/` (relative to the git homedir).
To change the local storage path:
**Omnibus GitLab installations**
1. Edit `/etc/gitlab/gitlab.rb` and add the following line:
```ruby
gitlab_rails['packages_storage_path'] = "/mnt/packages"
```
1. Save the file and [reconfigure GitLab][] for the changes to take effect.
**Installations from source**
1. Edit the `packages` section in `config/gitlab.yml`:
```yaml
packages:
enabled: true
storage_path: shared/packages
```
1. [Restart GitLab] for the changes to take effect.
### Using object storage
Instead of relying on the local storage, you can use an object storage to
upload packages:
**Omnibus GitLab installations**
1. Edit `/etc/gitlab/gitlab.rb` and add the following lines (uncomment where
necessary):
```ruby
gitlab_rails['packages_enabled'] = true
gitlab_rails['packages_storage_path'] = "/var/opt/gitlab/gitlab-rails/shared/packages"
gitlab_rails['packages_object_store_enabled'] = true
gitlab_rails['packages_object_store_remote_directory'] = "packages" # The bucket name.
gitlab_rails['packages_object_store_direct_upload'] = false # Use Object Storage directly for uploads instead of background uploads if enabled (Default: false).
gitlab_rails['packages_object_store_background_upload'] = true # Temporary option to limit automatic upload (Default: true).
gitlab_rails['packages_object_store_proxy_download'] = false # Passthrough all downloads via GitLab instead of using Redirects to Object Storage.
gitlab_rails['packages_object_store_connection'] = {
##
## If the provider is AWS S3, uncomment the following
##
#'provider' => 'AWS',
#'region' => 'eu-west-1',
#'aws_access_key_id' => 'AWS_ACCESS_KEY_ID',
#'aws_secret_access_key' => 'AWS_SECRET_ACCESS_KEY',
##
## If the provider is other than AWS (an S3-compatible one), uncomment the following
##
#'host' => 's3.amazonaws.com',
#'aws_signature_version' => 4 # For creation of signed URLs. Set to 2 if provider does not support v4.
#'endpoint' => 'https://s3.amazonaws.com' # Useful for S3-compliant services such as DigitalOcean Spaces.
#'path_style' => false # If true, use 'host/bucket_name/object' instead of 'bucket_name.host/object'.
}
```
1. Save the file and [reconfigure GitLab][] for the changes to take effect.
**Installations from source**
1. Edit the `packages` section in `config/gitlab.yml` (uncomment where necessary):
```yaml
packages:
enabled: true
##
## The location where build packages are stored (default: shared/packages).
##
#storage_path: shared/packages
object_store:
enabled: false
remote_directory: packages # The bucket name.
#direct_upload: false # Use Object Storage directly for uploads instead of background uploads if enabled (Default: false).
#background_upload: true # Temporary option to limit automatic upload (Default: true).
#proxy_download: false # Passthrough all downloads via GitLab instead of using Redirects to Object Storage.
connection:
##
## If the provider is AWS S3, uncomment the following
##
#provider: AWS
#region: us-east-1
#aws_access_key_id: AWS_ACCESS_KEY_ID
#aws_secret_access_key: AWS_SECRET_ACCESS_KEY
##
## If the provider is other than AWS (an S3-compatible one), uncomment the following
##
#host: 's3.amazonaws.com' # default: s3.amazonaws.com.
#aws_signature_version: 4 # For creation of signed URLs. Set to 2 if provider does not support v4.
#endpoint: 'https://s3.amazonaws.com' # Useful for S3-compliant services such as DigitalOcean Spaces.
#path_style: false # If true, use 'host/bucket_name/object' instead of 'bucket_name.host/object'.
```
1. [Restart GitLab] for the changes to take effect.
### Migrating local packages to object storage
After [configuring the object storage](#using-object-storage), you may use the
following task to migrate existing packages from the local storage to the remote one.
The processing will be done in a background worker and requires **no downtime**.
For Omnibus GitLab:
```sh
sudo gitlab-rake "gitlab:packages:migrate"
```
For installations from source:
```bash
RAILS_ENV=production sudo -u git -H bundle exec rake gitlab:packages:migrate
```
[reconfigure gitlab]: restart_gitlab.md#omnibus-gitlab-reconfigure "How to reconfigure Omnibus GitLab"
[restart gitlab]: restart_gitlab.md#omnibus-gitlab-reconfigure "How to reconfigure Omnibus GitLab"
......@@ -89,6 +89,7 @@ website with GitLab Pages
your code blocks, overriding GitLab's default choice of language
- [Badges](badges.md): Badges for the project overview
- [Maven packages](packages/maven_repository.md): Your private Maven repository in GitLab **[PREMIUM]**
- [NPM packages](packages/npm_registry.md): Your private NPM package registry in GitLab **[PREMIUM]**
- [Code owners](code_owners.md): Specify code owners for certain files **[STARTER]**
- [License Management](merge_requests/license_management.md): Approve and blacklist licenses for projects **[ULTIMATE]**
......
# GitLab NPM Registry **[PREMIUM]**
> Introduced in [GitLab Premium](https://about.gitlab.com/pricing/) 11.7.
With the GitLab NPM Registry, every
project can have its own space to store NPM packages.
![GitLab NPM Registry](img/npm_package_view.png)
NOTE: **Note:**
Only [scoped](https://docs.npmjs.com/misc/scope) packages are supported.
## Enabling NPM Registry
NOTE: **Note:**
This option is available only if your GitLab administrator has
[enabled Packages feature](../../../administration/npm_registry.md).
In order to use the GitLab NPM Registry, you must enable the Packages feature.
To enable (or disable) it:
1. Navigate to your project's **Settings > General > Permissions**.
1. Find the "Packages" feature and enable it.
1. Click on **Save changes** for the changes to take effect.
You should then be able to see the **Packages** section on the left sidebar.
Next, you must configure your project to authorize with the GitLab NPM
registry.
## Package naming convention
Note that **only packages that have the same path as the project** are supported.
| Project | Package | Supported |
| ------- | ------- | --------------------------------- |
| `foo/bar` | `@foo/bar` | Yes |
| `gitlab-org/gitlab-ce` | `@foo/bar` | No |
| `gitlab-org/gitlab-ce` | `@gitlab-org/gitlab-ce` | Yes |
## Authenticating to the GitLab NPM Registry
If a project is private or you want to upload NPM package to GitLab,
credentials will need to be provided for authentication. Support is available for
[oauth tokens](#authenticating-with-an-oauth-token) only.
### Authenticating with an oauth token
To authenticate with a [oauth token](../../../api/oauth2.md),
add a corresponding section to your `.npmrc` file:
```
; Set URL for your scoped packages.
; For example package with name `@foo/bar` will use this URL for download
@foo:registry=https://gitlab.com/api/v4/packages/npm/
; Add OAuth token for scoped packages URL. This will allow you to download
; `@foo/` packages from private projects.
//localhost:3001/api/v4/packages/npm/:_authToken=1da4f6691c92a543f7416b8fe013357fda23b0730466841311b89809a51349ce
; Add OAuth token for uploading to the registry. Replace `YOUR_PROJECT_ID`
; with a project you want your package uploaded to.
//gitlab.com/api/v4/projects/YOUR_PROJECT_ID/packages/npm/:_authToken=YOUR_OAUTH_TOKEN
```
You should now be able to download and upload NPM packages to your project.
## Uploading packages
Before you will be able to upload a package, you need to specify registry for NPM.
To do this, you need to add next section to the bottom of `package.json`:
```json
"publishConfig": {
"@foo:registry":"https://gitlab.com/api/v4/projects/YOUR_PROJECT_ID/packages/npm/"
}
```
Replace `YOUR_PROJECT_ID` with a project you want your package uploaded to.
And replace `@foo` with your own scope.
Once you did it and have set up the [authorization](#authorizing-with-the-gitlab-npm-registry),
test to upload an NPM package from a project of yours:
```sh
npm publish
```
You can then navigate to your project's **Packages** page and see the uploaded
packages or even delete them.
## Uploading a package with the same version twice
If you upload a package with a same name and version twice, GitLab will show
both packages in UI. But API will expose only one package per version for `npm install`
and it will be the most recent one.
# frozen_string_literal: true
class Packages::NpmPackagesFinder
attr_reader :project, :package_name
def initialize(project, package_name)
@project = project
@package_name = package_name
end
def execute
packages
end
private
def packages
project.packages
.npm
.with_name(package_name)
.last_of_each_version
.preload_files
end
end
......@@ -12,7 +12,11 @@ class Packages::Package < ActiveRecord::Base
presence: true,
format: { with: Gitlab::Regex.package_name_regex }
enum package_type: { maven: 1 }
enum package_type: { maven: 1, npm: 2 }
scope :with_name, ->(name) { where(name: name) }
scope :preload_files, -> { preload(:package_files) }
scope :last_of_each_version, -> { where(id: all.select('MAX(id) AS id').group(:version)) }
def self.for_projects(projects)
return none unless projects.any?
......@@ -23,4 +27,10 @@ class Packages::Package < ActiveRecord::Base
def self.only_maven_packages_with_path(path)
joins(:maven_metadatum).where(packages_maven_metadata: { path: path })
end
def self.by_name_and_file_name(name, file_name)
with_name(name)
.joins(:package_files)
.where(packages_package_files: { file_name: file_name }).last!
end
end
# frozen_string_literal: true
class NpmPackagePresenter
include API::Helpers::RelatedResourcesHelpers
attr_reader :project, :name, :packages
def initialize(project, name, packages)
@project = project
@name = name
@packages = packages
end
def versions
package_versions = {}
packages.each do |package|
package_file = package.package_files.last
next unless package_file
package_versions[package.version] = build_package_version(package, package_file)
end
package_versions
end
private
def build_package_version(package, package_file)
{
"name": package.name,
"version": package.version,
"dist": {
"shasum": package_file.file_sha1,
"tarball": tarball_url(package, package_file)
}
}
end
def tarball_url(package, package_file)
expose_url "#{api_v4_projects_path(id: package.project_id)}" \
"/packages/npm/#{package.name}" \
"/-/#{package_file.file_name}"
end
end
# frozen_string_literal: true
module Packages
class CreateNpmPackageService < BaseService
def execute
name = params[:name]
version = params[:versions].keys.first
version_data = params[:versions][version]
package = project.packages.create!(
name: name,
version: version,
package_type: 'npm'
)
package_file_name = "#{name}-#{version}.tgz"
attachment = params['_attachments'][package_file_name]
file_params = {
file: CarrierWaveStringFile.new(Base64.decode64(attachment['data'])),
size: attachment['length'],
file_sha1: version_data[:dist][:shasum],
file_name: package_file_name
}
::Packages::CreatePackageFileService.new(package, file_params).execute
package
end
end
end
......@@ -26,7 +26,7 @@
.table-section.section-20
.table-mobile-header{ role: "rowheader" }= _("Type")
.table-mobile-content
= _('Maven package')
= package.package_type
.table-section.section-20
.table-mobile-header{ role: "rowheader" }= _("Created")
.table-mobile-content
......
---
title: Add NPM registry support to GitLab packages
merge_request: 8673
author:
type: added
# frozen_string_literal: true
module API
module Helpers
module PackagesHelpers
def require_packages_enabled!
not_found! unless ::Gitlab.config.packages.enabled
end
def authorize_packages_feature!
forbidden! unless user_project.feature_available?(:packages)
end
def authorize_download_package!
authorize!(:read_package, user_project)
end
def authorize_create_package!
authorize!(:create_package, user_project)
end
end
end
end
......@@ -14,23 +14,9 @@ module API
authenticate_non_get!
end
helpers do
def require_packages_enabled!
not_found! unless Gitlab.config.packages.enabled
end
def authorize_packages_feature!
forbidden! unless user_project.feature_available?(:packages)
end
def authorize_download_package!
authorize!(:read_package, user_project)
end
def authorize_create_package!
authorize!(:create_package, user_project)
end
helpers ::API::Helpers::PackagesHelpers
helpers do
def extract_format(file_name)
name, _, format = file_name.rpartition('.')
......
# frozen_string_literal: true
module API
class NpmPackages < Grape::API
NPM_ENDPOINT_REQUIREMENTS = {
package_name: API::NO_SLASH_URL_PART_REGEX
}.freeze
before do
require_packages_enabled!
authenticate_non_get!
end
helpers ::API::Helpers::PackagesHelpers
helpers do
def find_project_by_package_name(name)
Project.find_by_full_path(name.sub('@', ''))
end
def project_package_name_match?
"@#{user_project.full_path}" == params[:package_name]
end
def ensure_project_package_match!
bad_request!(:package_name) unless project_package_name_match?
end
end
desc 'NPM registry endpoint at instance level' do
detail 'This feature was introduced in GitLab 11.8'
end
params do
requires :package_name, type: String, desc: 'Package name'
end
get 'packages/npm/*package_name', requirements: NPM_ENDPOINT_REQUIREMENTS do
package_name = params[:package_name]
# To avoid name collision we require project path and project package be the same.
project = find_project_by_package_name(package_name)
authorize!(:read_package, project)
forbidden! unless project.feature_available?(:packages)
packages = ::Packages::NpmPackagesFinder
.new(project, package_name).execute
present NpmPackagePresenter.new(project, package_name, packages),
with: EE::API::Entities::NpmPackage
end
params do
requires :id, type: String, desc: 'The ID of a project'
end
resource :projects, requirements: API::NAMESPACE_OR_PROJECT_REQUIREMENTS do
before do
authorize_packages_feature!
ensure_project_package_match!
end
desc 'Download the NPM tarball' do
detail 'This feature was introduced in GitLab 11.8'
end
params do
requires :package_name, type: String, desc: 'Package name'
requires :file_name, type: String, desc: 'Package file name'
end
get ':id/packages/npm/*package_name/-/*file_name', format: false do
authorize_download_package!
package = user_project.packages.npm
.by_name_and_file_name(params[:package_name], params[:file_name])
package_file = ::Packages::PackageFileFinder
.new(package, params[:file_name]).execute!
present_carrierwave_file!(package_file.file)
end
desc 'Create NPM package' do
detail 'This feature was introduced in GitLab 11.8'
end
params do
requires :package_name, type: String, desc: 'Package name'
requires :versions, type: Hash, desc: 'Package version info'
end
put ':id/packages/npm/:package_name', requirements: NPM_ENDPOINT_REQUIREMENTS do
authorize_create_package!
::Packages::CreateNpmPackageService
.new(user_project, current_user, params).execute
end
end
end
end
......@@ -21,6 +21,7 @@ module EE
mount ::API::ProjectMirror
mount ::API::ProjectPushRule
mount ::API::MavenPackages
mount ::API::NpmPackages
end
end
end
......
......@@ -494,6 +494,11 @@ module EE
expose :trial_ends_on
end
end
class NpmPackage < Grape::Entity
expose :name
expose :versions
end
end
end
end
......@@ -16,7 +16,7 @@ module EE
end
def package_name_regex
@package_name_regex ||= %r{\A(([\w\-\.]*)/)*([\w\-\.]*)\z}.freeze
@package_name_regex ||= %r{\A\@?(([\w\-\.]*)/)*([\w\-\.]*)\z}.freeze
end
def maven_path_regex
......
......@@ -19,6 +19,16 @@ FactoryBot.define do
create :package_file, :pom, package: package
end
end
factory :npm_package do
name 'foo'
version '1.0.0'
package_type 'npm'
after :create do |package|
create :package_file, :npm, package: package
end
end
end
factory :package_file, class: Packages::PackageFile do
......@@ -44,6 +54,17 @@ FactoryBot.define do
file_sha1 '42b1bdc80de64953b6876f5a8c644f20204011b0'
file_type 'xml'
end
trait(:npm) do
file { fixture_file_upload('ee/spec/fixtures/npm/foo-1.0.1.tgz') }
file_name 'foo-1.0.1.tgz'
file_sha1 'be93151dc23ac34a82752444556fe79b32c7a1ad'
file_type 'tgz'
end
trait :object_storage do
file_store { Packages::PackageFileUploader::Store::REMOTE }
end
end
factory :maven_metadatum, class: Packages::MavenMetadatum do
......
# frozen_string_literal: true
require 'spec_helper'
describe Packages::NpmPackagesFinder do
let(:package) { create(:npm_package) }
let(:project) { package.project }
describe '#execute!' do
it 'returns project packages' do
finder = described_class.new(project, package.name)
expect(finder.execute).to eq([package])
end
it 'returns an empty collection' do
finder = described_class.new(project, 'baz')
expect(finder.execute).to be_empty
end
end
end
{
"type": "object",
"required" : ["name", "versions"],
"properties" : {
"name": { "type": "string" },
"versions": { "type": "object" }
}
}
{
"type": "object",
"required": ["name", "version", "dist"],
"properties" : {
"name": { "type": "string" },
"version": { "type": "string" },
"dist": {
"type": "object",
"required": ["shasum", "tarball"],
"properties" : {
"shasum": { "type": "string" },
"tarball": { "type": "string" }
}
}
}
}
......@@ -19,4 +19,18 @@ describe Gitlab::Regex do
it { is_expected.not_to match('my feature flag') }
it { is_expected.not_to match('!!()()') }
end
describe '.package_name_regex' do
subject { described_class.package_name_regex }
it { is_expected.to match('123') }
it { is_expected.to match('foo') }
it { is_expected.to match('foo/bar') }
it { is_expected.to match('@foo/bar') }
it { is_expected.to match('com/mycompany/app/my-app') }
it { is_expected.not_to match('$foo/bar') }
it { is_expected.not_to match('@foo/@/bar') }
it { is_expected.not_to match('my package name') }
it { is_expected.not_to match('!!()()') }
end
end
......@@ -16,4 +16,32 @@ RSpec.describe Packages::Package, type: :model do
it { is_expected.not_to allow_value("my(dom$$$ain)com.my-app").for(:name) }
end
end
describe '.by_name_and_file_name' do
let!(:package) { create(:npm_package) }
let!(:package_file) { package.package_files.first }
subject { described_class }
it 'finds a package with correct arguiments' do
expect(subject.by_name_and_file_name(package.name, package_file.file_name)).to eq(package)
end
it 'will raise error if not found' do
expect { subject.by_name_and_file_name('foo', 'foo-5.5.5.tgz') }.to raise_error(ActiveRecord::RecordNotFound)
end
end
describe '.last_of_each_version' do
let!(:package1) { create(:npm_package, version: '1.0.0') }
let!(:package2) { create(:npm_package, version: '1.0.1') }
let!(:package3) { create(:npm_package, version: '1.0.1') }
subject { described_class.last_of_each_version }
it 'includes only latest package per version' do
is_expected.to include(package1, package3)
is_expected.not_to include(package2)
end
end
end
# frozen_string_literal: true
require 'spec_helper'
describe API::NpmPackages do
let(:group) { create(:group) }
let(:user) { create(:user) }
let(:project) { create(:project, :public, namespace: group) }
let(:token) { create(:oauth_access_token, scopes: 'api', resource_owner: user) }
before do
project.add_developer(user)
stub_licensed_features(packages: true)
end
shared_examples 'a package that requires auth' do
it 'returns the package info with oauth token' do
get_package_with_token(package)
expect_a_valid_package_response
end
it 'denies request without oauth token' do
get_package(package)
expect(response).to have_gitlab_http_status(403)
end
end
describe 'GET /api/v4/packages/npm/*package_name' do
let(:package) { create(:npm_package, project: project, name: "@#{project.full_path}") }
context 'a public project' do
it 'returns the package info without oauth token' do
get_package(package)
expect_a_valid_package_response
end
end
context 'internal project' do
before do
project.team.truncate
project.update!(visibility_level: Gitlab::VisibilityLevel::INTERNAL)
end
it_behaves_like 'a package that requires auth'
end
context 'private project' do
before do
project.update!(visibility_level: Gitlab::VisibilityLevel::PRIVATE)
end
it_behaves_like 'a package that requires auth'
it 'denies request when not enough permissions' do
project.add_guest(user)
get_package_with_token(package)
expect(response).to have_gitlab_http_status(403)
end
end
it 'rejects request if feature is not in the license' do
stub_licensed_features(packages: false)
get_package(package)
expect(response).to have_gitlab_http_status(403)
end
context 'project name is different from a package name' do
let(:package) { create(:npm_package, project: project) }
it 'rejects request' do
get_package(package)
expect(response).to have_gitlab_http_status(403)
end
end
def get_package(package, params = {})
get api("/packages/npm/#{package.name}"), params
end
def get_package_with_token(package, params = {})
get_package(package, params.merge(access_token: token.token))
end
end
describe 'GET /api/v4/projects/:id/packages/npm/*package_name/-/*file_name' do
let(:package) { create(:npm_package, project: project, name: "@#{project.full_path}") }
let(:package_file) { package.package_files.first }
context 'a public project' do
it 'returns the file' do
get_file_with_token(package_file)
expect(response).to have_gitlab_http_status(200)
expect(response.content_type.to_s).to eq('application/octet-stream')
end
end
context 'private project' do
before do
project.update!(visibility_level: Gitlab::VisibilityLevel::PRIVATE)
end
it 'returns the file' do
get_file_with_token(package_file)
expect(response).to have_gitlab_http_status(200)
expect(response.content_type.to_s).to eq('application/octet-stream')
end
it 'denies download when not enough permissions' do
project.add_guest(user)
get_file_with_token(package_file)
expect(response).to have_gitlab_http_status(403)
end
it 'denies download when no private token' do
get_file(package_file)
expect(response).to have_gitlab_http_status(404)
end
end
it 'rejects request if feature is not in the license' do
stub_licensed_features(packages: false)
get_file(package_file)
expect(response).to have_gitlab_http_status(403)
end
def get_file(package_file, params = {})
get api("/projects/#{project.id}/packages/npm/" \
"#{package_file.package.name}/-/#{package_file.file_name}"), params
end
def get_file_with_token(package_file, params = {})
get_file(package_file, params.merge(access_token: token.token))
end
end
describe 'PUT /api/v4/projects/:id/packages/npm/:package_name' do
context 'when params are correct' do
context 'unscoped package' do
let(:package_name) { project.path }
let(:params) { upload_params(package_name) }
it 'denies the request with 400 error' do
expect { upload_package_with_token(package_name, params) }
.not_to change { project.packages.count }
expect(response).to have_gitlab_http_status(400)
end
end
context 'scoped package' do
let(:package_name) { "@#{project.full_path}" }
let(:params) { upload_params(package_name) }
it 'creates npm package with file' do
expect { upload_package_with_token(package_name, params) }
.to change { project.packages.count }.by(1)
.and change { Packages::PackageFile.count }.by(1)
expect(response).to have_gitlab_http_status(200)
end
end
end
def upload_package(package_name, params = {})
put api("/projects/#{project.id}/packages/npm/#{package_name.sub('/', '%2f')}"), params
end
def upload_package_with_token(package_name, params = {})
upload_package(package_name, params.merge(access_token: token.token))
end
def upload_params(package_name)
{
name: package_name,
versions: {
'1.0.1' => {
dist: {
shasum: 'f572d396fae9206628714fb2ce00f72e94f2258f'
}
}
},
'_attachments' => {
"#{package_name}-1.0.1.tgz" => {
'data' => 'aGVsbG8K',
'length' => 8
}
}
}
end
end
def expect_a_valid_package_response
expect(response).to have_gitlab_http_status(200)
expect(response.content_type.to_s).to eq('application/json')
expect(response).to match_response_schema('public_api/v4/packages/npm_package', dir: 'ee')
expect(json_response['name']).to eq(package.name)
expect(json_response['versions'][package.version]).to match_schema('public_api/v4/packages/npm_package_version', dir: 'ee')
end
end
# frozen_string_literal: true
require 'spec_helper'
describe Packages::CreateNpmPackageService do
let(:project) { create(:project) }
let(:user) { create(:user) }
let(:version) { '1.0.1'.freeze }
let(:params) do
{
name: package_name,
versions: {
version => {
dist: {
shasum: 'f572d396fae9206628714fb2ce00f72e94f2258f'
}
}
},
'_attachments' => {
"#{package_name}-#{version}.tgz" => {
'content_type' => 'application/octet-stream',
'data' => 'aGVsbG8K',
'length' => 8
}
}
}
end
shared_examples 'valid package' do
it 'creates a valid package' do
package = described_class.new(project, user, params).execute
expect(package).to be_valid
expect(package.name).to eq(package_name)
expect(package.version).to eq(version)
end
end
describe '#execute' do
context 'scoped package' do
let(:package_name) { '@gitlab/my-app'.freeze }
it_behaves_like 'valid package'
end
context 'normal package' do
let(:package_name) { 'my-app'.freeze }
it_behaves_like 'valid package'
end
end
end
# frozen_string_literal: true
module EE
module StubObjectStorage
def stub_packages_object_storage(**params)
stub_object_storage_uploader(config: ::Gitlab.config.packages.object_store,
uploader: ::Packages::PackageFileUploader,
remote_directory: 'packages',
**params)
end
end
end
......@@ -34,11 +34,4 @@ describe 'gitlab:packages namespace rake task' do
end
end
end
def stub_packages_object_storage(**params)
stub_object_storage_uploader(config: Gitlab.config.packages.object_store,
uploader: ::Packages::PackageFileUploader,
remote_directory: 'packages',
**params)
end
end
# frozen_string_literal: true
require 'spec_helper'
describe Packages::PackageFileUploader do
let(:package_file) { create(:package_file, :xml) }
let(:uploader) { described_class.new(package_file, :file) }
let(:path) { Gitlab.config.packages.storage_path }
subject { uploader }
it_behaves_like "builds correct paths",
store_dir: %r[\h{2}/\h{2}],
cache_dir: %r[/packages/tmp/cache],
work_dir: %r[/packages/tmp/work]
context 'object store is remote' do
before do
stub_packages_object_storage
end
include_context 'with storage', described_class::Store::REMOTE
it_behaves_like "builds correct paths",
store_dir: %r[\h{2}/\h{2}]
end
describe 'remote file' do
let(:package_file) { create(:package_file, :object_storage, :xml) }
context 'with object storage enabled' do
before do
stub_packages_object_storage
end
it 'can store file remotely' do
allow(ObjectStorage::BackgroundMoveWorker).to receive(:perform_async)
package_file
expect(package_file.file_store).to eq(described_class::Store::REMOTE)
expect(package_file.file.path).not_to be_blank
end
end
end
end
......@@ -5363,9 +5363,6 @@ msgstr ""
msgid "Maven Metadata"
msgstr ""
msgid "Maven package"
msgstr ""
msgid "Max access level"
msgstr ""
......
module StubObjectStorage
prepend EE::StubObjectStorage
def stub_object_storage_uploader(
config:,
uploader:,
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment