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