Add latest changes from gitlab-org/gitlab@master
This commit is contained in:
parent
76ef00aac9
commit
a8c1bc6f75
|
@ -39,7 +39,7 @@ review-build-cng:
|
|||
.review-workflow-base:
|
||||
extends:
|
||||
- .default-retry
|
||||
image: registry.gitlab.com/gitlab-org/gitlab-build-images:gitlab-helm3-kubectl1.14
|
||||
image: registry.gitlab.com/gitlab-org/gitlab-build-images:gitlab-helm3.5-kubectl1.17
|
||||
variables:
|
||||
HOST_SUFFIX: "${CI_ENVIRONMENT_SLUG}"
|
||||
DOMAIN: "-${CI_ENVIRONMENT_SLUG}.${REVIEW_APPS_DOMAIN}"
|
||||
|
|
|
@ -60,7 +60,9 @@ const createFlashEl = (message, type) => `
|
|||
`;
|
||||
|
||||
const removeFlashClickListener = (flashEl, fadeTransition) => {
|
||||
getCloseEl(flashEl).addEventListener('click', () => hideFlash(flashEl, fadeTransition));
|
||||
// There are some flash elements which do not have a closeEl.
|
||||
// https://gitlab.com/gitlab-org/gitlab/blob/763426ef344488972eb63ea5be8744e0f8459e6b/ee/app/views/layouts/header/_read_only_banner.html.haml
|
||||
getCloseEl(flashEl)?.addEventListener('click', () => hideFlash(flashEl, fadeTransition));
|
||||
};
|
||||
|
||||
/*
|
||||
|
|
|
@ -72,11 +72,13 @@ export function bytesToGiB(number) {
|
|||
* @returns {String}
|
||||
*/
|
||||
export function numberToHumanSize(size) {
|
||||
if (size < BYTES_IN_KIB) {
|
||||
const abs = Math.abs(size);
|
||||
|
||||
if (abs < BYTES_IN_KIB) {
|
||||
return sprintf(__('%{size} bytes'), { size });
|
||||
} else if (size < BYTES_IN_KIB * BYTES_IN_KIB) {
|
||||
} else if (abs < BYTES_IN_KIB ** 2) {
|
||||
return sprintf(__('%{size} KiB'), { size: bytesToKiB(size).toFixed(2) });
|
||||
} else if (size < BYTES_IN_KIB * BYTES_IN_KIB * BYTES_IN_KIB) {
|
||||
} else if (abs < BYTES_IN_KIB ** 3) {
|
||||
return sprintf(__('%{size} MiB'), { size: bytesToMiB(size).toFixed(2) });
|
||||
}
|
||||
return sprintf(__('%{size} GiB'), { size: bytesToGiB(size).toFixed(2) });
|
||||
|
|
|
@ -510,7 +510,7 @@ class ProjectsController < Projects::ApplicationController
|
|||
|
||||
# `project` calls `find_routable!`, so this will trigger the usual not-found
|
||||
# behaviour when the user isn't authorized to see the project
|
||||
return unless project
|
||||
return if project.nil? || performed?
|
||||
|
||||
redirect_to(request.original_url.sub(%r{\.git/?\Z}, ''))
|
||||
end
|
||||
|
|
|
@ -26,9 +26,9 @@ module Packages
|
|||
|
||||
def base
|
||||
if project?
|
||||
packages_for_project(@project_or_group)
|
||||
project_packages
|
||||
elsif group?
|
||||
packages_visible_to_user(@current_user, within_group: @project_or_group)
|
||||
group_packages
|
||||
else
|
||||
::Packages::Package.none
|
||||
end
|
||||
|
@ -41,5 +41,13 @@ module Packages
|
|||
def group?
|
||||
@project_or_group.is_a?(::Group)
|
||||
end
|
||||
|
||||
def project_packages
|
||||
packages_for_project(@project_or_group)
|
||||
end
|
||||
|
||||
def group_packages
|
||||
packages_visible_to_user(@current_user, within_group: @project_or_group)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -12,6 +12,16 @@ module Packages
|
|||
def packages
|
||||
base.pypi.has_version
|
||||
end
|
||||
|
||||
def group_packages
|
||||
# PyPI finds packages without checking permissions.
|
||||
# The package download endpoint uses obfuscation to secure the file
|
||||
# instead of authentication. This is behavior the PyPI package
|
||||
# manager defines and is not something GitLab controls.
|
||||
::Packages::Package.for_projects(
|
||||
@project_or_group.all_projects.select(:id)
|
||||
).installable
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -275,12 +275,6 @@ class Namespace < ApplicationRecord
|
|||
Project.where(namespace: namespace)
|
||||
end
|
||||
|
||||
# Includes pipelines from this namespace and pipelines from all subgroups
|
||||
# that belongs to this namespace
|
||||
def all_pipelines
|
||||
Ci::Pipeline.where(project: all_projects)
|
||||
end
|
||||
|
||||
def has_parent?
|
||||
parent_id.present? || parent.present?
|
||||
end
|
||||
|
|
|
@ -636,6 +636,12 @@ class Project < ApplicationRecord
|
|||
scope :with_tracing_enabled, -> { joins(:tracing_setting) }
|
||||
scope :with_enabled_error_tracking, -> { joins(:error_tracking_setting).where(project_error_tracking_settings: { enabled: true }) }
|
||||
|
||||
scope :with_service_desk_key, -> (key) do
|
||||
# project_key is not indexed for now
|
||||
# see https://gitlab.com/gitlab-org/gitlab/-/merge_requests/24063#note_282435524 for details
|
||||
joins(:service_desk_setting).where('service_desk_settings.project_key' => key)
|
||||
end
|
||||
|
||||
enum auto_cancel_pending_pipelines: { disabled: 0, enabled: 1 }
|
||||
|
||||
chronic_duration_attr :build_timeout_human_readable, :build_timeout,
|
||||
|
@ -837,12 +843,6 @@ class Project < ApplicationRecord
|
|||
|
||||
from_union([with_issues_enabled, with_merge_requests_enabled]).select(:id)
|
||||
end
|
||||
|
||||
def find_by_service_desk_project_key(key)
|
||||
# project_key is not indexed for now
|
||||
# see https://gitlab.com/gitlab-org/gitlab/-/merge_requests/24063#note_282435524 for details
|
||||
joins(:service_desk_setting).find_by('service_desk_settings.project_key' => key)
|
||||
end
|
||||
end
|
||||
|
||||
def initialize(attributes = nil)
|
||||
|
|
|
@ -7,9 +7,9 @@ module Packages
|
|||
class PackagePresenter
|
||||
include API::Helpers::RelatedResourcesHelpers
|
||||
|
||||
def initialize(packages, project)
|
||||
def initialize(packages, project_or_group)
|
||||
@packages = packages
|
||||
@project = project
|
||||
@project_or_group = project_or_group
|
||||
end
|
||||
|
||||
# Returns the HTML body for PyPI simple API.
|
||||
|
@ -51,16 +51,27 @@ module Packages
|
|||
end
|
||||
|
||||
def build_pypi_package_path(file)
|
||||
expose_url(
|
||||
api_v4_projects_packages_pypi_files_file_identifier_path(
|
||||
{
|
||||
id: @project.id,
|
||||
sha256: file.file_sha256,
|
||||
file_identifier: file.file_name
|
||||
},
|
||||
true
|
||||
)
|
||||
) + "#sha256=#{file.file_sha256}"
|
||||
params = {
|
||||
id: @project_or_group.id,
|
||||
sha256: file.file_sha256,
|
||||
file_identifier: file.file_name
|
||||
}
|
||||
|
||||
if project?
|
||||
expose_url(
|
||||
api_v4_projects_packages_pypi_files_file_identifier_path(
|
||||
params, true
|
||||
)
|
||||
) + "#sha256=#{file.file_sha256}"
|
||||
elsif group?
|
||||
expose_url(
|
||||
api_v4_groups___packages_pypi_files_file_identifier_path(
|
||||
params, true
|
||||
)
|
||||
) + "#sha256=#{file.file_sha256}"
|
||||
else
|
||||
''
|
||||
end
|
||||
end
|
||||
|
||||
def name
|
||||
|
@ -70,6 +81,14 @@ module Packages
|
|||
def escape(str)
|
||||
ERB::Util.html_escape(str)
|
||||
end
|
||||
|
||||
def project?
|
||||
@project_or_group.is_a?(::Project)
|
||||
end
|
||||
|
||||
def group?
|
||||
@project_or_group.is_a?(::Group)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -21,6 +21,15 @@
|
|||
:weight: 1
|
||||
:idempotent: true
|
||||
:tags: []
|
||||
- :name: authorized_project_update:authorized_project_update_project_recalculate
|
||||
:worker_name: AuthorizedProjectUpdate::ProjectRecalculateWorker
|
||||
:feature_category: :authentication_and_authorization
|
||||
:has_external_dependencies:
|
||||
:urgency: :high
|
||||
:resource_boundary: :unknown
|
||||
:weight: 1
|
||||
:idempotent: true
|
||||
:tags: []
|
||||
- :name: authorized_project_update:authorized_project_update_user_refresh_over_user_range
|
||||
:worker_name: AuthorizedProjectUpdate::UserRefreshOverUserRangeWorker
|
||||
:feature_category: :authentication_and_authorization
|
||||
|
@ -39,15 +48,6 @@
|
|||
:weight: 1
|
||||
:idempotent: true
|
||||
:tags: []
|
||||
- :name: authorized_projects:authorized_project_update_project_recalculate
|
||||
:worker_name: AuthorizedProjectUpdate::ProjectRecalculateWorker
|
||||
:feature_category: :authentication_and_authorization
|
||||
:has_external_dependencies:
|
||||
:urgency: :high
|
||||
:resource_boundary: :unknown
|
||||
:weight: 1
|
||||
:idempotent: true
|
||||
:tags: []
|
||||
- :name: auto_devops:auto_devops_disable
|
||||
:worker_name: AutoDevops::DisableWorker
|
||||
:feature_category: :auto_devops
|
||||
|
|
|
@ -7,7 +7,7 @@ module AuthorizedProjectUpdate
|
|||
|
||||
feature_category :authentication_and_authorization
|
||||
urgency :high
|
||||
queue_namespace :authorized_projects
|
||||
queue_namespace :authorized_project_update
|
||||
|
||||
deduplicate :until_executing, including_scheduled: true
|
||||
idempotent!
|
||||
|
|
|
@ -0,0 +1,8 @@
|
|||
---
|
||||
name: security_ci_lint_authorization
|
||||
introduced_by_url: https://gitlab.com/gitlab-org/security/gitlab/-/merge_requests/1279
|
||||
rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/326708
|
||||
milestone: '14.0'
|
||||
type: development
|
||||
group: group::pipeline authoring
|
||||
default_enabled: false
|
|
@ -44,8 +44,6 @@
|
|||
- 2
|
||||
- - authorized_project_update
|
||||
- 1
|
||||
- - authorized_projects
|
||||
- 1
|
||||
- - authorized_projects
|
||||
- 2
|
||||
- - auto_devops
|
||||
|
|
|
@ -0,0 +1,36 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
class CleanupMoveContainerRegistryEnabledToProjectFeature < ActiveRecord::Migration[6.0]
|
||||
MIGRATION = 'MoveContainerRegistryEnabledToProjectFeature'
|
||||
|
||||
disable_ddl_transaction!
|
||||
|
||||
def up
|
||||
Gitlab::BackgroundMigration.steal(MIGRATION)
|
||||
|
||||
bg_migration_job_class = define_background_migration_jobs_class
|
||||
bg_migration_job_class.where(class_name: MIGRATION, status: bg_migration_job_class.statuses['pending']).each do |job|
|
||||
Gitlab::BackgroundMigration::MoveContainerRegistryEnabledToProjectFeature.new.perform(*job.arguments)
|
||||
end
|
||||
|
||||
bg_migration_job_class.where(class_name: MIGRATION).delete_all
|
||||
end
|
||||
|
||||
def down
|
||||
# no-op
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def define_background_migration_jobs_class
|
||||
Class.new(ActiveRecord::Base) do
|
||||
self.table_name = 'background_migration_jobs'
|
||||
self.inheritance_column = :_type_disabled
|
||||
|
||||
enum status: {
|
||||
pending: 0,
|
||||
succeeded: 1
|
||||
}
|
||||
end
|
||||
end
|
||||
end
|
|
@ -0,0 +1 @@
|
|||
3c4905fbe29227da7a2386f73d9df30e82da48efff24a1193ba3db0ac325cfcf
|
|
@ -23,6 +23,9 @@ If you use a cloud-managed service, or provide your own PostgreSQL instance:
|
|||
- Amazon RDS requires the [`rds_superuser`](https://docs.aws.amazon.com/AmazonRDS/latest/UserGuide/Appendix.PostgreSQL.CommonDBATasks.html#Appendix.PostgreSQL.CommonDBATasks.Roles) role.
|
||||
- Azure Database for PostgreSQL requires the [`azure_pg_admin`](https://docs.microsoft.com/en-us/azure/postgresql/howto-create-users#how-to-create-additional-admin-users-in-azure-database-for-postgresql) role.
|
||||
|
||||
This is for the installation of extensions during installation and upgrades. As an alternative,
|
||||
[ensure the extensions are installed manually, and read about the problems that may arise during future GitLab upgrades](../../install/postgresql_extensions.md).
|
||||
|
||||
1. Configure the GitLab application servers with the appropriate connection details
|
||||
for your external PostgreSQL service in your `/etc/gitlab/gitlab.rb` file:
|
||||
|
||||
|
|
|
@ -13,7 +13,16 @@ info: To determine the technical writer assigned to the Stage/Group associated w
|
|||
Checks if CI/CD YAML configuration is valid. This endpoint validates basic CI/CD
|
||||
configuration syntax. It doesn't have any namespace specific context.
|
||||
|
||||
Access to this endpoint requires authentication.
|
||||
Access to this endpoint does not require authentication when the instance
|
||||
[allows new sign ups](../user/admin_area/settings/sign_up_restrictions.md#disable-new-sign-ups)
|
||||
and:
|
||||
|
||||
- Does not have an [allowlist or denylist](../user/admin_area/settings/sign_up_restrictions.md#allow-or-deny-sign-ups-using-specific-email-domains).
|
||||
- Does not [require administrator approval for new sign ups](../user/admin_area/settings/sign_up_restrictions.md#require-administrator-approval-for-new-sign-ups).
|
||||
- Does not have additional [sign up
|
||||
restrictions](../user/admin_area/settings/sign_up_restrictions.html#sign-up-restrictions).
|
||||
|
||||
Otherwise, authentication is required.
|
||||
|
||||
```plaintext
|
||||
POST /ci/lint
|
||||
|
|
|
@ -20,11 +20,82 @@ These endpoints do not adhere to the standard API authentication methods.
|
|||
See the [PyPI package registry documentation](../../user/packages/pypi_repository/index.md)
|
||||
for details on which headers and token types are supported.
|
||||
|
||||
## Download a package file
|
||||
## Download a package file from a group
|
||||
|
||||
> [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/225545) in GitLab 13.12.
|
||||
|
||||
Download a PyPI package file. The [simple API](#group-level-simple-api-entry-point)
|
||||
normally supplies this URL.
|
||||
|
||||
```plaintext
|
||||
GET groups/:id/packages/pypi/files/:sha256/:file_identifier
|
||||
```
|
||||
|
||||
| Attribute | Type | Required | Description |
|
||||
| ----------------- | ------ | -------- | ----------- |
|
||||
| `id` | string | yes | The ID or full path of the group. |
|
||||
| `sha256` | string | yes | The PyPI package file's sha256 checksum. |
|
||||
| `file_identifier` | string | yes | The PyPI package file's name. |
|
||||
|
||||
```shell
|
||||
curl --user <username>:<personal_access_token> "https://gitlab.example.com/api/v4/groups/1/packages/pypi/files/5y57017232013c8ac80647f4ca153k3726f6cba62d055cd747844ed95b3c65ff/my.pypi.package-0.0.1.tar.gz"
|
||||
```
|
||||
|
||||
To write the output to a file:
|
||||
|
||||
```shell
|
||||
curl --user <username>:<personal_access_token> "https://gitlab.example.com/api/v4/groups/1/packages/pypi/files/5y57017232013c8ac80647f4ca153k3726f6cba62d055cd747844ed95b3c65ff/my.pypi.package-0.0.1.tar.gz" >> my.pypi.package-0.0.1.tar.gz
|
||||
```
|
||||
|
||||
This writes the downloaded file to `my.pypi.package-0.0.1.tar.gz` in the current directory.
|
||||
|
||||
## Group level simple API entry point
|
||||
|
||||
> [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/225545) in GitLab 13.12.
|
||||
|
||||
Returns the package descriptor as an HTML file:
|
||||
|
||||
```plaintext
|
||||
GET groups/:id/packages/pypi/simple/:package_name
|
||||
```
|
||||
|
||||
| Attribute | Type | Required | Description |
|
||||
| -------------- | ------ | -------- | ----------- |
|
||||
| `id` | string | yes | The ID or full path of the group. |
|
||||
| `package_name` | string | yes | The name of the package. |
|
||||
|
||||
```shell
|
||||
curl --user <username>:<personal_access_token> "https://gitlab.example.com/api/v4/groups/1/packages/pypi/simple/my.pypi.package"
|
||||
```
|
||||
|
||||
Example response:
|
||||
|
||||
```html
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<title>Links for my.pypi.package</title>
|
||||
</head>
|
||||
<body>
|
||||
<h1>Links for my.pypi.package</h1>
|
||||
<a href="https://gitlab.example.com/api/v4/groups/1/packages/pypi/files/5y57017232013c8ac80647f4ca153k3726f6cba62d055cd747844ed95b3c65ff/my.pypi.package-0.0.1-py3-none-any.whl#sha256=5y57017232013c8ac80647f4ca153k3726f6cba62d055cd747844ed95b3c65ff" data-requires-python=">=3.6">my.pypi.package-0.0.1-py3-none-any.whl</a><br><a href="https://gitlab.example.com/api/v4/groups/1/packages/pypi/files/9s9w01b0bcd52b709ec052084e33a5517ffca96f7728ddd9f8866a30cdf76f2/my.pypi.package-0.0.1.tar.gz#sha256=9s9w011b0bcd52b709ec052084e33a5517ffca96f7728ddd9f8866a30cdf76f2" data-requires-python=">=3.6">my.pypi.package-0.0.1.tar.gz</a><br>
|
||||
</body>
|
||||
</html>
|
||||
```
|
||||
|
||||
To write the output to a file:
|
||||
|
||||
```shell
|
||||
curl --user <username>:<personal_access_token> "https://gitlab.example.com/api/v4/groups/1/packages/pypi/simple/my.pypi.package" >> simple.html
|
||||
```
|
||||
|
||||
This writes the downloaded file to `simple.html` in the current directory.
|
||||
|
||||
## Download a package file from a project
|
||||
|
||||
> Introduced in GitLab 12.10.
|
||||
|
||||
Download a PyPI package file. The [simple API](#simple-api-entry-point)
|
||||
Download a PyPI package file. The [simple API](#project-level-simple-api-entry-point)
|
||||
normally supplies this URL.
|
||||
|
||||
```plaintext
|
||||
|
@ -49,7 +120,7 @@ curl --user <username>:<personal_access_token> "https://gitlab.example.com/api/v
|
|||
|
||||
This writes the downloaded file to `my.pypi.package-0.0.1.tar.gz` in the current directory.
|
||||
|
||||
## Simple API entry point
|
||||
## Project-level simple API entry point
|
||||
|
||||
> Introduced in GitLab 12.10.
|
||||
|
||||
|
|
|
@ -87,6 +87,7 @@ are very appreciative of the work done by translators and proofreaders!
|
|||
- Polish
|
||||
- Filip Mech - [GitLab](https://gitlab.com/mehenz), [CrowdIn](https://crowdin.com/profile/mehenz)
|
||||
- Maksymilian Roman - [GitLab](https://gitlab.com/villaincandle), [CrowdIn](https://crowdin.com/profile/villaincandle)
|
||||
- Jakub Gładykowski - [GitLab](https://gitlab.com/gladykov), [CrowdIn](https://crowdin.com/profile/gladykov)
|
||||
- Portuguese
|
||||
- Diogo Trindade - [GitLab](https://gitlab.com/luisdiogo2071317), [CrowdIn](https://crowdin.com/profile/ldiogotrindade)
|
||||
- Portuguese, Brazilian
|
||||
|
@ -108,7 +109,7 @@ are very appreciative of the work done by translators and proofreaders!
|
|||
- Spanish
|
||||
- Pedro Garcia - [GitLab](https://gitlab.com/pedgarrod), [CrowdIn](https://crowdin.com/profile/breaking_pitt)
|
||||
- Swedish
|
||||
- Proofreaders needed.
|
||||
- Johannes Nilsson - [GitLab](https://gitlab.com/nlssn), [CrowdIn](https://crowdin.com/profile/nlssn)
|
||||
- Turkish
|
||||
- Ali Demirtaş - [GitLab](https://gitlab.com/alidemirtas), [CrowdIn](https://crowdin.com/profile/alidemirtas)
|
||||
- Rıfat Ünalmış (Rifat Unalmis) - [GitLab](https://gitlab.com/runalmis), [CrowdIn](https://crowdin.com/profile/runalmis)
|
||||
|
|
|
@ -54,7 +54,19 @@ In order to install a PostgreSQL extension, this procedure should be followed:
|
|||
On some systems you may need to install an additional package (for example,
|
||||
`postgresql-contrib`) for certain extensions to become available.
|
||||
|
||||
## A typical migration failure scenario
|
||||
## Typical failure scenarios
|
||||
|
||||
The following is an example of a new GitLab installation failing because the extension hasn't been
|
||||
installed first.
|
||||
|
||||
```shell
|
||||
---- Begin output of "bash" "/tmp/chef-script20210513-52940-d9b1gs" ----
|
||||
STDOUT: psql:/opt/gitlab/embedded/service/gitlab-rails/db/structure.sql:9: ERROR: permission denied to create extension "btree_gist"
|
||||
HINT: Must be superuser to create this extension.
|
||||
rake aborted!
|
||||
failed to execute:
|
||||
psql -v ON_ERROR_STOP=1 -q -X -f /opt/gitlab/embedded/service/gitlab-rails/db/structure.sql --single-transaction gitlabhq_production
|
||||
```
|
||||
|
||||
The following is an example of a situation when the extension hasn't been installed before running migrations.
|
||||
In this scenario, the database migration fails to create the extension `btree_gist` because of insufficient
|
||||
|
@ -79,5 +91,9 @@ This query will grant the user superuser permissions, ensuring any database exte
|
|||
can be installed through migrations.
|
||||
```
|
||||
|
||||
In order to recover from this situation, the extension needs to be installed manually using a superuser, and
|
||||
the database migration (or GitLab upgrade) can be retried afterwards.
|
||||
To recover from failed migrations, the extension must be installed manually by a superuser, and the
|
||||
GitLab upgrade completed by [re-running the database migrations](../administration/raketasks/maintenance.md#run-incomplete-database-migrations):
|
||||
|
||||
```shell
|
||||
sudo gitlab-rake db:migrate
|
||||
```
|
||||
|
|
|
@ -117,13 +117,14 @@ the following table) as these were used for development and testing:
|
|||
| 10.0 | 9.6 |
|
||||
| 13.0 | 11 |
|
||||
|
||||
You must also ensure the following extensions are [loaded into every
|
||||
GitLab database](postgresql_extensions.html):
|
||||
You must also ensure the following extensions are loaded into every
|
||||
GitLab database. [Read more about this requirement, and troubleshooting](postgresql_extensions.md).
|
||||
|
||||
| Extension | Minimum GitLab version |
|
||||
| ------------ | ---------------------- |
|
||||
| `pg_trgm` | 8.6 |
|
||||
| `btree_gist` | 13.1 |
|
||||
| `plpgsql` | 11.7 |
|
||||
|
||||
NOTE:
|
||||
Support for [PostgreSQL 9.6 and 10 was removed in GitLab 13.0](https://about.gitlab.com/releases/2020/05/22/gitlab-13-0-released/#postgresql-11-is-now-the-minimum-required-version-to-install-gitlab) so that GitLab can benefit from PostgreSQL 11 improvements, such as partitioning. For the schedule of transitioning to PostgreSQL 12, see [the related epic](https://gitlab.com/groups/gitlab-org/-/epics/2184).
|
||||
|
@ -136,6 +137,35 @@ test based on those. We try to be compatible with most external (not managed by
|
|||
Omnibus GitLab) databases (for example, [AWS Relational Database Service (RDS)](https://aws.amazon.com/rds/)),
|
||||
but we can't guarantee compatibility.
|
||||
|
||||
#### Gitaly Cluster database requirements
|
||||
|
||||
[Read more in the Gitaly Cluster documentation](../administration/gitaly/praefect.md).
|
||||
|
||||
#### Exclusive use of GitLab databases
|
||||
|
||||
Databases created or used for GitLab, Geo, Gitaly Cluster, or other components should be for the
|
||||
exclusive use of GitLab. Do not make direct changes to the database, schemas, users, or other
|
||||
properties except when following procedures in the GitLab documentation or following the directions
|
||||
of GitLab Support or other GitLab engineers.
|
||||
|
||||
- The main GitLab application currently uses three schemas:
|
||||
|
||||
- The default `public` schema
|
||||
- `gitlab_partitions_static` (automatically created)
|
||||
- `gitlab_partitions_dynamic` (automatically created)
|
||||
|
||||
No other schemas should be manually created.
|
||||
|
||||
- GitLab may create new schemas as part of Rails database migrations. This happens when performing
|
||||
a GitLab upgrade. The GitLab database account requires access to do this.
|
||||
|
||||
- GitLab creates and modifies tables during the upgrade process, and also as part of normal
|
||||
operations to manage partitioned tables.
|
||||
|
||||
- You should not modify the GitLab schema (for example, adding triggers or modifying tables).
|
||||
Database migrations are tested against the schema definition in the GitLab code base. GitLab
|
||||
version upgrades may fail if the schema is modified.
|
||||
|
||||
## Puma settings
|
||||
|
||||
The recommended settings for Puma are determined by the infrastructure on which it's running.
|
||||
|
|
|
@ -102,7 +102,7 @@ How you enable container scanning depends on your GitLab version:
|
|||
`container_scanning` job's [`before_script`](../../../ci/yaml/README.md#before_script)
|
||||
and [`after_script`](../../../ci/yaml/README.md#after_script)
|
||||
blocks may not work with the new version. To roll back to the previous [`alpine:3.11.3`](https://hub.docker.com/_/alpine)-based
|
||||
Docker image, you can specify the major version through the [`CS_MAJOR_VERSION`](#available-variables)
|
||||
Docker image, you can specify the major version through the [`CS_MAJOR_VERSION`](#available-cicd-variables)
|
||||
variable.
|
||||
- GitLab 13.9 [introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/322656) integration with
|
||||
[Trivy](https://github.com/aquasecurity/trivy) by upgrading `CS_MAJOR_VERSION` from `3` to `4`.
|
||||
|
@ -159,7 +159,7 @@ include:
|
|||
There may be cases where you want to customize how GitLab scans your containers. For example, you
|
||||
may want to enable more verbose output, access a Docker registry that requires
|
||||
authentication, and more. To change such settings, use the [`variables`](../../../ci/yaml/README.md#variables)
|
||||
parameter in your `.gitlab-ci.yml` to set [CI/CD variables](#available-variables).
|
||||
parameter in your `.gitlab-ci.yml` to set [CI/CD variables](#available-cicd-variables).
|
||||
The variables you set in your `.gitlab-ci.yml` overwrite those in
|
||||
`Container-Scanning.gitlab-ci.yml`.
|
||||
|
||||
|
@ -201,7 +201,7 @@ variables:
|
|||
make a change to this heading, make sure to update the documentation URLs used in the"
|
||||
container scanning tool (https://gitlab.com/gitlab-org/security-products/analyzers/klar)" -->
|
||||
|
||||
#### Available variables
|
||||
#### Available CI/CD variables
|
||||
|
||||
You can [configure](#customizing-the-container-scanning-settings) both analyzers by using the following CI/CD variables:
|
||||
|
||||
|
@ -289,7 +289,7 @@ taking the following steps:
|
|||
that instead of overriding this variable, you can use `CS_MAJOR_VERSION`.
|
||||
|
||||
1. Remove any variables that are only applicable to Clair. For a complete list of these variables,
|
||||
see the [available variables](#available-variables).
|
||||
see the [available variables](#available-cicd-variables).
|
||||
1. Make any [necessary customizations](#customizing-the-container-scanning-settings) to the
|
||||
`Trivy` scanner. We strongly recommended that you minimize customizations, as they
|
||||
might require changes in future GitLab major releases.
|
||||
|
@ -711,7 +711,7 @@ Some vulnerabilities can be fixed by applying the solution that GitLab
|
|||
automatically generates.
|
||||
|
||||
To enable remediation support, the scanning tool _must_ have access to the `Dockerfile` specified by
|
||||
the [`DOCKERFILE_PATH`](#available-variables) CI/CD variable. To ensure that the scanning tool
|
||||
the [`DOCKERFILE_PATH`](#available-cicd-variables) CI/CD variable. To ensure that the scanning tool
|
||||
has access to this
|
||||
file, it's necessary to set [`GIT_STRATEGY: fetch`](../../../ci/runners/README.md#git-strategy) in
|
||||
your `.gitlab-ci.yml` file by following the instructions described in this document's
|
||||
|
|
|
@ -44,7 +44,7 @@ dast:
|
|||
DAST_BROWSER_SCAN: "true"
|
||||
```
|
||||
|
||||
### Available variables
|
||||
### Available CI/CD variables
|
||||
|
||||
The browser-based crawler can be configured using CI/CD variables.
|
||||
|
||||
|
@ -72,7 +72,7 @@ The browser-based crawler can be configured using CI/CD variables.
|
|||
| `DAST_BROWSER_AUTH_VERIFICATION_SELECTOR` | selector | `css:.user-photo` | Verifies successful authentication by checking for presence of a selector once the login form has been submitted. |
|
||||
| `DAST_BROWSER_AUTH_VERIFICATION_LOGIN_FORM` | boolean | `true` | Verifies successful authentication by checking for the lack of a login form once the login form has been submitted. |
|
||||
|
||||
The [DAST variables](index.md#available-variables) `SECURE_ANALYZERS_PREFIX`, `DAST_FULL_SCAN_ENABLED`, `DAST_AUTO_UPDATE_ADDONS`, `DAST_EXCLUDE_RULES`, `DAST_REQUEST_HEADERS`, `DAST_HTML_REPORT`, `DAST_MARKDOWN_REPORT`, `DAST_XML_REPORT`,
|
||||
The [DAST variables](index.md#available-cicd-variables) `SECURE_ANALYZERS_PREFIX`, `DAST_FULL_SCAN_ENABLED`, `DAST_AUTO_UPDATE_ADDONS`, `DAST_EXCLUDE_RULES`, `DAST_REQUEST_HEADERS`, `DAST_HTML_REPORT`, `DAST_MARKDOWN_REPORT`, `DAST_XML_REPORT`,
|
||||
`DAST_INCLUDE_ALPHA_VULNERABILITIES`, `DAST_PATHS_FILE`, `DAST_PATHS`, `DAST_ZAP_CLI_OPTIONS`, and `DAST_ZAP_LOG_CONFIGURATION` are also compatible with browser-based crawler scans.
|
||||
|
||||
#### Selectors
|
||||
|
@ -284,9 +284,9 @@ This can come at a cost of increased scan time.
|
|||
|
||||
You can manage the trade-off between coverage and scan time with the following measures:
|
||||
|
||||
- Limit the number of actions executed by the browser with the [variable](#available-variables) `DAST_BROWSER_MAX_ACTIONS`. The default is `10,000`.
|
||||
- Limit the page depth that the browser-based crawler will check coverage on with the [variable](#available-variables) `DAST_BROWSER_MAX_DEPTH`. The crawler uses a breadth-first search strategy, so pages with smaller depth are crawled first. The default is `10`.
|
||||
- Vertically scaling the runner and using a higher number of browsers with [variable](#available-variables) `DAST_BROWSER_NUMBER_OF_BROWSERS`. The default is `3`.
|
||||
- Limit the number of actions executed by the browser with the [variable](#available-cicd-variables) `DAST_BROWSER_MAX_ACTIONS`. The default is `10,000`.
|
||||
- Limit the page depth that the browser-based crawler will check coverage on with the [variable](#available-cicd-variables) `DAST_BROWSER_MAX_DEPTH`. The crawler uses a breadth-first search strategy, so pages with smaller depth are crawled first. The default is `10`.
|
||||
- Vertically scaling the runner and using a higher number of browsers with [variable](#available-cicd-variables) `DAST_BROWSER_NUMBER_OF_BROWSERS`. The default is `3`.
|
||||
|
||||
## Debugging scans using logging
|
||||
|
||||
|
|
|
@ -270,7 +270,7 @@ authorization credentials. By default, the following headers are masked:
|
|||
- `Set-Cookie` (values only).
|
||||
- `Cookie` (values only).
|
||||
|
||||
Using the [`DAST_MASK_HTTP_HEADERS` CI/CD variable](#available-variables), you can list the
|
||||
Using the [`DAST_MASK_HTTP_HEADERS` CI/CD variable](#available-cicd-variables), you can list the
|
||||
headers whose values you want masked. For details on how to mask headers, see
|
||||
[Customizing the DAST settings](#customizing-the-dast-settings).
|
||||
|
||||
|
@ -348,7 +348,7 @@ and potentially damage them. You could even take down your production environmen
|
|||
For that reason, you should use domain validation.
|
||||
|
||||
Domain validation is not required by default. It can be required by setting the
|
||||
[CI/CD variable](#available-variables) `DAST_FULL_SCAN_DOMAIN_VALIDATION_REQUIRED` to `"true"`.
|
||||
[CI/CD variable](#available-cicd-variables) `DAST_FULL_SCAN_DOMAIN_VALIDATION_REQUIRED` to `"true"`.
|
||||
|
||||
```yaml
|
||||
include:
|
||||
|
@ -661,7 +661,7 @@ is no longer supported. When overriding the template, you must use [`rules`](../
|
|||
|
||||
The DAST settings can be changed through CI/CD variables by using the
|
||||
[`variables`](../../../ci/yaml/README.md#variables) parameter in `.gitlab-ci.yml`.
|
||||
These variables are documented in [available variables](#available-variables).
|
||||
These variables are documented in [available variables](#available-cicd-variables).
|
||||
|
||||
For example:
|
||||
|
||||
|
@ -677,7 +677,7 @@ variables:
|
|||
Because the template is [evaluated before](../../../ci/yaml/README.md#include) the pipeline
|
||||
configuration, the last mention of the variable takes precedence.
|
||||
|
||||
### Available variables
|
||||
### Available CI/CD variables
|
||||
|
||||
DAST can be [configured](#customizing-the-dast-settings) using CI/CD variables.
|
||||
|
||||
|
|
|
@ -112,7 +112,7 @@ always take the latest dependency scanning artifact available.
|
|||
|
||||
### Customizing the dependency scanning settings
|
||||
|
||||
The dependency scanning settings can be changed through [CI/CD variables](#available-variables) by using the
|
||||
The dependency scanning settings can be changed through [CI/CD variables](#available-cicd-variables) by using the
|
||||
[`variables`](../../../ci/yaml/README.md#variables) parameter in `.gitlab-ci.yml`.
|
||||
For example:
|
||||
|
||||
|
@ -157,7 +157,7 @@ gemnasium-dependency_scanning:
|
|||
dependencies: ["build"]
|
||||
```
|
||||
|
||||
### Available variables
|
||||
### Available CI/CD variables
|
||||
|
||||
Dependency scanning can be [configured](#customizing-the-dependency-scanning-settings)
|
||||
using environment variables.
|
||||
|
|
|
@ -48,7 +48,7 @@ GitLab, but users can also integrate their own **custom images**.
|
|||
For an analyzer to be considered Generally Available, it is expected to minimally
|
||||
support the following features:
|
||||
|
||||
- [Customizable configuration](index.md#available-variables)
|
||||
- [Customizable configuration](index.md#available-cicd-variables)
|
||||
- [Customizable rulesets](index.md#customize-rulesets)
|
||||
- [Scan projects](index.md#supported-languages-and-frameworks)
|
||||
- [Multi-project support](index.md#multi-project-support)
|
||||
|
|
|
@ -202,7 +202,7 @@ page:
|
|||
|
||||
### Customizing the SAST settings
|
||||
|
||||
The SAST settings can be changed through [CI/CD variables](#available-variables)
|
||||
The SAST settings can be changed through [CI/CD variables](#available-cicd-variables)
|
||||
by using the
|
||||
[`variables`](../../../ci/yaml/README.md#variables) parameter in `.gitlab-ci.yml`.
|
||||
In the following example, we include the SAST template and at the same time we
|
||||
|
@ -411,7 +411,7 @@ the vendored directory. This configuration can vary per analyzer but in the case
|
|||
can use `MAVEN_REPO_PATH`. See
|
||||
[Analyzer settings](#analyzer-settings) for the complete list of available options.
|
||||
|
||||
### Available variables
|
||||
### Available CI/CD variables
|
||||
|
||||
SAST can be [configured](#customizing-the-sast-settings) using CI/CD variables.
|
||||
|
||||
|
|
|
@ -160,7 +160,7 @@ that you can review and merge to complete the configuration.
|
|||
|
||||
### Customizing settings
|
||||
|
||||
The Secret Detection scan settings can be changed through [CI/CD variables](#available-variables)
|
||||
The Secret Detection scan settings can be changed through [CI/CD variables](#available-cicd-variables)
|
||||
by using the
|
||||
[`variables`](../../../ci/yaml/README.md#variables) parameter in `.gitlab-ci.yml`.
|
||||
|
||||
|
@ -196,7 +196,7 @@ secret_detection:
|
|||
Because the template is [evaluated before](../../../ci/yaml/README.md#include)
|
||||
the pipeline configuration, the last mention of the variable takes precedence.
|
||||
|
||||
#### Available variables
|
||||
#### Available CI/CD variables
|
||||
|
||||
Secret Detection can be customized by defining available CI/CD variables:
|
||||
|
||||
|
@ -298,7 +298,7 @@ want to perform a full secret scan. Running a secret scan on the full history ca
|
|||
especially for larger repositories with lengthy Git histories. We recommend not setting this CI/CD variable
|
||||
as part of your normal job definition.
|
||||
|
||||
A new configuration variable ([`SECRET_DETECTION_HISTORIC_SCAN`](#available-variables))
|
||||
A new configuration variable ([`SECRET_DETECTION_HISTORIC_SCAN`](#available-cicd-variables))
|
||||
can be set to change the behavior of the GitLab Secret Detection scan to run on the entire Git history of a repository.
|
||||
|
||||
We have created a [short video walkthrough](https://youtu.be/wDtc_K00Y0A) showcasing how you can perform a full history secret scan.
|
||||
|
|
|
@ -121,7 +121,7 @@ always take the latest License Compliance artifact available. Behind the scenes,
|
|||
[GitLab License Compliance Docker image](https://gitlab.com/gitlab-org/security-products/analyzers/license-finder)
|
||||
is used to detect the languages/frameworks and in turn analyzes the licenses.
|
||||
|
||||
The License Compliance settings can be changed through [CI/CD variables](#available-variables) by using the
|
||||
The License Compliance settings can be changed through [CI/CD variables](#available-cicd-variables) by using the
|
||||
[`variables`](../../../ci/yaml/README.md#variables) parameter in `.gitlab-ci.yml`.
|
||||
|
||||
### When License Compliance runs
|
||||
|
@ -129,7 +129,7 @@ The License Compliance settings can be changed through [CI/CD variables](#availa
|
|||
When using the GitLab `License-Scanning.gitlab-ci.yml` template, the License Compliance job doesn't
|
||||
wait for other stages to complete.
|
||||
|
||||
### Available variables
|
||||
### Available CI/CD variables
|
||||
|
||||
License Compliance can be configured using CI/CD variables.
|
||||
|
||||
|
@ -265,11 +265,11 @@ license_scanning:
|
|||
### Custom root certificates for Python
|
||||
|
||||
You can supply a custom root certificate to complete TLS verification by using the
|
||||
`ADDITIONAL_CA_CERT_BUNDLE` [CI/CD variable](#available-variables).
|
||||
`ADDITIONAL_CA_CERT_BUNDLE` [CI/CD variable](#available-cicd-variables).
|
||||
|
||||
#### Using private Python repositories
|
||||
|
||||
If you have a private Python repository you can use the `PIP_INDEX_URL` [CI/CD variable](#available-variables)
|
||||
If you have a private Python repository you can use the `PIP_INDEX_URL` [CI/CD variable](#available-cicd-variables)
|
||||
to specify its location.
|
||||
|
||||
### Configuring npm projects
|
||||
|
@ -292,7 +292,7 @@ registry = https://npm.example.com
|
|||
#### Custom root certificates for npm
|
||||
|
||||
You can supply a custom root certificate to complete TLS verification by using the
|
||||
`ADDITIONAL_CA_CERT_BUNDLE` [CI/CD variable](#available-variables).
|
||||
`ADDITIONAL_CA_CERT_BUNDLE` [CI/CD variable](#available-cicd-variables).
|
||||
|
||||
To disable TLS verification you can provide the [`strict-ssl`](https://docs.npmjs.com/using-npm/config/#strict-ssl)
|
||||
setting.
|
||||
|
@ -323,7 +323,7 @@ npmRegistryServer: "https://npm.example.com"
|
|||
#### Custom root certificates for Yarn
|
||||
|
||||
You can supply a custom root certificate to complete TLS verification by using the
|
||||
`ADDITIONAL_CA_CERT_BUNDLE` [CI/CD variable](#available-variables).
|
||||
`ADDITIONAL_CA_CERT_BUNDLE` [CI/CD variable](#available-cicd-variables).
|
||||
|
||||
### Configuring Bower projects
|
||||
|
||||
|
@ -347,7 +347,7 @@ For example:
|
|||
#### Custom root certificates for Bower
|
||||
|
||||
You can supply a custom root certificate to complete TLS verification by using the
|
||||
`ADDITIONAL_CA_CERT_BUNDLE` [CI/CD variable](#available-variables), or by
|
||||
`ADDITIONAL_CA_CERT_BUNDLE` [CI/CD variable](#available-cicd-variables), or by
|
||||
specifying a `ca` setting in a [`.bowerrc`](https://bower.io/docs/config/#bowerrc-specification)
|
||||
file.
|
||||
|
||||
|
@ -368,7 +368,7 @@ source "https://gems.example.com"
|
|||
#### Custom root certificates for Bundler
|
||||
|
||||
You can supply a custom root certificate to complete TLS verification by using the
|
||||
`ADDITIONAL_CA_CERT_BUNDLE` [CI/CD variable](#available-variables), or by
|
||||
`ADDITIONAL_CA_CERT_BUNDLE` [CI/CD variable](#available-cicd-variables), or by
|
||||
specifying a [`BUNDLE_SSL_CA_CERT`](https://bundler.io/v2.0/man/bundle-config.1.html)
|
||||
[variable](../../../ci/variables/README.md#custom-cicd-variables)
|
||||
in the job definition.
|
||||
|
@ -392,7 +392,7 @@ my-registry = { index = "https://my-intranet:8080/git/index" }
|
|||
|
||||
To supply a custom root certificate to complete TLS verification, do one of the following:
|
||||
|
||||
- Use the `ADDITIONAL_CA_CERT_BUNDLE` [CI/CD variable](#available-variables).
|
||||
- Use the `ADDITIONAL_CA_CERT_BUNDLE` [CI/CD variable](#available-cicd-variables).
|
||||
- Specify a [`CARGO_HTTP_CAINFO`](https://doc.rust-lang.org/cargo/reference/environment-variables.html)
|
||||
[variable](../../../ci/variables/README.md#custom-cicd-variables)
|
||||
in the job definition.
|
||||
|
@ -425,7 +425,7 @@ For example:
|
|||
#### Custom root certificates for Composer
|
||||
|
||||
You can supply a custom root certificate to complete TLS verification by using the
|
||||
`ADDITIONAL_CA_CERT_BUNDLE` [CI/CD variable](#available-variables), or by
|
||||
`ADDITIONAL_CA_CERT_BUNDLE` [CI/CD variable](#available-cicd-variables), or by
|
||||
specifying a [`COMPOSER_CAFILE`](https://getcomposer.org/doc/03-cli.md#composer-cafile)
|
||||
[variable](../../../ci/variables/README.md#custom-cicd-variables)
|
||||
in the job definition.
|
||||
|
@ -499,7 +499,7 @@ You can provide custom certificates by adding a `.conan/cacert.pem` file to the
|
|||
setting [`CA_CERT_PATH`](https://docs.conan.io/en/latest/reference/env_vars.html#conan-cacert-path)
|
||||
to `.conan/cacert.pem`.
|
||||
|
||||
If you specify the `ADDITIONAL_CA_CERT_BUNDLE` [CI/CD variable](#available-variables), this
|
||||
If you specify the `ADDITIONAL_CA_CERT_BUNDLE` [CI/CD variable](#available-cicd-variables), this
|
||||
variable's X.509 certificates are installed in the Docker image's default trust store and Conan is
|
||||
configured to use this as the default `CA_CERT_PATH`.
|
||||
|
||||
|
@ -507,7 +507,7 @@ configured to use this as the default `CA_CERT_PATH`.
|
|||
|
||||
To configure [Go modules](https://github.com/golang/go/wiki/Modules)
|
||||
based projects, specify [CI/CD variables](https://golang.org/pkg/cmd/go/#hdr-Environment_variables)
|
||||
in the `license_scanning` job's [variables](#available-variables) section in `.gitlab-ci.yml`.
|
||||
in the `license_scanning` job's [variables](#available-cicd-variables) section in `.gitlab-ci.yml`.
|
||||
|
||||
If a project has [vendored](https://golang.org/pkg/cmd/go/#hdr-Vendor_Directories) its modules,
|
||||
then the combination of the `vendor` directory and `mod.sum` file are used to detect the software
|
||||
|
@ -556,7 +556,7 @@ For example:
|
|||
#### Custom root certificates for NuGet
|
||||
|
||||
You can supply a custom root certificate to complete TLS verification by using the
|
||||
`ADDITIONAL_CA_CERT_BUNDLE` [CI/CD variable](#available-variables).
|
||||
`ADDITIONAL_CA_CERT_BUNDLE` [CI/CD variable](#available-cicd-variables).
|
||||
|
||||
### Migration from `license_management` to `license_scanning`
|
||||
|
||||
|
@ -816,7 +816,7 @@ license_scanning:
|
|||
ASDF_RUBY_VERSION: '2.7.2'
|
||||
```
|
||||
|
||||
A full list of variables can be found in [CI/CD variables](#available-variables).
|
||||
A full list of variables can be found in [CI/CD variables](#available-cicd-variables).
|
||||
|
||||
To find out what tools are pre-installed in the `license_scanning` Docker image use the following command:
|
||||
|
||||
|
|
|
@ -1395,26 +1395,32 @@ while the equation for the theory of relativity is E = mc<sup>2</sup>.
|
|||
Tables are not part of the core Markdown spec, but they are part of GitLab Flavored Markdown.
|
||||
|
||||
1. The first line contains the headers, separated by "pipes" (`|`).
|
||||
1. The second line separates the headers from the cells, and must contain three or more dashes.
|
||||
1. The second line separates the headers from the cells.
|
||||
- The cells can contain only empty spaces, hyphens, and
|
||||
(optionally) colons for horizontal alignment.
|
||||
- Each cell must contain at least one hyphen, but adding more hyphens to a
|
||||
cell does not change the cell's rendering.
|
||||
- Any content other than hyphens, whitespace, or colons is not allowed
|
||||
1. The third, and any following lines, contain the cell values.
|
||||
- You **can't** have cells separated over many lines in the Markdown, they must be kept to single lines,
|
||||
but they can be very long. You can also include HTML `<br>` tags to force newlines if needed.
|
||||
- The cell sizes **don't** have to match each other. They are flexible, but must be separated
|
||||
by pipes (`|`).
|
||||
- You **can** have blank cells.
|
||||
1. Column widths are calculated dynamically based on the content of the cells.
|
||||
|
||||
Example:
|
||||
|
||||
```markdown
|
||||
| header 1 | header 2 | header 3 |
|
||||
| --- | ------ |----------|
|
||||
| --- | --- | --- |
|
||||
| cell 1 | cell 2 | cell 3 |
|
||||
| cell 4 | cell 5 is longer | cell 6 is much longer than the others, but that's ok. It eventually wraps the text when the cell is too large for the display size. |
|
||||
| cell 7 | | cell 9 |
|
||||
```
|
||||
|
||||
| header 1 | header 2 | header 3 |
|
||||
| --- | ------ |----------|
|
||||
| --- | --- | --- |
|
||||
| cell 1 | cell 2 | cell 3 |
|
||||
| cell 4 | cell 5 is longer | cell 6 is much longer than the others, but that's ok. It eventually wraps the text when the cell is too large for the display size. |
|
||||
| cell 7 | | cell 9 |
|
||||
|
@ -1423,16 +1429,16 @@ Additionally, you can choose the alignment of text in columns by adding colons (
|
|||
to the sides of the "dash" lines in the second row. This affects every cell in the column:
|
||||
|
||||
```markdown
|
||||
| Left Aligned | Centered | Right Aligned | Left Aligned | Centered | Right Aligned |
|
||||
| :--- | :---: | ---: | :----------- | :------: | ------------: |
|
||||
| Cell 1 | Cell 2 | Cell 3 | Cell 4 | Cell 5 | Cell 6 |
|
||||
| Cell 7 | Cell 8 | Cell 9 | Cell 10 | Cell 11 | Cell 12 |
|
||||
| Left Aligned | Centered | Right Aligned |
|
||||
| :--- | :---: | ---: |
|
||||
| Cell 1 | Cell 2 | Cell 3 |
|
||||
| Cell 4 | Cell 5 | Cell 6 |
|
||||
```
|
||||
|
||||
| Left Aligned | Centered | Right Aligned | Left Aligned | Centered | Right Aligned |
|
||||
| :--- | :---: | ---: | :----------- | :------: | ------------: |
|
||||
| Cell 1 | Cell 2 | Cell 3 | Cell 4 | Cell 5 | Cell 6 |
|
||||
| Cell 7 | Cell 8 | Cell 9 | Cell 10 | Cell 11 | Cell 12 |
|
||||
| Left Aligned | Centered | Right Aligned |
|
||||
| :--- | :---: | ---: |
|
||||
| Cell 1 | Cell 2 | Cell 3 |
|
||||
| Cell 4 | Cell 5 | Cell 6 |
|
||||
|
||||
[In GitLab itself](https://gitlab.com/gitlab-org/gitlab/blob/master/doc/user/markdown.md#tables),
|
||||
the headers are always left-aligned in Chrome and Firefox, and centered in Safari.
|
||||
|
@ -1442,13 +1448,13 @@ use `<br>` tags to force a cell to have multiple lines:
|
|||
|
||||
```markdown
|
||||
| Name | Details |
|
||||
|------|---------|
|
||||
| --- | --- |
|
||||
| Item1 | This is on one line |
|
||||
| Item2 | This item has:<br>- Multiple items<br>- That we want listed separately |
|
||||
```
|
||||
|
||||
| Name | Details |
|
||||
|------|---------|
|
||||
| --- | --- |
|
||||
| Item1 | This is on one line |
|
||||
| Item2 | This item has:<br>- Multiple items<br>- That we want listed separately |
|
||||
|
||||
|
@ -1457,7 +1463,7 @@ but they do not render properly on `docs.gitlab.com`:
|
|||
|
||||
```markdown
|
||||
| header 1 | header 2 |
|
||||
|----------|----------|
|
||||
| --- | --- |
|
||||
| cell 1 | cell 2 |
|
||||
| cell 3 | <ul><li> - [ ] Task one </li><li> - [ ] Task two </li></ul> |
|
||||
```
|
||||
|
|
|
@ -316,6 +316,8 @@ more than once, a `404 Bad Request` error occurs.
|
|||
|
||||
## Install a PyPI package
|
||||
|
||||
### Install from the project level
|
||||
|
||||
To install the latest version of a package, use the following command:
|
||||
|
||||
```shell
|
||||
|
@ -350,6 +352,33 @@ Installing collected packages: mypypipackage
|
|||
Successfully installed mypypipackage-0.0.1
|
||||
```
|
||||
|
||||
### Install from the group level
|
||||
|
||||
To install the latest version of a package from a group, use the following command:
|
||||
|
||||
```shell
|
||||
pip install --index-url https://<personal_access_token_name>:<personal_access_token>@gitlab.example.com/api/v4/groups/<group_id>/packages/pypi/simple --no-deps <package_name>
|
||||
```
|
||||
|
||||
In this command:
|
||||
|
||||
- `<package_name>` is the package name.
|
||||
- `<personal_access_token_name>` is a personal access token name with the `read_api` scope.
|
||||
- `<personal_access_token>` is a personal access token with the `read_api` scope.
|
||||
- `<group_id>` is the group ID.
|
||||
|
||||
In these commands, you can use `--extra-index-url` instead of `--index-url`. However, using
|
||||
`--extra-index-url` makes you vulnerable to dependency confusion attacks because it checks the PyPi
|
||||
repository for the package before it checks the custom repository. `--extra-index-url` adds the
|
||||
provided URL as an additional registry which the client checks if the package is present.
|
||||
`--index-url` tells the client to check for the package at the provided URL only.
|
||||
|
||||
If you're following the guide and want to install the `MyPyPiPackage` package, you can run:
|
||||
|
||||
```shell
|
||||
pip install mypypipackage --no-deps --index-url https://<personal_access_token_name>:<personal_access_token>@gitlab.example.com/api/v4/groups/<your_group_id>/packages/pypi/simple
|
||||
```
|
||||
|
||||
### Package names
|
||||
|
||||
GitLab looks for packages that use
|
||||
|
|
|
@ -37,6 +37,21 @@ module API
|
|||
|
||||
track_package_event('push_package', :debian)
|
||||
|
||||
file_params = {
|
||||
file: params['file'],
|
||||
file_name: params['file_name'],
|
||||
file_sha1: params['file.sha1'],
|
||||
file_md5: params['file.md5']
|
||||
}
|
||||
|
||||
package = ::Packages::Debian::FindOrCreateIncomingService.new(authorized_user_project, current_user).execute
|
||||
|
||||
package_file = ::Packages::Debian::CreatePackageFileService.new(package, file_params).execute
|
||||
|
||||
if params['file_name'].end_with? '.changes'
|
||||
::Packages::Debian::ProcessChangesWorker.perform_async(package_file.id, current_user.id) # rubocop:disable CodeReuse/Worker
|
||||
end
|
||||
|
||||
created!
|
||||
rescue ObjectStorage::RemoteStoreError => e
|
||||
Gitlab::ErrorTracking.track_exception(e, extra: { file_name: params[:file_name], project_id: authorized_user_project.id })
|
||||
|
|
|
@ -22,6 +22,14 @@ module API
|
|||
unauthorized_user_project || not_found!
|
||||
end
|
||||
|
||||
def unauthorized_user_group
|
||||
@unauthorized_user_group ||= find_group(params[:id])
|
||||
end
|
||||
|
||||
def unauthorized_user_group!
|
||||
unauthorized_user_group || not_found!
|
||||
end
|
||||
|
||||
def authorized_user_project
|
||||
@authorized_user_project ||= authorized_project_find!
|
||||
end
|
||||
|
|
|
@ -11,7 +11,11 @@ module API
|
|||
optional :include_merged_yaml, type: Boolean, desc: 'Whether or not to include merged CI config yaml in the response'
|
||||
end
|
||||
post '/lint' do
|
||||
unauthorized! if Gitlab::CurrentSettings.signup_disabled? && current_user.nil?
|
||||
if Feature.enabled?(:security_ci_lint_authorization)
|
||||
unauthorized! if (Gitlab::CurrentSettings.signup_disabled? || Gitlab::CurrentSettings.signup_limited?) && current_user.nil?
|
||||
else
|
||||
unauthorized! if Gitlab::CurrentSettings.signup_disabled? && current_user.nil?
|
||||
end
|
||||
|
||||
result = Gitlab::Ci::YamlProcessor.new(params[:content], user: current_user).execute
|
||||
|
||||
|
|
|
@ -28,6 +28,73 @@ module API
|
|||
require_packages_enabled!
|
||||
end
|
||||
|
||||
helpers do
|
||||
params :package_download do
|
||||
requires :file_identifier, type: String, desc: 'The PyPi package file identifier', file_path: true
|
||||
requires :sha256, type: String, desc: 'The PyPi package sha256 check sum'
|
||||
end
|
||||
|
||||
params :package_name do
|
||||
requires :package_name, type: String, file_path: true, desc: 'The PyPi package name'
|
||||
end
|
||||
end
|
||||
|
||||
params do
|
||||
requires :id, type: Integer, desc: 'The ID of a group'
|
||||
end
|
||||
resource :groups, requirements: API::NAMESPACE_OR_PROJECT_REQUIREMENTS do
|
||||
after_validation do
|
||||
unauthorized_user_group!
|
||||
end
|
||||
|
||||
namespace ':id/-/packages/pypi' do
|
||||
params do
|
||||
use :package_download
|
||||
end
|
||||
|
||||
route_setting :authentication, deploy_token_allowed: true, basic_auth_personal_access_token: true, job_token_allowed: :basic_auth
|
||||
get 'files/:sha256/*file_identifier' do
|
||||
group = unauthorized_user_group!
|
||||
|
||||
filename = "#{params[:file_identifier]}.#{params[:format]}"
|
||||
package = Packages::Pypi::PackageFinder.new(current_user, group, { filename: filename, sha256: params[:sha256] }).execute
|
||||
package_file = ::Packages::PackageFileFinder.new(package, filename, with_file_name_like: false).execute
|
||||
|
||||
track_package_event('pull_package', :pypi)
|
||||
|
||||
present_carrierwave_file!(package_file.file, supports_direct_download: true)
|
||||
end
|
||||
|
||||
desc 'The PyPi Simple Endpoint' do
|
||||
detail 'This feature was introduced in GitLab 12.10'
|
||||
end
|
||||
|
||||
params do
|
||||
use :package_name
|
||||
end
|
||||
|
||||
# An Api entry point but returns an HTML file instead of JSON.
|
||||
# PyPi simple API returns the package descriptor as a simple HTML file.
|
||||
route_setting :authentication, deploy_token_allowed: true, basic_auth_personal_access_token: true, job_token_allowed: :basic_auth
|
||||
get 'simple/*package_name', format: :txt do
|
||||
group = find_authorized_group!
|
||||
authorize_read_package!(group)
|
||||
|
||||
track_package_event('list_package', :pypi)
|
||||
|
||||
packages = Packages::Pypi::PackagesFinder.new(current_user, group, { package_name: params[:package_name] }).execute!
|
||||
presenter = ::Packages::Pypi::PackagePresenter.new(packages, group)
|
||||
|
||||
# Adjusts grape output format
|
||||
# to be HTML
|
||||
content_type "text/html; charset=utf-8"
|
||||
env['api.format'] = :binary
|
||||
|
||||
body presenter.body
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
params do
|
||||
requires :id, type: Integer, desc: 'The ID of a project'
|
||||
end
|
||||
|
@ -43,8 +110,7 @@ module API
|
|||
end
|
||||
|
||||
params do
|
||||
requires :file_identifier, type: String, desc: 'The PyPi package file identifier', file_path: true
|
||||
requires :sha256, type: String, desc: 'The PyPi package sha256 check sum'
|
||||
use :package_download
|
||||
end
|
||||
|
||||
route_setting :authentication, deploy_token_allowed: true, basic_auth_personal_access_token: true, job_token_allowed: :basic_auth
|
||||
|
@ -65,7 +131,7 @@ module API
|
|||
end
|
||||
|
||||
params do
|
||||
requires :package_name, type: String, file_path: true, desc: 'The PyPi package name'
|
||||
use :package_name
|
||||
end
|
||||
|
||||
# An Api entry point but returns an HTML file instead of JSON.
|
||||
|
|
|
@ -9,8 +9,8 @@ apply:
|
|||
script:
|
||||
- gitlab-managed-apps /usr/local/share/gitlab-managed-apps/helmfile.yaml
|
||||
only:
|
||||
refs:
|
||||
- master
|
||||
variables:
|
||||
- $CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH
|
||||
artifacts:
|
||||
reports:
|
||||
cluster_applications: gl-cluster-applications.json
|
||||
|
|
|
@ -1,8 +1,7 @@
|
|||
# Read more about this feature here: https://docs.gitlab.com/ee/user/application_security/api_fuzzing/
|
||||
|
||||
# Configure the scanning tool through the environment variables.
|
||||
# List of the variables: https://docs.gitlab.com/ee/user/application_security/api_fuzzing/#available-variables
|
||||
# How to set: https://docs.gitlab.com/ee/ci/yaml/#variables
|
||||
# Configure API fuzzing with CI/CD variables (https://docs.gitlab.com/ee/ci/variables/README.html).
|
||||
# List of available variables: https://docs.gitlab.com/ee/user/application_security/api_fuzzing/#available-cicd-variables
|
||||
|
||||
stages:
|
||||
- build
|
||||
|
|
|
@ -1,8 +1,7 @@
|
|||
# Read more about this feature here: https://docs.gitlab.com/ee/user/application_security/api_fuzzing/
|
||||
|
||||
# Configure the scanning tool through the environment variables.
|
||||
# List of the variables: https://docs.gitlab.com/ee/user/application_security/api_fuzzing/#available-variables
|
||||
# How to set: https://docs.gitlab.com/ee/ci/yaml/#variables
|
||||
# Configure API fuzzing with CI/CD variables (https://docs.gitlab.com/ee/ci/variables/README.html).
|
||||
# List of available variables: https://docs.gitlab.com/ee/user/application_security/api_fuzzing/#available-cicd-variables
|
||||
|
||||
variables:
|
||||
FUZZAPI_VERSION: "1"
|
||||
|
|
|
@ -10,7 +10,8 @@
|
|||
# - For auto-remediation, a readable Dockerfile in the root of the project or as defined by the
|
||||
# DOCKERFILE_PATH variable.
|
||||
#
|
||||
# For more information, see https://docs.gitlab.com/ee/user/application_security/container_scanning/#available-variables
|
||||
# Configure container scanning with CI/CD variables (https://docs.gitlab.com/ee/ci/variables/README.html).
|
||||
# List of available variables: https://docs.gitlab.com/ee/user/application_security/container_scanning/#available-variables
|
||||
|
||||
variables:
|
||||
# Setting this variable will affect all Security templates (e.g.: SAST, Dependency Scanning)
|
||||
|
|
|
@ -1,8 +1,7 @@
|
|||
# Read more about this feature https://docs.gitlab.com/ee/user/application_security/coverage_fuzzing
|
||||
|
||||
# Configure the fuzzing tool through the environment variables.
|
||||
# List of the variables: https://docs.gitlab.com/ee/user/application_security/coverage_fuzzing/#available-cicd-variables
|
||||
# How to set: https://docs.gitlab.com/ee/ci/yaml/#variables
|
||||
# Configure coverage fuzzing with CI/CD variables (https://docs.gitlab.com/ee/ci/variables/README.html).
|
||||
# List of available variables: https://docs.gitlab.com/ee/user/application_security/coverage_fuzzing/#available-cicd-variables
|
||||
|
||||
variables:
|
||||
# Which branch we want to run full fledged long running fuzzing jobs.
|
||||
|
|
|
@ -13,9 +13,8 @@
|
|||
|
||||
# Read more about this feature here: https://docs.gitlab.com/ee/user/application_security/dast_api/index.html
|
||||
|
||||
# Configure the scanning tool with CI/CD variables (https://docs.gitlab.com/ee/ci/variables/README.html)
|
||||
# List of variables available to configure the DAST API scanning tool:
|
||||
# https://docs.gitlab.com/ee/user/application_security/dast_api/index.html#available-cicd-variables
|
||||
# Configure DAST API scanning with CI/CD variables (https://docs.gitlab.com/ee/ci/variables/README.html).
|
||||
# List of available variables: https://docs.gitlab.com/ee/user/application_security/dast_api/index.html#available-cicd-variables
|
||||
|
||||
variables:
|
||||
# Setting this variable affects all Security templates
|
||||
|
|
|
@ -1,8 +1,7 @@
|
|||
# Read more about this feature here: https://docs.gitlab.com/ee/user/application_security/dast/
|
||||
|
||||
# Configure the scanning tool through the environment variables.
|
||||
# List of the variables: https://docs.gitlab.com/ee/user/application_security/dast/#available-variables
|
||||
# How to set: https://docs.gitlab.com/ee/ci/yaml/#variables
|
||||
# Configure DAST with CI/CD variables (https://docs.gitlab.com/ee/ci/variables/README.html).
|
||||
# List of available variables: https://docs.gitlab.com/ee/user/application_security/dast/#available-variables
|
||||
|
||||
stages:
|
||||
- build
|
||||
|
|
|
@ -13,9 +13,8 @@
|
|||
|
||||
# Read more about this feature here: https://docs.gitlab.com/ee/user/application_security/dast/
|
||||
|
||||
# Configure the scanning tool through the environment variables.
|
||||
# List of the variables: https://docs.gitlab.com/ee/user/application_security/dast/#available-variables
|
||||
# How to set: https://docs.gitlab.com/ee/ci/yaml/#variables
|
||||
# Configure DAST with CI/CD variables (https://docs.gitlab.com/ee/ci/variables/README.html).
|
||||
# List of available variables: https://docs.gitlab.com/ee/user/application_security/dast/#available-variables
|
||||
|
||||
variables:
|
||||
DAST_VERSION: 1
|
||||
|
|
|
@ -1,8 +1,7 @@
|
|||
# Read more about this feature here: https://docs.gitlab.com/ee/user/application_security/dependency_scanning/
|
||||
#
|
||||
# Configure the scanning tool through the environment variables.
|
||||
# List of the variables: https://gitlab.com/gitlab-org/security-products/dependency-scanning#settings
|
||||
# How to set: https://docs.gitlab.com/ee/ci/yaml/#variables
|
||||
# Configure dependency scanning with CI/CD variables (https://docs.gitlab.com/ee/ci/variables/README.html).
|
||||
# List of available variables: https://docs.gitlab.com/ee/user/application_security/dependency_scanning/index.html#available-variables
|
||||
|
||||
variables:
|
||||
# Setting this variable will affect all Security templates
|
||||
|
|
|
@ -1,8 +1,7 @@
|
|||
# Read more about this feature here: https://docs.gitlab.com/ee/user/compliance/license_compliance/index.html
|
||||
#
|
||||
# Configure the scanning tool through the environment variables.
|
||||
# List of the variables: https://gitlab.com/gitlab-org/security-products/analyzers/license-finder#settings
|
||||
# How to set: https://docs.gitlab.com/ee/ci/yaml/#variables
|
||||
# Configure license scanning with CI/CD variables (https://docs.gitlab.com/ee/ci/variables/README.html).
|
||||
# List of available variables: https://docs.gitlab.com/ee/user/compliance/license_compliance/#available-variables
|
||||
|
||||
variables:
|
||||
# Setting this variable will affect all Security templates
|
||||
|
|
|
@ -1,8 +1,7 @@
|
|||
# Read more about this feature here: https://docs.gitlab.com/ee/user/application_security/sast/
|
||||
#
|
||||
# Configure the scanning tool through the environment variables.
|
||||
# List of the variables: https://gitlab.com/gitlab-org/security-products/sast#settings
|
||||
# How to set: https://docs.gitlab.com/ee/ci/yaml/#variables
|
||||
# Configure SAST with CI/CD variables (https://docs.gitlab.com/ee/ci/variables/README.html).
|
||||
# List of available variables: https://docs.gitlab.com/ee/user/application_security/sast/index.html#available-variables
|
||||
|
||||
variables:
|
||||
# Setting this variable will affect all Security templates
|
||||
|
|
|
@ -1,8 +1,7 @@
|
|||
# Read more about this feature here: https://docs.gitlab.com/ee/user/application_security/secret_detection
|
||||
#
|
||||
# Configure the scanning tool through the environment variables.
|
||||
# List of the variables: https://docs.gitlab.com/ee/user/application_security/secret_detection/#available-variables
|
||||
# How to set: https://docs.gitlab.com/ee/ci/yaml/#variables
|
||||
# Configure secret detection with CI/CD variables (https://docs.gitlab.com/ee/ci/variables/README.html).
|
||||
# List of available variables: https://docs.gitlab.com/ee/user/application_security/secret_detection/#available-variables
|
||||
|
||||
variables:
|
||||
SECURE_ANALYZERS_PREFIX: "registry.gitlab.com/gitlab-org/security-products/analyzers"
|
||||
|
|
|
@ -7,6 +7,10 @@ module Gitlab
|
|||
!signup_enabled?
|
||||
end
|
||||
|
||||
def signup_limited?
|
||||
domain_allowlist.present? || email_restrictions_enabled? || require_admin_approval_after_user_signup?
|
||||
end
|
||||
|
||||
def current_application_settings
|
||||
Gitlab::SafeRequestStore.fetch(:current_application_settings) { ensure_application_settings! }
|
||||
end
|
||||
|
|
|
@ -65,10 +65,9 @@ module Gitlab
|
|||
def project_from_key
|
||||
return unless match = service_desk_key.match(PROJECT_KEY_PATTERN)
|
||||
|
||||
project = Project.find_by_service_desk_project_key(match[:key])
|
||||
return unless valid_project_key?(project, match[:slug])
|
||||
|
||||
project
|
||||
Project.with_service_desk_key(match[:key]).find do |project|
|
||||
valid_project_key?(project, match[:slug])
|
||||
end
|
||||
end
|
||||
|
||||
def valid_project_key?(project, slug)
|
||||
|
|
|
@ -11,12 +11,16 @@ module Gitlab
|
|||
end
|
||||
|
||||
def cache_key(key)
|
||||
"#{cache_type}:#{key}:set"
|
||||
"#{cache_namespace}:#{key}:set"
|
||||
end
|
||||
|
||||
def new_cache_key(key)
|
||||
super(key)
|
||||
end
|
||||
|
||||
def clear_cache!(key)
|
||||
with do |redis|
|
||||
keys = read(key).map { |value| "#{cache_type}:#{value}" }
|
||||
keys = read(key).map { |value| "#{cache_namespace}:#{value}" }
|
||||
keys << cache_key(key)
|
||||
|
||||
redis.pipelined do
|
||||
|
@ -24,11 +28,5 @@ module Gitlab
|
|||
end
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def cache_type
|
||||
Gitlab::Redis::Cache::CACHE_NAMESPACE
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -17,6 +17,11 @@ module Gitlab
|
|||
"#{type}:#{namespace}:set"
|
||||
end
|
||||
|
||||
# NOTE Remove as part of #331319
|
||||
def new_cache_key(type)
|
||||
super("#{type}:#{namespace}")
|
||||
end
|
||||
|
||||
def write(key, value)
|
||||
full_key = cache_key(key)
|
||||
|
||||
|
|
|
@ -14,15 +14,21 @@ module Gitlab
|
|||
"#{key}:set"
|
||||
end
|
||||
|
||||
# NOTE Remove as part of https://gitlab.com/gitlab-org/gitlab/-/issues/331319
|
||||
def new_cache_key(key)
|
||||
"#{cache_namespace}:#{key}:set"
|
||||
end
|
||||
|
||||
# Returns the number of keys deleted by Redis
|
||||
def expire(*keys)
|
||||
return 0 if keys.empty?
|
||||
|
||||
with do |redis|
|
||||
keys = keys.map { |key| cache_key(key) }
|
||||
keys_to_expire = keys.map { |key| cache_key(key) }
|
||||
keys_to_expire += keys.map { |key| new_cache_key(key) } # NOTE Remove as part of #331319
|
||||
|
||||
Gitlab::Instrumentation::RedisClusterValidator.allow_cross_slot_commands do
|
||||
redis.unlink(*keys)
|
||||
redis.unlink(*keys_to_expire)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
@ -73,5 +79,9 @@ module Gitlab
|
|||
def with(&blk)
|
||||
Gitlab::Redis::Cache.with(&blk) # rubocop:disable CodeReuse/ActiveRecord
|
||||
end
|
||||
|
||||
def cache_namespace
|
||||
Gitlab::Redis::Cache::CACHE_NAMESPACE
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -14726,6 +14726,9 @@ msgstr ""
|
|||
msgid "Geo|Allowed Geo IP should contain valid IP addresses"
|
||||
msgstr ""
|
||||
|
||||
msgid "Geo|Checksummed"
|
||||
msgstr ""
|
||||
|
||||
msgid "Geo|Connection timeout can't be blank"
|
||||
msgstr ""
|
||||
|
||||
|
@ -14828,6 +14831,9 @@ msgstr ""
|
|||
msgid "Geo|Next sync scheduled at"
|
||||
msgstr ""
|
||||
|
||||
msgid "Geo|No available replication slots"
|
||||
msgstr ""
|
||||
|
||||
msgid "Geo|Node name can't be blank"
|
||||
msgstr ""
|
||||
|
||||
|
@ -14840,10 +14846,13 @@ msgstr ""
|
|||
msgid "Geo|Not synced yet"
|
||||
msgstr ""
|
||||
|
||||
msgid "Geo|Nothing to checksum"
|
||||
msgstr ""
|
||||
|
||||
msgid "Geo|Nothing to synchronize"
|
||||
msgstr ""
|
||||
|
||||
msgid "Geo|Number of %{title}"
|
||||
msgid "Geo|Nothing to verify"
|
||||
msgstr ""
|
||||
|
||||
msgid "Geo|Offline"
|
||||
|
@ -15035,6 +15044,9 @@ msgstr ""
|
|||
msgid "Geo|Verification status"
|
||||
msgstr ""
|
||||
|
||||
msgid "Geo|Verified"
|
||||
msgstr ""
|
||||
|
||||
msgid "Geo|Waiting for scheduler"
|
||||
msgstr ""
|
||||
|
||||
|
@ -34915,6 +34927,9 @@ msgstr ""
|
|||
msgid "Until"
|
||||
msgstr ""
|
||||
|
||||
msgid "Unused"
|
||||
msgstr ""
|
||||
|
||||
msgid "Unused, previous indices: %{index_names} will be deleted after %{time} automatically."
|
||||
msgstr ""
|
||||
|
||||
|
@ -35398,6 +35413,9 @@ msgstr ""
|
|||
msgid "Use your smart card to authenticate with the LDAP server."
|
||||
msgstr ""
|
||||
|
||||
msgid "Used"
|
||||
msgstr ""
|
||||
|
||||
msgid "Used by members to sign in to your group in GitLab"
|
||||
msgstr ""
|
||||
|
||||
|
|
|
@ -297,10 +297,43 @@ module Trigger
|
|||
end
|
||||
|
||||
class DatabaseTesting < Base
|
||||
IDENTIFIABLE_NOTE_TAG = 'gitlab-org/database-team/gitlab-com-database-testing:identifiable-note'
|
||||
|
||||
def self.access_token
|
||||
ENV['GITLABCOM_DATABASE_TESTING_ACCESS_TOKEN']
|
||||
end
|
||||
|
||||
def invoke!(post_comment: false, downstream_job_name: nil)
|
||||
pipeline = super
|
||||
gitlab = gitlab_client(:upstream)
|
||||
project_path = base_variables['TOP_UPSTREAM_SOURCE_PROJECT']
|
||||
merge_request_id = base_variables['TOP_UPSTREAM_MERGE_REQUEST_IID']
|
||||
comment = "<!-- #{IDENTIFIABLE_NOTE_TAG} --> \nStarted database testing [pipeline](https://ops.gitlab.net/#{downstream_project_path}/-/pipelines/#{pipeline.id}) " \
|
||||
"(limited access). This comment will be updated once the pipeline has finished running."
|
||||
|
||||
# Look for a note to update
|
||||
db_testing_notes = gitlab.merge_request_notes(project_path, merge_request_id).auto_paginate.select do |note|
|
||||
note.body.include?(IDENTIFIABLE_NOTE_TAG)
|
||||
end
|
||||
|
||||
note = db_testing_notes.max_by { |note| Time.parse(note.created_at) }
|
||||
|
||||
if note && note.type != 'DiscussionNote'
|
||||
# The latest note has not led to a discussion. Update it.
|
||||
gitlab.edit_merge_request_note(project_path, merge_request_id, note.id, comment)
|
||||
|
||||
puts "Updated comment:\n"
|
||||
else
|
||||
# This is the first note or the latest note has been discussed on the MR.
|
||||
# Don't update, create new note instead.
|
||||
note = gitlab.create_merge_request_note(project_path, merge_request_id, comment)
|
||||
|
||||
puts "Posted comment to:\n"
|
||||
end
|
||||
|
||||
puts "https://gitlab.com/#{project_path}/-/merge_requests/#{merge_request_id}#note_#{note.id}"
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def gitlab_client(type)
|
||||
|
@ -356,6 +389,8 @@ module Trigger
|
|||
INTERVAL = 60 # seconds
|
||||
MAX_DURATION = 3600 * 3 # 3 hours
|
||||
|
||||
attr_reader :id
|
||||
|
||||
def self.unscoped_class_name
|
||||
name.split('::').last
|
||||
end
|
||||
|
@ -405,7 +440,7 @@ module Trigger
|
|||
|
||||
private
|
||||
|
||||
attr_reader :project, :id, :gitlab_client, :start_time
|
||||
attr_reader :project, :gitlab_client, :start_time
|
||||
end
|
||||
|
||||
Job = Class.new(Pipeline)
|
||||
|
|
|
@ -375,6 +375,23 @@ RSpec.describe ProjectsController do
|
|||
end
|
||||
end
|
||||
|
||||
context 'when project is moved and git format is requested' do
|
||||
let(:old_path) { project.path + 'old' }
|
||||
|
||||
before do
|
||||
project.redirect_routes.create!(path: "#{project.namespace.full_path}/#{old_path}")
|
||||
|
||||
project.add_developer(user)
|
||||
sign_in(user)
|
||||
end
|
||||
|
||||
it 'redirects to new project path' do
|
||||
get :show, params: { namespace_id: project.namespace, id: old_path }, format: :git
|
||||
|
||||
expect(response).to redirect_to(project_path(project, format: :git))
|
||||
end
|
||||
end
|
||||
|
||||
context 'when the project is forked and has a repository', :request_store do
|
||||
let(:public_project) { create(:project, :public, :repository) }
|
||||
let(:other_user) { create(:user) }
|
||||
|
|
|
@ -31,15 +31,7 @@ RSpec.describe Packages::Pypi::PackageFinder do
|
|||
context 'within a group' do
|
||||
let(:scope) { group }
|
||||
|
||||
it { expect { subject }.to raise_error(ActiveRecord::RecordNotFound) }
|
||||
|
||||
context 'user with access' do
|
||||
before do
|
||||
project.add_developer(user)
|
||||
end
|
||||
|
||||
it { is_expected.to eq(package2) }
|
||||
end
|
||||
it { is_expected.to eq(package2) }
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -1,280 +0,0 @@
|
|||
import '~/commons';
|
||||
import MockAdapter from 'axios-mock-adapter';
|
||||
import Vue from 'vue';
|
||||
import mountComponent from 'helpers/vue_mount_component_helper';
|
||||
import Api from '~/api';
|
||||
import pipelinesTable from '~/commit/pipelines/pipelines_table.vue';
|
||||
import axios from '~/lib/utils/axios_utils';
|
||||
|
||||
describe('Pipelines table in Commits and Merge requests', () => {
|
||||
const jsonFixtureName = 'pipelines/pipelines.json';
|
||||
let pipeline;
|
||||
let PipelinesTable;
|
||||
let mock;
|
||||
let vm;
|
||||
const props = {
|
||||
endpoint: 'endpoint.json',
|
||||
emptyStateSvgPath: 'foo',
|
||||
errorStateSvgPath: 'foo',
|
||||
};
|
||||
|
||||
const findRunPipelineBtn = () => vm.$el.querySelector('[data-testid="run_pipeline_button"]');
|
||||
const findRunPipelineBtnMobile = () =>
|
||||
vm.$el.querySelector('[data-testid="run_pipeline_button_mobile"]');
|
||||
|
||||
beforeEach(() => {
|
||||
mock = new MockAdapter(axios);
|
||||
|
||||
const { pipelines } = getJSONFixture(jsonFixtureName);
|
||||
|
||||
PipelinesTable = Vue.extend(pipelinesTable);
|
||||
pipeline = pipelines.find((p) => p.user !== null && p.commit !== null);
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
vm.$destroy();
|
||||
mock.restore();
|
||||
});
|
||||
|
||||
describe('successful request', () => {
|
||||
describe('without pipelines', () => {
|
||||
beforeEach(() => {
|
||||
mock.onGet('endpoint.json').reply(200, []);
|
||||
|
||||
vm = mountComponent(PipelinesTable, props);
|
||||
});
|
||||
|
||||
it('should render the empty state', (done) => {
|
||||
setImmediate(() => {
|
||||
expect(vm.$el.querySelector('.empty-state')).toBeDefined();
|
||||
expect(vm.$el.querySelector('.realtime-loading')).toBe(null);
|
||||
expect(vm.$el.querySelector('.js-pipelines-error-state')).toBe(null);
|
||||
done();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('with pipelines', () => {
|
||||
beforeEach(() => {
|
||||
mock.onGet('endpoint.json').reply(200, [pipeline]);
|
||||
vm = mountComponent(PipelinesTable, props);
|
||||
});
|
||||
|
||||
it('should render a table with the received pipelines', (done) => {
|
||||
setImmediate(() => {
|
||||
expect(vm.$el.querySelectorAll('.ci-table .commit').length).toEqual(1);
|
||||
expect(vm.$el.querySelector('.realtime-loading')).toBe(null);
|
||||
expect(vm.$el.querySelector('.empty-state')).toBe(null);
|
||||
expect(vm.$el.querySelector('.js-pipelines-error-state')).toBe(null);
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
describe('with pagination', () => {
|
||||
it('should make an API request when using pagination', (done) => {
|
||||
setImmediate(() => {
|
||||
jest.spyOn(vm, 'updateContent').mockImplementation(() => {});
|
||||
|
||||
vm.store.state.pageInfo = {
|
||||
page: 1,
|
||||
total: 10,
|
||||
perPage: 2,
|
||||
nextPage: 2,
|
||||
totalPages: 5,
|
||||
};
|
||||
|
||||
vm.$nextTick(() => {
|
||||
vm.$el.querySelector('.next-page-item').click();
|
||||
|
||||
expect(vm.updateContent).toHaveBeenCalledWith({ page: '2' });
|
||||
done();
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('pipeline badge counts', () => {
|
||||
beforeEach(() => {
|
||||
mock.onGet('endpoint.json').reply(200, [pipeline]);
|
||||
});
|
||||
|
||||
it('should receive update-pipelines-count event', (done) => {
|
||||
const element = document.createElement('div');
|
||||
document.body.appendChild(element);
|
||||
|
||||
element.addEventListener('update-pipelines-count', (event) => {
|
||||
expect(event.detail.pipelines).toEqual([pipeline]);
|
||||
done();
|
||||
});
|
||||
|
||||
vm = mountComponent(PipelinesTable, props);
|
||||
|
||||
element.appendChild(vm.$el);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('run pipeline button', () => {
|
||||
let pipelineCopy;
|
||||
|
||||
beforeEach(() => {
|
||||
pipelineCopy = { ...pipeline };
|
||||
});
|
||||
|
||||
describe('when latest pipeline has detached flag', () => {
|
||||
it('renders the run pipeline button', (done) => {
|
||||
pipelineCopy.flags.detached_merge_request_pipeline = true;
|
||||
pipelineCopy.flags.merge_request_pipeline = true;
|
||||
|
||||
mock.onGet('endpoint.json').reply(200, [pipelineCopy]);
|
||||
|
||||
vm = mountComponent(PipelinesTable, { ...props });
|
||||
|
||||
setImmediate(() => {
|
||||
expect(findRunPipelineBtn()).not.toBeNull();
|
||||
expect(findRunPipelineBtnMobile()).not.toBeNull();
|
||||
done();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('when latest pipeline does not have detached flag', () => {
|
||||
it('does not render the run pipeline button', (done) => {
|
||||
pipelineCopy.flags.detached_merge_request_pipeline = false;
|
||||
pipelineCopy.flags.merge_request_pipeline = false;
|
||||
|
||||
mock.onGet('endpoint.json').reply(200, [pipelineCopy]);
|
||||
|
||||
vm = mountComponent(PipelinesTable, { ...props });
|
||||
|
||||
setImmediate(() => {
|
||||
expect(findRunPipelineBtn()).toBeNull();
|
||||
expect(findRunPipelineBtnMobile()).toBeNull();
|
||||
done();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('on click', () => {
|
||||
const findModal = () =>
|
||||
document.querySelector('#create-pipeline-for-fork-merge-request-modal');
|
||||
|
||||
beforeEach((done) => {
|
||||
pipelineCopy.flags.detached_merge_request_pipeline = true;
|
||||
|
||||
mock.onGet('endpoint.json').reply(200, [pipelineCopy]);
|
||||
|
||||
vm = mountComponent(PipelinesTable, {
|
||||
...props,
|
||||
canRunPipeline: true,
|
||||
projectId: '5',
|
||||
mergeRequestId: 3,
|
||||
});
|
||||
|
||||
jest.spyOn(Api, 'postMergeRequestPipeline').mockReturnValue(Promise.resolve());
|
||||
|
||||
setImmediate(() => {
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('on desktop, shows a loading button', (done) => {
|
||||
findRunPipelineBtn().click();
|
||||
|
||||
vm.$nextTick(() => {
|
||||
expect(findModal()).toBeNull();
|
||||
|
||||
expect(findRunPipelineBtn().disabled).toBe(true);
|
||||
expect(findRunPipelineBtn().querySelector('.gl-spinner')).not.toBeNull();
|
||||
|
||||
setImmediate(() => {
|
||||
expect(findRunPipelineBtn().disabled).toBe(false);
|
||||
expect(findRunPipelineBtn().querySelector('.gl-spinner')).toBeNull();
|
||||
|
||||
done();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it('on mobile, shows a loading button', (done) => {
|
||||
findRunPipelineBtnMobile().click();
|
||||
|
||||
vm.$nextTick(() => {
|
||||
expect(findModal()).toBeNull();
|
||||
|
||||
expect(findModal()).toBeNull();
|
||||
expect(findRunPipelineBtn().querySelector('.gl-spinner')).not.toBeNull();
|
||||
|
||||
setImmediate(() => {
|
||||
expect(findRunPipelineBtn().disabled).toBe(false);
|
||||
expect(findRunPipelineBtn().querySelector('.gl-spinner')).toBeNull();
|
||||
|
||||
done();
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('on click for fork merge request', () => {
|
||||
const findModal = () =>
|
||||
document.querySelector('#create-pipeline-for-fork-merge-request-modal');
|
||||
|
||||
beforeEach((done) => {
|
||||
pipelineCopy.flags.detached_merge_request_pipeline = true;
|
||||
|
||||
mock.onGet('endpoint.json').reply(200, [pipelineCopy]);
|
||||
|
||||
vm = mountComponent(PipelinesTable, {
|
||||
...props,
|
||||
projectId: '5',
|
||||
mergeRequestId: 3,
|
||||
canCreatePipelineInTargetProject: true,
|
||||
sourceProjectFullPath: 'test/parent-project',
|
||||
targetProjectFullPath: 'test/fork-project',
|
||||
});
|
||||
|
||||
jest.spyOn(Api, 'postMergeRequestPipeline').mockReturnValue(Promise.resolve());
|
||||
|
||||
setImmediate(() => {
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('on desktop, shows a security warning modal', (done) => {
|
||||
findRunPipelineBtn().click();
|
||||
|
||||
vm.$nextTick(() => {
|
||||
expect(findModal()).not.toBeNull();
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('on mobile, shows a security warning modal', (done) => {
|
||||
findRunPipelineBtnMobile().click();
|
||||
|
||||
vm.$nextTick(() => {
|
||||
expect(findModal()).not.toBeNull();
|
||||
done();
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('unsuccessfull request', () => {
|
||||
beforeEach(() => {
|
||||
mock.onGet('endpoint.json').reply(500, []);
|
||||
|
||||
vm = mountComponent(PipelinesTable, props);
|
||||
});
|
||||
|
||||
it('should render error state', (done) => {
|
||||
setImmediate(() => {
|
||||
expect(vm.$el.querySelector('.js-pipelines-error-state')).toBeDefined();
|
||||
expect(vm.$el.querySelector('.realtime-loading')).toBe(null);
|
||||
expect(vm.$el.querySelector('.ci-table')).toBe(null);
|
||||
done();
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
|
@ -0,0 +1,253 @@
|
|||
import { GlEmptyState, GlLoadingIcon, GlModal, GlTable } from '@gitlab/ui';
|
||||
import { mount } from '@vue/test-utils';
|
||||
import MockAdapter from 'axios-mock-adapter';
|
||||
import { extendedWrapper } from 'helpers/vue_test_utils_helper';
|
||||
import waitForPromises from 'helpers/wait_for_promises';
|
||||
import Api from '~/api';
|
||||
import PipelinesTable from '~/commit/pipelines/pipelines_table.vue';
|
||||
import axios from '~/lib/utils/axios_utils';
|
||||
|
||||
describe('Pipelines table in Commits and Merge requests', () => {
|
||||
const jsonFixtureName = 'pipelines/pipelines.json';
|
||||
let wrapper;
|
||||
let pipeline;
|
||||
let mock;
|
||||
|
||||
const findRunPipelineBtn = () => wrapper.findByTestId('run_pipeline_button');
|
||||
const findRunPipelineBtnMobile = () => wrapper.findByTestId('run_pipeline_button_mobile');
|
||||
const findLoadingState = () => wrapper.findComponent(GlLoadingIcon);
|
||||
const findEmptyState = () => wrapper.findComponent(GlEmptyState);
|
||||
const findTable = () => wrapper.findComponent(GlTable);
|
||||
const findTableRows = () => wrapper.findAllByTestId('pipeline-table-row');
|
||||
const findModal = () => wrapper.findComponent(GlModal);
|
||||
|
||||
const createComponent = (props = {}) => {
|
||||
wrapper = extendedWrapper(
|
||||
mount(PipelinesTable, {
|
||||
propsData: {
|
||||
endpoint: 'endpoint.json',
|
||||
emptyStateSvgPath: 'foo',
|
||||
errorStateSvgPath: 'foo',
|
||||
...props,
|
||||
},
|
||||
}),
|
||||
);
|
||||
};
|
||||
|
||||
beforeEach(() => {
|
||||
mock = new MockAdapter(axios);
|
||||
|
||||
const { pipelines } = getJSONFixture(jsonFixtureName);
|
||||
|
||||
pipeline = pipelines.find((p) => p.user !== null && p.commit !== null);
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
wrapper.destroy();
|
||||
mock.restore();
|
||||
});
|
||||
|
||||
describe('successful request', () => {
|
||||
describe('without pipelines', () => {
|
||||
beforeEach(async () => {
|
||||
mock.onGet('endpoint.json').reply(200, []);
|
||||
|
||||
createComponent();
|
||||
|
||||
await waitForPromises();
|
||||
});
|
||||
|
||||
it('should render the empty state', () => {
|
||||
expect(findTableRows()).toHaveLength(0);
|
||||
expect(findLoadingState().exists()).toBe(false);
|
||||
expect(findEmptyState().exists()).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
describe('with pipelines', () => {
|
||||
beforeEach(async () => {
|
||||
mock.onGet('endpoint.json').reply(200, [pipeline]);
|
||||
|
||||
createComponent();
|
||||
|
||||
await waitForPromises();
|
||||
});
|
||||
|
||||
it('should render a table with the received pipelines', () => {
|
||||
expect(findTable().exists()).toBe(true);
|
||||
expect(findTableRows()).toHaveLength(1);
|
||||
expect(findLoadingState().exists()).toBe(false);
|
||||
expect(findEmptyState().exists()).toBe(false);
|
||||
});
|
||||
|
||||
describe('with pagination', () => {
|
||||
it('should make an API request when using pagination', async () => {
|
||||
jest.spyOn(wrapper.vm, 'updateContent').mockImplementation(() => {});
|
||||
|
||||
await wrapper.setData({
|
||||
store: {
|
||||
state: {
|
||||
pageInfo: {
|
||||
page: 1,
|
||||
total: 10,
|
||||
perPage: 2,
|
||||
nextPage: 2,
|
||||
totalPages: 5,
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
wrapper.find('.next-page-item').trigger('click');
|
||||
|
||||
expect(wrapper.vm.updateContent).toHaveBeenCalledWith({ page: '2' });
|
||||
});
|
||||
});
|
||||
|
||||
describe('pipeline badge counts', () => {
|
||||
it('should receive update-pipelines-count event', (done) => {
|
||||
const element = document.createElement('div');
|
||||
document.body.appendChild(element);
|
||||
|
||||
element.addEventListener('update-pipelines-count', (event) => {
|
||||
expect(event.detail.pipelines).toEqual([pipeline]);
|
||||
done();
|
||||
});
|
||||
|
||||
createComponent();
|
||||
|
||||
element.appendChild(wrapper.vm.$el);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('run pipeline button', () => {
|
||||
let pipelineCopy;
|
||||
|
||||
beforeEach(() => {
|
||||
pipelineCopy = { ...pipeline };
|
||||
});
|
||||
|
||||
describe('when latest pipeline has detached flag', () => {
|
||||
it('renders the run pipeline button', async () => {
|
||||
pipelineCopy.flags.detached_merge_request_pipeline = true;
|
||||
pipelineCopy.flags.merge_request_pipeline = true;
|
||||
|
||||
mock.onGet('endpoint.json').reply(200, [pipelineCopy]);
|
||||
|
||||
createComponent();
|
||||
|
||||
await waitForPromises();
|
||||
|
||||
expect(findRunPipelineBtn().exists()).toBe(true);
|
||||
expect(findRunPipelineBtnMobile().exists()).toBe(true);
|
||||
});
|
||||
});
|
||||
|
||||
describe('when latest pipeline does not have detached flag', () => {
|
||||
it('does not render the run pipeline button', async () => {
|
||||
pipelineCopy.flags.detached_merge_request_pipeline = false;
|
||||
pipelineCopy.flags.merge_request_pipeline = false;
|
||||
|
||||
mock.onGet('endpoint.json').reply(200, [pipelineCopy]);
|
||||
|
||||
createComponent();
|
||||
|
||||
await waitForPromises();
|
||||
|
||||
expect(findRunPipelineBtn().exists()).toBe(false);
|
||||
expect(findRunPipelineBtnMobile().exists()).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
describe('on click', () => {
|
||||
beforeEach(async () => {
|
||||
pipelineCopy.flags.detached_merge_request_pipeline = true;
|
||||
|
||||
mock.onGet('endpoint.json').reply(200, [pipelineCopy]);
|
||||
|
||||
createComponent({
|
||||
canRunPipeline: true,
|
||||
projectId: '5',
|
||||
mergeRequestId: 3,
|
||||
});
|
||||
|
||||
jest.spyOn(Api, 'postMergeRequestPipeline').mockReturnValue(Promise.resolve());
|
||||
|
||||
await waitForPromises();
|
||||
});
|
||||
|
||||
it('on desktop, shows a loading button', async () => {
|
||||
await findRunPipelineBtn().trigger('click');
|
||||
|
||||
expect(findRunPipelineBtn().props('loading')).toBe(true);
|
||||
|
||||
await waitForPromises();
|
||||
|
||||
expect(findRunPipelineBtn().props('loading')).toBe(false);
|
||||
});
|
||||
|
||||
it('on mobile, shows a loading button', async () => {
|
||||
await findRunPipelineBtnMobile().trigger('click');
|
||||
|
||||
expect(findRunPipelineBtn().props('loading')).toBe(true);
|
||||
|
||||
await waitForPromises();
|
||||
|
||||
expect(findRunPipelineBtn().props('disabled')).toBe(false);
|
||||
expect(findRunPipelineBtn().props('loading')).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
describe('on click for fork merge request', () => {
|
||||
beforeEach(async () => {
|
||||
pipelineCopy.flags.detached_merge_request_pipeline = true;
|
||||
|
||||
mock.onGet('endpoint.json').reply(200, [pipelineCopy]);
|
||||
|
||||
createComponent({
|
||||
projectId: '5',
|
||||
mergeRequestId: 3,
|
||||
canCreatePipelineInTargetProject: true,
|
||||
sourceProjectFullPath: 'test/parent-project',
|
||||
targetProjectFullPath: 'test/fork-project',
|
||||
});
|
||||
|
||||
jest.spyOn(Api, 'postMergeRequestPipeline').mockReturnValue(Promise.resolve());
|
||||
|
||||
await waitForPromises();
|
||||
});
|
||||
|
||||
it('on desktop, shows a security warning modal', async () => {
|
||||
await findRunPipelineBtn().trigger('click');
|
||||
|
||||
await wrapper.vm.$nextTick();
|
||||
|
||||
expect(findModal()).not.toBeNull();
|
||||
});
|
||||
|
||||
it('on mobile, shows a security warning modal', async () => {
|
||||
await findRunPipelineBtnMobile().trigger('click');
|
||||
|
||||
expect(findModal()).not.toBeNull();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('unsuccessfull request', () => {
|
||||
beforeEach(async () => {
|
||||
mock.onGet('endpoint.json').reply(500, []);
|
||||
|
||||
createComponent();
|
||||
|
||||
await waitForPromises();
|
||||
});
|
||||
|
||||
it('should render error state', () => {
|
||||
expect(findEmptyState().text()).toBe(
|
||||
'There was an error fetching the pipelines. Try again in a few moments or contact your support team.',
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
|
@ -357,27 +357,46 @@ describe('Flash', () => {
|
|||
});
|
||||
|
||||
describe('removeFlashClickListener', () => {
|
||||
beforeEach(() => {
|
||||
document.body.innerHTML += `
|
||||
<div class="flash-container">
|
||||
<div class="flash">
|
||||
<div class="close-icon js-close-icon"></div>
|
||||
let el;
|
||||
|
||||
describe('with close icon', () => {
|
||||
beforeEach(() => {
|
||||
el = document.createElement('div');
|
||||
el.innerHTML = `
|
||||
<div class="flash-container">
|
||||
<div class="flash">
|
||||
<div class="close-icon js-close-icon"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
`;
|
||||
});
|
||||
|
||||
it('removes global flash on click', (done) => {
|
||||
removeFlashClickListener(el, false);
|
||||
|
||||
el.querySelector('.js-close-icon').click();
|
||||
|
||||
setImmediate(() => {
|
||||
expect(document.querySelector('.flash')).toBeNull();
|
||||
|
||||
done();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it('removes global flash on click', (done) => {
|
||||
const flashEl = document.querySelector('.flash');
|
||||
describe('without close icon', () => {
|
||||
beforeEach(() => {
|
||||
el = document.createElement('div');
|
||||
el.innerHTML = `
|
||||
<div class="flash-container">
|
||||
<div class="flash">
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
});
|
||||
|
||||
removeFlashClickListener(flashEl, false);
|
||||
|
||||
flashEl.querySelector('.js-close-icon').click();
|
||||
|
||||
setImmediate(() => {
|
||||
expect(document.querySelector('.flash')).toBeNull();
|
||||
|
||||
done();
|
||||
it('does not throw', () => {
|
||||
expect(() => removeFlashClickListener(el, false)).not.toThrow();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
@ -80,18 +80,22 @@ describe('Number Utils', () => {
|
|||
describe('numberToHumanSize', () => {
|
||||
it('should return bytes', () => {
|
||||
expect(numberToHumanSize(654)).toEqual('654 bytes');
|
||||
expect(numberToHumanSize(-654)).toEqual('-654 bytes');
|
||||
});
|
||||
|
||||
it('should return KiB', () => {
|
||||
expect(numberToHumanSize(1079)).toEqual('1.05 KiB');
|
||||
expect(numberToHumanSize(-1079)).toEqual('-1.05 KiB');
|
||||
});
|
||||
|
||||
it('should return MiB', () => {
|
||||
expect(numberToHumanSize(10485764)).toEqual('10.00 MiB');
|
||||
expect(numberToHumanSize(-10485764)).toEqual('-10.00 MiB');
|
||||
});
|
||||
|
||||
it('should return GiB', () => {
|
||||
expect(numberToHumanSize(10737418240)).toEqual('10.00 GiB');
|
||||
expect(numberToHumanSize(-10737418240)).toEqual('-10.00 GiB');
|
||||
});
|
||||
});
|
||||
|
||||
|
|
|
@ -24,6 +24,42 @@ RSpec.describe Gitlab::CurrentSettings do
|
|||
end
|
||||
end
|
||||
|
||||
describe '.signup_limited?' do
|
||||
subject { described_class.signup_limited? }
|
||||
|
||||
context 'when there are allowed domains' do
|
||||
before do
|
||||
create(:application_setting, domain_allowlist: ['www.gitlab.com'])
|
||||
end
|
||||
|
||||
it { is_expected.to be_truthy }
|
||||
end
|
||||
|
||||
context 'when there are email restrictions' do
|
||||
before do
|
||||
create(:application_setting, email_restrictions_enabled: true)
|
||||
end
|
||||
|
||||
it { is_expected.to be_truthy }
|
||||
end
|
||||
|
||||
context 'when the admin has to approve signups' do
|
||||
before do
|
||||
create(:application_setting, require_admin_approval_after_user_signup: true)
|
||||
end
|
||||
|
||||
it { is_expected.to be_truthy }
|
||||
end
|
||||
|
||||
context 'when there are no restrictions' do
|
||||
before do
|
||||
create(:application_setting, domain_allowlist: [], email_restrictions_enabled: false, require_admin_approval_after_user_signup: false)
|
||||
end
|
||||
|
||||
it { is_expected.to be_falsey }
|
||||
end
|
||||
end
|
||||
|
||||
describe '.signup_disabled?' do
|
||||
subject { described_class.signup_disabled? }
|
||||
|
||||
|
|
|
@ -168,7 +168,7 @@ RSpec.describe Gitlab::Email::Handler::ServiceDeskHandler do
|
|||
end
|
||||
|
||||
context 'when using service desk key' do
|
||||
let_it_be(:service_desk_settings) { create(:service_desk_setting, project: project, project_key: 'mykey') }
|
||||
let_it_be(:service_desk_key) { 'mykey' }
|
||||
let(:email_raw) { service_desk_fixture('emails/service_desk_custom_address.eml') }
|
||||
let(:receiver) { Gitlab::Email::ServiceDeskReceiver.new(email_raw) }
|
||||
|
||||
|
@ -176,6 +176,10 @@ RSpec.describe Gitlab::Email::Handler::ServiceDeskHandler do
|
|||
stub_service_desk_email_setting(enabled: true, address: 'support+%{key}@example.com')
|
||||
end
|
||||
|
||||
before_all do
|
||||
create(:service_desk_setting, project: project, project_key: service_desk_key)
|
||||
end
|
||||
|
||||
it_behaves_like 'a new issue request'
|
||||
|
||||
context 'when there is no project with the key' do
|
||||
|
@ -193,6 +197,20 @@ RSpec.describe Gitlab::Email::Handler::ServiceDeskHandler do
|
|||
expect { receiver.execute }.to raise_error(Gitlab::Email::ProjectNotFound)
|
||||
end
|
||||
end
|
||||
|
||||
context 'when there are multiple projects with same key' do
|
||||
let_it_be(:project_with_same_key) { create(:project, group: group, service_desk_enabled: true) }
|
||||
let(:email_raw) { service_desk_fixture('emails/service_desk_custom_address.eml', slug: project_with_same_key.full_path_slug.to_s) }
|
||||
|
||||
before do
|
||||
create(:service_desk_setting, project: project_with_same_key, project_key: service_desk_key)
|
||||
end
|
||||
|
||||
it 'process email for project with matching slug' do
|
||||
expect { receiver.execute }.to change { Issue.count }.by(1)
|
||||
expect(Issue.last.project).to eq(project_with_same_key)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
|
|
|
@ -7,6 +7,7 @@ RSpec.describe Gitlab::RepositorySetCache, :clean_gitlab_redis_cache do
|
|||
|
||||
let(:repository) { project.repository }
|
||||
let(:namespace) { "#{repository.full_path}:#{project.id}" }
|
||||
let(:gitlab_cache_namespace) { Gitlab::Redis::Cache::CACHE_NAMESPACE }
|
||||
let(:cache) { described_class.new(repository) }
|
||||
|
||||
describe '#cache_key' do
|
||||
|
@ -52,6 +53,24 @@ RSpec.describe Gitlab::RepositorySetCache, :clean_gitlab_redis_cache do
|
|||
end
|
||||
end
|
||||
|
||||
describe '#write' do
|
||||
subject(:write_cache) { cache.write('branch_names', ['main']) }
|
||||
|
||||
it 'writes the value to the cache' do
|
||||
write_cache
|
||||
|
||||
redis_keys = Gitlab::Redis::Cache.with { |redis| redis.scan(0, match: "*") }.last
|
||||
expect(redis_keys).to include("branch_names:#{namespace}:set")
|
||||
expect(cache.fetch('branch_names')).to contain_exactly('main')
|
||||
end
|
||||
|
||||
it 'sets the expiry of the set' do
|
||||
write_cache
|
||||
|
||||
expect(cache.ttl('branch_names')).to be_within(1).of(cache.expires_in.seconds)
|
||||
end
|
||||
end
|
||||
|
||||
describe '#expire' do
|
||||
subject { cache.expire(*keys) }
|
||||
|
||||
|
@ -75,6 +94,12 @@ RSpec.describe Gitlab::RepositorySetCache, :clean_gitlab_redis_cache do
|
|||
|
||||
expect(cache.read(:foo)).to be_empty
|
||||
end
|
||||
|
||||
it 'expires the new key format' do
|
||||
expect_any_instance_of(Redis).to receive(:unlink).with(cache.cache_key(:foo), cache.new_cache_key(:foo)) # rubocop:disable RSpec/AnyInstanceOf
|
||||
|
||||
subject
|
||||
end
|
||||
end
|
||||
|
||||
context 'multiple keys' do
|
||||
|
|
|
@ -0,0 +1,45 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
require 'spec_helper'
|
||||
require Rails.root.join('db', 'post_migrate', '20210513163904_cleanup_move_container_registry_enabled_to_project_feature.rb')
|
||||
|
||||
RSpec.describe CleanupMoveContainerRegistryEnabledToProjectFeature, :migration do
|
||||
let(:namespace) { table(:namespaces).create!(name: 'gitlab', path: 'gitlab-org') }
|
||||
let(:non_null_project_features) { { pages_access_level: 20 } }
|
||||
let(:bg_class_name) { 'MoveContainerRegistryEnabledToProjectFeature' }
|
||||
|
||||
let!(:project1) { table(:projects).create!(namespace_id: namespace.id, name: 'project 1', container_registry_enabled: true) }
|
||||
let!(:project2) { table(:projects).create!(namespace_id: namespace.id, name: 'project 2', container_registry_enabled: false) }
|
||||
let!(:project3) { table(:projects).create!(namespace_id: namespace.id, name: 'project 3', container_registry_enabled: nil) }
|
||||
|
||||
let!(:project4) { table(:projects).create!(namespace_id: namespace.id, name: 'project 4', container_registry_enabled: true) }
|
||||
let!(:project5) { table(:projects).create!(namespace_id: namespace.id, name: 'project 5', container_registry_enabled: false) }
|
||||
let!(:project6) { table(:projects).create!(namespace_id: namespace.id, name: 'project 6', container_registry_enabled: nil) }
|
||||
|
||||
let!(:project_feature1) { table(:project_features).create!(project_id: project1.id, container_registry_access_level: 20, **non_null_project_features) }
|
||||
let!(:project_feature2) { table(:project_features).create!(project_id: project2.id, container_registry_access_level: 0, **non_null_project_features) }
|
||||
let!(:project_feature3) { table(:project_features).create!(project_id: project3.id, container_registry_access_level: 0, **non_null_project_features) }
|
||||
|
||||
let!(:project_feature4) { table(:project_features).create!(project_id: project4.id, container_registry_access_level: 0, **non_null_project_features) }
|
||||
let!(:project_feature5) { table(:project_features).create!(project_id: project5.id, container_registry_access_level: 20, **non_null_project_features) }
|
||||
let!(:project_feature6) { table(:project_features).create!(project_id: project6.id, container_registry_access_level: 20, **non_null_project_features) }
|
||||
|
||||
let!(:background_migration_job1) { table(:background_migration_jobs).create!(class_name: bg_class_name, arguments: [project4.id, project5.id], status: 0) }
|
||||
let!(:background_migration_job2) { table(:background_migration_jobs).create!(class_name: bg_class_name, arguments: [project6.id, project6.id], status: 0) }
|
||||
let!(:background_migration_job3) { table(:background_migration_jobs).create!(class_name: bg_class_name, arguments: [project1.id, project3.id], status: 1) }
|
||||
|
||||
it 'steals remaining jobs, updates any remaining rows and deletes background_migration_jobs rows' do
|
||||
expect(Gitlab::BackgroundMigration).to receive(:steal).with(bg_class_name).and_call_original
|
||||
|
||||
migrate!
|
||||
|
||||
expect(project_feature1.reload.container_registry_access_level).to eq(20)
|
||||
expect(project_feature2.reload.container_registry_access_level).to eq(0)
|
||||
expect(project_feature3.reload.container_registry_access_level).to eq(0)
|
||||
expect(project_feature4.reload.container_registry_access_level).to eq(20)
|
||||
expect(project_feature5.reload.container_registry_access_level).to eq(0)
|
||||
expect(project_feature6.reload.container_registry_access_level).to eq(0)
|
||||
|
||||
expect(table(:background_migration_jobs).where(class_name: bg_class_name).count).to eq(0)
|
||||
end
|
||||
end
|
|
@ -1042,17 +1042,6 @@ RSpec.describe Namespace do
|
|||
end
|
||||
end
|
||||
|
||||
describe '#all_pipelines' do
|
||||
let(:group) { create(:group) }
|
||||
let(:child) { create(:group, parent: group) }
|
||||
let!(:project1) { create(:project_empty_repo, namespace: group) }
|
||||
let!(:project2) { create(:project_empty_repo, namespace: child) }
|
||||
let!(:pipeline1) { create(:ci_empty_pipeline, project: project1) }
|
||||
let!(:pipeline2) { create(:ci_empty_pipeline, project: project2) }
|
||||
|
||||
it { expect(group.all_pipelines.to_a).to match_array([pipeline1, pipeline2]) }
|
||||
end
|
||||
|
||||
describe '#share_with_group_lock with subgroups' do
|
||||
context 'when creating a subgroup' do
|
||||
let(:subgroup) { create(:group, parent: root_group )}
|
||||
|
|
|
@ -1584,19 +1584,20 @@ RSpec.describe Project, factory_default: :keep do
|
|||
end
|
||||
end
|
||||
|
||||
describe '.find_by_service_desk_project_key' do
|
||||
it 'returns the correct project' do
|
||||
describe '.with_service_desk_key' do
|
||||
it 'returns projects with given key' do
|
||||
project1 = create(:project)
|
||||
project2 = create(:project)
|
||||
create(:service_desk_setting, project: project1, project_key: 'key1')
|
||||
create(:service_desk_setting, project: project2, project_key: 'key2')
|
||||
create(:service_desk_setting, project: project2, project_key: 'key1')
|
||||
create(:service_desk_setting, project_key: 'key2')
|
||||
create(:service_desk_setting)
|
||||
|
||||
expect(Project.find_by_service_desk_project_key('key1')).to eq(project1)
|
||||
expect(Project.find_by_service_desk_project_key('key2')).to eq(project2)
|
||||
expect(Project.with_service_desk_key('key1')).to contain_exactly(project1, project2)
|
||||
end
|
||||
|
||||
it 'returns nil if there is no project with the key' do
|
||||
expect(Project.find_by_service_desk_project_key('some_key')).to be_nil
|
||||
it 'returns empty if there is no project with the key' do
|
||||
expect(Project.with_service_desk_key('key1')).to be_empty
|
||||
end
|
||||
end
|
||||
|
||||
|
|
|
@ -5,45 +5,52 @@ require 'spec_helper'
|
|||
RSpec.describe ::Packages::Pypi::PackagePresenter do
|
||||
using RSpec::Parameterized::TableSyntax
|
||||
|
||||
let_it_be(:project) { create(:project) }
|
||||
let_it_be(:group) { create(:group) }
|
||||
let_it_be(:project) { create(:project, group: group) }
|
||||
let_it_be(:package_name) { 'sample-project' }
|
||||
let_it_be(:package1) { create(:pypi_package, project: project, name: package_name, version: '1.0.0') }
|
||||
let_it_be(:package2) { create(:pypi_package, project: project, name: package_name, version: '2.0.0') }
|
||||
|
||||
let(:packages) { [package1, package2] }
|
||||
let(:presenter) { described_class.new(packages, project) }
|
||||
|
||||
let(:file) { package.package_files.first }
|
||||
let(:filename) { file.file_name }
|
||||
|
||||
subject(:presenter) { described_class.new(packages, project_or_group).body}
|
||||
|
||||
describe '#body' do
|
||||
subject { presenter.body}
|
||||
|
||||
shared_examples_for "pypi package presenter" do
|
||||
let(:file) { package.package_files.first }
|
||||
let(:filename) { file.file_name }
|
||||
let(:expected_file) { "<a href=\"http://localhost/api/v4/projects/#{project.id}/packages/pypi/files/#{file.file_sha256}/#{filename}#sha256=#{file.file_sha256}\" data-requires-python=\"#{expected_python_version}\">#{filename}</a><br>" }
|
||||
|
||||
before do
|
||||
package.pypi_metadatum.required_python = python_version
|
||||
where(:version, :expected_version, :with_package1) do
|
||||
'>=2.7' | '>=2.7' | true
|
||||
'"><script>alert(1)</script>' | '"><script>alert(1)</script>' | true
|
||||
'>=2.7, !=3.0' | '>=2.7, !=3.0' | false
|
||||
end
|
||||
|
||||
it { is_expected.to include expected_file }
|
||||
with_them do
|
||||
let(:python_version) { version }
|
||||
let(:expected_python_version) { expected_version }
|
||||
let(:package) { with_package1 ? package1 : package2 }
|
||||
|
||||
before do
|
||||
package.pypi_metadatum.required_python = python_version
|
||||
end
|
||||
|
||||
it { is_expected.to include expected_file }
|
||||
end
|
||||
end
|
||||
|
||||
it_behaves_like "pypi package presenter" do
|
||||
let(:python_version) { '>=2.7' }
|
||||
let(:expected_python_version) { '>=2.7' }
|
||||
let(:package) { package1 }
|
||||
context 'for project' do
|
||||
let(:project_or_group) { project }
|
||||
let(:expected_file) { "<a href=\"http://localhost/api/v4/projects/#{project.id}/packages/pypi/files/#{file.file_sha256}/#{filename}#sha256=#{file.file_sha256}\" data-requires-python=\"#{expected_python_version}\">#{filename}</a><br>" }
|
||||
|
||||
it_behaves_like 'pypi package presenter'
|
||||
end
|
||||
|
||||
it_behaves_like "pypi package presenter" do
|
||||
let(:python_version) { '"><script>alert(1)</script>' }
|
||||
let(:expected_python_version) { '"><script>alert(1)</script>' }
|
||||
let(:package) { package1 }
|
||||
end
|
||||
context 'for group' do
|
||||
let(:project_or_group) { group }
|
||||
let(:expected_file) { "<a href=\"http://localhost/api/v4/groups/#{group.id}/-/packages/pypi/files/#{file.file_sha256}/#{filename}#sha256=#{file.file_sha256}\" data-requires-python=\"#{expected_python_version}\">#{filename}</a><br>" }
|
||||
|
||||
it_behaves_like "pypi package presenter" do
|
||||
let(:python_version) { '>=2.7, !=3.0' }
|
||||
let(:expected_python_version) { '>=2.7, !=3.0' }
|
||||
let(:package) { package2 }
|
||||
it_behaves_like 'pypi package presenter'
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -40,10 +40,21 @@ RSpec.describe API::DebianProjectPackages do
|
|||
let(:method) { :put }
|
||||
let(:url) { "/projects/#{container.id}/packages/debian/#{file_name}" }
|
||||
|
||||
it_behaves_like 'Debian repository write endpoint', 'upload request', :created
|
||||
context 'with a deb' do
|
||||
let(:file_name) { 'libsample0_1.2.3~alpha2_amd64.deb' }
|
||||
|
||||
it_behaves_like 'Debian repository write endpoint', 'upload request', :created
|
||||
end
|
||||
|
||||
context 'with a changes file' do
|
||||
let(:file_name) { 'sample_1.2.3~alpha2_amd64.changes' }
|
||||
|
||||
it_behaves_like 'Debian repository write endpoint', 'upload request', :created
|
||||
end
|
||||
end
|
||||
|
||||
describe 'PUT projects/:id/packages/debian/:file_name/authorize' do
|
||||
let(:file_name) { 'libsample0_1.2.3~alpha2_amd64.deb' }
|
||||
let(:method) { :put }
|
||||
let(:url) { "/projects/#{container.id}/packages/debian/#{file_name}/authorize" }
|
||||
|
||||
|
|
|
@ -27,9 +27,10 @@ RSpec.describe API::Lint do
|
|||
end
|
||||
end
|
||||
|
||||
context 'when signup settings are enabled' do
|
||||
context 'when signup is enabled and not limited' do
|
||||
before do
|
||||
Gitlab::CurrentSettings.signup_enabled = true
|
||||
stub_application_setting(domain_allowlist: [], email_restrictions_enabled: false, require_admin_approval_after_user_signup: false)
|
||||
end
|
||||
|
||||
context 'when unauthenticated' do
|
||||
|
@ -50,6 +51,31 @@ RSpec.describe API::Lint do
|
|||
end
|
||||
end
|
||||
|
||||
context 'when limited signup is enabled' do
|
||||
before do
|
||||
stub_application_setting(domain_allowlist: ['www.gitlab.com'])
|
||||
Gitlab::CurrentSettings.signup_enabled = true
|
||||
end
|
||||
|
||||
context 'when unauthenticated' do
|
||||
it 'returns unauthorized' do
|
||||
post api('/ci/lint'), params: { content: 'content' }
|
||||
|
||||
expect(response).to have_gitlab_http_status(:unauthorized)
|
||||
end
|
||||
end
|
||||
|
||||
context 'when authenticated' do
|
||||
let_it_be(:api_user) { create(:user) }
|
||||
|
||||
it 'returns authentication success' do
|
||||
post api('/ci/lint', api_user), params: { content: 'content' }
|
||||
|
||||
expect(response).to have_gitlab_http_status(:ok)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context 'when authenticated' do
|
||||
let_it_be(:api_user) { create(:user) }
|
||||
|
||||
|
|
|
@ -8,69 +8,57 @@ RSpec.describe API::PypiPackages do
|
|||
using RSpec::Parameterized::TableSyntax
|
||||
|
||||
let_it_be(:user) { create(:user) }
|
||||
let_it_be(:project, reload: true) { create(:project, :public) }
|
||||
let_it_be_with_reload(:group) { create(:group) }
|
||||
let_it_be_with_reload(:project) { create(:project, :public, group: group) }
|
||||
let_it_be(:personal_access_token) { create(:personal_access_token, user: user) }
|
||||
let_it_be(:deploy_token) { create(:deploy_token, read_package_registry: true, write_package_registry: true) }
|
||||
let_it_be(:project_deploy_token) { create(:project_deploy_token, deploy_token: deploy_token, project: project) }
|
||||
let_it_be(:job) { create(:ci_build, :running, user: user) }
|
||||
let(:headers) { {} }
|
||||
|
||||
describe 'GET /api/v4/projects/:id/packages/pypi/simple/:package_name' do
|
||||
context 'simple API endpoint' do
|
||||
let_it_be(:package) { create(:pypi_package, project: project) }
|
||||
let(:url) { "/projects/#{project.id}/packages/pypi/simple/#{package.name}" }
|
||||
|
||||
subject { get api(url) }
|
||||
subject { get api(url), headers: headers }
|
||||
|
||||
context 'with valid project' do
|
||||
where(:project_visibility_level, :user_role, :member, :user_token, :shared_examples_name, :expected_status) do
|
||||
'PUBLIC' | :developer | true | true | 'PyPI package versions' | :success
|
||||
'PUBLIC' | :guest | true | true | 'PyPI package versions' | :success
|
||||
'PUBLIC' | :developer | true | false | 'PyPI package versions' | :success
|
||||
'PUBLIC' | :guest | true | false | 'PyPI package versions' | :success
|
||||
'PUBLIC' | :developer | false | true | 'PyPI package versions' | :success
|
||||
'PUBLIC' | :guest | false | true | 'PyPI package versions' | :success
|
||||
'PUBLIC' | :developer | false | false | 'PyPI package versions' | :success
|
||||
'PUBLIC' | :guest | false | false | 'PyPI package versions' | :success
|
||||
'PUBLIC' | :anonymous | false | true | 'PyPI package versions' | :success
|
||||
'PRIVATE' | :developer | true | true | 'PyPI package versions' | :success
|
||||
'PRIVATE' | :guest | true | true | 'process PyPI api request' | :forbidden
|
||||
'PRIVATE' | :developer | true | false | 'process PyPI api request' | :unauthorized
|
||||
'PRIVATE' | :guest | true | false | 'process PyPI api request' | :unauthorized
|
||||
'PRIVATE' | :developer | false | true | 'process PyPI api request' | :not_found
|
||||
'PRIVATE' | :guest | false | true | 'process PyPI api request' | :not_found
|
||||
'PRIVATE' | :developer | false | false | 'process PyPI api request' | :unauthorized
|
||||
'PRIVATE' | :guest | false | false | 'process PyPI api request' | :unauthorized
|
||||
'PRIVATE' | :anonymous | false | true | 'process PyPI api request' | :unauthorized
|
||||
end
|
||||
describe 'GET /api/v4/groups/:id/-/packages/pypi/simple/:package_name' do
|
||||
let(:url) { "/groups/#{group.id}/-/packages/pypi/simple/#{package.name}" }
|
||||
|
||||
with_them do
|
||||
let(:token) { user_token ? personal_access_token.token : 'wrong' }
|
||||
let(:headers) { user_role == :anonymous ? {} : basic_auth_header(user.username, token) }
|
||||
it_behaves_like 'pypi simple API endpoint'
|
||||
it_behaves_like 'rejects PyPI access with unknown group id'
|
||||
|
||||
subject { get api(url), headers: headers }
|
||||
context 'deploy tokens' do
|
||||
let_it_be(:group_deploy_token) { create(:group_deploy_token, deploy_token: deploy_token, group: group) }
|
||||
|
||||
before do
|
||||
project.update!(visibility_level: Gitlab::VisibilityLevel.const_get(project_visibility_level, false))
|
||||
project.update_column(:visibility_level, Gitlab::VisibilityLevel::PRIVATE)
|
||||
group.update_column(:visibility_level, Gitlab::VisibilityLevel::PRIVATE)
|
||||
end
|
||||
|
||||
it_behaves_like params[:shared_examples_name], params[:user_role], params[:expected_status], params[:member]
|
||||
it_behaves_like 'deploy token for package GET requests'
|
||||
end
|
||||
|
||||
context 'job token' do
|
||||
before do
|
||||
project.update_column(:visibility_level, Gitlab::VisibilityLevel::PRIVATE)
|
||||
group.update_column(:visibility_level, Gitlab::VisibilityLevel::PRIVATE)
|
||||
group.add_developer(user)
|
||||
end
|
||||
|
||||
it_behaves_like 'job token for package GET requests'
|
||||
end
|
||||
|
||||
it_behaves_like 'a pypi user namespace endpoint'
|
||||
end
|
||||
|
||||
context 'with a normalized package name' do
|
||||
let_it_be(:package) { create(:pypi_package, project: project, name: 'my.package') }
|
||||
let(:url) { "/projects/#{project.id}/packages/pypi/simple/my-package" }
|
||||
let(:headers) { basic_auth_header(user.username, personal_access_token.token) }
|
||||
describe 'GET /api/v4/projects/:id/packages/pypi/simple/:package_name' do
|
||||
let(:url) { "/projects/#{project.id}/packages/pypi/simple/#{package.name}" }
|
||||
|
||||
subject { get api(url), headers: headers }
|
||||
|
||||
it_behaves_like 'PyPI package versions', :developer, :success
|
||||
it_behaves_like 'pypi simple API endpoint'
|
||||
it_behaves_like 'rejects PyPI access with unknown project id'
|
||||
it_behaves_like 'deploy token for package GET requests'
|
||||
it_behaves_like 'job token for package GET requests'
|
||||
end
|
||||
|
||||
it_behaves_like 'deploy token for package GET requests'
|
||||
|
||||
it_behaves_like 'job token for package GET requests'
|
||||
|
||||
it_behaves_like 'rejects PyPI access with unknown project id'
|
||||
end
|
||||
|
||||
describe 'POST /api/v4/projects/:id/packages/pypi/authorize' do
|
||||
|
@ -82,25 +70,25 @@ RSpec.describe API::PypiPackages do
|
|||
subject { post api(url), headers: headers }
|
||||
|
||||
context 'with valid project' do
|
||||
where(:project_visibility_level, :user_role, :member, :user_token, :shared_examples_name, :expected_status) do
|
||||
'PUBLIC' | :developer | true | true | 'process PyPI api request' | :success
|
||||
'PUBLIC' | :guest | true | true | 'process PyPI api request' | :forbidden
|
||||
'PUBLIC' | :developer | true | false | 'process PyPI api request' | :unauthorized
|
||||
'PUBLIC' | :guest | true | false | 'process PyPI api request' | :unauthorized
|
||||
'PUBLIC' | :developer | false | true | 'process PyPI api request' | :forbidden
|
||||
'PUBLIC' | :guest | false | true | 'process PyPI api request' | :forbidden
|
||||
'PUBLIC' | :developer | false | false | 'process PyPI api request' | :unauthorized
|
||||
'PUBLIC' | :guest | false | false | 'process PyPI api request' | :unauthorized
|
||||
'PUBLIC' | :anonymous | false | true | 'process PyPI api request' | :unauthorized
|
||||
'PRIVATE' | :developer | true | true | 'process PyPI api request' | :success
|
||||
'PRIVATE' | :guest | true | true | 'process PyPI api request' | :forbidden
|
||||
'PRIVATE' | :developer | true | false | 'process PyPI api request' | :unauthorized
|
||||
'PRIVATE' | :guest | true | false | 'process PyPI api request' | :unauthorized
|
||||
'PRIVATE' | :developer | false | true | 'process PyPI api request' | :not_found
|
||||
'PRIVATE' | :guest | false | true | 'process PyPI api request' | :not_found
|
||||
'PRIVATE' | :developer | false | false | 'process PyPI api request' | :unauthorized
|
||||
'PRIVATE' | :guest | false | false | 'process PyPI api request' | :unauthorized
|
||||
'PRIVATE' | :anonymous | false | true | 'process PyPI api request' | :unauthorized
|
||||
where(:visibility_level, :user_role, :member, :user_token, :shared_examples_name, :expected_status) do
|
||||
:public | :developer | true | true | 'process PyPI api request' | :success
|
||||
:public | :guest | true | true | 'process PyPI api request' | :forbidden
|
||||
:public | :developer | true | false | 'process PyPI api request' | :unauthorized
|
||||
:public | :guest | true | false | 'process PyPI api request' | :unauthorized
|
||||
:public | :developer | false | true | 'process PyPI api request' | :forbidden
|
||||
:public | :guest | false | true | 'process PyPI api request' | :forbidden
|
||||
:public | :developer | false | false | 'process PyPI api request' | :unauthorized
|
||||
:public | :guest | false | false | 'process PyPI api request' | :unauthorized
|
||||
:public | :anonymous | false | true | 'process PyPI api request' | :unauthorized
|
||||
:private | :developer | true | true | 'process PyPI api request' | :success
|
||||
:private | :guest | true | true | 'process PyPI api request' | :forbidden
|
||||
:private | :developer | true | false | 'process PyPI api request' | :unauthorized
|
||||
:private | :guest | true | false | 'process PyPI api request' | :unauthorized
|
||||
:private | :developer | false | true | 'process PyPI api request' | :not_found
|
||||
:private | :guest | false | true | 'process PyPI api request' | :not_found
|
||||
:private | :developer | false | false | 'process PyPI api request' | :unauthorized
|
||||
:private | :guest | false | false | 'process PyPI api request' | :unauthorized
|
||||
:private | :anonymous | false | true | 'process PyPI api request' | :unauthorized
|
||||
end
|
||||
|
||||
with_them do
|
||||
|
@ -109,7 +97,7 @@ RSpec.describe API::PypiPackages do
|
|||
let(:headers) { user_headers.merge(workhorse_headers) }
|
||||
|
||||
before do
|
||||
project.update!(visibility_level: Gitlab::VisibilityLevel.const_get(project_visibility_level, false))
|
||||
project.update_column(:visibility_level, Gitlab::VisibilityLevel.level_value(visibility_level.to_s))
|
||||
end
|
||||
|
||||
it_behaves_like params[:shared_examples_name], params[:user_role], params[:expected_status], params[:member]
|
||||
|
@ -146,25 +134,25 @@ RSpec.describe API::PypiPackages do
|
|||
end
|
||||
|
||||
context 'with valid project' do
|
||||
where(:project_visibility_level, :user_role, :member, :user_token, :shared_examples_name, :expected_status) do
|
||||
'PUBLIC' | :developer | true | true | 'PyPI package creation' | :created
|
||||
'PUBLIC' | :guest | true | true | 'process PyPI api request' | :forbidden
|
||||
'PUBLIC' | :developer | true | false | 'process PyPI api request' | :unauthorized
|
||||
'PUBLIC' | :guest | true | false | 'process PyPI api request' | :unauthorized
|
||||
'PUBLIC' | :developer | false | true | 'process PyPI api request' | :forbidden
|
||||
'PUBLIC' | :guest | false | true | 'process PyPI api request' | :forbidden
|
||||
'PUBLIC' | :developer | false | false | 'process PyPI api request' | :unauthorized
|
||||
'PUBLIC' | :guest | false | false | 'process PyPI api request' | :unauthorized
|
||||
'PUBLIC' | :anonymous | false | true | 'process PyPI api request' | :unauthorized
|
||||
'PRIVATE' | :developer | true | true | 'process PyPI api request' | :created
|
||||
'PRIVATE' | :guest | true | true | 'process PyPI api request' | :forbidden
|
||||
'PRIVATE' | :developer | true | false | 'process PyPI api request' | :unauthorized
|
||||
'PRIVATE' | :guest | true | false | 'process PyPI api request' | :unauthorized
|
||||
'PRIVATE' | :developer | false | true | 'process PyPI api request' | :not_found
|
||||
'PRIVATE' | :guest | false | true | 'process PyPI api request' | :not_found
|
||||
'PRIVATE' | :developer | false | false | 'process PyPI api request' | :unauthorized
|
||||
'PRIVATE' | :guest | false | false | 'process PyPI api request' | :unauthorized
|
||||
'PRIVATE' | :anonymous | false | true | 'process PyPI api request' | :unauthorized
|
||||
where(:visibility_level, :user_role, :member, :user_token, :shared_examples_name, :expected_status) do
|
||||
:public | :developer | true | true | 'PyPI package creation' | :created
|
||||
:public | :guest | true | true | 'process PyPI api request' | :forbidden
|
||||
:public | :developer | true | false | 'process PyPI api request' | :unauthorized
|
||||
:public | :guest | true | false | 'process PyPI api request' | :unauthorized
|
||||
:public | :developer | false | true | 'process PyPI api request' | :forbidden
|
||||
:public | :guest | false | true | 'process PyPI api request' | :forbidden
|
||||
:public | :developer | false | false | 'process PyPI api request' | :unauthorized
|
||||
:public | :guest | false | false | 'process PyPI api request' | :unauthorized
|
||||
:public | :anonymous | false | true | 'process PyPI api request' | :unauthorized
|
||||
:private | :developer | true | true | 'process PyPI api request' | :created
|
||||
:private | :guest | true | true | 'process PyPI api request' | :forbidden
|
||||
:private | :developer | true | false | 'process PyPI api request' | :unauthorized
|
||||
:private | :guest | true | false | 'process PyPI api request' | :unauthorized
|
||||
:private | :developer | false | true | 'process PyPI api request' | :not_found
|
||||
:private | :guest | false | true | 'process PyPI api request' | :not_found
|
||||
:private | :developer | false | false | 'process PyPI api request' | :unauthorized
|
||||
:private | :guest | false | false | 'process PyPI api request' | :unauthorized
|
||||
:private | :anonymous | false | true | 'process PyPI api request' | :unauthorized
|
||||
end
|
||||
|
||||
with_them do
|
||||
|
@ -173,7 +161,7 @@ RSpec.describe API::PypiPackages do
|
|||
let(:headers) { user_headers.merge(workhorse_headers) }
|
||||
|
||||
before do
|
||||
project.update!(visibility_level: Gitlab::VisibilityLevel.const_get(project_visibility_level, false))
|
||||
project.update_column(:visibility_level, Gitlab::VisibilityLevel.level_value(visibility_level.to_s))
|
||||
end
|
||||
|
||||
it_behaves_like params[:shared_examples_name], params[:user_role], params[:expected_status], params[:member]
|
||||
|
@ -187,7 +175,7 @@ RSpec.describe API::PypiPackages do
|
|||
let(:headers) { user_headers.merge(workhorse_headers) }
|
||||
|
||||
before do
|
||||
project.update!(visibility_level: Gitlab::VisibilityLevel::PRIVATE)
|
||||
project.update_column(:visibility_level, Gitlab::VisibilityLevel::PRIVATE)
|
||||
end
|
||||
|
||||
it_behaves_like 'process PyPI api request', :developer, :bad_request, true
|
||||
|
@ -225,84 +213,25 @@ RSpec.describe API::PypiPackages do
|
|||
end
|
||||
end
|
||||
|
||||
describe 'GET /api/v4/projects/:id/packages/pypi/files/:sha256/*file_identifier' do
|
||||
context 'file download endpoint' do
|
||||
let_it_be(:package_name) { 'Dummy-Package' }
|
||||
let_it_be(:package) { create(:pypi_package, project: project, name: package_name, version: '1.0.0') }
|
||||
|
||||
let(:url) { "/projects/#{project.id}/packages/pypi/files/#{package.package_files.first.file_sha256}/#{package_name}-1.0.0.tar.gz" }
|
||||
subject { get api(url), headers: headers }
|
||||
|
||||
subject { get api(url) }
|
||||
describe 'GET /api/v4/groups/:id/-/packages/pypi/files/:sha256/*file_identifier' do
|
||||
let(:url) { "/groups/#{group.id}/-/packages/pypi/files/#{package.package_files.first.file_sha256}/#{package_name}-1.0.0.tar.gz" }
|
||||
|
||||
context 'with valid project' do
|
||||
where(:project_visibility_level, :user_role, :member, :user_token, :shared_examples_name, :expected_status) do
|
||||
'PUBLIC' | :developer | true | true | 'PyPI package download' | :success
|
||||
'PUBLIC' | :guest | true | true | 'PyPI package download' | :success
|
||||
'PUBLIC' | :developer | true | false | 'PyPI package download' | :success
|
||||
'PUBLIC' | :guest | true | false | 'PyPI package download' | :success
|
||||
'PUBLIC' | :developer | false | true | 'PyPI package download' | :success
|
||||
'PUBLIC' | :guest | false | true | 'PyPI package download' | :success
|
||||
'PUBLIC' | :developer | false | false | 'PyPI package download' | :success
|
||||
'PUBLIC' | :guest | false | false | 'PyPI package download' | :success
|
||||
'PUBLIC' | :anonymous | false | true | 'PyPI package download' | :success
|
||||
'PRIVATE' | :developer | true | true | 'PyPI package download' | :success
|
||||
'PRIVATE' | :guest | true | true | 'PyPI package download' | :success
|
||||
'PRIVATE' | :developer | true | false | 'PyPI package download' | :success
|
||||
'PRIVATE' | :guest | true | false | 'PyPI package download' | :success
|
||||
'PRIVATE' | :developer | false | true | 'PyPI package download' | :success
|
||||
'PRIVATE' | :guest | false | true | 'PyPI package download' | :success
|
||||
'PRIVATE' | :developer | false | false | 'PyPI package download' | :success
|
||||
'PRIVATE' | :guest | false | false | 'PyPI package download' | :success
|
||||
'PRIVATE' | :anonymous | false | true | 'PyPI package download' | :success
|
||||
end
|
||||
|
||||
with_them do
|
||||
let(:token) { user_token ? personal_access_token.token : 'wrong' }
|
||||
let(:headers) { user_role == :anonymous ? {} : basic_auth_header(user.username, token) }
|
||||
|
||||
subject { get api(url), headers: headers }
|
||||
|
||||
before do
|
||||
project.update!(visibility_level: Gitlab::VisibilityLevel.const_get(project_visibility_level, false))
|
||||
end
|
||||
|
||||
it_behaves_like params[:shared_examples_name], params[:user_role], params[:expected_status], params[:member]
|
||||
end
|
||||
it_behaves_like 'pypi file download endpoint'
|
||||
it_behaves_like 'rejects PyPI access with unknown group id'
|
||||
it_behaves_like 'a pypi user namespace endpoint'
|
||||
end
|
||||
|
||||
context 'with deploy token headers' do
|
||||
let(:headers) { basic_auth_header(deploy_token.username, deploy_token.token) }
|
||||
describe 'GET /api/v4/projects/:id/packages/pypi/files/:sha256/*file_identifier' do
|
||||
let(:url) { "/projects/#{project.id}/packages/pypi/files/#{package.package_files.first.file_sha256}/#{package_name}-1.0.0.tar.gz" }
|
||||
|
||||
context 'valid token' do
|
||||
it_behaves_like 'returning response status', :success
|
||||
end
|
||||
|
||||
context 'invalid token' do
|
||||
let(:headers) { basic_auth_header('foo', 'bar') }
|
||||
|
||||
it_behaves_like 'returning response status', :success
|
||||
end
|
||||
it_behaves_like 'pypi file download endpoint'
|
||||
it_behaves_like 'rejects PyPI access with unknown project id'
|
||||
end
|
||||
|
||||
context 'with job token headers' do
|
||||
let(:headers) { basic_auth_header(::Gitlab::Auth::CI_JOB_USER, job.token) }
|
||||
|
||||
context 'valid token' do
|
||||
it_behaves_like 'returning response status', :success
|
||||
end
|
||||
|
||||
context 'invalid token' do
|
||||
let(:headers) { basic_auth_header(::Gitlab::Auth::CI_JOB_USER, 'bar') }
|
||||
|
||||
it_behaves_like 'returning response status', :success
|
||||
end
|
||||
|
||||
context 'invalid user' do
|
||||
let(:headers) { basic_auth_header('foo', job.token) }
|
||||
|
||||
it_behaves_like 'returning response status', :success
|
||||
end
|
||||
end
|
||||
|
||||
it_behaves_like 'rejects PyPI access with unknown project id'
|
||||
end
|
||||
end
|
||||
|
|
|
@ -107,10 +107,19 @@ RSpec.shared_examples 'Debian repository upload request' do |status, body = nil|
|
|||
|
||||
if status == :created
|
||||
it 'creates package files', :aggregate_failures do
|
||||
pending "Debian package creation not implemented"
|
||||
expect(::Packages::Debian::FindOrCreateIncomingService).to receive(:new).with(container, user).and_call_original
|
||||
expect(::Packages::Debian::CreatePackageFileService).to receive(:new).with(be_a(Packages::Package), be_an(Hash)).and_call_original
|
||||
|
||||
if file_name.end_with? '.changes'
|
||||
expect(::Packages::Debian::ProcessChangesWorker).to receive(:perform_async)
|
||||
else
|
||||
expect(::Packages::Debian::ProcessChangesWorker).not_to receive(:perform_async)
|
||||
end
|
||||
|
||||
expect { subject }
|
||||
.to change { container.packages.debian.count }.by(1)
|
||||
.and change { container.packages.debian.where(name: 'incoming').count }.by(1)
|
||||
.and change { container.package_files.count }.by(1)
|
||||
|
||||
expect(response).to have_gitlab_http_status(status)
|
||||
expect(response.media_type).to eq('text/plain')
|
||||
|
|
|
@ -110,6 +110,7 @@ RSpec.shared_examples 'PyPI package versions' do |user_type, status, add_member
|
|||
context "for user type #{user_type}" do
|
||||
before do
|
||||
project.send("add_#{user_type}", user) if add_member && user_type != :anonymous
|
||||
group.send("add_#{user_type}", user) if add_member && user_type != :anonymous
|
||||
end
|
||||
|
||||
it 'returns the package listing' do
|
||||
|
@ -127,6 +128,7 @@ RSpec.shared_examples 'PyPI package download' do |user_type, status, add_member
|
|||
context "for user type #{user_type}" do
|
||||
before do
|
||||
project.send("add_#{user_type}", user) if add_member && user_type != :anonymous
|
||||
group.send("add_#{user_type}", user) if add_member && user_type != :anonymous
|
||||
end
|
||||
|
||||
it 'returns the package listing' do
|
||||
|
@ -144,24 +146,184 @@ RSpec.shared_examples 'process PyPI api request' do |user_type, status, add_memb
|
|||
context "for user type #{user_type}" do
|
||||
before do
|
||||
project.send("add_#{user_type}", user) if add_member && user_type != :anonymous
|
||||
group.send("add_#{user_type}", user) if add_member && user_type != :anonymous
|
||||
end
|
||||
|
||||
it_behaves_like 'returning response status', status
|
||||
end
|
||||
end
|
||||
|
||||
RSpec.shared_examples 'unknown PyPI scope id' do
|
||||
context 'as anonymous' do
|
||||
it_behaves_like 'process PyPI api request', :anonymous, :not_found
|
||||
end
|
||||
|
||||
context 'as authenticated user' do
|
||||
subject { get api(url), headers: basic_auth_header(user.username, personal_access_token.token) }
|
||||
|
||||
it_behaves_like 'process PyPI api request', :anonymous, :not_found
|
||||
end
|
||||
end
|
||||
|
||||
RSpec.shared_examples 'rejects PyPI access with unknown project id' do
|
||||
context 'with an unknown project' do
|
||||
let(:project) { OpenStruct.new(id: 1234567890) }
|
||||
|
||||
context 'as anonymous' do
|
||||
it_behaves_like 'process PyPI api request', :anonymous, :not_found
|
||||
it_behaves_like 'unknown PyPI scope id'
|
||||
end
|
||||
end
|
||||
|
||||
RSpec.shared_examples 'rejects PyPI access with unknown group id' do
|
||||
context 'with an unknown project' do
|
||||
let(:group) { OpenStruct.new(id: 1234567890) }
|
||||
|
||||
it_behaves_like 'unknown PyPI scope id'
|
||||
end
|
||||
end
|
||||
|
||||
RSpec.shared_examples 'pypi simple API endpoint' do
|
||||
using RSpec::Parameterized::TableSyntax
|
||||
|
||||
context 'with valid project' do
|
||||
where(:visibility_level, :user_role, :member, :user_token, :shared_examples_name, :expected_status) do
|
||||
:public | :developer | true | true | 'PyPI package versions' | :success
|
||||
:public | :guest | true | true | 'PyPI package versions' | :success
|
||||
:public | :developer | true | false | 'PyPI package versions' | :success
|
||||
:public | :guest | true | false | 'PyPI package versions' | :success
|
||||
:public | :developer | false | true | 'PyPI package versions' | :success
|
||||
:public | :guest | false | true | 'PyPI package versions' | :success
|
||||
:public | :developer | false | false | 'PyPI package versions' | :success
|
||||
:public | :guest | false | false | 'PyPI package versions' | :success
|
||||
:public | :anonymous | false | true | 'PyPI package versions' | :success
|
||||
:private | :developer | true | true | 'PyPI package versions' | :success
|
||||
:private | :guest | true | true | 'process PyPI api request' | :forbidden
|
||||
:private | :developer | true | false | 'process PyPI api request' | :unauthorized
|
||||
:private | :guest | true | false | 'process PyPI api request' | :unauthorized
|
||||
:private | :developer | false | true | 'process PyPI api request' | :not_found
|
||||
:private | :guest | false | true | 'process PyPI api request' | :not_found
|
||||
:private | :developer | false | false | 'process PyPI api request' | :unauthorized
|
||||
:private | :guest | false | false | 'process PyPI api request' | :unauthorized
|
||||
:private | :anonymous | false | true | 'process PyPI api request' | :unauthorized
|
||||
end
|
||||
|
||||
context 'as authenticated user' do
|
||||
subject { get api(url), headers: basic_auth_header(user.username, personal_access_token.token) }
|
||||
with_them do
|
||||
let(:token) { user_token ? personal_access_token.token : 'wrong' }
|
||||
let(:headers) { user_role == :anonymous ? {} : basic_auth_header(user.username, token) }
|
||||
|
||||
it_behaves_like 'process PyPI api request', :anonymous, :not_found
|
||||
before do
|
||||
project.update_column(:visibility_level, Gitlab::VisibilityLevel.level_value(visibility_level.to_s))
|
||||
group.update_column(:visibility_level, Gitlab::VisibilityLevel.level_value(visibility_level.to_s))
|
||||
end
|
||||
|
||||
it_behaves_like params[:shared_examples_name], params[:user_role], params[:expected_status], params[:member]
|
||||
end
|
||||
end
|
||||
|
||||
context 'with a normalized package name' do
|
||||
let_it_be(:package) { create(:pypi_package, project: project, name: 'my.package') }
|
||||
|
||||
let(:url) { "/projects/#{project.id}/packages/pypi/simple/my-package" }
|
||||
let(:headers) { basic_auth_header(user.username, personal_access_token.token) }
|
||||
|
||||
it_behaves_like 'PyPI package versions', :developer, :success
|
||||
end
|
||||
end
|
||||
|
||||
RSpec.shared_examples 'pypi file download endpoint' do
|
||||
using RSpec::Parameterized::TableSyntax
|
||||
|
||||
context 'with valid project' do
|
||||
where(:visibility_level, :user_role, :member, :user_token) do
|
||||
:public | :developer | true | true
|
||||
:public | :guest | true | true
|
||||
:public | :developer | true | false
|
||||
:public | :guest | true | false
|
||||
:public | :developer | false | true
|
||||
:public | :guest | false | true
|
||||
:public | :developer | false | false
|
||||
:public | :guest | false | false
|
||||
:public | :anonymous | false | true
|
||||
:private | :developer | true | true
|
||||
:private | :guest | true | true
|
||||
:private | :developer | true | false
|
||||
:private | :guest | true | false
|
||||
:private | :developer | false | true
|
||||
:private | :guest | false | true
|
||||
:private | :developer | false | false
|
||||
:private | :guest | false | false
|
||||
:private | :anonymous | false | true
|
||||
end
|
||||
|
||||
with_them do
|
||||
let(:token) { user_token ? personal_access_token.token : 'wrong' }
|
||||
let(:headers) { user_role == :anonymous ? {} : basic_auth_header(user.username, token) }
|
||||
|
||||
before do
|
||||
project.update_column(:visibility_level, Gitlab::VisibilityLevel.level_value(visibility_level.to_s))
|
||||
group.update_column(:visibility_level, Gitlab::VisibilityLevel.level_value(visibility_level.to_s))
|
||||
end
|
||||
|
||||
it_behaves_like 'PyPI package download', params[:user_role], :success, params[:member]
|
||||
end
|
||||
end
|
||||
|
||||
context 'with deploy token headers' do
|
||||
let(:headers) { basic_auth_header(deploy_token.username, deploy_token.token) }
|
||||
|
||||
context 'valid token' do
|
||||
it_behaves_like 'returning response status', :success
|
||||
end
|
||||
|
||||
context 'invalid token' do
|
||||
let(:headers) { basic_auth_header('foo', 'bar') }
|
||||
|
||||
it_behaves_like 'returning response status', :success
|
||||
end
|
||||
end
|
||||
|
||||
context 'with job token headers' do
|
||||
let(:headers) { basic_auth_header(::Gitlab::Auth::CI_JOB_USER, job.token) }
|
||||
|
||||
context 'valid token' do
|
||||
it_behaves_like 'returning response status', :success
|
||||
end
|
||||
|
||||
context 'invalid token' do
|
||||
let(:headers) { basic_auth_header(::Gitlab::Auth::CI_JOB_USER, 'bar') }
|
||||
|
||||
it_behaves_like 'returning response status', :unauthorized
|
||||
end
|
||||
|
||||
context 'invalid user' do
|
||||
let(:headers) { basic_auth_header('foo', job.token) }
|
||||
|
||||
it_behaves_like 'returning response status', :success
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
RSpec.shared_examples 'a pypi user namespace endpoint' do
|
||||
using RSpec::Parameterized::TableSyntax
|
||||
|
||||
# only group namespaces are supported at this time
|
||||
where(:visibility_level, :user_role, :expected_status) do
|
||||
:public | :owner | :not_found
|
||||
:private | :owner | :not_found
|
||||
:public | :external | :not_found
|
||||
:private | :external | :not_found
|
||||
:public | :anonymous | :not_found
|
||||
:private | :anonymous | :not_found
|
||||
end
|
||||
|
||||
with_them do
|
||||
let_it_be_with_reload(:group) { create(:namespace) }
|
||||
let(:headers) { user_role == :anonymous ? {} : basic_auth_header(user.username, personal_access_token.token) }
|
||||
|
||||
before do
|
||||
group.update_column(:visibility_level, Gitlab::VisibilityLevel.level_value(visibility_level.to_s))
|
||||
group.update_column(:owner_id, user.id) if user_role == :owner
|
||||
end
|
||||
|
||||
it_behaves_like 'returning response status', params[:expected_status]
|
||||
end
|
||||
end
|
||||
|
|
|
@ -2,11 +2,15 @@
|
|||
|
||||
require 'rake_helper'
|
||||
|
||||
RSpec.describe 'clearing redis cache' do
|
||||
RSpec.describe 'clearing redis cache', :clean_gitlab_redis_cache do
|
||||
before do
|
||||
Rake.application.rake_require 'tasks/cache'
|
||||
end
|
||||
|
||||
shared_examples 'clears the cache' do
|
||||
it { expect { run_rake_task('cache:clear:redis') }.to change { redis_keys.size }.by(-1) }
|
||||
end
|
||||
|
||||
describe 'clearing pipeline status cache' do
|
||||
let(:pipeline_status) do
|
||||
project = create(:project, :repository)
|
||||
|
@ -20,5 +24,38 @@ RSpec.describe 'clearing redis cache' do
|
|||
it 'clears pipeline status cache' do
|
||||
expect { run_rake_task('cache:clear:redis') }.to change { pipeline_status.has_cache? }
|
||||
end
|
||||
|
||||
it_behaves_like 'clears the cache'
|
||||
end
|
||||
|
||||
describe 'clearing set caches' do
|
||||
context 'repository set' do
|
||||
let(:project) { create(:project) }
|
||||
let(:repository) { project.repository }
|
||||
|
||||
let(:cache) { Gitlab::RepositorySetCache.new(repository) }
|
||||
|
||||
before do
|
||||
pending "Enable as part of https://gitlab.com/gitlab-org/gitlab/-/issues/331319"
|
||||
|
||||
cache.write(:foo, [:bar])
|
||||
end
|
||||
|
||||
it_behaves_like 'clears the cache'
|
||||
end
|
||||
|
||||
context 'reactive cache set' do
|
||||
let(:cache) { Gitlab::ReactiveCacheSetCache.new }
|
||||
|
||||
before do
|
||||
cache.write(:foo, :bar)
|
||||
end
|
||||
|
||||
it_behaves_like 'clears the cache'
|
||||
end
|
||||
end
|
||||
|
||||
def redis_keys
|
||||
Gitlab::Redis::Cache.with { |redis| redis.scan(0, match: "*") }.last
|
||||
end
|
||||
end
|
||||
|
|
Loading…
Reference in New Issue