summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--.gitlab/ci/review-apps/qa.gitlab-ci.yml2
-rw-r--r--.rubocop_todo/layout/line_length.yml1
-rw-r--r--.rubocop_todo/style/percent_literal_delimiters.yml1
-rw-r--r--GITALY_SERVER_VERSION2
-rw-r--r--app/assets/images/auth_buttons/gitlab_64.pngbin2070 -> 1622 bytes
-rw-r--r--app/assets/javascripts/ide/components/ide_side_bar.vue6
-rw-r--r--app/assets/javascripts/packages_and_registries/container_registry/explorer/components/details_page/tags_list_row.vue1
-rw-r--r--app/controllers/pwa_controller.rb3
-rw-r--r--app/helpers/tooling/visual_review_helper.rb26
-rw-r--r--app/presenters/project_presenter.rb17
-rw-r--r--app/views/layouts/_head.html.haml1
-rw-r--r--app/views/layouts/_visual_review.html.haml1
-rw-r--r--app/views/layouts/application.html.haml1
-rw-r--r--app/views/pwa/manifest.json.erb27
-rw-r--r--app/views/shared/notes/_edit_form.html.haml2
-rw-r--r--config/initializers/1_settings.rb6
-rw-r--r--config/routes.rb1
-rw-r--r--db/optional_migrations/composite_primary_keys.rb63
-rw-r--r--doc/api/features.md8
-rw-r--r--doc/ci/pipelines/cicd_minutes.md2
-rw-r--r--doc/ci/runners/runners_scope.md47
-rw-r--r--doc/user/admin_area/settings/continuous_integration.md21
-rw-r--r--lib/api/environments.rb5
-rw-r--r--lib/api/features.rb13
-rw-r--r--lib/backup/manager.rb90
-rw-r--r--lib/banzai/filter/references/commit_reference_filter.rb7
-rw-r--r--lib/feature.rb30
-rw-r--r--lib/gitlab/content_security_policy/config_loader.rb6
-rw-r--r--lib/gitlab/git.rb7
-rw-r--r--lib/tasks/gitlab/db/lock_writes.rake85
-rw-r--r--lib/tasks/migrate/composite_primary_keys.rake17
-rw-r--r--locale/gitlab.pot8
-rw-r--r--public/-/pwa-icons/logo-192.pngbin0 -> 1855 bytes
-rw-r--r--public/-/pwa-icons/logo-512.pngbin0 -> 3620 bytes
-rw-r--r--public/-/pwa-icons/maskable-logo.pngbin0 -> 9126 bytes
-rw-r--r--qa/qa/page/project/registry/show.rb8
-rw-r--r--qa/qa/resource/project.rb14
-rwxr-xr-xscripts/review_apps/review-apps.sh9
-rw-r--r--spec/features/snippets/notes_on_personal_snippets_spec.rb2
-rw-r--r--spec/frontend/ide/components/ide_side_bar_spec.js4
-rw-r--r--spec/helpers/tooling/visual_review_helper_spec.rb25
-rw-r--r--spec/lib/backup/manager_spec.rb55
-rw-r--r--spec/lib/gitlab/content_security_policy/config_loader_spec.rb10
-rw-r--r--spec/presenters/project_presenter_spec.rb26
-rw-r--r--spec/requests/api/environments_spec.rb27
-rw-r--r--spec/requests/api/features_spec.rb133
-rw-r--r--spec/requests/pwa_controller_spec.rb9
-rw-r--r--spec/support/shared_examples/workers/batched_background_migration_worker_shared_examples.rb7
-rw-r--r--spec/tasks/gitlab/db/lock_writes_rake_spec.rb152
-rw-r--r--spec/views/layouts/application.html.haml_spec.rb29
-rw-r--r--spec/workers/database/batched_background_migration/ci_database_worker_spec.rb2
51 files changed, 744 insertions, 275 deletions
diff --git a/.gitlab/ci/review-apps/qa.gitlab-ci.yml b/.gitlab/ci/review-apps/qa.gitlab-ci.yml
index f026018b61a..9af8b3d9ead 100644
--- a/.gitlab/ci/review-apps/qa.gitlab-ci.yml
+++ b/.gitlab/ci/review-apps/qa.gitlab-ci.yml
@@ -50,6 +50,8 @@ include:
--tag ~orchestrated \
--tag ~transient \
--tag ~skip_signup_disabled \
+ --tag ~requires_git_protocol_v2 \
+ --tag ~requires_praefect \
--force-color \
--order random \
--format documentation \
diff --git a/.rubocop_todo/layout/line_length.yml b/.rubocop_todo/layout/line_length.yml
index a9200ee9fba..adcd5c941a7 100644
--- a/.rubocop_todo/layout/line_length.yml
+++ b/.rubocop_todo/layout/line_length.yml
@@ -1001,7 +1001,6 @@ Layout/LineLength:
- 'db/migrate/20220310101118_update_holder_name_limit.rb'
- 'db/migrate/20220314184209_add_group_fk_to_protected_environment_approval_rules.rb'
- 'db/migrate/20220314204009_add_approval_rule_fk_to_deployment_approvals.rb'
- - 'db/optional_migrations/composite_primary_keys.rb'
- 'db/post_migrate/20210328214434_remove_temporary_index_from_vulnerabilities_table.rb'
- 'db/post_migrate/20210401131948_move_container_registry_enabled_to_project_features2.rb'
- 'db/post_migrate/20210402005225_add_source_and_level_index_on_notification_settings.rb'
diff --git a/.rubocop_todo/style/percent_literal_delimiters.yml b/.rubocop_todo/style/percent_literal_delimiters.yml
index bf50c4c1922..b555882b7ac 100644
--- a/.rubocop_todo/style/percent_literal_delimiters.yml
+++ b/.rubocop_todo/style/percent_literal_delimiters.yml
@@ -229,7 +229,6 @@ Style/PercentLiteralDelimiters:
- 'db/migrate/20210621044000_rename_services_indexes_to_integrations.rb'
- 'db/migrate/20210709085759_index_batched_migration_jobs_by_max_value.rb'
- 'db/migrate/20210928155022_improve_index_for_error_tracking.rb'
- - 'db/optional_migrations/composite_primary_keys.rb'
- 'db/post_migrate/20210329102724_add_new_trail_plans.rb'
- 'db/post_migrate/20210420121149_backfill_conversion_of_ci_job_artifacts.rb'
- 'db/post_migrate/20210426094549_backfill_ci_builds_for_bigint_conversion.rb'
diff --git a/GITALY_SERVER_VERSION b/GITALY_SERVER_VERSION
index 8aa8a81ede5..301f4080bf2 100644
--- a/GITALY_SERVER_VERSION
+++ b/GITALY_SERVER_VERSION
@@ -1 +1 @@
-2106629e3af3e8949b23f20825d6bfee62c10992
+d7eedd059daf9059990a95e53c76e567eac64899
diff --git a/app/assets/images/auth_buttons/gitlab_64.png b/app/assets/images/auth_buttons/gitlab_64.png
index f675678dc9d..860f9c1be9b 100644
--- a/app/assets/images/auth_buttons/gitlab_64.png
+++ b/app/assets/images/auth_buttons/gitlab_64.png
Binary files differ
diff --git a/app/assets/javascripts/ide/components/ide_side_bar.vue b/app/assets/javascripts/ide/components/ide_side_bar.vue
index c3d6494692a..f32d35bf774 100644
--- a/app/assets/javascripts/ide/components/ide_side_bar.vue
+++ b/app/assets/javascripts/ide/components/ide_side_bar.vue
@@ -1,5 +1,5 @@
<script>
-import { GlDeprecatedSkeletonLoading as GlSkeletonLoading } from '@gitlab/ui';
+import { GlSkeletonLoader } from '@gitlab/ui';
import { mapState, mapGetters } from 'vuex';
import { SIDEBAR_INIT_WIDTH, leftSidebarViews } from '../constants';
import ActivityBar from './activity_bar.vue';
@@ -10,7 +10,7 @@ import ResizablePanel from './resizable_panel.vue';
export default {
components: {
- GlSkeletonLoading,
+ GlSkeletonLoader,
ResizablePanel,
ActivityBar,
IdeTree,
@@ -38,7 +38,7 @@ export default {
<template v-if="loading">
<div class="multi-file-commit-panel-inner" data-testid="ide-side-bar-inner">
<div v-for="n in 3" :key="n" class="multi-file-loading-container">
- <gl-skeleton-loading />
+ <gl-skeleton-loader />
</div>
</div>
</template>
diff --git a/app/assets/javascripts/packages_and_registries/container_registry/explorer/components/details_page/tags_list_row.vue b/app/assets/javascripts/packages_and_registries/container_registry/explorer/components/details_page/tags_list_row.vue
index 9176210eb22..acf810257e6 100644
--- a/app/assets/javascripts/packages_and_registries/container_registry/explorer/components/details_page/tags_list_row.vue
+++ b/app/assets/javascripts/packages_and_registries/container_registry/explorer/components/details_page/tags_list_row.vue
@@ -132,6 +132,7 @@ export default {
<div
v-gl-tooltip="{ title: tag.name }"
data-testid="name"
+ data-qa-selector="tag_name_content"
class="gl-text-overflow-ellipsis gl-overflow-hidden gl-white-space-nowrap"
:class="mobileClasses"
>
diff --git a/app/controllers/pwa_controller.rb b/app/controllers/pwa_controller.rb
index ea14dfb27b3..2345182a624 100644
--- a/app/controllers/pwa_controller.rb
+++ b/app/controllers/pwa_controller.rb
@@ -7,6 +7,9 @@ class PwaController < ApplicationController # rubocop:disable Gitlab/NamespacedC
skip_before_action :authenticate_user!
+ def manifest
+ end
+
def offline
end
end
diff --git a/app/helpers/tooling/visual_review_helper.rb b/app/helpers/tooling/visual_review_helper.rb
new file mode 100644
index 00000000000..da6eb3ec434
--- /dev/null
+++ b/app/helpers/tooling/visual_review_helper.rb
@@ -0,0 +1,26 @@
+# frozen_string_literal: true
+
+module Tooling
+ module VisualReviewHelper
+ # Since we only use the visual review toolbar for the gitlab project,
+ # we can hardcode the project ID and project path for now.
+ #
+ # If we need to extend the review apps to other applications in the future,
+ # we should create REVIEW_APPS_PROJECT_ID and REVIEW_APPS_PROJECT_PATH
+ # environment variables (mapped to CI_PROJECT_ID and CI_PROJECT_PATH respectively),
+ # as well as setting `data-require-auth` according to the project visibility.
+ GITLAB_INSTANCE_URL = 'https://gitlab.com'
+ GITLAB_ORG_GITLAB_PROJECT_ID = '278964'
+ GITLAB_ORG_GITLAB_PROJECT_PATH = 'gitlab-org/gitlab'
+
+ def visual_review_toolbar_options
+ { 'data-merge-request-id': "#{ENV['REVIEW_APPS_MERGE_REQUEST_IID']}",
+ 'data-mr-url': "#{GITLAB_INSTANCE_URL}",
+ 'data-project-id': "#{GITLAB_ORG_GITLAB_PROJECT_ID}",
+ 'data-project-path': "#{GITLAB_ORG_GITLAB_PROJECT_PATH}",
+ 'data-require-auth': false,
+ 'id': 'review-app-toolbar-script',
+ 'src': 'https://gitlab.com/assets/webpack/visual_review_toolbar.js' }
+ end
+ end
+end
diff --git a/app/presenters/project_presenter.rb b/app/presenters/project_presenter.rb
index af1b254c46f..84aec19cba0 100644
--- a/app/presenters/project_presenter.rb
+++ b/app/presenters/project_presenter.rb
@@ -28,7 +28,6 @@ class ProjectPresenter < Gitlab::View::Presenter::Delegated
commits_anchor_data,
branches_anchor_data,
tags_anchor_data,
- files_anchor_data,
storage_anchor_data,
releases_anchor_data
].compact.select(&:is_link)
@@ -161,26 +160,16 @@ class ProjectPresenter < Gitlab::View::Presenter::Delegated
can_current_user_push_to_branch?(default_branch)
end
- def files_anchor_data
- AnchorData.new(true,
- statistic_icon('doc-code') +
- _('%{strong_start}%{human_size}%{strong_end} Files').html_safe % {
- human_size: storage_counter(statistics.total_repository_size),
- strong_start: '<strong class="project-stat-value">'.html_safe,
- strong_end: '</strong>'.html_safe
- },
- empty_repo? ? nil : project_tree_path(project))
- end
-
def storage_anchor_data
+ can_show_quota = can?(current_user, :admin_project, project) && !empty_repo?
AnchorData.new(true,
statistic_icon('disk') +
- _('%{strong_start}%{human_size}%{strong_end} Storage').html_safe % {
+ _('%{strong_start}%{human_size}%{strong_end} Project Storage').html_safe % {
human_size: storage_counter(statistics.storage_size),
strong_start: '<strong class="project-stat-value">'.html_safe,
strong_end: '</strong>'.html_safe
},
- empty_repo? ? nil : project_tree_path(project))
+ can_show_quota ? project_usage_quotas_path(project) : nil)
end
def releases_anchor_data
diff --git a/app/views/layouts/_head.html.haml b/app/views/layouts/_head.html.haml
index 55c66454d0b..84eb2706929 100644
--- a/app/views/layouts/_head.html.haml
+++ b/app/views/layouts/_head.html.haml
@@ -68,6 +68,7 @@
%meta{ name: "description", content: page_description }
+ %link{ rel: 'manifest', href: manifest_path(format: :json) }
%meta{ name: 'viewport', content: 'width=device-width, initial-scale=1, maximum-scale=1' }
%meta{ name: 'theme-color', content: user_theme_primary_color }
diff --git a/app/views/layouts/_visual_review.html.haml b/app/views/layouts/_visual_review.html.haml
new file mode 100644
index 00000000000..73da841964a
--- /dev/null
+++ b/app/views/layouts/_visual_review.html.haml
@@ -0,0 +1 @@
+= javascript_tag "", visual_review_toolbar_options
diff --git a/app/views/layouts/application.html.haml b/app/views/layouts/application.html.haml
index bdab5d7ea07..455d18a5ae8 100644
--- a/app/views/layouts/application.html.haml
+++ b/app/views/layouts/application.html.haml
@@ -8,6 +8,7 @@
%body{ class: body_classes, data: body_data }
= render "layouts/init_auto_complete" if @gfm_form
= render "layouts/init_client_detection_flags"
+ = render "layouts/visual_review" if ENV['REVIEW_APPS_ENABLED']
= render 'peek/bar'
= header_message
= render partial: "layouts/header/default", locals: { project: @project, group: @group }
diff --git a/app/views/pwa/manifest.json.erb b/app/views/pwa/manifest.json.erb
new file mode 100644
index 00000000000..557a39ee157
--- /dev/null
+++ b/app/views/pwa/manifest.json.erb
@@ -0,0 +1,27 @@
+{
+ "name": "GitLab",
+ "short_name": "GitLab",
+ "description": "<%= _("The complete DevOps platform. One application with endless possibilities. Organizations rely on GitLab’s source code management, CI/CD, security, and more to deliver software rapidly.") %>",
+ "start_url": "<%= explore_projects_path %>",
+ "scope": "<%= root_path %>",
+ "display": "browser",
+ "orientation": "any",
+ "background_color": "#fff",
+ "theme_color": "<%= user_theme_primary_color %>",
+ "icons": [{
+ "src": "<%= Gitlab::Utils.append_path(Gitlab.config.gitlab.relative_url_root, '/-/pwa-icons/logo-192.png') %>",
+ "sizes": "192x192",
+ "type": "image/png"
+ },
+ {
+ "src": "<%= Gitlab::Utils.append_path(Gitlab.config.gitlab.relative_url_root, '/-/pwa-icons/logo-512.png') %>",
+ "sizes": "512x512",
+ "type": "image/png"
+ },
+ {
+ "src": "<%= Gitlab::Utils.append_path(Gitlab.config.gitlab.relative_url_root, '/-/pwa-icons/maskable-logo.png') %>",
+ "sizes": "512x512",
+ "type": "image/png",
+ "purpose": "maskable"
+ }]
+}
diff --git a/app/views/shared/notes/_edit_form.html.haml b/app/views/shared/notes/_edit_form.html.haml
index 63c895a5a03..b41ed8f63e4 100644
--- a/app/views/shared/notes/_edit_form.html.haml
+++ b/app/views/shared/notes/_edit_form.html.haml
@@ -9,6 +9,6 @@
.note-form-actions.clearfix
.settings-message.note-edit-warning.js-finish-edit-warning
= _("Finish editing this message first!")
- = submit_tag _('Save comment'), class: 'gl-button btn btn-success js-comment-save-button', data: { qa_selector: 'save_comment_button' }
+ = submit_tag _('Save comment'), class: 'gl-button btn btn-confirm js-comment-save-button', data: { qa_selector: 'save_comment_button' }
%button.btn.gl-button.btn-cancel.note-edit-cancel{ type: 'button' }
= _("Cancel")
diff --git a/config/initializers/1_settings.rb b/config/initializers/1_settings.rb
index 2a9ce327d9d..0bcaf0a1454 100644
--- a/config/initializers/1_settings.rb
+++ b/config/initializers/1_settings.rb
@@ -629,6 +629,9 @@ Settings.cron_jobs['projects_schedule_refresh_build_artifacts_size_statistics_wo
Settings.cron_jobs['inactive_projects_deletion_cron_worker'] ||= Settingslogic.new({})
Settings.cron_jobs['inactive_projects_deletion_cron_worker']['cron'] ||= '0 1 * * *'
Settings.cron_jobs['inactive_projects_deletion_cron_worker']['job_class'] = 'Projects::InactiveProjectsDeletionCronWorker'
+Settings.cron_jobs['loose_foreign_keys_cleanup_worker'] ||= Settingslogic.new({})
+Settings.cron_jobs['loose_foreign_keys_cleanup_worker']['cron'] ||= '*/1 * * * *'
+Settings.cron_jobs['loose_foreign_keys_cleanup_worker']['job_class'] = 'LooseForeignKeys::CleanupWorker'
Gitlab.ee do
Settings.cron_jobs['analytics_devops_adoption_create_all_snapshots_worker'] ||= Settingslogic.new({})
@@ -760,9 +763,6 @@ Gitlab.ee do
Settings.cron_jobs['app_sec_dast_profile_schedule_worker'] ||= Settingslogic.new({})
Settings.cron_jobs['app_sec_dast_profile_schedule_worker']['cron'] ||= '7-59/15 * * * *'
Settings.cron_jobs['app_sec_dast_profile_schedule_worker']['job_class'] = 'AppSec::Dast::ProfileScheduleWorker'
- Settings.cron_jobs['loose_foreign_keys_cleanup_worker'] ||= Settingslogic.new({})
- Settings.cron_jobs['loose_foreign_keys_cleanup_worker']['cron'] ||= '*/1 * * * *'
- Settings.cron_jobs['loose_foreign_keys_cleanup_worker']['job_class'] = 'LooseForeignKeys::CleanupWorker'
Settings.cron_jobs['ci_namespace_mirrors_consistency_check_worker'] ||= Settingslogic.new({})
Settings.cron_jobs['ci_namespace_mirrors_consistency_check_worker']['cron'] ||= '*/4 * * * *'
Settings.cron_jobs['ci_namespace_mirrors_consistency_check_worker']['job_class'] = 'Database::CiNamespaceMirrorsConsistencyCheckWorker'
diff --git a/config/routes.rb b/config/routes.rb
index 20098c422ec..30fdaca78aa 100644
--- a/config/routes.rb
+++ b/config/routes.rb
@@ -117,6 +117,7 @@ Rails.application.routes.draw do
get '/whats_new' => 'whats_new#index'
get 'offline' => "pwa#offline"
+ get 'manifest' => "pwa#manifest", constraints: lambda { |req| req.format == :json }
# '/-/health' implemented by BasicHealthCheck middleware
get 'liveness' => 'health#liveness'
diff --git a/db/optional_migrations/composite_primary_keys.rb b/db/optional_migrations/composite_primary_keys.rb
deleted file mode 100644
index 13bc58b8692..00000000000
--- a/db/optional_migrations/composite_primary_keys.rb
+++ /dev/null
@@ -1,63 +0,0 @@
-# frozen_string_literal: true
-
-# This migration adds a primary key constraint to tables
-# that only have a composite unique key.
-#
-# This is not strictly relevant to Rails (v4 does not
-# support composite primary keys). However this becomes
-# useful for e.g. PostgreSQL's logical replication (pglogical)
-# which requires all tables to have a primary key constraint.
-#
-# In that sense, the migration is optional and not strictly needed.
-class CompositePrimaryKeysMigration < ActiveRecord::Migration[4.2]
- include Gitlab::Database::MigrationHelpers
-
- DOWNTIME = false
-
- Index = Struct.new(:table, :name, :columns)
-
- TABLES = [
- Index.new(:issue_assignees, 'index_issue_assignees_on_issue_id_and_user_id', %i(issue_id user_id)),
- Index.new(:user_interacted_projects, 'index_user_interacted_projects_on_project_id_and_user_id', %i(project_id user_id)),
- Index.new(:merge_request_diff_files, 'index_merge_request_diff_files_on_mr_diff_id_and_order', %i(merge_request_diff_id relative_order)),
- Index.new(:merge_request_diff_commits, 'index_merge_request_diff_commits_on_mr_diff_id_and_order', %i(merge_request_diff_id relative_order)),
- Index.new(:project_authorizations, 'index_project_authorizations_on_user_id_project_id_access_level', %i(user_id project_id access_level)),
- Index.new(:push_event_payloads, 'index_push_event_payloads_on_event_id', %i(event_id)),
- Index.new(:schema_migrations, 'unique_schema_migrations', %(version))
- ].freeze
-
- disable_ddl_transaction!
-
- def up
- disable_statement_timeout do
- TABLES.each do |index|
- add_primary_key(index)
- end
- end
- end
-
- def down
- disable_statement_timeout do
- TABLES.each do |index|
- remove_primary_key(index)
- end
- end
- end
-
- private
-
- def add_primary_key(index)
- execute "ALTER TABLE #{index.table} ADD PRIMARY KEY USING INDEX #{index.name}"
- end
-
- def remove_primary_key(index)
- temp_index_name = "#{index.name[0..58]}_old"
- rename_index index.table, index.name, temp_index_name if index_exists_by_name?(index.table, index.name)
-
- # re-create unique key index
- add_concurrent_index index.table, index.columns, unique: true, name: index.name
-
- # This also drops the `temp_index_name` as this is owned by the constraint
- execute "ALTER TABLE #{index.table} DROP CONSTRAINT IF EXISTS #{temp_index_name}"
- end
-end
diff --git a/doc/api/features.md b/doc/api/features.md
index 346f4879358..d4829f72958 100644
--- a/doc/api/features.md
+++ b/doc/api/features.md
@@ -127,10 +127,10 @@ POST /features/:name
| `value` | integer/string | yes | `true` or `false` to enable/disable, or an integer for percentage of time |
| `key` | string | no | `percentage_of_actors` or `percentage_of_time` (default) |
| `feature_group` | string | no | A Feature group name |
-| `user` | string | no | A GitLab username |
-| `group` | string | no | A GitLab group's path, for example `gitlab-org` |
-| `namespace` | string | no | A GitLab group or user namespace's path, for example `gitlab-org` or username path. [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/353117) in GitLab 15.0. |
-| `project` | string | no | A projects path, for example `gitlab-org/gitlab-foss` |
+| `user` | string | no | A GitLab username or comma-separated multiple usernames |
+| `group` | string | no | A GitLab group's path, for example `gitlab-org`, or comma-separated multiple group paths |
+| `namespace` | string | no | A GitLab group or user namespace's path, for example `john-doe`, or comma-separated multiple namespace paths. [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/353117) in GitLab 15.0. |
+| `project` | string | no | A projects path, for example `gitlab-org/gitlab-foss`, or comma-separated multiple project paths |
| `force` | boolean | no | Skip feature flag validation checks, such as a YAML definition |
You can enable or disable a feature for a `feature_group`, a `user`,
diff --git a/doc/ci/pipelines/cicd_minutes.md b/doc/ci/pipelines/cicd_minutes.md
index 2b18b1d353b..e211f76e02b 100644
--- a/doc/ci/pipelines/cicd_minutes.md
+++ b/doc/ci/pipelines/cicd_minutes.md
@@ -74,6 +74,8 @@ If you set a quota for a subgroup, it is not used.
## View CI/CD minutes used by a group
+> Displaying shared runners duration per project [introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/355666) in GitLab 15.0.
+
You can view the number of CI/CD minutes being used by a group.
Prerequisite:
diff --git a/doc/ci/runners/runners_scope.md b/doc/ci/runners/runners_scope.md
index 6082a17d001..01032c116df 100644
--- a/doc/ci/runners/runners_scope.md
+++ b/doc/ci/runners/runners_scope.md
@@ -226,33 +226,47 @@ A fork *does* copy the CI/CD settings of the cloned repository.
### Create a specific runner
You can create a specific runner for your self-managed GitLab instance or for GitLab.com.
-You must have the Owner role for the project.
+
+Prerequisite:
+
+- You must have at least the Maintainer role for the project.
To create a specific runner:
-1. [Install runner](https://docs.gitlab.com/runner/install/).
-1. Go to the project's **Settings > CI/CD** and expand the **Runners** section.
-1. Note the URL and token.
+1. [Install GitLab Runner](https://docs.gitlab.com/runner/install/).
+1. On the top bar, select **Menu > Projects** and find the project where you want to use the runner.
+1. On the left sidebar, select **Settings > CI/CD**.
+1. Expand **Runners**.
+1. In the **Specific runners** section, note the URL and token.
1. [Register the runner](https://docs.gitlab.com/runner/register/).
-### Enable a specific runner for a specific project
+The runner is now enabled for the project.
-A specific runner is available in the project it was created for. An administrator can
-enable a specific runner to apply to additional projects.
+### Enable a specific runner for a different project
-- You must have the Owner role for the
- project.
+After a specific runner is created, you can enable it for other projects.
+
+Prerequisites:
+You must have at least the Maintainer role for:
+
+- The project where the runner is already enabled.
+- The project where you want to enable the runner.
- The specific runner must not be [locked](#prevent-a-specific-runner-from-being-enabled-for-other-projects).
-To enable or disable a specific runner for a project:
+To enable a specific runner for a project:
-1. Go to the project's **Settings > CI/CD** and expand the **Runners** section.
-1. Click **Enable for this project** or **Disable for this project**.
+1. On the top bar, select **Menu > Projects** and find the project where you want to enable the runner.
+1. On the left sidebar, select **Settings > CI/CD**.
+1. Expand **General pipelines**.
+1. Expand **Runners**.
+1. By the runner you want, select **Enable for this project**.
You can edit a specific runner from any of the projects it's enabled for.
-The modifications, which include unlocking, editing tags and the description,
+The modifications, which include unlocking and editing tags and the description,
affect all projects that use the runner.
+An administrator can [enable the runner for multiple projects](../../user/admin_area/settings/continuous_integration.md#enable-a-specific-runner-for-multiple-projects).
+
### Prevent a specific runner from being enabled for other projects
You can configure a specific runner so it is "locked" and cannot be enabled for other projects.
@@ -261,8 +275,9 @@ but can also be changed later.
To lock or unlock a specific runner:
-1. Go to the project's **Settings > CI/CD** and expand the **Runners** section.
+1. Go to the project's **Settings > CI/CD**.
+1. Expand the **Runners** section.
1. Find the specific runner you want to lock or unlock. Make sure it's enabled. You cannot lock shared or group runners.
-1. Click the pencil button.
+1. Select **Edit** (**{pencil}**).
1. Check the **Lock to current projects** option.
-1. Click **Save changes**.
+1. Select **Save changes**.
diff --git a/doc/user/admin_area/settings/continuous_integration.md b/doc/user/admin_area/settings/continuous_integration.md
index 170d3cf4c90..7f37c99259a 100644
--- a/doc/user/admin_area/settings/continuous_integration.md
+++ b/doc/user/admin_area/settings/continuous_integration.md
@@ -39,6 +39,23 @@ You can set all new projects to have the instance's shared runners available by
Any time a new project is created, the shared runners are available.
+## Shared runners CI/CD minutes
+
+As an administrator you can set either a global or namespace-specific
+limit on the number of [CI/CD minutes](../../../ci/pipelines/cicd_minutes.md) you can use.
+
+## Enable a specific runner for multiple projects
+
+To enable a specific runner for one or more projects:
+
+1. On the top bar, select **Menu > Admin**.
+1. From the left sidebar, select **Overview > Runners**.
+1. Select the runner you want to edit.
+1. In the top right, select **Edit** (**{pencil}**).
+1. Under **Restrict projects for this runner**, search for a project.
+1. To the left of the project, select **Enable**.
+1. Repeat this process for each additional project.
+
## Add a message for shared runners
To display details about the instance's shared runners in all projects'
@@ -143,10 +160,6 @@ A new pipeline must run before the latest artifacts can expire and be deleted.
NOTE:
All application settings have a [customizable cache expiry interval](../../../administration/application_settings_cache.md) which can delay the settings affect.
-## Shared runners CI/CD minutes
-
-As an administrator you can set either a global or namespace-specific limit on the number of [CI/CD minutes](../../../ci/pipelines/cicd_minutes.md) you can use.
-
## Archive jobs
Archiving jobs is useful for reducing the CI/CD footprint on the system by removing some
diff --git a/lib/api/environments.rb b/lib/api/environments.rb
index 11f1cab0c72..efc86bdc19a 100644
--- a/lib/api/environments.rb
+++ b/lib/api/environments.rb
@@ -22,16 +22,15 @@ module API
use :pagination
optional :name, type: String, desc: 'Returns the environment with this name'
optional :search, type: String, desc: 'Returns list of environments matching the search criteria'
+ optional :states, type: String, values: Environment.valid_states.map(&:to_s), desc: 'List all environments that match a specific state'
mutually_exclusive :name, :search, message: 'cannot be used together'
end
get ':id/environments' do
authorize! :read_environment, user_project
- environments = ::Environments::EnvironmentsFinder.new(user_project, current_user, params).execute
+ environments = ::Environments::EnvironmentsFinder.new(user_project, current_user, declared_params(include_missing: false)).execute
present paginate(environments), with: Entities::Environment, current_user: current_user
- rescue ::Environments::EnvironmentsFinder::InvalidStatesError => exception
- bad_request!(exception.message)
end
desc 'Creates a new environment' do
diff --git a/lib/api/features.rb b/lib/api/features.rb
index bff2817a2ec..13a6aedc2df 100644
--- a/lib/api/features.rb
+++ b/lib/api/features.rb
@@ -68,10 +68,13 @@ module API
requires :value, type: String, desc: '`true` or `false` to enable/disable, a float for percentage of time'
optional :key, type: String, desc: '`percentage_of_actors` or the default `percentage_of_time`'
optional :feature_group, type: String, desc: 'A Feature group name'
- optional :user, type: String, desc: 'A GitLab username'
- optional :group, type: String, desc: "A GitLab group's path, such as 'gitlab-org'"
- optional :namespace, type: String, desc: "A GitLab group or user namespace path, such as 'gitlab-org'"
- optional :project, type: String, desc: 'A projects path, like gitlab-org/gitlab-ce'
+ optional :user, type: String, desc: 'A GitLab username or comma-separated multiple usernames'
+ optional :group, type: String,
+ desc: "A GitLab group's path, such as 'gitlab-org', or comma-separated multiple group paths"
+ optional :namespace, type: String,
+ desc: "A GitLab group or user namespace path, such as 'john-doe', or comma-separated multiple namespace paths"
+ optional :project, type: String,
+ desc: "A projects path, such as `gitlab-org/gitlab-ce`, or comma-separated multiple project paths"
optional :force, type: Boolean, desc: 'Skip feature flag validation checks, ie. YAML definition'
mutually_exclusive :key, :feature_group
@@ -110,6 +113,8 @@ module API
present Feature.get(params[:name]), # rubocop:disable Gitlab/AvoidFeatureGet
with: Entities::Feature, current_user: current_user
+ rescue Feature::Target::UnknowTargetError => e
+ bad_request!(e.message)
end
desc 'Remove the gate value for the given feature'
diff --git a/lib/backup/manager.rb b/lib/backup/manager.rb
index 0991177d044..f249becb8f9 100644
--- a/lib/backup/manager.rb
+++ b/lib/backup/manager.rb
@@ -41,29 +41,8 @@ module Backup
end
def create
- if incremental?
- unpack(ENV.fetch('PREVIOUS_BACKUP', ENV['BACKUP']))
- read_backup_information
- verify_backup_version
- update_backup_information
- end
-
- build_backup_information
-
- definitions.keys.each do |task_name|
- run_create_task(task_name)
- end
-
- write_backup_information
-
- if skipped?('tar')
- upload
- else
- pack
- upload
- cleanup
- remove_old
- end
+ unpack(ENV.fetch('PREVIOUS_BACKUP', ENV['BACKUP'])) if incremental?
+ run_all_create_tasks
puts_time "Warning: Your gitlab.rb and gitlab-secrets.json files contain sensitive data \n" \
"and are not included in this backup. You will need these files to restore a backup.\n" \
@@ -95,22 +74,8 @@ module Backup
end
def restore
- cleanup_required = unpack(ENV['BACKUP'])
- read_backup_information
- verify_backup_version
-
- definitions.keys.each do |task_name|
- run_restore_task(task_name) if !skipped?(task_name) && enabled_task?(task_name)
- end
-
- Rake::Task['gitlab:shell:setup'].invoke
- Rake::Task['cache:clear'].invoke
-
- if cleanup_required
- cleanup
- end
-
- remove_tmp
+ unpack(ENV['BACKUP'])
+ run_all_restore_tasks
puts_time "Warning: Your gitlab.rb and gitlab-secrets.json files contain sensitive data \n" \
"and are not included in this backup. You will need to restore these files manually.".color(:red)
@@ -232,6 +197,48 @@ module Backup
Files.new(progress, app_files_dir, excludes: excludes)
end
+ def run_all_create_tasks
+ if incremental?
+ read_backup_information
+ verify_backup_version
+ update_backup_information
+ end
+
+ build_backup_information
+
+ definitions.keys.each do |task_name|
+ run_create_task(task_name)
+ end
+
+ write_backup_information
+
+ unless skipped?('tar')
+ pack
+ upload
+ remove_old
+ end
+
+ ensure
+ cleanup unless skipped?('tar')
+ remove_tmp
+ end
+
+ def run_all_restore_tasks
+ read_backup_information
+ verify_backup_version
+
+ definitions.keys.each do |task_name|
+ run_restore_task(task_name) if !skipped?(task_name) && enabled_task?(task_name)
+ end
+
+ Rake::Task['gitlab:shell:setup'].invoke
+ Rake::Task['cache:clear'].invoke
+
+ ensure
+ cleanup unless skipped?('tar')
+ remove_tmp
+ end
+
def incremental?
@incremental
end
@@ -299,7 +306,7 @@ module Backup
def upload
connection_settings = Gitlab.config.backup.upload.connection
- if connection_settings.blank? || skipped?('remote')
+ if connection_settings.blank? || skipped?('remote') || skipped?('tar')
puts_time "Uploading backup archive to remote storage #{remote_directory} ... ".color(:blue) + "[SKIPPED]".color(:cyan)
return
end
@@ -405,8 +412,7 @@ module Backup
def unpack(source_backup_id)
if source_backup_id.blank? && non_tarred_backup?
puts_time "Non tarred backup found in #{backup_path}, using that"
-
- return false
+ return
end
Dir.chdir(backup_path) do
diff --git a/lib/banzai/filter/references/commit_reference_filter.rb b/lib/banzai/filter/references/commit_reference_filter.rb
index 157dc696cc8..86ab8597cf5 100644
--- a/lib/banzai/filter/references/commit_reference_filter.rb
+++ b/lib/banzai/filter/references/commit_reference_filter.rb
@@ -19,7 +19,12 @@ module Banzai
def find_object(project, id)
return unless project.is_a?(Project) && project.valid_repo?
- _, record = reference_cache.records_per_parent[project].detect { |k, _v| Gitlab::Git.shas_eql?(k, id) }
+ # Optimization: try exact commit hash match first
+ record = reference_cache.records_per_parent[project].fetch(id, nil)
+
+ unless record
+ _, record = reference_cache.records_per_parent[project].detect { |k, _v| Gitlab::Git.shas_eql?(k, id) }
+ end
record
end
diff --git a/lib/feature.rb b/lib/feature.rb
index b5a97ee8f9b..3bba4be7514 100644
--- a/lib/feature.rb
+++ b/lib/feature.rb
@@ -281,6 +281,8 @@ class Feature
end
class Target
+ UnknowTargetError = Class.new(StandardError)
+
attr_reader :params
def initialize(params)
@@ -292,7 +294,7 @@ class Feature
end
def targets
- [feature_group, user, project, group, namespace].compact
+ [feature_group, users, projects, groups, namespaces].flatten.compact
end
private
@@ -305,29 +307,37 @@ class Feature
end
# rubocop: enable CodeReuse/ActiveRecord
- def user
+ def users
return unless params.key?(:user)
- UserFinder.new(params[:user]).find_by_username!
+ params[:user].split(',').map do |arg|
+ UserFinder.new(arg).find_by_username || (raise UnknowTargetError, "#{arg} is not found!")
+ end
end
- def project
+ def projects
return unless params.key?(:project)
- Project.find_by_full_path(params[:project])
+ params[:project].split(',').map do |arg|
+ Project.find_by_full_path(arg) || (raise UnknowTargetError, "#{arg} is not found!")
+ end
end
- def group
+ def groups
return unless params.key?(:group)
- Group.find_by_full_path(params[:group])
+ params[:group].split(',').map do |arg|
+ Group.find_by_full_path(arg) || (raise UnknowTargetError, "#{arg} is not found!")
+ end
end
- def namespace
+ def namespaces
return unless params.key?(:namespace)
- # We are interested in Group or UserNamespace
- Namespace.without_project_namespaces.find_by_full_path(params[:namespace])
+ params[:namespace].split(',').map do |arg|
+ # We are interested in Group or UserNamespace
+ Namespace.without_project_namespaces.find_by_full_path(arg) || (raise UnknowTargetError, "#{arg} is not found!")
+ end
end
end
end
diff --git a/lib/gitlab/content_security_policy/config_loader.rb b/lib/gitlab/content_security_policy/config_loader.rb
index 521dec110a8..574a7dceaa4 100644
--- a/lib/gitlab/content_security_policy/config_loader.rb
+++ b/lib/gitlab/content_security_policy/config_loader.rb
@@ -44,6 +44,7 @@ module Gitlab
allow_sentry(directives) if Gitlab.config.sentry&.enabled && Gitlab.config.sentry&.clientside_dsn
allow_framed_gitlab_paths(directives)
allow_customersdot(directives) if ENV['CUSTOMER_PORTAL_URL'].present?
+ allow_review_apps(directives) if ENV['REVIEW_APPS_ENABLED']
# The follow section contains workarounds to patch Safari's lack of support for CSP Level 3
# See https://gitlab.com/gitlab-org/gitlab/-/issues/343579
@@ -154,6 +155,11 @@ module Gitlab
append_to_directive(directives, 'frame_src', Gitlab::Utils.append_path(Gitlab.config.gitlab.url, path))
end
end
+
+ def self.allow_review_apps(directives)
+ # Allow-listed to allow POSTs to https://gitlab.com/api/v4/projects/278964/merge_requests/:merge_request_iid/visual_review_discussions
+ append_to_directive(directives, 'connect_src', 'https://gitlab.com/api/v4/projects/278964/merge_requests/')
+ end
end
end
end
diff --git a/lib/gitlab/git.rb b/lib/gitlab/git.rb
index 2da30b88d55..505d0b8d728 100644
--- a/lib/gitlab/git.rb
+++ b/lib/gitlab/git.rb
@@ -87,7 +87,12 @@ module Gitlab
length = [sha1.length, sha2.length].min
return false if length < Gitlab::Git::Commit::MIN_SHA_LENGTH
- sha1[0, length] == sha2[0, length]
+ # Optimization: prevent unnecessary substring creation
+ if sha1.length == sha2.length
+ sha1 == sha2
+ else
+ sha1[0, length] == sha2[0, length]
+ end
end
end
end
diff --git a/lib/tasks/gitlab/db/lock_writes.rake b/lib/tasks/gitlab/db/lock_writes.rake
new file mode 100644
index 00000000000..1d1a67aa2a1
--- /dev/null
+++ b/lib/tasks/gitlab/db/lock_writes.rake
@@ -0,0 +1,85 @@
+# frozen_string_literal: true
+
+namespace :gitlab do
+ namespace :db do
+ TRIGGER_FUNCTION_NAME = 'gitlab_schema_prevent_write'
+
+ desc "GitLab | DB | Install prevent write triggers on all databases"
+ task lock_writes: [:environment, 'gitlab:db:validate_config'] do
+ Gitlab::Database::EachDatabase.each_database_connection do |connection, database_name|
+ create_write_trigger_function(connection)
+
+ schemas_for_connection = Gitlab::Database.gitlab_schemas_for_connection(connection)
+ Gitlab::Database::GitlabSchema.tables_to_schema.each do |table_name, schema_name|
+ connection.transaction do
+ if schemas_for_connection.include?(schema_name.to_sym)
+ drop_write_trigger(database_name, connection, table_name)
+ else
+ create_write_trigger(database_name, connection, table_name)
+ end
+ end
+ end
+ end
+ end
+
+ desc "GitLab | DB | Remove all triggers that prevents writes from all databases"
+ task unlock_writes: :environment do
+ Gitlab::Database::EachDatabase.each_database_connection do |connection, database_name|
+ Gitlab::Database::GitlabSchema.tables_to_schema.each do |table_name, schema_name|
+ drop_write_trigger(database_name, connection, table_name)
+ end
+ drop_write_trigger_function(connection)
+ end
+ end
+
+ def create_write_trigger_function(connection)
+ sql = <<-SQL
+ CREATE OR REPLACE FUNCTION #{TRIGGER_FUNCTION_NAME}()
+ RETURNS TRIGGER AS
+ $$
+ BEGIN
+ RAISE EXCEPTION 'Table: "%" is write protected within this Gitlab database.', TG_TABLE_NAME
+ USING ERRCODE = 'modifying_sql_data_not_permitted',
+ HINT = 'Make sure you are using the right database connection';
+ END
+ $$ LANGUAGE PLPGSQL
+ SQL
+
+ connection.execute(sql)
+ end
+
+ def drop_write_trigger_function(connection)
+ sql = <<-SQL
+ DROP FUNCTION IF EXISTS #{TRIGGER_FUNCTION_NAME}()
+ SQL
+
+ connection.execute(sql)
+ end
+
+ def create_write_trigger(database_name, connection, table_name)
+ puts "#{database_name}: '#{table_name}'... Lock Writes".color(:red)
+ sql = <<-SQL
+ DROP TRIGGER IF EXISTS #{write_trigger_name(table_name)} ON #{table_name};
+ CREATE TRIGGER #{write_trigger_name(table_name)}
+ BEFORE INSERT OR UPDATE OR DELETE OR TRUNCATE
+ ON #{table_name}
+ FOR EACH STATEMENT EXECUTE FUNCTION #{TRIGGER_FUNCTION_NAME}();
+ SQL
+
+ connection.execute(sql)
+ end
+
+ def drop_write_trigger(database_name, connection, table_name)
+ puts "#{database_name}: '#{table_name}'... Allow Writes".color(:green)
+ sql = <<-SQL
+ DROP TRIGGER IF EXISTS #{write_trigger_name(table_name)} ON #{table_name}
+ SQL
+
+ connection.execute(sql)
+ end
+
+ def write_trigger_name(table_name)
+ "gitlab_schema_write_trigger_for_#{table_name}"
+ end
+ end
+end
diff --git a/lib/tasks/migrate/composite_primary_keys.rake b/lib/tasks/migrate/composite_primary_keys.rake
deleted file mode 100644
index 68f7c4d6c4a..00000000000
--- a/lib/tasks/migrate/composite_primary_keys.rake
+++ /dev/null
@@ -1,17 +0,0 @@
-# frozen_string_literal: true
-
-namespace :gitlab do
- namespace :db do
- desc 'GitLab | DB | Adds primary keys to tables that only have composite unique keys'
- task composite_primary_keys_add: :environment do
- require Rails.root.join('db/optional_migrations/composite_primary_keys')
- CompositePrimaryKeysMigration.new.up
- end
-
- desc 'GitLab | DB | Removes previously added composite primary keys'
- task composite_primary_keys_drop: :environment do
- require Rails.root.join('db/optional_migrations/composite_primary_keys')
- CompositePrimaryKeysMigration.new.down
- end
- end
-end
diff --git a/locale/gitlab.pot b/locale/gitlab.pot
index 558bc7a43fe..a4d299b455b 100644
--- a/locale/gitlab.pot
+++ b/locale/gitlab.pot
@@ -1008,10 +1008,7 @@ msgid_plural "%{strong_start}%{count} members%{strong_end} must approve to merge
msgstr[0] ""
msgstr[1] ""
-msgid "%{strong_start}%{human_size}%{strong_end} Files"
-msgstr ""
-
-msgid "%{strong_start}%{human_size}%{strong_end} Storage"
+msgid "%{strong_start}%{human_size}%{strong_end} Project Storage"
msgstr ""
msgid "%{strong_start}%{release_count}%{strong_end} Release"
@@ -37702,6 +37699,9 @@ msgstr ""
msgid "The comparison view may be inaccurate due to merge conflicts."
msgstr ""
+msgid "The complete DevOps platform. One application with endless possibilities. Organizations rely on GitLab’s source code management, CI/CD, security, and more to deliver software rapidly."
+msgstr ""
+
msgid "The compliance report shows the merge request violations merged in protected environments."
msgstr ""
diff --git a/public/-/pwa-icons/logo-192.png b/public/-/pwa-icons/logo-192.png
new file mode 100644
index 00000000000..745bb6e3628
--- /dev/null
+++ b/public/-/pwa-icons/logo-192.png
Binary files differ
diff --git a/public/-/pwa-icons/logo-512.png b/public/-/pwa-icons/logo-512.png
new file mode 100644
index 00000000000..90cc18986a2
--- /dev/null
+++ b/public/-/pwa-icons/logo-512.png
Binary files differ
diff --git a/public/-/pwa-icons/maskable-logo.png b/public/-/pwa-icons/maskable-logo.png
new file mode 100644
index 00000000000..1b6379795b9
--- /dev/null
+++ b/public/-/pwa-icons/maskable-logo.png
Binary files differ
diff --git a/qa/qa/page/project/registry/show.rb b/qa/qa/page/project/registry/show.rb
index 270445560be..95850f34962 100644
--- a/qa/qa/page/project/registry/show.rb
+++ b/qa/qa/page/project/registry/show.rb
@@ -11,10 +11,8 @@ module QA
view 'app/assets/javascripts/packages_and_registries/container_registry/explorer/components/details_page/tags_list_row.vue' do
element :more_actions_menu
- end
-
- view 'app/assets/javascripts/packages_and_registries/container_registry/explorer/components/details_page/tags_list_row.vue' do
element :tag_delete_button
+ element :tag_name_content
end
def has_registry_repository?(name)
@@ -26,11 +24,11 @@ module QA
end
def has_tag?(tag_name)
- has_button?(tag_name)
+ has_element?(:tag_name_content, text: tag_name)
end
def has_no_tag?(tag_name)
- has_no_button?(tag_name)
+ has_no_element?(:tag_name_content, text: tag_name)
end
def click_delete
diff --git a/qa/qa/resource/project.rb b/qa/qa/resource/project.rb
index 2db4f4b5f65..59964c5833d 100644
--- a/qa/qa/resource/project.rb
+++ b/qa/qa/resource/project.rb
@@ -7,14 +7,15 @@ module QA
include Members
include Visibility
- attr_accessor :repository_storage, # requires admin access
- :initialize_with_readme,
+ attr_accessor :initialize_with_readme,
:auto_devops_enabled,
:github_personal_access_token,
:github_repository_path,
:gitlab_repository_path,
:personal_namespace
+ attr_reader :repository_storage
+
attributes :id,
:name,
:path,
@@ -70,6 +71,15 @@ module QA
@name = @add_name_uuid ? "#{raw_name}-#{SecureRandom.hex(8)}" : raw_name
end
+ # Sets the project's repository storage
+ # This feature requires admin access so be sure to fabricate the project as an admin user, and add the metadata
+ # `:requires_admin` to the test it's used in.
+ def repository_storage=(name)
+ raise ArgumentError, "Please provide a valid repository storage name" if name.to_s.empty?
+
+ @repository_storage = name
+ end
+
def fabricate!
return if @import
diff --git a/scripts/review_apps/review-apps.sh b/scripts/review_apps/review-apps.sh
index 829e806e378..b6ac7b4281b 100755
--- a/scripts/review_apps/review-apps.sh
+++ b/scripts/review_apps/review-apps.sh
@@ -297,12 +297,21 @@ function deploy() {
create_application_secret
+cat > review_apps.values.yml <<EOF
+ gitlab:
+ webservice:
+ extraEnv:
+ REVIEW_APPS_ENABLED: "true"
+ REVIEW_APPS_MERGE_REQUEST_IID: "${CI_MERGE_REQUEST_IID}"
+EOF
+
HELM_CMD=$(cat << EOF
helm upgrade \
--namespace="${namespace}" \
--create-namespace \
--install \
--wait \
+ -f review_apps.values.yml \
--timeout "${HELM_INSTALL_TIMEOUT:-20m}" \
--set ci.branch="${CI_COMMIT_REF_NAME}" \
--set ci.commit.sha="${CI_COMMIT_SHORT_SHA}" \
diff --git a/spec/features/snippets/notes_on_personal_snippets_spec.rb b/spec/features/snippets/notes_on_personal_snippets_spec.rb
index 6bd31d7314c..97bb4c23d25 100644
--- a/spec/features/snippets/notes_on_personal_snippets_spec.rb
+++ b/spec/features/snippets/notes_on_personal_snippets_spec.rb
@@ -124,7 +124,7 @@ RSpec.describe 'Comments on personal snippets', :js do
page.within('.current-note-edit-form') do
fill_in 'note[note]', with: 'new content'
- find('.btn-success').click
+ find('.btn-confirm').click
end
page.within("#notes-list li#note_#{snippet_notes[0].id}") do
diff --git a/spec/frontend/ide/components/ide_side_bar_spec.js b/spec/frontend/ide/components/ide_side_bar_spec.js
index ace8988b8c9..4469c3fc901 100644
--- a/spec/frontend/ide/components/ide_side_bar_spec.js
+++ b/spec/frontend/ide/components/ide_side_bar_spec.js
@@ -1,4 +1,4 @@
-import { GlDeprecatedSkeletonLoading as GlSkeletonLoading } from '@gitlab/ui';
+import { GlSkeletonLoader } from '@gitlab/ui';
import { mount } from '@vue/test-utils';
import Vue, { nextTick } from 'vue';
import Vuex from 'vuex';
@@ -47,7 +47,7 @@ describe('IdeSidebar', () => {
await nextTick();
- expect(wrapper.findAll(GlSkeletonLoading)).toHaveLength(3);
+ expect(wrapper.findAll(GlSkeletonLoader)).toHaveLength(3);
});
describe('deferred rendering components', () => {
diff --git a/spec/helpers/tooling/visual_review_helper_spec.rb b/spec/helpers/tooling/visual_review_helper_spec.rb
new file mode 100644
index 00000000000..7fb9f5fadf2
--- /dev/null
+++ b/spec/helpers/tooling/visual_review_helper_spec.rb
@@ -0,0 +1,25 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe Tooling::VisualReviewHelper do
+ describe '#visual_review_toolbar_options' do
+ subject(:result) { helper.visual_review_toolbar_options }
+
+ before do
+ stub_env('REVIEW_APPS_MERGE_REQUEST_IID', '123')
+ end
+
+ it 'returns the correct params' do
+ expect(result).to eq(
+ 'data-merge-request-id': '123',
+ 'data-mr-url': 'https://gitlab.com',
+ 'data-project-id': '278964',
+ 'data-project-path': 'gitlab-org/gitlab',
+ 'data-require-auth': false,
+ 'id': 'review-app-toolbar-script',
+ 'src': 'https://gitlab.com/assets/webpack/visual_review_toolbar.js'
+ )
+ end
+ end
+end
diff --git a/spec/lib/backup/manager_spec.rb b/spec/lib/backup/manager_spec.rb
index a2477834dde..519d414f643 100644
--- a/spec/lib/backup/manager_spec.rb
+++ b/spec/lib/backup/manager_spec.rb
@@ -15,6 +15,7 @@ RSpec.describe Backup::Manager do
# is trying to display a diff and `File.exist?` is stubbed. Adding a
# default stub fixes this.
allow(File).to receive(:exist?).and_call_original
+ allow(FileUtils).to receive(:rm_rf).and_call_original
allow(progress).to receive(:puts)
allow(progress).to receive(:print)
@@ -171,12 +172,14 @@ RSpec.describe Backup::Manager do
allow(task2).to receive(:dump).with(File.join(Gitlab.config.backup.path, 'task2.tar.gz'), full_backup_id)
end
- it 'executes tar' do
+ it 'creates a backup tar' do
travel_to(backup_time) do
subject.create # rubocop:disable Rails/SaveBang
-
- expect(Kernel).to have_received(:system).with(*pack_tar_cmdline)
end
+
+ expect(Kernel).to have_received(:system).with(*pack_tar_cmdline)
+ expect(FileUtils).to have_received(:rm_rf).with(File.join(Gitlab.config.backup.path, 'backup_information.yml'))
+ expect(FileUtils).to have_received(:rm_rf).with(File.join(Gitlab.config.backup.path, 'tmp'))
end
context 'when BACKUP is set' do
@@ -203,6 +206,8 @@ RSpec.describe Backup::Manager do
end.to raise_error(Backup::Error, 'Backup failed')
expect(Gitlab::BackupLogger).to have_received(:info).with(message: "Creating archive #{pack_tar_file} failed")
+ expect(FileUtils).to have_received(:rm_rf).with(File.join(Gitlab.config.backup.path, 'backup_information.yml'))
+ expect(FileUtils).to have_received(:rm_rf).with(File.join(Gitlab.config.backup.path, 'tmp'))
end
end
@@ -597,6 +602,7 @@ RSpec.describe Backup::Manager do
skipped: 'tar',
tar_version: be_a(String)
)
+ expect(FileUtils).to have_received(:rm_rf).with(File.join(Gitlab.config.backup.path, 'tmp'))
end
end
@@ -697,6 +703,8 @@ RSpec.describe Backup::Manager do
expect(Kernel).to have_received(:system).with(*unpack_tar_cmdline)
expect(Kernel).to have_received(:system).with(*pack_tar_cmdline)
+ expect(FileUtils).to have_received(:rm_rf).with(File.join(Gitlab.config.backup.path, 'backup_information.yml'))
+ expect(FileUtils).to have_received(:rm_rf).with(File.join(Gitlab.config.backup.path, 'tmp'))
end
context 'untar fails' do
@@ -724,6 +732,8 @@ RSpec.describe Backup::Manager do
end.to raise_error(Backup::Error, 'Backup failed')
expect(Gitlab::BackupLogger).to have_received(:info).with(message: "Creating archive #{pack_tar_file} failed")
+ expect(FileUtils).to have_received(:rm_rf).with(File.join(Gitlab.config.backup.path, 'backup_information.yml'))
+ expect(FileUtils).to have_received(:rm_rf).with(File.join(Gitlab.config.backup.path, 'tmp'))
end
end
@@ -786,6 +796,8 @@ RSpec.describe Backup::Manager do
expect(Kernel).to have_received(:system).with(*unpack_tar_cmdline)
expect(Kernel).to have_received(:system).with(*pack_tar_cmdline)
+ expect(FileUtils).to have_received(:rm_rf).with(File.join(Gitlab.config.backup.path, 'backup_information.yml'))
+ expect(FileUtils).to have_received(:rm_rf).with(File.join(Gitlab.config.backup.path, 'tmp'))
end
context 'untar fails' do
@@ -817,6 +829,8 @@ RSpec.describe Backup::Manager do
end.to raise_error(Backup::Error, 'Backup failed')
expect(Gitlab::BackupLogger).to have_received(:info).with(message: "Creating archive #{pack_tar_file} failed")
+ expect(FileUtils).to have_received(:rm_rf).with(File.join(Gitlab.config.backup.path, 'backup_information.yml'))
+ expect(FileUtils).to have_received(:rm_rf).with(File.join(Gitlab.config.backup.path, 'tmp'))
end
end
@@ -1001,6 +1015,8 @@ RSpec.describe Backup::Manager do
subject.restore
expect(Kernel).to have_received(:system).with(*tar_cmdline)
+ expect(FileUtils).to have_received(:rm_rf).with(File.join(Gitlab.config.backup.path, 'backup_information.yml'))
+ expect(FileUtils).to have_received(:rm_rf).with(File.join(Gitlab.config.backup.path, 'tmp'))
end
context 'tar fails' do
@@ -1031,22 +1047,6 @@ RSpec.describe Backup::Manager do
.with(a_string_matching('GitLab version mismatch'))
end
end
-
- describe 'tmp files' do
- let(:path) { File.join(Gitlab.config.backup.path, 'tmp') }
-
- before do
- allow(FileUtils).to receive(:rm_rf).and_call_original
- end
-
- it 'removes backups/tmp dir' do
- expect(FileUtils).to receive(:rm_rf).with(path).and_call_original
-
- subject.restore
-
- expect(Gitlab::BackupLogger).to have_received(:info).with(message: 'Deleting backups/tmp ... ')
- end
- end
end
context 'when there is a non-tarred backup in the directory' do
@@ -1066,6 +1066,7 @@ RSpec.describe Backup::Manager do
expect(progress).to have_received(:puts)
.with(a_string_matching('Non tarred backup found '))
+ expect(FileUtils).to have_received(:rm_rf).with(File.join(Gitlab.config.backup.path, 'tmp'))
end
context 'on version mismatch' do
@@ -1082,22 +1083,6 @@ RSpec.describe Backup::Manager do
.with(a_string_matching('GitLab version mismatch'))
end
end
-
- describe 'tmp files' do
- let(:path) { File.join(Gitlab.config.backup.path, 'tmp') }
-
- before do
- allow(FileUtils).to receive(:rm_rf).and_call_original
- end
-
- it 'removes backups/tmp dir' do
- expect(FileUtils).to receive(:rm_rf).with(path).and_call_original
-
- subject.restore
-
- expect(Gitlab::BackupLogger).to have_received(:info).with(message: 'Deleting backups/tmp ... ')
- end
- end
end
end
end
diff --git a/spec/lib/gitlab/content_security_policy/config_loader_spec.rb b/spec/lib/gitlab/content_security_policy/config_loader_spec.rb
index 2df85434f0e..109e83be294 100644
--- a/spec/lib/gitlab/content_security_policy/config_loader_spec.rb
+++ b/spec/lib/gitlab/content_security_policy/config_loader_spec.rb
@@ -178,6 +178,16 @@ RSpec.describe Gitlab::ContentSecurityPolicy::ConfigLoader do
expect(directives['connect_src']).not_to include(snowplow_micro_url)
end
end
+
+ context 'when REVIEW_APPS_ENABLED is set' do
+ before do
+ stub_env('REVIEW_APPS_ENABLED', 'true')
+ end
+
+ it 'adds gitlab-org/gitlab merge requests API endpoint to CSP' do
+ expect(directives['connect_src']).to include('https://gitlab.com/api/v4/projects/278964/merge_requests/')
+ end
+ end
end
end
end
diff --git a/spec/presenters/project_presenter_spec.rb b/spec/presenters/project_presenter_spec.rb
index 33a4a1b9d4c..7ff020f05e8 100644
--- a/spec/presenters/project_presenter_spec.rb
+++ b/spec/presenters/project_presenter_spec.rb
@@ -211,16 +211,6 @@ RSpec.describe ProjectPresenter do
context 'statistics anchors (empty repo)' do
let_it_be(:project) { create(:project, :empty_repo) }
- describe '#files_anchor_data' do
- it 'returns files data' do
- expect(presenter.files_anchor_data).to have_attributes(
- is_link: true,
- label: a_string_including('0 Bytes'),
- link: nil
- )
- end
- end
-
describe '#storage_anchor_data' do
it 'returns storage data' do
expect(presenter.storage_anchor_data).to have_attributes(
@@ -275,22 +265,22 @@ RSpec.describe ProjectPresenter do
let(:presenter) { described_class.new(project, current_user: user) }
- describe '#files_anchor_data' do
- it 'returns files data' do
- expect(presenter.files_anchor_data).to have_attributes(
+ describe '#storage_anchor_data' do
+ it 'returns storage data without usage quotas link for non-admin users' do
+ expect(presenter.storage_anchor_data).to have_attributes(
is_link: true,
label: a_string_including('0 Bytes'),
- link: presenter.project_tree_path(project)
+ link: nil
)
end
- end
- describe '#storage_anchor_data' do
- it 'returns storage data' do
+ it 'returns storage data with usage quotas link for admin users' do
+ project.add_owner(user)
+
expect(presenter.storage_anchor_data).to have_attributes(
is_link: true,
label: a_string_including('0 Bytes'),
- link: presenter.project_tree_path(project)
+ link: presenter.project_usage_quotas_path(project)
)
end
end
diff --git a/spec/requests/api/environments_spec.rb b/spec/requests/api/environments_spec.rb
index 8328b454122..93f21c880a4 100644
--- a/spec/requests/api/environments_spec.rb
+++ b/spec/requests/api/environments_spec.rb
@@ -113,7 +113,7 @@ RSpec.describe API::Environments do
end
context 'when filtering' do
- let_it_be(:environment2) { create(:environment, project: project) }
+ let_it_be(:stopped_environment) { create(:environment, :stopped, project: project) }
it 'returns environment by name' do
get api("/projects/#{project.id}/environments?name=#{environment.name}", user)
@@ -152,11 +152,32 @@ RSpec.describe API::Environments do
expect(json_response.size).to eq(0)
end
- it 'returns a 400 status code with invalid states' do
+ it 'returns environment by valid state' do
+ get api("/projects/#{project.id}/environments?states=available", user)
+
+ expect(response).to have_gitlab_http_status(:ok)
+ expect(response).to include_pagination_headers
+ expect(json_response).to be_an Array
+ expect(json_response.size).to eq(1)
+ expect(json_response.first['name']).to eq(environment.name)
+ end
+
+ it 'returns all environments when state is not specified' do
+ get api("/projects/#{project.id}/environments", user)
+
+ expect(response).to have_gitlab_http_status(:ok)
+ expect(response).to include_pagination_headers
+ expect(json_response).to be_an Array
+ expect(json_response.size).to eq(2)
+ expect(json_response.first['name']).to eq(environment.name)
+ expect(json_response.last['name']).to eq(stopped_environment.name)
+ end
+
+ it 'returns a 400 when filtering by invalid state' do
get api("/projects/#{project.id}/environments?states=test", user)
expect(response).to have_gitlab_http_status(:bad_request)
- expect(json_response['message']).to include('Requested states are invalid')
+ expect(json_response['error']).to eq('states does not have a valid value')
end
end
end
diff --git a/spec/requests/api/features_spec.rb b/spec/requests/api/features_spec.rb
index 4e75b0510d0..b54be4f5258 100644
--- a/spec/requests/api/features_spec.rb
+++ b/spec/requests/api/features_spec.rb
@@ -168,19 +168,15 @@ RSpec.describe API::Features, stub_feature_flags: false do
end
end
- shared_examples 'does not enable the flag' do |actor_type, actor_path|
+ shared_examples 'does not enable the flag' do |actor_type|
+ let(:actor_path) { raise NotImplementedError }
+ let(:expected_inexistent_path) { actor_path }
+
it 'returns the current state of the flag without changes' do
post api("/features/#{feature_name}", admin), params: { value: 'true', actor_type => actor_path }
- expect(response).to have_gitlab_http_status(:created)
- expect(json_response).to match(
- "name" => feature_name,
- "state" => "off",
- "gates" => [
- { "key" => "boolean", "value" => false }
- ],
- 'definition' => known_feature_flag_definition_hash
- )
+ expect(response).to have_gitlab_http_status(:bad_request)
+ expect(json_response['message']).to eq("400 Bad request - #{expected_inexistent_path} is not found!")
end
end
@@ -201,6 +197,19 @@ RSpec.describe API::Features, stub_feature_flags: false do
end
end
+ shared_examples 'creates an enabled feature for the specified entries' do
+ it do
+ post api("/features/#{feature_name}", admin), params: { value: 'true', **gate_params }
+
+ expect(response).to have_gitlab_http_status(:created)
+ expect(json_response['name']).to eq(feature_name)
+ expect(json_response['gates']).to contain_exactly(
+ { 'key' => 'boolean', 'value' => false },
+ { 'key' => 'actors', 'value' => array_including(expected_gate_params) }
+ )
+ end
+ end
+
context 'when enabling for a project by path' do
context 'when the project exists' do
it_behaves_like 'enables the flag for the actor', :project do
@@ -209,7 +218,9 @@ RSpec.describe API::Features, stub_feature_flags: false do
end
context 'when the project does not exist' do
- it_behaves_like 'does not enable the flag', :project, 'mep/to/the/mep/mep'
+ it_behaves_like 'does not enable the flag', :project do
+ let(:actor_path) { 'mep/to/the/mep/mep' }
+ end
end
end
@@ -221,7 +232,9 @@ RSpec.describe API::Features, stub_feature_flags: false do
end
context 'when the group does not exist' do
- it_behaves_like 'does not enable the flag', :group, 'not/a/group'
+ it_behaves_like 'does not enable the flag', :group do
+ let(:actor_path) { 'not/a/group' }
+ end
end
end
@@ -239,7 +252,9 @@ RSpec.describe API::Features, stub_feature_flags: false do
end
context 'when the user namespace does not exist' do
- it_behaves_like 'does not enable the flag', :namespace, 'not/a/group'
+ it_behaves_like 'does not enable the flag', :namespace do
+ let(:actor_path) { 'not/a/group' }
+ end
end
context 'when a project namespace exists' do
@@ -251,6 +266,98 @@ RSpec.describe API::Features, stub_feature_flags: false do
end
end
+ context 'with multiple users' do
+ let_it_be(:users) { create_list(:user, 3) }
+
+ it_behaves_like 'creates an enabled feature for the specified entries' do
+ let(:gate_params) { { user: users.map(&:username).join(',') } }
+ let(:expected_gate_params) { users.map(&:flipper_id) }
+ end
+
+ context 'when empty value exists between comma' do
+ it_behaves_like 'creates an enabled feature for the specified entries' do
+ let(:gate_params) { { user: "#{users.first.username},,,," } }
+ let(:expected_gate_params) { users.first.flipper_id }
+ end
+ end
+
+ context 'when one of the users does not exist' do
+ it_behaves_like 'does not enable the flag', :user do
+ let(:actor_path) { "#{users.first.username},inexistent-entry" }
+ let(:expected_inexistent_path) { "inexistent-entry" }
+ end
+ end
+ end
+
+ context 'with multiple projects' do
+ let_it_be(:projects) { create_list(:project, 3) }
+
+ it_behaves_like 'creates an enabled feature for the specified entries' do
+ let(:gate_params) { { project: projects.map(&:full_path).join(',') } }
+ let(:expected_gate_params) { projects.map(&:flipper_id) }
+ end
+
+ context 'when empty value exists between comma' do
+ it_behaves_like 'creates an enabled feature for the specified entries' do
+ let(:gate_params) { { project: "#{projects.first.full_path},,,," } }
+ let(:expected_gate_params) { projects.first.flipper_id }
+ end
+ end
+
+ context 'when one of the projects does not exist' do
+ it_behaves_like 'does not enable the flag', :project do
+ let(:actor_path) { "#{projects.first.full_path},inexistent-entry" }
+ let(:expected_inexistent_path) { "inexistent-entry" }
+ end
+ end
+ end
+
+ context 'with multiple groups' do
+ let_it_be(:groups) { create_list(:group, 3) }
+
+ it_behaves_like 'creates an enabled feature for the specified entries' do
+ let(:gate_params) { { group: groups.map(&:full_path).join(',') } }
+ let(:expected_gate_params) { groups.map(&:flipper_id) }
+ end
+
+ context 'when empty value exists between comma' do
+ it_behaves_like 'creates an enabled feature for the specified entries' do
+ let(:gate_params) { { group: "#{groups.first.full_path},,,," } }
+ let(:expected_gate_params) { groups.first.flipper_id }
+ end
+ end
+
+ context 'when one of the groups does not exist' do
+ it_behaves_like 'does not enable the flag', :group do
+ let(:actor_path) { "#{groups.first.full_path},inexistent-entry" }
+ let(:expected_inexistent_path) { "inexistent-entry" }
+ end
+ end
+ end
+
+ context 'with multiple namespaces' do
+ let_it_be(:namespaces) { create_list(:namespace, 3) }
+
+ it_behaves_like 'creates an enabled feature for the specified entries' do
+ let(:gate_params) { { namespace: namespaces.map(&:full_path).join(',') } }
+ let(:expected_gate_params) { namespaces.map(&:flipper_id) }
+ end
+
+ context 'when empty value exists between comma' do
+ it_behaves_like 'creates an enabled feature for the specified entries' do
+ let(:gate_params) { { namespace: "#{namespaces.first.full_path},,,," } }
+ let(:expected_gate_params) { namespaces.first.flipper_id }
+ end
+ end
+
+ context 'when one of the namespaces does not exist' do
+ it_behaves_like 'does not enable the flag', :namespace do
+ let(:actor_path) { "#{namespaces.first.full_path},inexistent-entry" }
+ let(:expected_inexistent_path) { "inexistent-entry" }
+ end
+ end
+ end
+
it 'creates a feature with the given percentage of time if passed an integer' do
post api("/features/#{feature_name}", admin), params: { value: '50' }
diff --git a/spec/requests/pwa_controller_spec.rb b/spec/requests/pwa_controller_spec.rb
index f74f37ea9d0..7a295b17231 100644
--- a/spec/requests/pwa_controller_spec.rb
+++ b/spec/requests/pwa_controller_spec.rb
@@ -3,6 +3,15 @@
require 'spec_helper'
RSpec.describe PwaController do
+ describe 'GET #manifest' do
+ it 'responds with json' do
+ get manifest_path(format: :json)
+
+ expect(response.body).to include('The complete DevOps platform.')
+ expect(response).to have_gitlab_http_status(:success)
+ end
+ end
+
describe 'GET #offline' do
it 'responds with static HTML page' do
get offline_path
diff --git a/spec/support/shared_examples/workers/batched_background_migration_worker_shared_examples.rb b/spec/support/shared_examples/workers/batched_background_migration_worker_shared_examples.rb
index ee0c2dbfff4..1349ca37c78 100644
--- a/spec/support/shared_examples/workers/batched_background_migration_worker_shared_examples.rb
+++ b/spec/support/shared_examples/workers/batched_background_migration_worker_shared_examples.rb
@@ -239,6 +239,7 @@ RSpec.shared_examples 'it runs batched background migration jobs' do |tracking_d
end
end
+ let(:gitlab_schema) { "gitlab_#{tracking_database}" }
let!(:migration) do
create(
:batched_background_migration,
@@ -249,10 +250,12 @@ RSpec.shared_examples 'it runs batched background migration jobs' do |tracking_d
batch_size: batch_size,
sub_batch_size: sub_batch_size,
job_class_name: 'ExampleDataMigration',
- job_arguments: [1]
+ job_arguments: [1],
+ gitlab_schema: gitlab_schema
)
end
+ let(:base_model) { Gitlab::Database.database_base_models[tracking_database] }
let(:table_name) { 'example_data' }
let(:batch_size) { 5 }
let(:sub_batch_size) { 2 }
@@ -289,7 +292,7 @@ RSpec.shared_examples 'it runs batched background migration jobs' do |tracking_d
WHERE some_column = #{migration_records - 5};
SQL
- stub_feature_flags(execute_batched_migrations_on_schedule: true)
+ stub_feature_flags(feature_flag => true)
stub_const('Gitlab::BackgroundMigration::ExampleDataMigration', migration_class)
end
diff --git a/spec/tasks/gitlab/db/lock_writes_rake_spec.rb b/spec/tasks/gitlab/db/lock_writes_rake_spec.rb
new file mode 100644
index 00000000000..da62d136c25
--- /dev/null
+++ b/spec/tasks/gitlab/db/lock_writes_rake_spec.rb
@@ -0,0 +1,152 @@
+# frozen_string_literal: true
+
+require 'rake_helper'
+
+RSpec.describe 'gitlab:db:lock_writes', :silence_stdout, :reestablished_active_record_base do
+ before :all do
+ Rake.application.rake_require 'active_record/railties/databases'
+ Rake.application.rake_require 'tasks/seed_fu'
+ Rake.application.rake_require 'tasks/gitlab/db/validate_config'
+ Rake.application.rake_require 'tasks/gitlab/db/lock_writes'
+
+ # empty task as env is already loaded
+ Rake::Task.define_task :environment
+ end
+
+ let!(:project) { create(:project) }
+ let!(:ci_build) { create(:ci_build) }
+ let(:main_connection) { ApplicationRecord.connection }
+ let(:ci_connection) { Ci::ApplicationRecord.connection }
+
+ context 'single database' do
+ before do
+ skip_if_multiple_databases_are_setup
+ end
+
+ context 'when locking writes' do
+ it 'does not add any triggers to the main schema tables' do
+ expect do
+ run_rake_task('gitlab:db:lock_writes')
+ end.to change {
+ number_of_triggers(main_connection)
+ }.by(0)
+ end
+
+ it 'will be still able to modify tables that belong to the main two schemas' do
+ run_rake_task('gitlab:db:lock_writes')
+ expect do
+ Project.last.touch
+ Ci::Build.last.touch
+ end.not_to raise_error
+ end
+ end
+ end
+
+ context 'multiple databases' do
+ before do
+ skip_if_multiple_databases_not_setup
+ end
+
+ context 'when locking writes' do
+ it 'adds 3 triggers to the ci schema tables on the main database' do
+ expect do
+ run_rake_task('gitlab:db:lock_writes')
+ end.to change {
+ number_of_triggers_on(main_connection, Ci::Build.table_name)
+ }.by(3) # Triggers to block INSERT / UPDATE / DELETE
+ # Triggers on TRUNCATE are not added to the information_schema.triggers
+ # See https://www.postgresql.org/message-id/16934.1568989957%40sss.pgh.pa.us
+ end
+
+ it 'adds 3 triggers to the main schema tables on the ci database' do
+ expect do
+ run_rake_task('gitlab:db:lock_writes')
+ end.to change {
+ number_of_triggers_on(ci_connection, Project.table_name)
+ }.by(3) # Triggers to block INSERT / UPDATE / DELETE
+ # Triggers on TRUNCATE are not added to the information_schema.triggers
+ # See https://www.postgresql.org/message-id/16934.1568989957%40sss.pgh.pa.us
+ end
+
+ it 'still allows writes on the tables with the correct connections' do
+ Project.update_all(updated_at: Time.now)
+ Ci::Build.update_all(updated_at: Time.now)
+ end
+
+ it 'still allows writing to gitlab_shared schema on any connection' do
+ connections = [main_connection, ci_connection]
+ connections.each do |connection|
+ Gitlab::Database::SharedModel.using_connection(connection) do
+ LooseForeignKeys::DeletedRecord.create!(
+ fully_qualified_table_name: "public.projects",
+ primary_key_value: 1,
+ cleanup_attempts: 0
+ )
+ end
+ end
+ end
+
+ it 'prevents writes on the main tables on the ci database' do
+ run_rake_task('gitlab:db:lock_writes')
+ expect do
+ ci_connection.execute("delete from projects")
+ end.to raise_error(ActiveRecord::StatementInvalid, /Table: "projects" is write protected/)
+ end
+
+ it 'prevents writes on the ci tables on the main database' do
+ run_rake_task('gitlab:db:lock_writes')
+ expect do
+ main_connection.execute("delete from ci_builds")
+ end.to raise_error(ActiveRecord::StatementInvalid, /Table: "ci_builds" is write protected/)
+ end
+
+ it 'prevents truncating a ci table on the main database' do
+ run_rake_task('gitlab:db:lock_writes')
+ expect do
+ main_connection.execute("truncate ci_build_needs")
+ end.to raise_error(ActiveRecord::StatementInvalid, /Table: "ci_build_needs" is write protected/)
+ end
+ end
+
+ context 'when unlocking writes' do
+ before do
+ run_rake_task('gitlab:db:lock_writes')
+ end
+
+ it 'removes the write protection triggers from the gitlab_main tables on the ci database' do
+ expect do
+ run_rake_task('gitlab:db:unlock_writes')
+ end.to change {
+ number_of_triggers_on(ci_connection, Project.table_name)
+ }.by(-3) # Triggers to block INSERT / UPDATE / DELETE
+ # Triggers on TRUNCATE are not added to the information_schema.triggers
+ # See https://www.postgresql.org/message-id/16934.1568989957%40sss.pgh.pa.us
+
+ expect do
+ ci_connection.execute("delete from projects")
+ end.not_to raise_error
+ end
+
+ it 'removes the write protection triggers from the gitlab_ci tables on the main database' do
+ expect do
+ run_rake_task('gitlab:db:unlock_writes')
+ end.to change {
+ number_of_triggers_on(main_connection, Ci::Build.table_name)
+ }.by(-3)
+
+ expect do
+ main_connection.execute("delete from ci_builds")
+ end.not_to raise_error
+ end
+ end
+ end
+
+ def number_of_triggers(connection)
+ connection.select_value("SELECT count(*) FROM information_schema.triggers")
+ end
+
+ def number_of_triggers_on(connection, table_name)
+ connection
+ .select_value("SELECT count(*) FROM information_schema.triggers WHERE event_object_table=$1", nil, [table_name])
+ end
+end
diff --git a/spec/views/layouts/application.html.haml_spec.rb b/spec/views/layouts/application.html.haml_spec.rb
index 679d0b1ff60..0f359219718 100644
--- a/spec/views/layouts/application.html.haml_spec.rb
+++ b/spec/views/layouts/application.html.haml_spec.rb
@@ -14,6 +14,35 @@ RSpec.describe 'layouts/application' do
allow(view).to receive(:current_user_mode).and_return(Gitlab::Auth::CurrentUserMode.new(user))
end
+ describe "visual review toolbar" do
+ context "ENV['REVIEW_APPS_ENABLED'] is set to true" do
+ before do
+ stub_env(
+ 'REVIEW_APPS_ENABLED' => true,
+ 'REVIEW_APPS_MERGE_REQUEST_IID' => '123'
+ )
+ end
+
+ it 'renders the visual review toolbar' do
+ render
+
+ expect(rendered).to include('review-app-toolbar-script')
+ end
+ end
+
+ context "ENV['REVIEW_APPS_ENABLED'] is set to false" do
+ before do
+ stub_env('REVIEW_APPS_ENABLED', false)
+ end
+
+ it 'does not render the visual review toolbar' do
+ render
+
+ expect(rendered).not_to include('review-app-toolbar-script')
+ end
+ end
+ end
+
context 'body data elements for pageview context' do
let(:body_data) do
{
diff --git a/spec/workers/database/batched_background_migration/ci_database_worker_spec.rb b/spec/workers/database/batched_background_migration/ci_database_worker_spec.rb
index eb34b84a506..f3cf5450048 100644
--- a/spec/workers/database/batched_background_migration/ci_database_worker_spec.rb
+++ b/spec/workers/database/batched_background_migration/ci_database_worker_spec.rb
@@ -2,6 +2,6 @@
require 'spec_helper'
-RSpec.describe Database::BatchedBackgroundMigration::CiDatabaseWorker, :clean_gitlab_redis_shared_state, quarantine: 'https://gitlab.com/gitlab-org/gitlab/-/issues/362821' do
+RSpec.describe Database::BatchedBackgroundMigration::CiDatabaseWorker, :clean_gitlab_redis_shared_state do
it_behaves_like 'it runs batched background migration jobs', 'ci', feature_flag: :execute_batched_migrations_on_schedule_ci_database
end