Add latest changes from gitlab-org/gitlab@master

This commit is contained in:
GitLab Bot 2022-05-19 09:09:08 +00:00
parent 224d2fe167
commit 3fbfc0075a
51 changed files with 748 additions and 279 deletions

View File

@ -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 \

View File

@ -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'

View File

@ -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'

View File

@ -1 +1 @@
2106629e3af3e8949b23f20825d6bfee62c10992 d7eedd059daf9059990a95e53c76e567eac64899

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.0 KiB

After

Width:  |  Height:  |  Size: 1.6 KiB

View File

@ -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>

View File

@ -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"
> >

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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 }

View File

@ -0,0 +1 @@
= javascript_tag "", visual_review_toolbar_options

View File

@ -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 }

View File

@ -0,0 +1,27 @@
{
"name": "GitLab",
"short_name": "GitLab",
"description": "<%= _("The complete DevOps platform. One application with endless possibilities. Organizations rely on GitLabs 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"
}]
}

View File

@ -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")

View File

@ -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'

View File

@ -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'

View File

@ -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

View File

@ -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`,

View File

@ -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:

View File

@ -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**.

View File

@ -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

View File

@ -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

View File

@ -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'

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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 GitLabs 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

View File

@ -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

View File

@ -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

View File

@ -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}" \

View File

@ -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

View File

@ -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', () => {

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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' }

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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
{ {

View File

@ -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