Add latest changes from gitlab-org/gitlab@master
This commit is contained in:
parent
224d2fe167
commit
3fbfc0075a
|
@ -50,6 +50,8 @@ include:
|
||||||
--tag ~orchestrated \
|
--tag ~orchestrated \
|
||||||
--tag ~transient \
|
--tag ~transient \
|
||||||
--tag ~skip_signup_disabled \
|
--tag ~skip_signup_disabled \
|
||||||
|
--tag ~requires_git_protocol_v2 \
|
||||||
|
--tag ~requires_praefect \
|
||||||
--force-color \
|
--force-color \
|
||||||
--order random \
|
--order random \
|
||||||
--format documentation \
|
--format documentation \
|
||||||
|
|
|
@ -1001,7 +1001,6 @@ Layout/LineLength:
|
||||||
- 'db/migrate/20220310101118_update_holder_name_limit.rb'
|
- 'db/migrate/20220310101118_update_holder_name_limit.rb'
|
||||||
- 'db/migrate/20220314184209_add_group_fk_to_protected_environment_approval_rules.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/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/20210328214434_remove_temporary_index_from_vulnerabilities_table.rb'
|
||||||
- 'db/post_migrate/20210401131948_move_container_registry_enabled_to_project_features2.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'
|
- 'db/post_migrate/20210402005225_add_source_and_level_index_on_notification_settings.rb'
|
||||||
|
|
|
@ -229,7 +229,6 @@ Style/PercentLiteralDelimiters:
|
||||||
- 'db/migrate/20210621044000_rename_services_indexes_to_integrations.rb'
|
- 'db/migrate/20210621044000_rename_services_indexes_to_integrations.rb'
|
||||||
- 'db/migrate/20210709085759_index_batched_migration_jobs_by_max_value.rb'
|
- 'db/migrate/20210709085759_index_batched_migration_jobs_by_max_value.rb'
|
||||||
- 'db/migrate/20210928155022_improve_index_for_error_tracking.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/20210329102724_add_new_trail_plans.rb'
|
||||||
- 'db/post_migrate/20210420121149_backfill_conversion_of_ci_job_artifacts.rb'
|
- 'db/post_migrate/20210420121149_backfill_conversion_of_ci_job_artifacts.rb'
|
||||||
- 'db/post_migrate/20210426094549_backfill_ci_builds_for_bigint_conversion.rb'
|
- 'db/post_migrate/20210426094549_backfill_ci_builds_for_bigint_conversion.rb'
|
||||||
|
|
|
@ -1 +1 @@
|
||||||
2106629e3af3e8949b23f20825d6bfee62c10992
|
d7eedd059daf9059990a95e53c76e567eac64899
|
||||||
|
|
Binary file not shown.
Before Width: | Height: | Size: 2.0 KiB After Width: | Height: | Size: 1.6 KiB |
|
@ -1,5 +1,5 @@
|
||||||
<script>
|
<script>
|
||||||
import { GlDeprecatedSkeletonLoading as GlSkeletonLoading } from '@gitlab/ui';
|
import { GlSkeletonLoader } from '@gitlab/ui';
|
||||||
import { mapState, mapGetters } from 'vuex';
|
import { mapState, mapGetters } from 'vuex';
|
||||||
import { SIDEBAR_INIT_WIDTH, leftSidebarViews } from '../constants';
|
import { SIDEBAR_INIT_WIDTH, leftSidebarViews } from '../constants';
|
||||||
import ActivityBar from './activity_bar.vue';
|
import ActivityBar from './activity_bar.vue';
|
||||||
|
@ -10,7 +10,7 @@ import ResizablePanel from './resizable_panel.vue';
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
components: {
|
components: {
|
||||||
GlSkeletonLoading,
|
GlSkeletonLoader,
|
||||||
ResizablePanel,
|
ResizablePanel,
|
||||||
ActivityBar,
|
ActivityBar,
|
||||||
IdeTree,
|
IdeTree,
|
||||||
|
@ -38,7 +38,7 @@ export default {
|
||||||
<template v-if="loading">
|
<template v-if="loading">
|
||||||
<div class="multi-file-commit-panel-inner" data-testid="ide-side-bar-inner">
|
<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">
|
<div v-for="n in 3" :key="n" class="multi-file-loading-container">
|
||||||
<gl-skeleton-loading />
|
<gl-skeleton-loader />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
|
@ -132,6 +132,7 @@ export default {
|
||||||
<div
|
<div
|
||||||
v-gl-tooltip="{ title: tag.name }"
|
v-gl-tooltip="{ title: tag.name }"
|
||||||
data-testid="name"
|
data-testid="name"
|
||||||
|
data-qa-selector="tag_name_content"
|
||||||
class="gl-text-overflow-ellipsis gl-overflow-hidden gl-white-space-nowrap"
|
class="gl-text-overflow-ellipsis gl-overflow-hidden gl-white-space-nowrap"
|
||||||
:class="mobileClasses"
|
:class="mobileClasses"
|
||||||
>
|
>
|
||||||
|
|
|
@ -7,6 +7,9 @@ class PwaController < ApplicationController # rubocop:disable Gitlab/NamespacedC
|
||||||
|
|
||||||
skip_before_action :authenticate_user!
|
skip_before_action :authenticate_user!
|
||||||
|
|
||||||
|
def manifest
|
||||||
|
end
|
||||||
|
|
||||||
def offline
|
def offline
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -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
|
|
@ -28,7 +28,6 @@ class ProjectPresenter < Gitlab::View::Presenter::Delegated
|
||||||
commits_anchor_data,
|
commits_anchor_data,
|
||||||
branches_anchor_data,
|
branches_anchor_data,
|
||||||
tags_anchor_data,
|
tags_anchor_data,
|
||||||
files_anchor_data,
|
|
||||||
storage_anchor_data,
|
storage_anchor_data,
|
||||||
releases_anchor_data
|
releases_anchor_data
|
||||||
].compact.select(&:is_link)
|
].compact.select(&:is_link)
|
||||||
|
@ -161,26 +160,16 @@ class ProjectPresenter < Gitlab::View::Presenter::Delegated
|
||||||
can_current_user_push_to_branch?(default_branch)
|
can_current_user_push_to_branch?(default_branch)
|
||||||
end
|
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
|
def storage_anchor_data
|
||||||
|
can_show_quota = can?(current_user, :admin_project, project) && !empty_repo?
|
||||||
AnchorData.new(true,
|
AnchorData.new(true,
|
||||||
statistic_icon('disk') +
|
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),
|
human_size: storage_counter(statistics.storage_size),
|
||||||
strong_start: '<strong class="project-stat-value">'.html_safe,
|
strong_start: '<strong class="project-stat-value">'.html_safe,
|
||||||
strong_end: '</strong>'.html_safe
|
strong_end: '</strong>'.html_safe
|
||||||
},
|
},
|
||||||
empty_repo? ? nil : project_tree_path(project))
|
can_show_quota ? project_usage_quotas_path(project) : nil)
|
||||||
end
|
end
|
||||||
|
|
||||||
def releases_anchor_data
|
def releases_anchor_data
|
||||||
|
|
|
@ -68,6 +68,7 @@
|
||||||
|
|
||||||
%meta{ name: "description", content: page_description }
|
%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: 'viewport', content: 'width=device-width, initial-scale=1, maximum-scale=1' }
|
||||||
%meta{ name: 'theme-color', content: user_theme_primary_color }
|
%meta{ name: 'theme-color', content: user_theme_primary_color }
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1 @@
|
||||||
|
= javascript_tag "", visual_review_toolbar_options
|
|
@ -8,6 +8,7 @@
|
||||||
%body{ class: body_classes, data: body_data }
|
%body{ class: body_classes, data: body_data }
|
||||||
= render "layouts/init_auto_complete" if @gfm_form
|
= render "layouts/init_auto_complete" if @gfm_form
|
||||||
= render "layouts/init_client_detection_flags"
|
= render "layouts/init_client_detection_flags"
|
||||||
|
= render "layouts/visual_review" if ENV['REVIEW_APPS_ENABLED']
|
||||||
= render 'peek/bar'
|
= render 'peek/bar'
|
||||||
= header_message
|
= header_message
|
||||||
= render partial: "layouts/header/default", locals: { project: @project, group: @group }
|
= render partial: "layouts/header/default", locals: { project: @project, group: @group }
|
||||||
|
|
|
@ -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"
|
||||||
|
}]
|
||||||
|
}
|
|
@ -9,6 +9,6 @@
|
||||||
.note-form-actions.clearfix
|
.note-form-actions.clearfix
|
||||||
.settings-message.note-edit-warning.js-finish-edit-warning
|
.settings-message.note-edit-warning.js-finish-edit-warning
|
||||||
= _("Finish editing this message first!")
|
= _("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' }
|
%button.btn.gl-button.btn-cancel.note-edit-cancel{ type: 'button' }
|
||||||
= _("Cancel")
|
= _("Cancel")
|
||||||
|
|
|
@ -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'] ||= Settingslogic.new({})
|
||||||
Settings.cron_jobs['inactive_projects_deletion_cron_worker']['cron'] ||= '0 1 * * *'
|
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['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
|
Gitlab.ee do
|
||||||
Settings.cron_jobs['analytics_devops_adoption_create_all_snapshots_worker'] ||= Settingslogic.new({})
|
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'] ||= Settingslogic.new({})
|
||||||
Settings.cron_jobs['app_sec_dast_profile_schedule_worker']['cron'] ||= '7-59/15 * * * *'
|
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['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'] ||= Settingslogic.new({})
|
||||||
Settings.cron_jobs['ci_namespace_mirrors_consistency_check_worker']['cron'] ||= '*/4 * * * *'
|
Settings.cron_jobs['ci_namespace_mirrors_consistency_check_worker']['cron'] ||= '*/4 * * * *'
|
||||||
Settings.cron_jobs['ci_namespace_mirrors_consistency_check_worker']['job_class'] = 'Database::CiNamespaceMirrorsConsistencyCheckWorker'
|
Settings.cron_jobs['ci_namespace_mirrors_consistency_check_worker']['job_class'] = 'Database::CiNamespaceMirrorsConsistencyCheckWorker'
|
||||||
|
|
|
@ -117,6 +117,7 @@ Rails.application.routes.draw do
|
||||||
get '/whats_new' => 'whats_new#index'
|
get '/whats_new' => 'whats_new#index'
|
||||||
|
|
||||||
get 'offline' => "pwa#offline"
|
get 'offline' => "pwa#offline"
|
||||||
|
get 'manifest' => "pwa#manifest", constraints: lambda { |req| req.format == :json }
|
||||||
|
|
||||||
# '/-/health' implemented by BasicHealthCheck middleware
|
# '/-/health' implemented by BasicHealthCheck middleware
|
||||||
get 'liveness' => 'health#liveness'
|
get 'liveness' => 'health#liveness'
|
||||||
|
|
|
@ -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
|
|
|
@ -127,10 +127,10 @@ POST /features/:name
|
||||||
| `value` | integer/string | yes | `true` or `false` to enable/disable, or an integer for percentage of time |
|
| `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) |
|
| `key` | string | no | `percentage_of_actors` or `percentage_of_time` (default) |
|
||||||
| `feature_group` | string | no | A Feature group name |
|
| `feature_group` | string | no | A Feature group name |
|
||||||
| `user` | string | no | A GitLab username |
|
| `user` | string | no | A GitLab username or comma-separated multiple usernames |
|
||||||
| `group` | string | no | A GitLab group's path, for example `gitlab-org` |
|
| `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 `gitlab-org` or username path. [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/353117) in GitLab 15.0. |
|
| `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` |
|
| `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 |
|
| `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`,
|
You can enable or disable a feature for a `feature_group`, a `user`,
|
||||||
|
|
|
@ -74,6 +74,8 @@ If you set a quota for a subgroup, it is not used.
|
||||||
|
|
||||||
## View CI/CD minutes used by a group
|
## 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.
|
You can view the number of CI/CD minutes being used by a group.
|
||||||
|
|
||||||
Prerequisite:
|
Prerequisite:
|
||||||
|
|
|
@ -226,33 +226,47 @@ A fork *does* copy the CI/CD settings of the cloned repository.
|
||||||
### Create a specific runner
|
### Create a specific runner
|
||||||
|
|
||||||
You can create a specific runner for your self-managed GitLab instance or for GitLab.com.
|
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:
|
To create a specific runner:
|
||||||
|
|
||||||
1. [Install runner](https://docs.gitlab.com/runner/install/).
|
1. [Install GitLab Runner](https://docs.gitlab.com/runner/install/).
|
||||||
1. Go to the project's **Settings > CI/CD** and expand the **Runners** section.
|
1. On the top bar, select **Menu > Projects** and find the project where you want to use the runner.
|
||||||
1. Note the URL and token.
|
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/).
|
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 for a different project
|
||||||
enable a specific runner to apply to additional projects.
|
|
||||||
|
|
||||||
- You must have the Owner role for the
|
After a specific runner is created, you can enable it for other projects.
|
||||||
project.
|
|
||||||
|
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).
|
- 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. On the top bar, select **Menu > Projects** and find the project where you want to enable the runner.
|
||||||
1. Click **Enable for this project** or **Disable for this project**.
|
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.
|
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.
|
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
|
### 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.
|
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:
|
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. 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. Check the **Lock to current projects** option.
|
||||||
1. Click **Save changes**.
|
1. Select **Save changes**.
|
||||||
|
|
|
@ -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.
|
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
|
## Add a message for shared runners
|
||||||
|
|
||||||
To display details about the instance's shared runners in all projects'
|
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:
|
NOTE:
|
||||||
All application settings have a [customizable cache expiry interval](../../../administration/application_settings_cache.md) which can delay the settings affect.
|
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
|
## Archive jobs
|
||||||
|
|
||||||
Archiving jobs is useful for reducing the CI/CD footprint on the system by removing some
|
Archiving jobs is useful for reducing the CI/CD footprint on the system by removing some
|
||||||
|
|
|
@ -22,16 +22,15 @@ module API
|
||||||
use :pagination
|
use :pagination
|
||||||
optional :name, type: String, desc: 'Returns the environment with this name'
|
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 :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'
|
mutually_exclusive :name, :search, message: 'cannot be used together'
|
||||||
end
|
end
|
||||||
get ':id/environments' do
|
get ':id/environments' do
|
||||||
authorize! :read_environment, user_project
|
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
|
present paginate(environments), with: Entities::Environment, current_user: current_user
|
||||||
rescue ::Environments::EnvironmentsFinder::InvalidStatesError => exception
|
|
||||||
bad_request!(exception.message)
|
|
||||||
end
|
end
|
||||||
|
|
||||||
desc 'Creates a new environment' do
|
desc 'Creates a new environment' do
|
||||||
|
|
|
@ -68,10 +68,13 @@ module API
|
||||||
requires :value, type: String, desc: '`true` or `false` to enable/disable, a float for percentage of time'
|
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 :key, type: String, desc: '`percentage_of_actors` or the default `percentage_of_time`'
|
||||||
optional :feature_group, type: String, desc: 'A Feature group name'
|
optional :feature_group, type: String, desc: 'A Feature group name'
|
||||||
optional :user, type: String, desc: 'A GitLab username'
|
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'"
|
optional :group, type: String,
|
||||||
optional :namespace, type: String, desc: "A GitLab group or user namespace path, such as 'gitlab-org'"
|
desc: "A GitLab group's path, such as 'gitlab-org', or comma-separated multiple group paths"
|
||||||
optional :project, type: String, desc: 'A projects path, like gitlab-org/gitlab-ce'
|
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'
|
optional :force, type: Boolean, desc: 'Skip feature flag validation checks, ie. YAML definition'
|
||||||
|
|
||||||
mutually_exclusive :key, :feature_group
|
mutually_exclusive :key, :feature_group
|
||||||
|
@ -110,6 +113,8 @@ module API
|
||||||
|
|
||||||
present Feature.get(params[:name]), # rubocop:disable Gitlab/AvoidFeatureGet
|
present Feature.get(params[:name]), # rubocop:disable Gitlab/AvoidFeatureGet
|
||||||
with: Entities::Feature, current_user: current_user
|
with: Entities::Feature, current_user: current_user
|
||||||
|
rescue Feature::Target::UnknowTargetError => e
|
||||||
|
bad_request!(e.message)
|
||||||
end
|
end
|
||||||
|
|
||||||
desc 'Remove the gate value for the given feature'
|
desc 'Remove the gate value for the given feature'
|
||||||
|
|
|
@ -41,29 +41,8 @@ module Backup
|
||||||
end
|
end
|
||||||
|
|
||||||
def create
|
def create
|
||||||
if incremental?
|
unpack(ENV.fetch('PREVIOUS_BACKUP', ENV['BACKUP'])) if incremental?
|
||||||
unpack(ENV.fetch('PREVIOUS_BACKUP', ENV['BACKUP']))
|
run_all_create_tasks
|
||||||
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
|
|
||||||
|
|
||||||
puts_time "Warning: Your gitlab.rb and gitlab-secrets.json files contain sensitive data \n" \
|
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" \
|
"and are not included in this backup. You will need these files to restore a backup.\n" \
|
||||||
|
@ -95,22 +74,8 @@ module Backup
|
||||||
end
|
end
|
||||||
|
|
||||||
def restore
|
def restore
|
||||||
cleanup_required = unpack(ENV['BACKUP'])
|
unpack(ENV['BACKUP'])
|
||||||
read_backup_information
|
run_all_restore_tasks
|
||||||
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
|
|
||||||
|
|
||||||
puts_time "Warning: Your gitlab.rb and gitlab-secrets.json files contain sensitive data \n" \
|
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)
|
"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)
|
Files.new(progress, app_files_dir, excludes: excludes)
|
||||||
end
|
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?
|
def incremental?
|
||||||
@incremental
|
@incremental
|
||||||
end
|
end
|
||||||
|
@ -299,7 +306,7 @@ module Backup
|
||||||
|
|
||||||
def upload
|
def upload
|
||||||
connection_settings = Gitlab.config.backup.upload.connection
|
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)
|
puts_time "Uploading backup archive to remote storage #{remote_directory} ... ".color(:blue) + "[SKIPPED]".color(:cyan)
|
||||||
return
|
return
|
||||||
end
|
end
|
||||||
|
@ -405,8 +412,7 @@ module Backup
|
||||||
def unpack(source_backup_id)
|
def unpack(source_backup_id)
|
||||||
if source_backup_id.blank? && non_tarred_backup?
|
if source_backup_id.blank? && non_tarred_backup?
|
||||||
puts_time "Non tarred backup found in #{backup_path}, using that"
|
puts_time "Non tarred backup found in #{backup_path}, using that"
|
||||||
|
return
|
||||||
return false
|
|
||||||
end
|
end
|
||||||
|
|
||||||
Dir.chdir(backup_path) do
|
Dir.chdir(backup_path) do
|
||||||
|
|
|
@ -19,7 +19,12 @@ module Banzai
|
||||||
def find_object(project, id)
|
def find_object(project, id)
|
||||||
return unless project.is_a?(Project) && project.valid_repo?
|
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
|
record
|
||||||
end
|
end
|
||||||
|
|
|
@ -281,6 +281,8 @@ class Feature
|
||||||
end
|
end
|
||||||
|
|
||||||
class Target
|
class Target
|
||||||
|
UnknowTargetError = Class.new(StandardError)
|
||||||
|
|
||||||
attr_reader :params
|
attr_reader :params
|
||||||
|
|
||||||
def initialize(params)
|
def initialize(params)
|
||||||
|
@ -292,7 +294,7 @@ class Feature
|
||||||
end
|
end
|
||||||
|
|
||||||
def targets
|
def targets
|
||||||
[feature_group, user, project, group, namespace].compact
|
[feature_group, users, projects, groups, namespaces].flatten.compact
|
||||||
end
|
end
|
||||||
|
|
||||||
private
|
private
|
||||||
|
@ -305,29 +307,37 @@ class Feature
|
||||||
end
|
end
|
||||||
# rubocop: enable CodeReuse/ActiveRecord
|
# rubocop: enable CodeReuse/ActiveRecord
|
||||||
|
|
||||||
def user
|
def users
|
||||||
return unless params.key?(:user)
|
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
|
end
|
||||||
|
|
||||||
def project
|
def projects
|
||||||
return unless params.key?(:project)
|
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
|
end
|
||||||
|
|
||||||
def group
|
def groups
|
||||||
return unless params.key?(:group)
|
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
|
end
|
||||||
|
|
||||||
def namespace
|
def namespaces
|
||||||
return unless params.key?(:namespace)
|
return unless params.key?(:namespace)
|
||||||
|
|
||||||
# We are interested in Group or UserNamespace
|
params[:namespace].split(',').map do |arg|
|
||||||
Namespace.without_project_namespaces.find_by_full_path(params[:namespace])
|
# 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
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -44,6 +44,7 @@ module Gitlab
|
||||||
allow_sentry(directives) if Gitlab.config.sentry&.enabled && Gitlab.config.sentry&.clientside_dsn
|
allow_sentry(directives) if Gitlab.config.sentry&.enabled && Gitlab.config.sentry&.clientside_dsn
|
||||||
allow_framed_gitlab_paths(directives)
|
allow_framed_gitlab_paths(directives)
|
||||||
allow_customersdot(directives) if ENV['CUSTOMER_PORTAL_URL'].present?
|
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
|
# 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
|
# 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))
|
append_to_directive(directives, 'frame_src', Gitlab::Utils.append_path(Gitlab.config.gitlab.url, path))
|
||||||
end
|
end
|
||||||
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
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -87,7 +87,12 @@ module Gitlab
|
||||||
length = [sha1.length, sha2.length].min
|
length = [sha1.length, sha2.length].min
|
||||||
return false if length < Gitlab::Git::Commit::MIN_SHA_LENGTH
|
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
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -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
|
|
@ -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
|
|
|
@ -1008,10 +1008,7 @@ msgid_plural "%{strong_start}%{count} members%{strong_end} must approve to merge
|
||||||
msgstr[0] ""
|
msgstr[0] ""
|
||||||
msgstr[1] ""
|
msgstr[1] ""
|
||||||
|
|
||||||
msgid "%{strong_start}%{human_size}%{strong_end} Files"
|
msgid "%{strong_start}%{human_size}%{strong_end} Project Storage"
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
msgid "%{strong_start}%{human_size}%{strong_end} Storage"
|
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
msgid "%{strong_start}%{release_count}%{strong_end} Release"
|
msgid "%{strong_start}%{release_count}%{strong_end} Release"
|
||||||
|
@ -37702,6 +37699,9 @@ msgstr ""
|
||||||
msgid "The comparison view may be inaccurate due to merge conflicts."
|
msgid "The comparison view may be inaccurate due to merge conflicts."
|
||||||
msgstr ""
|
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."
|
msgid "The compliance report shows the merge request violations merged in protected environments."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
|
|
Binary file not shown.
After Width: | Height: | Size: 1.8 KiB |
Binary file not shown.
After Width: | Height: | Size: 3.5 KiB |
Binary file not shown.
After Width: | Height: | Size: 8.9 KiB |
|
@ -11,10 +11,8 @@ module QA
|
||||||
|
|
||||||
view 'app/assets/javascripts/packages_and_registries/container_registry/explorer/components/details_page/tags_list_row.vue' do
|
view 'app/assets/javascripts/packages_and_registries/container_registry/explorer/components/details_page/tags_list_row.vue' do
|
||||||
element :more_actions_menu
|
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_delete_button
|
||||||
|
element :tag_name_content
|
||||||
end
|
end
|
||||||
|
|
||||||
def has_registry_repository?(name)
|
def has_registry_repository?(name)
|
||||||
|
@ -26,11 +24,11 @@ module QA
|
||||||
end
|
end
|
||||||
|
|
||||||
def has_tag?(tag_name)
|
def has_tag?(tag_name)
|
||||||
has_button?(tag_name)
|
has_element?(:tag_name_content, text: tag_name)
|
||||||
end
|
end
|
||||||
|
|
||||||
def has_no_tag?(tag_name)
|
def has_no_tag?(tag_name)
|
||||||
has_no_button?(tag_name)
|
has_no_element?(:tag_name_content, text: tag_name)
|
||||||
end
|
end
|
||||||
|
|
||||||
def click_delete
|
def click_delete
|
||||||
|
|
|
@ -7,14 +7,15 @@ module QA
|
||||||
include Members
|
include Members
|
||||||
include Visibility
|
include Visibility
|
||||||
|
|
||||||
attr_accessor :repository_storage, # requires admin access
|
attr_accessor :initialize_with_readme,
|
||||||
:initialize_with_readme,
|
|
||||||
:auto_devops_enabled,
|
:auto_devops_enabled,
|
||||||
:github_personal_access_token,
|
:github_personal_access_token,
|
||||||
:github_repository_path,
|
:github_repository_path,
|
||||||
:gitlab_repository_path,
|
:gitlab_repository_path,
|
||||||
:personal_namespace
|
:personal_namespace
|
||||||
|
|
||||||
|
attr_reader :repository_storage
|
||||||
|
|
||||||
attributes :id,
|
attributes :id,
|
||||||
:name,
|
:name,
|
||||||
:path,
|
:path,
|
||||||
|
@ -70,6 +71,15 @@ module QA
|
||||||
@name = @add_name_uuid ? "#{raw_name}-#{SecureRandom.hex(8)}" : raw_name
|
@name = @add_name_uuid ? "#{raw_name}-#{SecureRandom.hex(8)}" : raw_name
|
||||||
end
|
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!
|
def fabricate!
|
||||||
return if @import
|
return if @import
|
||||||
|
|
||||||
|
|
|
@ -297,12 +297,21 @@ function deploy() {
|
||||||
|
|
||||||
create_application_secret
|
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_CMD=$(cat << EOF
|
||||||
helm upgrade \
|
helm upgrade \
|
||||||
--namespace="${namespace}" \
|
--namespace="${namespace}" \
|
||||||
--create-namespace \
|
--create-namespace \
|
||||||
--install \
|
--install \
|
||||||
--wait \
|
--wait \
|
||||||
|
-f review_apps.values.yml \
|
||||||
--timeout "${HELM_INSTALL_TIMEOUT:-20m}" \
|
--timeout "${HELM_INSTALL_TIMEOUT:-20m}" \
|
||||||
--set ci.branch="${CI_COMMIT_REF_NAME}" \
|
--set ci.branch="${CI_COMMIT_REF_NAME}" \
|
||||||
--set ci.commit.sha="${CI_COMMIT_SHORT_SHA}" \
|
--set ci.commit.sha="${CI_COMMIT_SHORT_SHA}" \
|
||||||
|
|
|
@ -124,7 +124,7 @@ RSpec.describe 'Comments on personal snippets', :js do
|
||||||
|
|
||||||
page.within('.current-note-edit-form') do
|
page.within('.current-note-edit-form') do
|
||||||
fill_in 'note[note]', with: 'new content'
|
fill_in 'note[note]', with: 'new content'
|
||||||
find('.btn-success').click
|
find('.btn-confirm').click
|
||||||
end
|
end
|
||||||
|
|
||||||
page.within("#notes-list li#note_#{snippet_notes[0].id}") do
|
page.within("#notes-list li#note_#{snippet_notes[0].id}") do
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
import { GlDeprecatedSkeletonLoading as GlSkeletonLoading } from '@gitlab/ui';
|
import { GlSkeletonLoader } from '@gitlab/ui';
|
||||||
import { mount } from '@vue/test-utils';
|
import { mount } from '@vue/test-utils';
|
||||||
import Vue, { nextTick } from 'vue';
|
import Vue, { nextTick } from 'vue';
|
||||||
import Vuex from 'vuex';
|
import Vuex from 'vuex';
|
||||||
|
@ -47,7 +47,7 @@ describe('IdeSidebar', () => {
|
||||||
|
|
||||||
await nextTick();
|
await nextTick();
|
||||||
|
|
||||||
expect(wrapper.findAll(GlSkeletonLoading)).toHaveLength(3);
|
expect(wrapper.findAll(GlSkeletonLoader)).toHaveLength(3);
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('deferred rendering components', () => {
|
describe('deferred rendering components', () => {
|
||||||
|
|
|
@ -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
|
|
@ -15,6 +15,7 @@ RSpec.describe Backup::Manager do
|
||||||
# is trying to display a diff and `File.exist?` is stubbed. Adding a
|
# is trying to display a diff and `File.exist?` is stubbed. Adding a
|
||||||
# default stub fixes this.
|
# default stub fixes this.
|
||||||
allow(File).to receive(:exist?).and_call_original
|
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(:puts)
|
||||||
allow(progress).to receive(:print)
|
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)
|
allow(task2).to receive(:dump).with(File.join(Gitlab.config.backup.path, 'task2.tar.gz'), full_backup_id)
|
||||||
end
|
end
|
||||||
|
|
||||||
it 'executes tar' do
|
it 'creates a backup tar' do
|
||||||
travel_to(backup_time) do
|
travel_to(backup_time) do
|
||||||
subject.create # rubocop:disable Rails/SaveBang
|
subject.create # rubocop:disable Rails/SaveBang
|
||||||
|
|
||||||
expect(Kernel).to have_received(:system).with(*pack_tar_cmdline)
|
|
||||||
end
|
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
|
end
|
||||||
|
|
||||||
context 'when BACKUP is set' do
|
context 'when BACKUP is set' do
|
||||||
|
@ -203,6 +206,8 @@ RSpec.describe Backup::Manager do
|
||||||
end.to raise_error(Backup::Error, 'Backup failed')
|
end.to raise_error(Backup::Error, 'Backup failed')
|
||||||
|
|
||||||
expect(Gitlab::BackupLogger).to have_received(:info).with(message: "Creating archive #{pack_tar_file} 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
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -597,6 +602,7 @@ RSpec.describe Backup::Manager do
|
||||||
skipped: 'tar',
|
skipped: 'tar',
|
||||||
tar_version: be_a(String)
|
tar_version: be_a(String)
|
||||||
)
|
)
|
||||||
|
expect(FileUtils).to have_received(:rm_rf).with(File.join(Gitlab.config.backup.path, 'tmp'))
|
||||||
end
|
end
|
||||||
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(*unpack_tar_cmdline)
|
||||||
expect(Kernel).to have_received(:system).with(*pack_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
|
end
|
||||||
|
|
||||||
context 'untar fails' do
|
context 'untar fails' do
|
||||||
|
@ -724,6 +732,8 @@ RSpec.describe Backup::Manager do
|
||||||
end.to raise_error(Backup::Error, 'Backup failed')
|
end.to raise_error(Backup::Error, 'Backup failed')
|
||||||
|
|
||||||
expect(Gitlab::BackupLogger).to have_received(:info).with(message: "Creating archive #{pack_tar_file} 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
|
||||||
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(*unpack_tar_cmdline)
|
||||||
expect(Kernel).to have_received(:system).with(*pack_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
|
end
|
||||||
|
|
||||||
context 'untar fails' do
|
context 'untar fails' do
|
||||||
|
@ -817,6 +829,8 @@ RSpec.describe Backup::Manager do
|
||||||
end.to raise_error(Backup::Error, 'Backup failed')
|
end.to raise_error(Backup::Error, 'Backup failed')
|
||||||
|
|
||||||
expect(Gitlab::BackupLogger).to have_received(:info).with(message: "Creating archive #{pack_tar_file} 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
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -1001,6 +1015,8 @@ RSpec.describe Backup::Manager do
|
||||||
subject.restore
|
subject.restore
|
||||||
|
|
||||||
expect(Kernel).to have_received(:system).with(*tar_cmdline)
|
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
|
end
|
||||||
|
|
||||||
context 'tar fails' do
|
context 'tar fails' do
|
||||||
|
@ -1031,22 +1047,6 @@ RSpec.describe Backup::Manager do
|
||||||
.with(a_string_matching('GitLab version mismatch'))
|
.with(a_string_matching('GitLab version mismatch'))
|
||||||
end
|
end
|
||||||
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
|
||||||
|
|
||||||
context 'when there is a non-tarred backup in the directory' do
|
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)
|
expect(progress).to have_received(:puts)
|
||||||
.with(a_string_matching('Non tarred backup found '))
|
.with(a_string_matching('Non tarred backup found '))
|
||||||
|
expect(FileUtils).to have_received(:rm_rf).with(File.join(Gitlab.config.backup.path, 'tmp'))
|
||||||
end
|
end
|
||||||
|
|
||||||
context 'on version mismatch' do
|
context 'on version mismatch' do
|
||||||
|
@ -1082,22 +1083,6 @@ RSpec.describe Backup::Manager do
|
||||||
.with(a_string_matching('GitLab version mismatch'))
|
.with(a_string_matching('GitLab version mismatch'))
|
||||||
end
|
end
|
||||||
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
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -178,6 +178,16 @@ RSpec.describe Gitlab::ContentSecurityPolicy::ConfigLoader do
|
||||||
expect(directives['connect_src']).not_to include(snowplow_micro_url)
|
expect(directives['connect_src']).not_to include(snowplow_micro_url)
|
||||||
end
|
end
|
||||||
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
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -211,16 +211,6 @@ RSpec.describe ProjectPresenter do
|
||||||
context 'statistics anchors (empty repo)' do
|
context 'statistics anchors (empty repo)' do
|
||||||
let_it_be(:project) { create(:project, :empty_repo) }
|
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
|
describe '#storage_anchor_data' do
|
||||||
it 'returns storage data' do
|
it 'returns storage data' do
|
||||||
expect(presenter.storage_anchor_data).to have_attributes(
|
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) }
|
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(
|
|
||||||
is_link: true,
|
|
||||||
label: a_string_including('0 Bytes'),
|
|
||||||
link: presenter.project_tree_path(project)
|
|
||||||
)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
describe '#storage_anchor_data' do
|
describe '#storage_anchor_data' do
|
||||||
it 'returns storage data' do
|
it 'returns storage data without usage quotas link for non-admin users' do
|
||||||
expect(presenter.storage_anchor_data).to have_attributes(
|
expect(presenter.storage_anchor_data).to have_attributes(
|
||||||
is_link: true,
|
is_link: true,
|
||||||
label: a_string_including('0 Bytes'),
|
label: a_string_including('0 Bytes'),
|
||||||
link: presenter.project_tree_path(project)
|
link: nil
|
||||||
|
)
|
||||||
|
end
|
||||||
|
|
||||||
|
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_usage_quotas_path(project)
|
||||||
)
|
)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -113,7 +113,7 @@ RSpec.describe API::Environments do
|
||||||
end
|
end
|
||||||
|
|
||||||
context 'when filtering' do
|
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
|
it 'returns environment by name' do
|
||||||
get api("/projects/#{project.id}/environments?name=#{environment.name}", user)
|
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)
|
expect(json_response.size).to eq(0)
|
||||||
end
|
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)
|
get api("/projects/#{project.id}/environments?states=test", user)
|
||||||
|
|
||||||
expect(response).to have_gitlab_http_status(:bad_request)
|
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
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -168,19 +168,15 @@ RSpec.describe API::Features, stub_feature_flags: false do
|
||||||
end
|
end
|
||||||
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
|
it 'returns the current state of the flag without changes' do
|
||||||
post api("/features/#{feature_name}", admin), params: { value: 'true', actor_type => actor_path }
|
post api("/features/#{feature_name}", admin), params: { value: 'true', actor_type => actor_path }
|
||||||
|
|
||||||
expect(response).to have_gitlab_http_status(:created)
|
expect(response).to have_gitlab_http_status(:bad_request)
|
||||||
expect(json_response).to match(
|
expect(json_response['message']).to eq("400 Bad request - #{expected_inexistent_path} is not found!")
|
||||||
"name" => feature_name,
|
|
||||||
"state" => "off",
|
|
||||||
"gates" => [
|
|
||||||
{ "key" => "boolean", "value" => false }
|
|
||||||
],
|
|
||||||
'definition' => known_feature_flag_definition_hash
|
|
||||||
)
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -201,6 +197,19 @@ RSpec.describe API::Features, stub_feature_flags: false do
|
||||||
end
|
end
|
||||||
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 enabling for a project by path' do
|
||||||
context 'when the project exists' do
|
context 'when the project exists' do
|
||||||
it_behaves_like 'enables the flag for the actor', :project 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
|
end
|
||||||
|
|
||||||
context 'when the project does not exist' do
|
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
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -221,7 +232,9 @@ RSpec.describe API::Features, stub_feature_flags: false do
|
||||||
end
|
end
|
||||||
|
|
||||||
context 'when the group does not exist' do
|
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
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -239,7 +252,9 @@ RSpec.describe API::Features, stub_feature_flags: false do
|
||||||
end
|
end
|
||||||
|
|
||||||
context 'when the user namespace does not exist' do
|
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
|
end
|
||||||
|
|
||||||
context 'when a project namespace exists' do
|
context 'when a project namespace exists' do
|
||||||
|
@ -251,6 +266,98 @@ RSpec.describe API::Features, stub_feature_flags: false do
|
||||||
end
|
end
|
||||||
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
|
it 'creates a feature with the given percentage of time if passed an integer' do
|
||||||
post api("/features/#{feature_name}", admin), params: { value: '50' }
|
post api("/features/#{feature_name}", admin), params: { value: '50' }
|
||||||
|
|
||||||
|
|
|
@ -3,6 +3,15 @@
|
||||||
require 'spec_helper'
|
require 'spec_helper'
|
||||||
|
|
||||||
RSpec.describe PwaController do
|
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
|
describe 'GET #offline' do
|
||||||
it 'responds with static HTML page' do
|
it 'responds with static HTML page' do
|
||||||
get offline_path
|
get offline_path
|
||||||
|
|
|
@ -239,6 +239,7 @@ RSpec.shared_examples 'it runs batched background migration jobs' do |tracking_d
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
let(:gitlab_schema) { "gitlab_#{tracking_database}" }
|
||||||
let!(:migration) do
|
let!(:migration) do
|
||||||
create(
|
create(
|
||||||
:batched_background_migration,
|
:batched_background_migration,
|
||||||
|
@ -249,10 +250,12 @@ RSpec.shared_examples 'it runs batched background migration jobs' do |tracking_d
|
||||||
batch_size: batch_size,
|
batch_size: batch_size,
|
||||||
sub_batch_size: sub_batch_size,
|
sub_batch_size: sub_batch_size,
|
||||||
job_class_name: 'ExampleDataMigration',
|
job_class_name: 'ExampleDataMigration',
|
||||||
job_arguments: [1]
|
job_arguments: [1],
|
||||||
|
gitlab_schema: gitlab_schema
|
||||||
)
|
)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
let(:base_model) { Gitlab::Database.database_base_models[tracking_database] }
|
||||||
let(:table_name) { 'example_data' }
|
let(:table_name) { 'example_data' }
|
||||||
let(:batch_size) { 5 }
|
let(:batch_size) { 5 }
|
||||||
let(:sub_batch_size) { 2 }
|
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};
|
WHERE some_column = #{migration_records - 5};
|
||||||
SQL
|
SQL
|
||||||
|
|
||||||
stub_feature_flags(execute_batched_migrations_on_schedule: true)
|
stub_feature_flags(feature_flag => true)
|
||||||
|
|
||||||
stub_const('Gitlab::BackgroundMigration::ExampleDataMigration', migration_class)
|
stub_const('Gitlab::BackgroundMigration::ExampleDataMigration', migration_class)
|
||||||
end
|
end
|
||||||
|
|
|
@ -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
|
|
@ -14,6 +14,35 @@ RSpec.describe 'layouts/application' do
|
||||||
allow(view).to receive(:current_user_mode).and_return(Gitlab::Auth::CurrentUserMode.new(user))
|
allow(view).to receive(:current_user_mode).and_return(Gitlab::Auth::CurrentUserMode.new(user))
|
||||||
end
|
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
|
context 'body data elements for pageview context' do
|
||||||
let(:body_data) do
|
let(:body_data) do
|
||||||
{
|
{
|
||||||
|
|
|
@ -2,6 +2,6 @@
|
||||||
|
|
||||||
require 'spec_helper'
|
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
|
it_behaves_like 'it runs batched background migration jobs', 'ci', feature_flag: :execute_batched_migrations_on_schedule_ci_database
|
||||||
end
|
end
|
||||||
|
|
Loading…
Reference in New Issue