Welcome to mirror list, hosted at ThFree Co, Russian Federation.

gitlab.com/gitlab-org/gitlab-foss.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorGitLab Bot <gitlab-bot@gitlab.com>2021-05-25 18:10:33 +0300
committerGitLab Bot <gitlab-bot@gitlab.com>2021-05-25 18:10:33 +0300
commita8c1bc6f757ecacbc3481e52a3f4cefb6c6db5fd (patch)
tree7ba85d57835274f11674c33155e68b6af33f2687
parent76ef00aac974a463243dcda6f692b17ff5d439bc (diff)
Add latest changes from gitlab-org/gitlab@master
-rw-r--r--.gitlab/ci/review.gitlab-ci.yml2
-rw-r--r--app/assets/javascripts/flash.js4
-rw-r--r--app/assets/javascripts/lib/utils/number_utils.js8
-rw-r--r--app/controllers/projects_controller.rb2
-rw-r--r--app/finders/packages/group_or_project_package_finder.rb12
-rw-r--r--app/finders/packages/pypi/package_finder.rb10
-rw-r--r--app/models/namespace.rb6
-rw-r--r--app/models/project.rb12
-rw-r--r--app/presenters/packages/pypi/package_presenter.rb43
-rw-r--r--app/workers/all_queues.yml18
-rw-r--r--app/workers/authorized_project_update/project_recalculate_worker.rb2
-rw-r--r--config/feature_flags/development/security_ci_lint_authorization.yml8
-rw-r--r--config/sidekiq_queues.yml2
-rw-r--r--db/post_migrate/20210513163904_cleanup_move_container_registry_enabled_to_project_feature.rb36
-rw-r--r--db/schema_migrations/202105131639041
-rw-r--r--doc/administration/postgresql/external.md3
-rw-r--r--doc/api/lint.md11
-rw-r--r--doc/api/packages/pypi.md77
-rw-r--r--doc/development/i18n/proofreader.md3
-rw-r--r--doc/install/postgresql_extensions.md22
-rw-r--r--doc/install/requirements.md34
-rw-r--r--doc/user/application_security/container_scanning/index.md10
-rw-r--r--doc/user/application_security/dast/browser_based.md10
-rw-r--r--doc/user/application_security/dast/index.md8
-rw-r--r--doc/user/application_security/dependency_scanning/index.md4
-rw-r--r--doc/user/application_security/sast/analyzers.md2
-rw-r--r--doc/user/application_security/sast/index.md4
-rw-r--r--doc/user/application_security/secret_detection/index.md6
-rw-r--r--doc/user/compliance/license_compliance/index.md28
-rw-r--r--doc/user/markdown.md34
-rw-r--r--doc/user/packages/pypi_repository/index.md29
-rw-r--r--lib/api/debian_project_packages.rb15
-rw-r--r--lib/api/helpers/packages/basic_auth_helpers.rb8
-rw-r--r--lib/api/lint.rb6
-rw-r--r--lib/api/pypi_packages.rb72
-rw-r--r--lib/gitlab/ci/templates/Managed-Cluster-Applications.gitlab-ci.yml4
-rw-r--r--lib/gitlab/ci/templates/Security/API-Fuzzing.gitlab-ci.yml5
-rw-r--r--lib/gitlab/ci/templates/Security/API-Fuzzing.latest.gitlab-ci.yml5
-rw-r--r--lib/gitlab/ci/templates/Security/Container-Scanning.gitlab-ci.yml3
-rw-r--r--lib/gitlab/ci/templates/Security/Coverage-Fuzzing.gitlab-ci.yml5
-rw-r--r--lib/gitlab/ci/templates/Security/DAST-API.gitlab-ci.yml5
-rw-r--r--lib/gitlab/ci/templates/Security/DAST.gitlab-ci.yml5
-rw-r--r--lib/gitlab/ci/templates/Security/DAST.latest.gitlab-ci.yml5
-rw-r--r--lib/gitlab/ci/templates/Security/Dependency-Scanning.gitlab-ci.yml5
-rw-r--r--lib/gitlab/ci/templates/Security/License-Scanning.gitlab-ci.yml5
-rw-r--r--lib/gitlab/ci/templates/Security/SAST.gitlab-ci.yml5
-rw-r--r--lib/gitlab/ci/templates/Security/Secret-Detection.gitlab-ci.yml5
-rw-r--r--lib/gitlab/current_settings.rb4
-rw-r--r--lib/gitlab/email/handler/service_desk_handler.rb7
-rw-r--r--lib/gitlab/reactive_cache_set_cache.rb14
-rw-r--r--lib/gitlab/repository_set_cache.rb5
-rw-r--r--lib/gitlab/set_cache.rb14
-rw-r--r--locale/gitlab.pot20
-rwxr-xr-xscripts/trigger-build37
-rw-r--r--spec/controllers/projects_controller_spec.rb17
-rw-r--r--spec/finders/packages/pypi/package_finder_spec.rb10
-rw-r--r--spec/frontend/commit/pipelines/pipelines_spec.js280
-rw-r--r--spec/frontend/commit/pipelines/pipelines_table_spec.js253
-rw-r--r--spec/frontend/flash_spec.js49
-rw-r--r--spec/frontend/lib/utils/number_utility_spec.js4
-rw-r--r--spec/lib/gitlab/current_settings_spec.rb36
-rw-r--r--spec/lib/gitlab/email/handler/service_desk_handler_spec.rb20
-rw-r--r--spec/lib/gitlab/repository_set_cache_spec.rb25
-rw-r--r--spec/migrations/cleanup_move_container_registry_enabled_to_project_features_spec.rb45
-rw-r--r--spec/models/namespace_spec.rb11
-rw-r--r--spec/models/project_spec.rb15
-rw-r--r--spec/presenters/packages/pypi/package_presenter_spec.rb55
-rw-r--r--spec/requests/api/debian_project_packages_spec.rb13
-rw-r--r--spec/requests/api/lint_spec.rb28
-rw-r--r--spec/requests/api/pypi_packages_spec.rb235
-rw-r--r--spec/support/shared_examples/requests/api/debian_packages_shared_examples.rb11
-rw-r--r--spec/support/shared_examples/requests/api/pypi_packages_shared_examples.rb172
-rw-r--r--spec/tasks/cache/clear/redis_spec.rb39
73 files changed, 1362 insertions, 666 deletions
diff --git a/.gitlab/ci/review.gitlab-ci.yml b/.gitlab/ci/review.gitlab-ci.yml
index e9ca16fdd4e..ac9dfe217bd 100644
--- a/.gitlab/ci/review.gitlab-ci.yml
+++ b/.gitlab/ci/review.gitlab-ci.yml
@@ -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}"
diff --git a/app/assets/javascripts/flash.js b/app/assets/javascripts/flash.js
index 7a79f8f5bfc..2edb6e79d3b 100644
--- a/app/assets/javascripts/flash.js
+++ b/app/assets/javascripts/flash.js
@@ -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));
};
/*
diff --git a/app/assets/javascripts/lib/utils/number_utils.js b/app/assets/javascripts/lib/utils/number_utils.js
index e3500d02a79..f3dedb7726a 100644
--- a/app/assets/javascripts/lib/utils/number_utils.js
+++ b/app/assets/javascripts/lib/utils/number_utils.js
@@ -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) });
diff --git a/app/controllers/projects_controller.rb b/app/controllers/projects_controller.rb
index e66893ac269..fb977a5ee42 100644
--- a/app/controllers/projects_controller.rb
+++ b/app/controllers/projects_controller.rb
@@ -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
diff --git a/app/finders/packages/group_or_project_package_finder.rb b/app/finders/packages/group_or_project_package_finder.rb
index fb8bcfc7d42..5b5f70bf459 100644
--- a/app/finders/packages/group_or_project_package_finder.rb
+++ b/app/finders/packages/group_or_project_package_finder.rb
@@ -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
diff --git a/app/finders/packages/pypi/package_finder.rb b/app/finders/packages/pypi/package_finder.rb
index 574e9770363..3a37e404b79 100644
--- a/app/finders/packages/pypi/package_finder.rb
+++ b/app/finders/packages/pypi/package_finder.rb
@@ -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
diff --git a/app/models/namespace.rb b/app/models/namespace.rb
index 8f03c6145cb..0fd0298cc6d 100644
--- a/app/models/namespace.rb
+++ b/app/models/namespace.rb
@@ -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
diff --git a/app/models/project.rb b/app/models/project.rb
index 4bc16771323..39415769e50 100644
--- a/app/models/project.rb
+++ b/app/models/project.rb
@@ -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)
diff --git a/app/presenters/packages/pypi/package_presenter.rb b/app/presenters/packages/pypi/package_presenter.rb
index 1cb11c7be1a..7997c1b9b79 100644
--- a/app/presenters/packages/pypi/package_presenter.rb
+++ b/app/presenters/packages/pypi/package_presenter.rb
@@ -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
diff --git a/app/workers/all_queues.yml b/app/workers/all_queues.yml
index a57b208f476..ea0845b00e8 100644
--- a/app/workers/all_queues.yml
+++ b/app/workers/all_queues.yml
@@ -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
diff --git a/app/workers/authorized_project_update/project_recalculate_worker.rb b/app/workers/authorized_project_update/project_recalculate_worker.rb
index 0ecef8b868b..3f0672992ef 100644
--- a/app/workers/authorized_project_update/project_recalculate_worker.rb
+++ b/app/workers/authorized_project_update/project_recalculate_worker.rb
@@ -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!
diff --git a/config/feature_flags/development/security_ci_lint_authorization.yml b/config/feature_flags/development/security_ci_lint_authorization.yml
new file mode 100644
index 00000000000..73b3bd45727
--- /dev/null
+++ b/config/feature_flags/development/security_ci_lint_authorization.yml
@@ -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
diff --git a/config/sidekiq_queues.yml b/config/sidekiq_queues.yml
index 78c073b354f..24d140abada 100644
--- a/config/sidekiq_queues.yml
+++ b/config/sidekiq_queues.yml
@@ -45,8 +45,6 @@
- - authorized_project_update
- 1
- - authorized_projects
- - 1
-- - authorized_projects
- 2
- - auto_devops
- 2
diff --git a/db/post_migrate/20210513163904_cleanup_move_container_registry_enabled_to_project_feature.rb b/db/post_migrate/20210513163904_cleanup_move_container_registry_enabled_to_project_feature.rb
new file mode 100644
index 00000000000..665d274a0ee
--- /dev/null
+++ b/db/post_migrate/20210513163904_cleanup_move_container_registry_enabled_to_project_feature.rb
@@ -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
diff --git a/db/schema_migrations/20210513163904 b/db/schema_migrations/20210513163904
new file mode 100644
index 00000000000..dc668704311
--- /dev/null
+++ b/db/schema_migrations/20210513163904
@@ -0,0 +1 @@
+3c4905fbe29227da7a2386f73d9df30e82da48efff24a1193ba3db0ac325cfcf \ No newline at end of file
diff --git a/doc/administration/postgresql/external.md b/doc/administration/postgresql/external.md
index a9d0af952a0..1e346a3b8aa 100644
--- a/doc/administration/postgresql/external.md
+++ b/doc/administration/postgresql/external.md
@@ -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:
diff --git a/doc/api/lint.md b/doc/api/lint.md
index 867a5e54663..69267fe51a5 100644
--- a/doc/api/lint.md
+++ b/doc/api/lint.md
@@ -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
diff --git a/doc/api/packages/pypi.md b/doc/api/packages/pypi.md
index 531193e59e2..77ba028c447 100644
--- a/doc/api/packages/pypi.md
+++ b/doc/api/packages/pypi.md
@@ -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="&gt;=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="&gt;=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.
diff --git a/doc/development/i18n/proofreader.md b/doc/development/i18n/proofreader.md
index 56767f73c61..6a35c192ce4 100644
--- a/doc/development/i18n/proofreader.md
+++ b/doc/development/i18n/proofreader.md
@@ -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)
diff --git a/doc/install/postgresql_extensions.md b/doc/install/postgresql_extensions.md
index 663ec547733..80bbb0671b9 100644
--- a/doc/install/postgresql_extensions.md
+++ b/doc/install/postgresql_extensions.md
@@ -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
+```
diff --git a/doc/install/requirements.md b/doc/install/requirements.md
index 5c723ee06cd..8accf094795 100644
--- a/doc/install/requirements.md
+++ b/doc/install/requirements.md
@@ -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.
diff --git a/doc/user/application_security/container_scanning/index.md b/doc/user/application_security/container_scanning/index.md
index 5ab56634d29..e5c984dd7bc 100644
--- a/doc/user/application_security/container_scanning/index.md
+++ b/doc/user/application_security/container_scanning/index.md
@@ -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
diff --git a/doc/user/application_security/dast/browser_based.md b/doc/user/application_security/dast/browser_based.md
index aaf4496076f..880078977b4 100644
--- a/doc/user/application_security/dast/browser_based.md
+++ b/doc/user/application_security/dast/browser_based.md
@@ -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
diff --git a/doc/user/application_security/dast/index.md b/doc/user/application_security/dast/index.md
index 1093e7cfabd..73acb03a04d 100644
--- a/doc/user/application_security/dast/index.md
+++ b/doc/user/application_security/dast/index.md
@@ -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.
diff --git a/doc/user/application_security/dependency_scanning/index.md b/doc/user/application_security/dependency_scanning/index.md
index 8e23db89dfd..bf17e62ad77 100644
--- a/doc/user/application_security/dependency_scanning/index.md
+++ b/doc/user/application_security/dependency_scanning/index.md
@@ -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.
diff --git a/doc/user/application_security/sast/analyzers.md b/doc/user/application_security/sast/analyzers.md
index 0e69f3b68eb..cbcfda16c35 100644
--- a/doc/user/application_security/sast/analyzers.md
+++ b/doc/user/application_security/sast/analyzers.md
@@ -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)
diff --git a/doc/user/application_security/sast/index.md b/doc/user/application_security/sast/index.md
index 886726d5d67..176b3c81960 100644
--- a/doc/user/application_security/sast/index.md
+++ b/doc/user/application_security/sast/index.md
@@ -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.
diff --git a/doc/user/application_security/secret_detection/index.md b/doc/user/application_security/secret_detection/index.md
index 02d117b1c0a..758b06c46aa 100644
--- a/doc/user/application_security/secret_detection/index.md
+++ b/doc/user/application_security/secret_detection/index.md
@@ -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.
diff --git a/doc/user/compliance/license_compliance/index.md b/doc/user/compliance/license_compliance/index.md
index 43dbafb8f6f..da601416174 100644
--- a/doc/user/compliance/license_compliance/index.md
+++ b/doc/user/compliance/license_compliance/index.md
@@ -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:
diff --git a/doc/user/markdown.md b/doc/user/markdown.md
index d8c7b0fd8fe..f642d0bd373 100644
--- a/doc/user/markdown.md
+++ b/doc/user/markdown.md
@@ -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> |
```
diff --git a/doc/user/packages/pypi_repository/index.md b/doc/user/packages/pypi_repository/index.md
index 1708bfdf2e5..a4d17595ddd 100644
--- a/doc/user/packages/pypi_repository/index.md
+++ b/doc/user/packages/pypi_repository/index.md
@@ -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
diff --git a/lib/api/debian_project_packages.rb b/lib/api/debian_project_packages.rb
index 0ed828fd639..feb83b52695 100644
--- a/lib/api/debian_project_packages.rb
+++ b/lib/api/debian_project_packages.rb
@@ -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 })
diff --git a/lib/api/helpers/packages/basic_auth_helpers.rb b/lib/api/helpers/packages/basic_auth_helpers.rb
index c32ce199dd6..6c381d85cd8 100644
--- a/lib/api/helpers/packages/basic_auth_helpers.rb
+++ b/lib/api/helpers/packages/basic_auth_helpers.rb
@@ -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
diff --git a/lib/api/lint.rb b/lib/api/lint.rb
index e0806674c6a..3580a7b5e24 100644
--- a/lib/api/lint.rb
+++ b/lib/api/lint.rb
@@ -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
diff --git a/lib/api/pypi_packages.rb b/lib/api/pypi_packages.rb
index 73b2f658825..969b619c1cd 100644
--- a/lib/api/pypi_packages.rb
+++ b/lib/api/pypi_packages.rb
@@ -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.
diff --git a/lib/gitlab/ci/templates/Managed-Cluster-Applications.gitlab-ci.yml b/lib/gitlab/ci/templates/Managed-Cluster-Applications.gitlab-ci.yml
index c599b261d77..a6d400b6350 100644
--- a/lib/gitlab/ci/templates/Managed-Cluster-Applications.gitlab-ci.yml
+++ b/lib/gitlab/ci/templates/Managed-Cluster-Applications.gitlab-ci.yml
@@ -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
diff --git a/lib/gitlab/ci/templates/Security/API-Fuzzing.gitlab-ci.yml b/lib/gitlab/ci/templates/Security/API-Fuzzing.gitlab-ci.yml
index 90fad1550ff..f3c9a93d9fb 100644
--- a/lib/gitlab/ci/templates/Security/API-Fuzzing.gitlab-ci.yml
+++ b/lib/gitlab/ci/templates/Security/API-Fuzzing.gitlab-ci.yml
@@ -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
diff --git a/lib/gitlab/ci/templates/Security/API-Fuzzing.latest.gitlab-ci.yml b/lib/gitlab/ci/templates/Security/API-Fuzzing.latest.gitlab-ci.yml
index 8fa33026011..0c4c39cbcd6 100644
--- a/lib/gitlab/ci/templates/Security/API-Fuzzing.latest.gitlab-ci.yml
+++ b/lib/gitlab/ci/templates/Security/API-Fuzzing.latest.gitlab-ci.yml
@@ -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"
diff --git a/lib/gitlab/ci/templates/Security/Container-Scanning.gitlab-ci.yml b/lib/gitlab/ci/templates/Security/Container-Scanning.gitlab-ci.yml
index 695aaa37924..84d9a92663a 100644
--- a/lib/gitlab/ci/templates/Security/Container-Scanning.gitlab-ci.yml
+++ b/lib/gitlab/ci/templates/Security/Container-Scanning.gitlab-ci.yml
@@ -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)
diff --git a/lib/gitlab/ci/templates/Security/Coverage-Fuzzing.gitlab-ci.yml b/lib/gitlab/ci/templates/Security/Coverage-Fuzzing.gitlab-ci.yml
index ab71049bac2..2dbfb80b419 100644
--- a/lib/gitlab/ci/templates/Security/Coverage-Fuzzing.gitlab-ci.yml
+++ b/lib/gitlab/ci/templates/Security/Coverage-Fuzzing.gitlab-ci.yml
@@ -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.
diff --git a/lib/gitlab/ci/templates/Security/DAST-API.gitlab-ci.yml b/lib/gitlab/ci/templates/Security/DAST-API.gitlab-ci.yml
index b40c4e982f7..9170e943e9d 100644
--- a/lib/gitlab/ci/templates/Security/DAST-API.gitlab-ci.yml
+++ b/lib/gitlab/ci/templates/Security/DAST-API.gitlab-ci.yml
@@ -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
diff --git a/lib/gitlab/ci/templates/Security/DAST.gitlab-ci.yml b/lib/gitlab/ci/templates/Security/DAST.gitlab-ci.yml
index 7abecfb7e49..b355b6e36a2 100644
--- a/lib/gitlab/ci/templates/Security/DAST.gitlab-ci.yml
+++ b/lib/gitlab/ci/templates/Security/DAST.gitlab-ci.yml
@@ -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
diff --git a/lib/gitlab/ci/templates/Security/DAST.latest.gitlab-ci.yml b/lib/gitlab/ci/templates/Security/DAST.latest.gitlab-ci.yml
index b6282da18a4..693cf1469c2 100644
--- a/lib/gitlab/ci/templates/Security/DAST.latest.gitlab-ci.yml
+++ b/lib/gitlab/ci/templates/Security/DAST.latest.gitlab-ci.yml
@@ -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
diff --git a/lib/gitlab/ci/templates/Security/Dependency-Scanning.gitlab-ci.yml b/lib/gitlab/ci/templates/Security/Dependency-Scanning.gitlab-ci.yml
index 53d68c24d26..8df5ce79fe8 100644
--- a/lib/gitlab/ci/templates/Security/Dependency-Scanning.gitlab-ci.yml
+++ b/lib/gitlab/ci/templates/Security/Dependency-Scanning.gitlab-ci.yml
@@ -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
diff --git a/lib/gitlab/ci/templates/Security/License-Scanning.gitlab-ci.yml b/lib/gitlab/ci/templates/Security/License-Scanning.gitlab-ci.yml
index 21e926ef275..870684c9f1d 100644
--- a/lib/gitlab/ci/templates/Security/License-Scanning.gitlab-ci.yml
+++ b/lib/gitlab/ci/templates/Security/License-Scanning.gitlab-ci.yml
@@ -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
diff --git a/lib/gitlab/ci/templates/Security/SAST.gitlab-ci.yml b/lib/gitlab/ci/templates/Security/SAST.gitlab-ci.yml
index a8d45e80356..65e1046ad0d 100644
--- a/lib/gitlab/ci/templates/Security/SAST.gitlab-ci.yml
+++ b/lib/gitlab/ci/templates/Security/SAST.gitlab-ci.yml
@@ -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
diff --git a/lib/gitlab/ci/templates/Security/Secret-Detection.gitlab-ci.yml b/lib/gitlab/ci/templates/Security/Secret-Detection.gitlab-ci.yml
index c255fb4707a..657ac43b78e 100644
--- a/lib/gitlab/ci/templates/Security/Secret-Detection.gitlab-ci.yml
+++ b/lib/gitlab/ci/templates/Security/Secret-Detection.gitlab-ci.yml
@@ -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"
diff --git a/lib/gitlab/current_settings.rb b/lib/gitlab/current_settings.rb
index 7f55734f796..e7ffeeb9849 100644
--- a/lib/gitlab/current_settings.rb
+++ b/lib/gitlab/current_settings.rb
@@ -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
diff --git a/lib/gitlab/email/handler/service_desk_handler.rb b/lib/gitlab/email/handler/service_desk_handler.rb
index cab3538a447..05daa08530e 100644
--- a/lib/gitlab/email/handler/service_desk_handler.rb
+++ b/lib/gitlab/email/handler/service_desk_handler.rb
@@ -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)
diff --git a/lib/gitlab/reactive_cache_set_cache.rb b/lib/gitlab/reactive_cache_set_cache.rb
index 8a432edbd78..e62e1172b65 100644
--- a/lib/gitlab/reactive_cache_set_cache.rb
+++ b/lib/gitlab/reactive_cache_set_cache.rb
@@ -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
diff --git a/lib/gitlab/repository_set_cache.rb b/lib/gitlab/repository_set_cache.rb
index f73ac628bce..a20e9845fe6 100644
--- a/lib/gitlab/repository_set_cache.rb
+++ b/lib/gitlab/repository_set_cache.rb
@@ -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)
diff --git a/lib/gitlab/set_cache.rb b/lib/gitlab/set_cache.rb
index 0f2b7b194c9..30cd63e80c0 100644
--- a/lib/gitlab/set_cache.rb
+++ b/lib/gitlab/set_cache.rb
@@ -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
diff --git a/locale/gitlab.pot b/locale/gitlab.pot
index 789dedeaa2a..476cf5abf55 100644
--- a/locale/gitlab.pot
+++ b/locale/gitlab.pot
@@ -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 ""
diff --git a/scripts/trigger-build b/scripts/trigger-build
index 0d8a46bdd2d..f2b7dd0e5a7 100755
--- a/scripts/trigger-build
+++ b/scripts/trigger-build
@@ -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)
diff --git a/spec/controllers/projects_controller_spec.rb b/spec/controllers/projects_controller_spec.rb
index ffe2d393b1e..fdc687496b7 100644
--- a/spec/controllers/projects_controller_spec.rb
+++ b/spec/controllers/projects_controller_spec.rb
@@ -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) }
diff --git a/spec/finders/packages/pypi/package_finder_spec.rb b/spec/finders/packages/pypi/package_finder_spec.rb
index 7d9eb8a5cd1..f065bd21f6d 100644
--- a/spec/finders/packages/pypi/package_finder_spec.rb
+++ b/spec/finders/packages/pypi/package_finder_spec.rb
@@ -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
diff --git a/spec/frontend/commit/pipelines/pipelines_spec.js b/spec/frontend/commit/pipelines/pipelines_spec.js
deleted file mode 100644
index fe928a01acf..00000000000
--- a/spec/frontend/commit/pipelines/pipelines_spec.js
+++ /dev/null
@@ -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();
- });
- });
- });
-});
diff --git a/spec/frontend/commit/pipelines/pipelines_table_spec.js b/spec/frontend/commit/pipelines/pipelines_table_spec.js
new file mode 100644
index 00000000000..4bf6727af3b
--- /dev/null
+++ b/spec/frontend/commit/pipelines/pipelines_table_spec.js
@@ -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.',
+ );
+ });
+ });
+});
diff --git a/spec/frontend/flash_spec.js b/spec/frontend/flash_spec.js
index 6a5ac76a4d0..28e8522cc12 100644
--- a/spec/frontend/flash_spec.js
+++ b/spec/frontend/flash_spec.js
@@ -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) => {
- const flashEl = document.querySelector('.flash');
+ it('removes global flash on click', (done) => {
+ removeFlashClickListener(el, false);
- removeFlashClickListener(flashEl, false);
+ el.querySelector('.js-close-icon').click();
- flashEl.querySelector('.js-close-icon').click();
+ setImmediate(() => {
+ expect(document.querySelector('.flash')).toBeNull();
- setImmediate(() => {
- expect(document.querySelector('.flash')).toBeNull();
+ done();
+ });
+ });
+ });
+
+ describe('without close icon', () => {
+ beforeEach(() => {
+ el = document.createElement('div');
+ el.innerHTML = `
+ <div class="flash-container">
+ <div class="flash">
+ </div>
+ </div>
+ `;
+ });
- done();
+ it('does not throw', () => {
+ expect(() => removeFlashClickListener(el, false)).not.toThrow();
});
});
});
diff --git a/spec/frontend/lib/utils/number_utility_spec.js b/spec/frontend/lib/utils/number_utility_spec.js
index f4483f5098b..e743678ea90 100644
--- a/spec/frontend/lib/utils/number_utility_spec.js
+++ b/spec/frontend/lib/utils/number_utility_spec.js
@@ -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');
});
});
diff --git a/spec/lib/gitlab/current_settings_spec.rb b/spec/lib/gitlab/current_settings_spec.rb
index f5cb1987c5c..a5ab1047a40 100644
--- a/spec/lib/gitlab/current_settings_spec.rb
+++ b/spec/lib/gitlab/current_settings_spec.rb
@@ -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? }
diff --git a/spec/lib/gitlab/email/handler/service_desk_handler_spec.rb b/spec/lib/gitlab/email/handler/service_desk_handler_spec.rb
index 6d26b3e1064..3a60564d8d2 100644
--- a/spec/lib/gitlab/email/handler/service_desk_handler_spec.rb
+++ b/spec/lib/gitlab/email/handler/service_desk_handler_spec.rb
@@ -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
diff --git a/spec/lib/gitlab/repository_set_cache_spec.rb b/spec/lib/gitlab/repository_set_cache_spec.rb
index 881591ae805..9aeb9f11bac 100644
--- a/spec/lib/gitlab/repository_set_cache_spec.rb
+++ b/spec/lib/gitlab/repository_set_cache_spec.rb
@@ -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
diff --git a/spec/migrations/cleanup_move_container_registry_enabled_to_project_features_spec.rb b/spec/migrations/cleanup_move_container_registry_enabled_to_project_features_spec.rb
new file mode 100644
index 00000000000..480730060af
--- /dev/null
+++ b/spec/migrations/cleanup_move_container_registry_enabled_to_project_features_spec.rb
@@ -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
diff --git a/spec/models/namespace_spec.rb b/spec/models/namespace_spec.rb
index ec0507db1b3..fd7125e3edc 100644
--- a/spec/models/namespace_spec.rb
+++ b/spec/models/namespace_spec.rb
@@ -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 )}
diff --git a/spec/models/project_spec.rb b/spec/models/project_spec.rb
index 8e606a00144..6a0bc714731 100644
--- a/spec/models/project_spec.rb
+++ b/spec/models/project_spec.rb
@@ -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
diff --git a/spec/presenters/packages/pypi/package_presenter_spec.rb b/spec/presenters/packages/pypi/package_presenter_spec.rb
index e4d234a4688..25aa5c31034 100644
--- a/spec/presenters/packages/pypi/package_presenter_spec.rb
+++ b/spec/presenters/packages/pypi/package_presenter_spec.rb
@@ -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) }
- describe '#body' do
- subject { presenter.body}
+ let(:file) { package.package_files.first }
+ let(:filename) { file.file_name }
- 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>" }
+ subject(:presenter) { described_class.new(packages, project_or_group).body}
- before do
- package.pypi_metadatum.required_python = python_version
+ describe '#body' do
+ shared_examples_for "pypi package presenter" do
+ where(:version, :expected_version, :with_package1) do
+ '>=2.7' | '&gt;=2.7' | true
+ '"><script>alert(1)</script>' | '&quot;&gt;&lt;script&gt;alert(1)&lt;/script&gt;' | true
+ '>=2.7, !=3.0' | '&gt;=2.7, !=3.0' | false
end
- it { is_expected.to include expected_file }
- end
+ with_them do
+ let(:python_version) { version }
+ let(:expected_python_version) { expected_version }
+ let(:package) { with_package1 ? package1 : package2 }
- it_behaves_like "pypi package presenter" do
- let(:python_version) { '>=2.7' }
- let(:expected_python_version) { '&gt;=2.7' }
- let(:package) { package1 }
+ 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) { '"><script>alert(1)</script>' }
- let(:expected_python_version) { '&quot;&gt;&lt;script&gt;alert(1)&lt;/script&gt;' }
- 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) { '>=2.7, !=3.0' }
- let(:expected_python_version) { '&gt;=2.7, !=3.0' }
- let(:package) { package2 }
+ 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'
end
end
end
diff --git a/spec/requests/api/debian_project_packages_spec.rb b/spec/requests/api/debian_project_packages_spec.rb
index f400b6e928c..c74c0ea1c2a 100644
--- a/spec/requests/api/debian_project_packages_spec.rb
+++ b/spec/requests/api/debian_project_packages_spec.rb
@@ -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" }
diff --git a/spec/requests/api/lint_spec.rb b/spec/requests/api/lint_spec.rb
index f26236e0253..57aa0f36192 100644
--- a/spec/requests/api/lint_spec.rb
+++ b/spec/requests/api/lint_spec.rb
@@ -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) }
diff --git a/spec/requests/api/pypi_packages_spec.rb b/spec/requests/api/pypi_packages_spec.rb
index 718004a0087..4e8c50fedb6 100644
--- a/spec/requests/api/pypi_packages_spec.rb
+++ b/spec/requests/api/pypi_packages_spec.rb
@@ -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
- 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) }
+ 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
- subject { get api(url), headers: headers }
+ it_behaves_like 'job token for package GET requests'
+ end
- it_behaves_like 'PyPI package versions', :developer, :success
+ it_behaves_like 'a pypi user namespace endpoint'
end
- it_behaves_like 'deploy token for package GET requests'
-
- it_behaves_like 'job token for package GET requests'
+ describe 'GET /api/v4/projects/:id/packages/pypi/simple/:package_name' do
+ let(:url) { "/projects/#{project.id}/packages/pypi/simple/#{package.name}" }
- it_behaves_like 'rejects PyPI access with unknown project id'
+ 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
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) }
-
- 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 }
+ 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
- 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') }
+ 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" }
- it_behaves_like 'returning response status', :success
- 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 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) }
+ 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" }
- 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
-
- it_behaves_like 'rejects PyPI access with unknown project id'
end
end
diff --git a/spec/support/shared_examples/requests/api/debian_packages_shared_examples.rb b/spec/support/shared_examples/requests/api/debian_packages_shared_examples.rb
index dfd19167dcd..7c47b900dc0 100644
--- a/spec/support/shared_examples/requests/api/debian_packages_shared_examples.rb
+++ b/spec/support/shared_examples/requests/api/debian_packages_shared_examples.rb
@@ -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')
diff --git a/spec/support/shared_examples/requests/api/pypi_packages_shared_examples.rb b/spec/support/shared_examples/requests/api/pypi_packages_shared_examples.rb
index bbcf856350d..cf749aab79a 100644
--- a/spec/support/shared_examples/requests/api/pypi_packages_shared_examples.rb
+++ b/spec/support/shared_examples/requests/api/pypi_packages_shared_examples.rb
@@ -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
diff --git a/spec/tasks/cache/clear/redis_spec.rb b/spec/tasks/cache/clear/redis_spec.rb
index d2de068f254..9cbb40a77ef 100644
--- a/spec/tasks/cache/clear/redis_spec.rb
+++ b/spec/tasks/cache/clear/redis_spec.rb
@@ -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