From 3fbfc0075a306ad85c70c006b978a2e96bd4283a Mon Sep 17 00:00:00 2001 From: GitLab Bot Date: Thu, 19 May 2022 09:09:08 +0000 Subject: [PATCH] Add latest changes from gitlab-org/gitlab@master --- .gitlab/ci/review-apps/qa.gitlab-ci.yml | 2 + .rubocop_todo/layout/line_length.yml | 1 - .../style/percent_literal_delimiters.yml | 1 - GITALY_SERVER_VERSION | 2 +- app/assets/images/auth_buttons/gitlab_64.png | Bin 2070 -> 1622 bytes .../ide/components/ide_side_bar.vue | 6 +- .../components/details_page/tags_list_row.vue | 1 + app/controllers/pwa_controller.rb | 3 + app/helpers/tooling/visual_review_helper.rb | 26 +++ app/presenters/project_presenter.rb | 17 +- app/views/layouts/_head.html.haml | 1 + app/views/layouts/_visual_review.html.haml | 1 + app/views/layouts/application.html.haml | 1 + app/views/pwa/manifest.json.erb | 27 ++++ app/views/shared/notes/_edit_form.html.haml | 2 +- config/initializers/1_settings.rb | 6 +- config/routes.rb | 1 + .../composite_primary_keys.rb | 63 -------- doc/api/features.md | 8 +- doc/ci/pipelines/cicd_minutes.md | 2 + doc/ci/runners/runners_scope.md | 47 ++++-- .../settings/continuous_integration.md | 21 ++- lib/api/environments.rb | 5 +- lib/api/features.rb | 13 +- lib/backup/manager.rb | 90 ++++++----- .../references/commit_reference_filter.rb | 7 +- lib/feature.rb | 30 ++-- .../content_security_policy/config_loader.rb | 6 + lib/gitlab/git.rb | 7 +- lib/tasks/gitlab/db/lock_writes.rake | 85 ++++++++++ lib/tasks/migrate/composite_primary_keys.rake | 17 -- locale/gitlab.pot | 8 +- public/-/pwa-icons/logo-192.png | Bin 0 -> 1855 bytes public/-/pwa-icons/logo-512.png | Bin 0 -> 3620 bytes public/-/pwa-icons/maskable-logo.png | Bin 0 -> 9126 bytes qa/qa/page/project/registry/show.rb | 8 +- qa/qa/resource/project.rb | 14 +- scripts/review_apps/review-apps.sh | 9 ++ .../notes_on_personal_snippets_spec.rb | 2 +- .../ide/components/ide_side_bar_spec.js | 4 +- .../tooling/visual_review_helper_spec.rb | 25 +++ spec/lib/backup/manager_spec.rb | 55 +++---- .../config_loader_spec.rb | 10 ++ spec/presenters/project_presenter_spec.rb | 34 ++-- spec/requests/api/environments_spec.rb | 27 +++- spec/requests/api/features_spec.rb | 133 +++++++++++++-- spec/requests/pwa_controller_spec.rb | 9 ++ ...ground_migration_worker_shared_examples.rb | 7 +- spec/tasks/gitlab/db/lock_writes_rake_spec.rb | 152 ++++++++++++++++++ .../layouts/application.html.haml_spec.rb | 29 ++++ .../ci_database_worker_spec.rb | 2 +- 51 files changed, 748 insertions(+), 279 deletions(-) create mode 100644 app/helpers/tooling/visual_review_helper.rb create mode 100644 app/views/layouts/_visual_review.html.haml create mode 100644 app/views/pwa/manifest.json.erb delete mode 100644 db/optional_migrations/composite_primary_keys.rb create mode 100644 lib/tasks/gitlab/db/lock_writes.rake delete mode 100644 lib/tasks/migrate/composite_primary_keys.rake create mode 100644 public/-/pwa-icons/logo-192.png create mode 100644 public/-/pwa-icons/logo-512.png create mode 100644 public/-/pwa-icons/maskable-logo.png create mode 100644 spec/helpers/tooling/visual_review_helper_spec.rb create mode 100644 spec/tasks/gitlab/db/lock_writes_rake_spec.rb diff --git a/.gitlab/ci/review-apps/qa.gitlab-ci.yml b/.gitlab/ci/review-apps/qa.gitlab-ci.yml index f026018b61a..9af8b3d9ead 100644 --- a/.gitlab/ci/review-apps/qa.gitlab-ci.yml +++ b/.gitlab/ci/review-apps/qa.gitlab-ci.yml @@ -50,6 +50,8 @@ include: --tag ~orchestrated \ --tag ~transient \ --tag ~skip_signup_disabled \ + --tag ~requires_git_protocol_v2 \ + --tag ~requires_praefect \ --force-color \ --order random \ --format documentation \ diff --git a/.rubocop_todo/layout/line_length.yml b/.rubocop_todo/layout/line_length.yml index a9200ee9fba..adcd5c941a7 100644 --- a/.rubocop_todo/layout/line_length.yml +++ b/.rubocop_todo/layout/line_length.yml @@ -1001,7 +1001,6 @@ Layout/LineLength: - 'db/migrate/20220310101118_update_holder_name_limit.rb' - 'db/migrate/20220314184209_add_group_fk_to_protected_environment_approval_rules.rb' - 'db/migrate/20220314204009_add_approval_rule_fk_to_deployment_approvals.rb' - - 'db/optional_migrations/composite_primary_keys.rb' - 'db/post_migrate/20210328214434_remove_temporary_index_from_vulnerabilities_table.rb' - 'db/post_migrate/20210401131948_move_container_registry_enabled_to_project_features2.rb' - 'db/post_migrate/20210402005225_add_source_and_level_index_on_notification_settings.rb' diff --git a/.rubocop_todo/style/percent_literal_delimiters.yml b/.rubocop_todo/style/percent_literal_delimiters.yml index bf50c4c1922..b555882b7ac 100644 --- a/.rubocop_todo/style/percent_literal_delimiters.yml +++ b/.rubocop_todo/style/percent_literal_delimiters.yml @@ -229,7 +229,6 @@ Style/PercentLiteralDelimiters: - 'db/migrate/20210621044000_rename_services_indexes_to_integrations.rb' - 'db/migrate/20210709085759_index_batched_migration_jobs_by_max_value.rb' - 'db/migrate/20210928155022_improve_index_for_error_tracking.rb' - - 'db/optional_migrations/composite_primary_keys.rb' - 'db/post_migrate/20210329102724_add_new_trail_plans.rb' - 'db/post_migrate/20210420121149_backfill_conversion_of_ci_job_artifacts.rb' - 'db/post_migrate/20210426094549_backfill_ci_builds_for_bigint_conversion.rb' diff --git a/GITALY_SERVER_VERSION b/GITALY_SERVER_VERSION index 8aa8a81ede5..301f4080bf2 100644 --- a/GITALY_SERVER_VERSION +++ b/GITALY_SERVER_VERSION @@ -1 +1 @@ -2106629e3af3e8949b23f20825d6bfee62c10992 +d7eedd059daf9059990a95e53c76e567eac64899 diff --git a/app/assets/images/auth_buttons/gitlab_64.png b/app/assets/images/auth_buttons/gitlab_64.png index f675678dc9d376dd166b8ad98207d192cc7db09c..860f9c1be9b926bba3d4bba28f7eb39b2d7642ec 100644 GIT binary patch delta 1618 zcmV-Y2Cey)5Y`Nk7k@wq1^@s6s%dfF00009a7bBm000XU000XU0RWnu7ytkO0drDE zLIAGL9O(c600d`2O+f$vv5yPVNub+8XGxCfSxz<(8k0!k>G08xny#LD8> zhx0hGu^qph>^#&9SHVn8bk5Gkd2wpB0fOcb5BaEz%Tr6>;tO#frl=YQ%qpE=b zLIF_dzZ=|KTF{N4VEBJ{Dd203($h^4K+FP!gjKXq8wAPUh7ec>0b&Ete!X7hyGCdM z1==7;{w^B7Hmv_1T}JE)IzINoDbfZZ@^|P_+JqQl1Ah>A6DY_AQS!GTgk6IeA_Ksm zuN!eUu?i``1~KxtiEW%X1qjKz2pb3}(?U_m`%%A#24I-{L|Zmj8;Ho?#cEWaAT$6< zFFf-H7DTDk`QtzhDa%Fn3BoNvqNOu>I?K%lgo-`mo%1951c3pNl$cF54;wsqZFv>s z|63#MzkdY=Krq9OUYCOnjK1201cW`Q0s{~<@DU*o8_3Gvxh`aYG!lUQSL)56fsY`W z+909)ZM3pl8F_T7Bd3KAe1uR`Wt|710~uhLq$xFp<-eY>Ex!B0{hg;geD*6kX~Qwb zFb=0`txnfI_E;cp;By?Hw6f(nb{~j#FP~=o1AinZ0HOn*V}PgI?gU__W^8B|OsHrK zloEsE2B8d2^SWUj7wU$S=$$0ke|N=G>nHyZIDAClad(`&TRi0ogNa!N&zHhiN?_93Ljv$d=a)?@xfIQrX$I%2 za+P;8umE4}m0T#cK%Xh4Vl07@pI#iv?tj9VjQouIVG^VrNC;YD&;t3Xp>%4H=#o|* zVT@Sgfq?~>*@7{!ErD{(8obBs3&|z#Fs!q?BlIWVC7lOw!2ZIc-zYC1Qbjh)$Iwwf z+^gXSCkgVtz^9M`Xo!fi;2G%bN;%v4j5ybG%9&VMU3 zP5{au|L~2;AQctZOwOC|Jl{xyhb>B8yTGh6XrhO8NV&jnlTSJw``A--N$ufeK^Icw z?6kZkY{^%&loJV`^m4a8;++uTqa$F(CZr6?7c;krC9;sb12e9>TOSz_mwM}`&lv;Z z*UUtrO0*?V@{{xFOMVE$I$_;L?tdG0g?@Eqn=dIzEsu#}=-?SkpgqoI)#{kvO>({b z(f2z+KSuUnd+#i-@)ec4kik@$X#o87JGIm>F_%fJBW=daw@bHvY=xZ=+krpas#i;f zc@xB!%EjQ8fQ8}beMlR|Mc-0X{Y@*cMIbzaN!1c%zOC@BGCY35xBGnSL4O)VO5Tsu z5H1x<@F_M}gwZuA48lXc9l$umO5Tt37&fsbY{}2ZocD0J$ssIxKZytNQkL*bJ`Ooc zNJ-w$ShE;HmcaMPaonYX9AcJ`mb{;_r*R@JaS`GrA4ixa7`vB5@_tgA$MRT0R`PK? z=Gz`Pmf(_qFRA4Hq&ksjS$|@ZTVjPNA;#yWA3L1|26p ziJ|oMH^^ou)zNBZQ@Q#bZV5=I<75&p`M1)Myq`?xvh_PJHVC7;{4P~U0^^b=T=JLG zlf0kIC-VoEFn0N^8X!eBlJ}F_YKDv@`26IJ(v`h!$bnp!bNZhx@k_+0+zhou4wCni z`|2iIqO2Hq86mzUc}wz}T=E^rk;0aDElV)4URYuTJ}6zzNAf3}aDs>b06{bzIi);$ Q)Bpeg07*qoM6N<$g844^pa1{> literal 2070 zcmV+x2})a8h(tkpj)>uc&DTXu=w`mzPx@I z7NV9v+Xt#j?9H0yz(RbQKieNFjMD@XDf|ak;GZ}Hs!E)UzrYGCgDzzWyZ{eYAcJS1 zs>CB`gcUG&y(9-9QRlS=wuSlfcU2Pq=>hBe7=O24(IvnKosNG7Oz=0HS|zvt9|G35 z80VCt1fE7FV1h%g^zP(t(B-lLam)xG( z7b+ha@}uFwUkgm|C!7S;MvlX8Ve|Mm&Mc+`9?RXX!P0mLs*T)@9k6+Lcs9-f5U-TP ze*&`w2C71i#7|)z z{RJmSDS>-(x2v1&xD%>EZo(E=M`=9bbO8U|760+D5shAadtxuBa$~MRun|oUDubZ- zAA?`PM)Y$W1yyE;;|H)2{Q)Pql)xR>4ja){+zM4@*I^TEL=Cv#F-BakN)^-d<2*=O9d-KEl-N zAMplj`e?%sSSE|6gSI=~NSLK}rF^NmQj*Wy{(8bNNAz{ILvQvppVxZ*OZIv+l#pSM z8?c!-STl<^ch%WAhv0pnvqaTM&6V;1Rz8q4nfF-}b3`V4nW>HJjXiF|Z9xan9~PVv zCTY)f)P9@^H$Ic_N_qf%)`q8(UaZZ_9wur}_SM9Q76)+Np}c+%wh7;i)H12x)@88t z&e-wy%?S2T%WLz!{S-$wO~BRI2;0meqBB5yQATJt>VGC|* zdH_9PTdBufYNu!POL4<9Ny?x!@n8Q`GFlSbp$BW8ldJn61EKcB{~NG@>M@;@?4q+# z%7Dn0qzokf+g^(p|IVy+s`d(OUVI-%Du(N@7PbK2jME;A@FSH0(Jl4>R@_hg--~qk zz8T9}Su<<_8*rOq7#jX&$VD<#N4bf8Zf?q;$oM}QG5$kXV@>#e;A5O(_${oH1v+c` z%6;Hi2CpSNiEvTze>Fk!9P3^hIM(R8AN*A@Y=w1FkJ(Jp4ecDufC$Hq|9wf*=o0^S zkY;CA?$UFWQ4HUNjdTc;iCHJ7y7alh0X_=@>&Q2w z9hxMfON8T<0Y9_#6{q;qur;~#++^`3T%s7nQ5cBzuuf|DkU`;1lHA~OlShw#D%<#O zNXptl8ns1{>V6}J;n=2)kUSCY&Fh2swR0)VBQ?E3?*o6`lTrpHj(8pG2r6`eJ&du%;|MWZB1a4j$u-T!P-aqg%_u2kXVPtT|2VaO!!{j{# zc?#W!3v1{cW?t#DacSt$72*WH*1ncG-;BE_?nhwAn|`BX_~~J z#;R4bvlZiUB2={=fZPrH^7oiIK^7?3h^}|WC>LKFaOZ)uUfBCz2c+Wvll_EpMnGfqL%yj+vO|K<(Y -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 { 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 {
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: ''.html_safe, - strong_end: ''.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: ''.html_safe, strong_end: ''.html_safe }, - empty_repo? ? nil : project_tree_path(project)) + can_show_quota ? project_usage_quotas_path(project) : nil) end def releases_anchor_data diff --git a/app/views/layouts/_head.html.haml b/app/views/layouts/_head.html.haml index 55c66454d0b..84eb2706929 100644 --- a/app/views/layouts/_head.html.haml +++ b/app/views/layouts/_head.html.haml @@ -68,6 +68,7 @@ %meta{ name: "description", content: page_description } + %link{ rel: 'manifest', href: manifest_path(format: :json) } %meta{ name: 'viewport', content: 'width=device-width, initial-scale=1, maximum-scale=1' } %meta{ name: 'theme-color', content: user_theme_primary_color } diff --git a/app/views/layouts/_visual_review.html.haml b/app/views/layouts/_visual_review.html.haml new file mode 100644 index 00000000000..73da841964a --- /dev/null +++ b/app/views/layouts/_visual_review.html.haml @@ -0,0 +1 @@ += javascript_tag "", visual_review_toolbar_options diff --git a/app/views/layouts/application.html.haml b/app/views/layouts/application.html.haml index bdab5d7ea07..455d18a5ae8 100644 --- a/app/views/layouts/application.html.haml +++ b/app/views/layouts/application.html.haml @@ -8,6 +8,7 @@ %body{ class: body_classes, data: body_data } = render "layouts/init_auto_complete" if @gfm_form = render "layouts/init_client_detection_flags" + = render "layouts/visual_review" if ENV['REVIEW_APPS_ENABLED'] = render 'peek/bar' = header_message = render partial: "layouts/header/default", locals: { project: @project, group: @group } diff --git a/app/views/pwa/manifest.json.erb b/app/views/pwa/manifest.json.erb new file mode 100644 index 00000000000..557a39ee157 --- /dev/null +++ b/app/views/pwa/manifest.json.erb @@ -0,0 +1,27 @@ +{ + "name": "GitLab", + "short_name": "GitLab", + "description": "<%= _("The complete DevOps platform. One application with endless possibilities. Organizations rely on GitLab’s source code management, CI/CD, security, and more to deliver software rapidly.") %>", + "start_url": "<%= explore_projects_path %>", + "scope": "<%= root_path %>", + "display": "browser", + "orientation": "any", + "background_color": "#fff", + "theme_color": "<%= user_theme_primary_color %>", + "icons": [{ + "src": "<%= Gitlab::Utils.append_path(Gitlab.config.gitlab.relative_url_root, '/-/pwa-icons/logo-192.png') %>", + "sizes": "192x192", + "type": "image/png" + }, + { + "src": "<%= Gitlab::Utils.append_path(Gitlab.config.gitlab.relative_url_root, '/-/pwa-icons/logo-512.png') %>", + "sizes": "512x512", + "type": "image/png" + }, + { + "src": "<%= Gitlab::Utils.append_path(Gitlab.config.gitlab.relative_url_root, '/-/pwa-icons/maskable-logo.png') %>", + "sizes": "512x512", + "type": "image/png", + "purpose": "maskable" + }] +} diff --git a/app/views/shared/notes/_edit_form.html.haml b/app/views/shared/notes/_edit_form.html.haml index 63c895a5a03..b41ed8f63e4 100644 --- a/app/views/shared/notes/_edit_form.html.haml +++ b/app/views/shared/notes/_edit_form.html.haml @@ -9,6 +9,6 @@ .note-form-actions.clearfix .settings-message.note-edit-warning.js-finish-edit-warning = _("Finish editing this message first!") - = submit_tag _('Save comment'), class: 'gl-button btn btn-success js-comment-save-button', data: { qa_selector: 'save_comment_button' } + = submit_tag _('Save comment'), class: 'gl-button btn btn-confirm js-comment-save-button', data: { qa_selector: 'save_comment_button' } %button.btn.gl-button.btn-cancel.note-edit-cancel{ type: 'button' } = _("Cancel") diff --git a/config/initializers/1_settings.rb b/config/initializers/1_settings.rb index 2a9ce327d9d..0bcaf0a1454 100644 --- a/config/initializers/1_settings.rb +++ b/config/initializers/1_settings.rb @@ -629,6 +629,9 @@ Settings.cron_jobs['projects_schedule_refresh_build_artifacts_size_statistics_wo Settings.cron_jobs['inactive_projects_deletion_cron_worker'] ||= Settingslogic.new({}) Settings.cron_jobs['inactive_projects_deletion_cron_worker']['cron'] ||= '0 1 * * *' Settings.cron_jobs['inactive_projects_deletion_cron_worker']['job_class'] = 'Projects::InactiveProjectsDeletionCronWorker' +Settings.cron_jobs['loose_foreign_keys_cleanup_worker'] ||= Settingslogic.new({}) +Settings.cron_jobs['loose_foreign_keys_cleanup_worker']['cron'] ||= '*/1 * * * *' +Settings.cron_jobs['loose_foreign_keys_cleanup_worker']['job_class'] = 'LooseForeignKeys::CleanupWorker' Gitlab.ee do Settings.cron_jobs['analytics_devops_adoption_create_all_snapshots_worker'] ||= Settingslogic.new({}) @@ -760,9 +763,6 @@ Gitlab.ee do Settings.cron_jobs['app_sec_dast_profile_schedule_worker'] ||= Settingslogic.new({}) Settings.cron_jobs['app_sec_dast_profile_schedule_worker']['cron'] ||= '7-59/15 * * * *' Settings.cron_jobs['app_sec_dast_profile_schedule_worker']['job_class'] = 'AppSec::Dast::ProfileScheduleWorker' - Settings.cron_jobs['loose_foreign_keys_cleanup_worker'] ||= Settingslogic.new({}) - Settings.cron_jobs['loose_foreign_keys_cleanup_worker']['cron'] ||= '*/1 * * * *' - Settings.cron_jobs['loose_foreign_keys_cleanup_worker']['job_class'] = 'LooseForeignKeys::CleanupWorker' Settings.cron_jobs['ci_namespace_mirrors_consistency_check_worker'] ||= Settingslogic.new({}) Settings.cron_jobs['ci_namespace_mirrors_consistency_check_worker']['cron'] ||= '*/4 * * * *' Settings.cron_jobs['ci_namespace_mirrors_consistency_check_worker']['job_class'] = 'Database::CiNamespaceMirrorsConsistencyCheckWorker' diff --git a/config/routes.rb b/config/routes.rb index 20098c422ec..30fdaca78aa 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -117,6 +117,7 @@ Rails.application.routes.draw do get '/whats_new' => 'whats_new#index' get 'offline' => "pwa#offline" + get 'manifest' => "pwa#manifest", constraints: lambda { |req| req.format == :json } # '/-/health' implemented by BasicHealthCheck middleware get 'liveness' => 'health#liveness' diff --git a/db/optional_migrations/composite_primary_keys.rb b/db/optional_migrations/composite_primary_keys.rb deleted file mode 100644 index 13bc58b8692..00000000000 --- a/db/optional_migrations/composite_primary_keys.rb +++ /dev/null @@ -1,63 +0,0 @@ -# frozen_string_literal: true - -# This migration adds a primary key constraint to tables -# that only have a composite unique key. -# -# This is not strictly relevant to Rails (v4 does not -# support composite primary keys). However this becomes -# useful for e.g. PostgreSQL's logical replication (pglogical) -# which requires all tables to have a primary key constraint. -# -# In that sense, the migration is optional and not strictly needed. -class CompositePrimaryKeysMigration < ActiveRecord::Migration[4.2] - include Gitlab::Database::MigrationHelpers - - DOWNTIME = false - - Index = Struct.new(:table, :name, :columns) - - TABLES = [ - Index.new(:issue_assignees, 'index_issue_assignees_on_issue_id_and_user_id', %i(issue_id user_id)), - Index.new(:user_interacted_projects, 'index_user_interacted_projects_on_project_id_and_user_id', %i(project_id user_id)), - Index.new(:merge_request_diff_files, 'index_merge_request_diff_files_on_mr_diff_id_and_order', %i(merge_request_diff_id relative_order)), - Index.new(:merge_request_diff_commits, 'index_merge_request_diff_commits_on_mr_diff_id_and_order', %i(merge_request_diff_id relative_order)), - Index.new(:project_authorizations, 'index_project_authorizations_on_user_id_project_id_access_level', %i(user_id project_id access_level)), - Index.new(:push_event_payloads, 'index_push_event_payloads_on_event_id', %i(event_id)), - Index.new(:schema_migrations, 'unique_schema_migrations', %(version)) - ].freeze - - disable_ddl_transaction! - - def up - disable_statement_timeout do - TABLES.each do |index| - add_primary_key(index) - end - end - end - - def down - disable_statement_timeout do - TABLES.each do |index| - remove_primary_key(index) - end - end - end - - private - - def add_primary_key(index) - execute "ALTER TABLE #{index.table} ADD PRIMARY KEY USING INDEX #{index.name}" - end - - def remove_primary_key(index) - temp_index_name = "#{index.name[0..58]}_old" - rename_index index.table, index.name, temp_index_name if index_exists_by_name?(index.table, index.name) - - # re-create unique key index - add_concurrent_index index.table, index.columns, unique: true, name: index.name - - # This also drops the `temp_index_name` as this is owned by the constraint - execute "ALTER TABLE #{index.table} DROP CONSTRAINT IF EXISTS #{temp_index_name}" - end -end diff --git a/doc/api/features.md b/doc/api/features.md index 346f4879358..d4829f72958 100644 --- a/doc/api/features.md +++ b/doc/api/features.md @@ -127,10 +127,10 @@ POST /features/:name | `value` | integer/string | yes | `true` or `false` to enable/disable, or an integer for percentage of time | | `key` | string | no | `percentage_of_actors` or `percentage_of_time` (default) | | `feature_group` | string | no | A Feature group name | -| `user` | string | no | A GitLab username | -| `group` | string | no | A GitLab group's path, for example `gitlab-org` | -| `namespace` | string | no | A GitLab group or user namespace's path, for example `gitlab-org` or username path. [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/353117) in GitLab 15.0. | -| `project` | string | no | A projects path, for example `gitlab-org/gitlab-foss` | +| `user` | string | no | A GitLab username or comma-separated multiple usernames | +| `group` | string | no | A GitLab group's path, for example `gitlab-org`, or comma-separated multiple group paths | +| `namespace` | string | no | A GitLab group or user namespace's path, for example `john-doe`, or comma-separated multiple namespace paths. [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/353117) in GitLab 15.0. | +| `project` | string | no | A projects path, for example `gitlab-org/gitlab-foss`, or comma-separated multiple project paths | | `force` | boolean | no | Skip feature flag validation checks, such as a YAML definition | You can enable or disable a feature for a `feature_group`, a `user`, diff --git a/doc/ci/pipelines/cicd_minutes.md b/doc/ci/pipelines/cicd_minutes.md index 2b18b1d353b..e211f76e02b 100644 --- a/doc/ci/pipelines/cicd_minutes.md +++ b/doc/ci/pipelines/cicd_minutes.md @@ -74,6 +74,8 @@ If you set a quota for a subgroup, it is not used. ## View CI/CD minutes used by a group +> Displaying shared runners duration per project [introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/355666) in GitLab 15.0. + You can view the number of CI/CD minutes being used by a group. Prerequisite: diff --git a/doc/ci/runners/runners_scope.md b/doc/ci/runners/runners_scope.md index 6082a17d001..01032c116df 100644 --- a/doc/ci/runners/runners_scope.md +++ b/doc/ci/runners/runners_scope.md @@ -226,33 +226,47 @@ A fork *does* copy the CI/CD settings of the cloned repository. ### Create a specific runner You can create a specific runner for your self-managed GitLab instance or for GitLab.com. -You must have the Owner role for the project. + +Prerequisite: + +- You must have at least the Maintainer role for the project. To create a specific runner: -1. [Install runner](https://docs.gitlab.com/runner/install/). -1. Go to the project's **Settings > CI/CD** and expand the **Runners** section. -1. Note the URL and token. +1. [Install GitLab Runner](https://docs.gitlab.com/runner/install/). +1. On the top bar, select **Menu > Projects** and find the project where you want to use the runner. +1. On the left sidebar, select **Settings > CI/CD**. +1. Expand **Runners**. +1. In the **Specific runners** section, note the URL and token. 1. [Register the runner](https://docs.gitlab.com/runner/register/). -### Enable a specific runner for a specific project +The runner is now enabled for the project. -A specific runner is available in the project it was created for. An administrator can -enable a specific runner to apply to additional projects. +### Enable a specific runner for a different project -- You must have the Owner role for the - project. +After a specific runner is created, you can enable it for other projects. + +Prerequisites: +You must have at least the Maintainer role for: + +- The project where the runner is already enabled. +- The project where you want to enable the runner. - The specific runner must not be [locked](#prevent-a-specific-runner-from-being-enabled-for-other-projects). -To enable or disable a specific runner for a project: +To enable a specific runner for a project: -1. Go to the project's **Settings > CI/CD** and expand the **Runners** section. -1. Click **Enable for this project** or **Disable for this project**. +1. On the top bar, select **Menu > Projects** and find the project where you want to enable the runner. +1. On the left sidebar, select **Settings > CI/CD**. +1. Expand **General pipelines**. +1. Expand **Runners**. +1. By the runner you want, select **Enable for this project**. You can edit a specific runner from any of the projects it's enabled for. -The modifications, which include unlocking, editing tags and the description, +The modifications, which include unlocking and editing tags and the description, affect all projects that use the runner. +An administrator can [enable the runner for multiple projects](../../user/admin_area/settings/continuous_integration.md#enable-a-specific-runner-for-multiple-projects). + ### Prevent a specific runner from being enabled for other projects You can configure a specific runner so it is "locked" and cannot be enabled for other projects. @@ -261,8 +275,9 @@ but can also be changed later. To lock or unlock a specific runner: -1. Go to the project's **Settings > CI/CD** and expand the **Runners** section. +1. Go to the project's **Settings > CI/CD**. +1. Expand the **Runners** section. 1. Find the specific runner you want to lock or unlock. Make sure it's enabled. You cannot lock shared or group runners. -1. Click the pencil button. +1. Select **Edit** (**{pencil}**). 1. Check the **Lock to current projects** option. -1. Click **Save changes**. +1. Select **Save changes**. diff --git a/doc/user/admin_area/settings/continuous_integration.md b/doc/user/admin_area/settings/continuous_integration.md index 170d3cf4c90..7f37c99259a 100644 --- a/doc/user/admin_area/settings/continuous_integration.md +++ b/doc/user/admin_area/settings/continuous_integration.md @@ -39,6 +39,23 @@ You can set all new projects to have the instance's shared runners available by Any time a new project is created, the shared runners are available. +## Shared runners CI/CD minutes + +As an administrator you can set either a global or namespace-specific +limit on the number of [CI/CD minutes](../../../ci/pipelines/cicd_minutes.md) you can use. + +## Enable a specific runner for multiple projects + +To enable a specific runner for one or more projects: + +1. On the top bar, select **Menu > Admin**. +1. From the left sidebar, select **Overview > Runners**. +1. Select the runner you want to edit. +1. In the top right, select **Edit** (**{pencil}**). +1. Under **Restrict projects for this runner**, search for a project. +1. To the left of the project, select **Enable**. +1. Repeat this process for each additional project. + ## Add a message for shared runners To display details about the instance's shared runners in all projects' @@ -143,10 +160,6 @@ A new pipeline must run before the latest artifacts can expire and be deleted. NOTE: All application settings have a [customizable cache expiry interval](../../../administration/application_settings_cache.md) which can delay the settings affect. -## Shared runners CI/CD minutes - -As an administrator you can set either a global or namespace-specific limit on the number of [CI/CD minutes](../../../ci/pipelines/cicd_minutes.md) you can use. - ## Archive jobs Archiving jobs is useful for reducing the CI/CD footprint on the system by removing some diff --git a/lib/api/environments.rb b/lib/api/environments.rb index 11f1cab0c72..efc86bdc19a 100644 --- a/lib/api/environments.rb +++ b/lib/api/environments.rb @@ -22,16 +22,15 @@ module API use :pagination optional :name, type: String, desc: 'Returns the environment with this name' optional :search, type: String, desc: 'Returns list of environments matching the search criteria' + optional :states, type: String, values: Environment.valid_states.map(&:to_s), desc: 'List all environments that match a specific state' mutually_exclusive :name, :search, message: 'cannot be used together' end get ':id/environments' do authorize! :read_environment, user_project - environments = ::Environments::EnvironmentsFinder.new(user_project, current_user, params).execute + environments = ::Environments::EnvironmentsFinder.new(user_project, current_user, declared_params(include_missing: false)).execute present paginate(environments), with: Entities::Environment, current_user: current_user - rescue ::Environments::EnvironmentsFinder::InvalidStatesError => exception - bad_request!(exception.message) end desc 'Creates a new environment' do diff --git a/lib/api/features.rb b/lib/api/features.rb index bff2817a2ec..13a6aedc2df 100644 --- a/lib/api/features.rb +++ b/lib/api/features.rb @@ -68,10 +68,13 @@ module API requires :value, type: String, desc: '`true` or `false` to enable/disable, a float for percentage of time' optional :key, type: String, desc: '`percentage_of_actors` or the default `percentage_of_time`' optional :feature_group, type: String, desc: 'A Feature group name' - optional :user, type: String, desc: 'A GitLab username' - optional :group, type: String, desc: "A GitLab group's path, such as 'gitlab-org'" - optional :namespace, type: String, desc: "A GitLab group or user namespace path, such as 'gitlab-org'" - optional :project, type: String, desc: 'A projects path, like gitlab-org/gitlab-ce' + optional :user, type: String, desc: 'A GitLab username or comma-separated multiple usernames' + optional :group, type: String, + desc: "A GitLab group's path, such as 'gitlab-org', or comma-separated multiple group paths" + optional :namespace, type: String, + desc: "A GitLab group or user namespace path, such as 'john-doe', or comma-separated multiple namespace paths" + optional :project, type: String, + desc: "A projects path, such as `gitlab-org/gitlab-ce`, or comma-separated multiple project paths" optional :force, type: Boolean, desc: 'Skip feature flag validation checks, ie. YAML definition' mutually_exclusive :key, :feature_group @@ -110,6 +113,8 @@ module API present Feature.get(params[:name]), # rubocop:disable Gitlab/AvoidFeatureGet with: Entities::Feature, current_user: current_user + rescue Feature::Target::UnknowTargetError => e + bad_request!(e.message) end desc 'Remove the gate value for the given feature' diff --git a/lib/backup/manager.rb b/lib/backup/manager.rb index 0991177d044..f249becb8f9 100644 --- a/lib/backup/manager.rb +++ b/lib/backup/manager.rb @@ -41,29 +41,8 @@ module Backup end def create - if incremental? - unpack(ENV.fetch('PREVIOUS_BACKUP', ENV['BACKUP'])) - read_backup_information - verify_backup_version - update_backup_information - end - - build_backup_information - - definitions.keys.each do |task_name| - run_create_task(task_name) - end - - write_backup_information - - if skipped?('tar') - upload - else - pack - upload - cleanup - remove_old - end + unpack(ENV.fetch('PREVIOUS_BACKUP', ENV['BACKUP'])) if incremental? + run_all_create_tasks puts_time "Warning: Your gitlab.rb and gitlab-secrets.json files contain sensitive data \n" \ "and are not included in this backup. You will need these files to restore a backup.\n" \ @@ -95,22 +74,8 @@ module Backup end def restore - cleanup_required = unpack(ENV['BACKUP']) - read_backup_information - verify_backup_version - - definitions.keys.each do |task_name| - run_restore_task(task_name) if !skipped?(task_name) && enabled_task?(task_name) - end - - Rake::Task['gitlab:shell:setup'].invoke - Rake::Task['cache:clear'].invoke - - if cleanup_required - cleanup - end - - remove_tmp + unpack(ENV['BACKUP']) + run_all_restore_tasks puts_time "Warning: Your gitlab.rb and gitlab-secrets.json files contain sensitive data \n" \ "and are not included in this backup. You will need to restore these files manually.".color(:red) @@ -232,6 +197,48 @@ module Backup Files.new(progress, app_files_dir, excludes: excludes) end + def run_all_create_tasks + if incremental? + read_backup_information + verify_backup_version + update_backup_information + end + + build_backup_information + + definitions.keys.each do |task_name| + run_create_task(task_name) + end + + write_backup_information + + unless skipped?('tar') + pack + upload + remove_old + end + + ensure + cleanup unless skipped?('tar') + remove_tmp + end + + def run_all_restore_tasks + read_backup_information + verify_backup_version + + definitions.keys.each do |task_name| + run_restore_task(task_name) if !skipped?(task_name) && enabled_task?(task_name) + end + + Rake::Task['gitlab:shell:setup'].invoke + Rake::Task['cache:clear'].invoke + + ensure + cleanup unless skipped?('tar') + remove_tmp + end + def incremental? @incremental end @@ -299,7 +306,7 @@ module Backup def upload connection_settings = Gitlab.config.backup.upload.connection - if connection_settings.blank? || skipped?('remote') + if connection_settings.blank? || skipped?('remote') || skipped?('tar') puts_time "Uploading backup archive to remote storage #{remote_directory} ... ".color(:blue) + "[SKIPPED]".color(:cyan) return end @@ -405,8 +412,7 @@ module Backup def unpack(source_backup_id) if source_backup_id.blank? && non_tarred_backup? puts_time "Non tarred backup found in #{backup_path}, using that" - - return false + return end Dir.chdir(backup_path) do diff --git a/lib/banzai/filter/references/commit_reference_filter.rb b/lib/banzai/filter/references/commit_reference_filter.rb index 157dc696cc8..86ab8597cf5 100644 --- a/lib/banzai/filter/references/commit_reference_filter.rb +++ b/lib/banzai/filter/references/commit_reference_filter.rb @@ -19,7 +19,12 @@ module Banzai def find_object(project, id) return unless project.is_a?(Project) && project.valid_repo? - _, record = reference_cache.records_per_parent[project].detect { |k, _v| Gitlab::Git.shas_eql?(k, id) } + # Optimization: try exact commit hash match first + record = reference_cache.records_per_parent[project].fetch(id, nil) + + unless record + _, record = reference_cache.records_per_parent[project].detect { |k, _v| Gitlab::Git.shas_eql?(k, id) } + end record end diff --git a/lib/feature.rb b/lib/feature.rb index b5a97ee8f9b..3bba4be7514 100644 --- a/lib/feature.rb +++ b/lib/feature.rb @@ -281,6 +281,8 @@ class Feature end class Target + UnknowTargetError = Class.new(StandardError) + attr_reader :params def initialize(params) @@ -292,7 +294,7 @@ class Feature end def targets - [feature_group, user, project, group, namespace].compact + [feature_group, users, projects, groups, namespaces].flatten.compact end private @@ -305,29 +307,37 @@ class Feature end # rubocop: enable CodeReuse/ActiveRecord - def user + def users return unless params.key?(:user) - UserFinder.new(params[:user]).find_by_username! + params[:user].split(',').map do |arg| + UserFinder.new(arg).find_by_username || (raise UnknowTargetError, "#{arg} is not found!") + end end - def project + def projects return unless params.key?(:project) - Project.find_by_full_path(params[:project]) + params[:project].split(',').map do |arg| + Project.find_by_full_path(arg) || (raise UnknowTargetError, "#{arg} is not found!") + end end - def group + def groups return unless params.key?(:group) - Group.find_by_full_path(params[:group]) + params[:group].split(',').map do |arg| + Group.find_by_full_path(arg) || (raise UnknowTargetError, "#{arg} is not found!") + end end - def namespace + def namespaces return unless params.key?(:namespace) - # We are interested in Group or UserNamespace - Namespace.without_project_namespaces.find_by_full_path(params[:namespace]) + params[:namespace].split(',').map do |arg| + # We are interested in Group or UserNamespace + Namespace.without_project_namespaces.find_by_full_path(arg) || (raise UnknowTargetError, "#{arg} is not found!") + end end end end diff --git a/lib/gitlab/content_security_policy/config_loader.rb b/lib/gitlab/content_security_policy/config_loader.rb index 521dec110a8..574a7dceaa4 100644 --- a/lib/gitlab/content_security_policy/config_loader.rb +++ b/lib/gitlab/content_security_policy/config_loader.rb @@ -44,6 +44,7 @@ module Gitlab allow_sentry(directives) if Gitlab.config.sentry&.enabled && Gitlab.config.sentry&.clientside_dsn allow_framed_gitlab_paths(directives) allow_customersdot(directives) if ENV['CUSTOMER_PORTAL_URL'].present? + allow_review_apps(directives) if ENV['REVIEW_APPS_ENABLED'] # The follow section contains workarounds to patch Safari's lack of support for CSP Level 3 # See https://gitlab.com/gitlab-org/gitlab/-/issues/343579 @@ -154,6 +155,11 @@ module Gitlab append_to_directive(directives, 'frame_src', Gitlab::Utils.append_path(Gitlab.config.gitlab.url, path)) end end + + def self.allow_review_apps(directives) + # Allow-listed to allow POSTs to https://gitlab.com/api/v4/projects/278964/merge_requests/:merge_request_iid/visual_review_discussions + append_to_directive(directives, 'connect_src', 'https://gitlab.com/api/v4/projects/278964/merge_requests/') + end end end end diff --git a/lib/gitlab/git.rb b/lib/gitlab/git.rb index 2da30b88d55..505d0b8d728 100644 --- a/lib/gitlab/git.rb +++ b/lib/gitlab/git.rb @@ -87,7 +87,12 @@ module Gitlab length = [sha1.length, sha2.length].min return false if length < Gitlab::Git::Commit::MIN_SHA_LENGTH - sha1[0, length] == sha2[0, length] + # Optimization: prevent unnecessary substring creation + if sha1.length == sha2.length + sha1 == sha2 + else + sha1[0, length] == sha2[0, length] + end end end end diff --git a/lib/tasks/gitlab/db/lock_writes.rake b/lib/tasks/gitlab/db/lock_writes.rake new file mode 100644 index 00000000000..1d1a67aa2a1 --- /dev/null +++ b/lib/tasks/gitlab/db/lock_writes.rake @@ -0,0 +1,85 @@ +# frozen_string_literal: true + +namespace :gitlab do + namespace :db do + TRIGGER_FUNCTION_NAME = 'gitlab_schema_prevent_write' + + desc "GitLab | DB | Install prevent write triggers on all databases" + task lock_writes: [:environment, 'gitlab:db:validate_config'] do + Gitlab::Database::EachDatabase.each_database_connection do |connection, database_name| + create_write_trigger_function(connection) + + schemas_for_connection = Gitlab::Database.gitlab_schemas_for_connection(connection) + Gitlab::Database::GitlabSchema.tables_to_schema.each do |table_name, schema_name| + connection.transaction do + if schemas_for_connection.include?(schema_name.to_sym) + drop_write_trigger(database_name, connection, table_name) + else + create_write_trigger(database_name, connection, table_name) + end + end + end + end + end + + desc "GitLab | DB | Remove all triggers that prevents writes from all databases" + task unlock_writes: :environment do + Gitlab::Database::EachDatabase.each_database_connection do |connection, database_name| + Gitlab::Database::GitlabSchema.tables_to_schema.each do |table_name, schema_name| + drop_write_trigger(database_name, connection, table_name) + end + drop_write_trigger_function(connection) + end + end + + def create_write_trigger_function(connection) + sql = <<-SQL + CREATE OR REPLACE FUNCTION #{TRIGGER_FUNCTION_NAME}() + RETURNS TRIGGER AS + $$ + BEGIN + RAISE EXCEPTION 'Table: "%" is write protected within this Gitlab database.', TG_TABLE_NAME + USING ERRCODE = 'modifying_sql_data_not_permitted', + HINT = 'Make sure you are using the right database connection'; + END + $$ LANGUAGE PLPGSQL + SQL + + connection.execute(sql) + end + + def drop_write_trigger_function(connection) + sql = <<-SQL + DROP FUNCTION IF EXISTS #{TRIGGER_FUNCTION_NAME}() + SQL + + connection.execute(sql) + end + + def create_write_trigger(database_name, connection, table_name) + puts "#{database_name}: '#{table_name}'... Lock Writes".color(:red) + sql = <<-SQL + DROP TRIGGER IF EXISTS #{write_trigger_name(table_name)} ON #{table_name}; + CREATE TRIGGER #{write_trigger_name(table_name)} + BEFORE INSERT OR UPDATE OR DELETE OR TRUNCATE + ON #{table_name} + FOR EACH STATEMENT EXECUTE FUNCTION #{TRIGGER_FUNCTION_NAME}(); + SQL + + connection.execute(sql) + end + + def drop_write_trigger(database_name, connection, table_name) + puts "#{database_name}: '#{table_name}'... Allow Writes".color(:green) + sql = <<-SQL + DROP TRIGGER IF EXISTS #{write_trigger_name(table_name)} ON #{table_name} + SQL + + connection.execute(sql) + end + + def write_trigger_name(table_name) + "gitlab_schema_write_trigger_for_#{table_name}" + end + end +end diff --git a/lib/tasks/migrate/composite_primary_keys.rake b/lib/tasks/migrate/composite_primary_keys.rake deleted file mode 100644 index 68f7c4d6c4a..00000000000 --- a/lib/tasks/migrate/composite_primary_keys.rake +++ /dev/null @@ -1,17 +0,0 @@ -# frozen_string_literal: true - -namespace :gitlab do - namespace :db do - desc 'GitLab | DB | Adds primary keys to tables that only have composite unique keys' - task composite_primary_keys_add: :environment do - require Rails.root.join('db/optional_migrations/composite_primary_keys') - CompositePrimaryKeysMigration.new.up - end - - desc 'GitLab | DB | Removes previously added composite primary keys' - task composite_primary_keys_drop: :environment do - require Rails.root.join('db/optional_migrations/composite_primary_keys') - CompositePrimaryKeysMigration.new.down - end - end -end diff --git a/locale/gitlab.pot b/locale/gitlab.pot index 558bc7a43fe..a4d299b455b 100644 --- a/locale/gitlab.pot +++ b/locale/gitlab.pot @@ -1008,10 +1008,7 @@ msgid_plural "%{strong_start}%{count} members%{strong_end} must approve to merge msgstr[0] "" msgstr[1] "" -msgid "%{strong_start}%{human_size}%{strong_end} Files" -msgstr "" - -msgid "%{strong_start}%{human_size}%{strong_end} Storage" +msgid "%{strong_start}%{human_size}%{strong_end} Project Storage" msgstr "" msgid "%{strong_start}%{release_count}%{strong_end} Release" @@ -37702,6 +37699,9 @@ msgstr "" msgid "The comparison view may be inaccurate due to merge conflicts." msgstr "" +msgid "The complete DevOps platform. One application with endless possibilities. Organizations rely on GitLab’s source code management, CI/CD, security, and more to deliver software rapidly." +msgstr "" + msgid "The compliance report shows the merge request violations merged in protected environments." msgstr "" diff --git a/public/-/pwa-icons/logo-192.png b/public/-/pwa-icons/logo-192.png new file mode 100644 index 0000000000000000000000000000000000000000..745bb6e36281e42ac78e1c477a85a85879fb3630 GIT binary patch literal 1855 zcmX|BdsGv57M>t0G%h8y%TpzxCX*q6ysD+kDe_q)Gy=A0?v z911qW+u7({$1d6IKP#W|)p3UNe!I z00aP4fTKFl06dM#Dxr4|i zAJ(N)bSL1|I4bA?l0Jj1&ww|0@GKjYl*K4R#snmN1~MOk=uS{Uxg3l9aTw8OP;?pa zW+p|K4(l`Fm7`R3kmK8<2%14zj5Mg)R2a?JqfMn^d@yC$i}6Tc1OQDaup*;;_+30z zD?ru~sH<^??K&~6D}qr0th)%Kau_Xxbzm?XMr&a-3r34z-E~-Z9yVM<3hPQ>^dhVi zz^Dw?31RdH7_Ej;F^ryv_1_w>{xYnUB4`7wlTvgI2186r(gRz-{75zU%PD#}d9#6n zHtaD-^@bi$YM(7#0pGIf+sH#v5Q)GU#-`&FrUas`WrwB22e>YhMTH30MyJbKrMTtc z2sd1LI73u{bI0Kbq+vHs0w$WqzE+7gm9Bu<+$6 zl5`0Z#A753Yc9M%y?D>wLHz>f`FCV=+FyZ?C zw;rFNOU9=ii;%RY%6~>zpBev~Y&-q_wMq}I9ZuRcP;kXV?ooJFyx)9&_Qx?*1U2tF zZ=ZQewI5-8L`3~Aj>)XMEXNzM1})0cc|NMIoJztRTIz8({6g)r+-7f!6Y6&+wo4H9 zZ}>sN#7KH+M5fxTt+VGWPm z?{i`8H@{mN`>H-!`J$sdVsmu&%5jw|TN`{8tmsvqad{=It=`xXc}x5y(QyVZF3Ko8 z*c)HLv#i{hRK{1lDd+e0U+5IesII1VTVisukH*te$hhGDt1=BQWQg7OzPq{T${rap zp0WG;OqTCM(=)=vvSOQ2VqCTBYmr#V-rvKuIzos}!A@~sBW%yUtn(-4QVK>}#AEy7 zz4g8AlG9*SU!KO=gS8g?jL~fUz_?@HT5ORW)hd?6Iu-pI=#pz0Ro(IRGZG_F2} znICs!z-$}CW@)6vDD~&;4yBheOpEPgncX2?y{jQBAqvf*_DXr(U*#F*GK;yEi+%xf^|Kc}}VcVE*>Ga z(z2W0T0`Hn`mL~&xLoDt!KQvBAE%vESJ#mO3YI&9*_m2v;4H(xm-#tAU|s`BsXgyuFW_t1@gIR~@F* z8@9J56f*0OkGeQf)*7?z?u2uPuPwKJcGoV2%jF_pw2auSNqzMDZp*Y0#;lti>F`i5 zl_o!Se0tEjVKp%^+WEg}L7(`2&+6-^HRi5++1L_?9wIRo7YiePY8KKg^8od)bo_Xm z#{~zeiD&w2OD0QEEVfwv>DsN(XC{8-+{)&K@%db=@uVMFf>&s^%Qc;`WXacu?|;?4 zx&B9iQea~V)3AfZRW>GMG9VaWkO9@PUE|n<1Y%5ao`6dVr)%}O-2|X1Ucb$k{|Q6 Sp++waRY=gGz}A2S@&5rfuY=eC literal 0 HcmV?d00001 diff --git a/public/-/pwa-icons/logo-512.png b/public/-/pwa-icons/logo-512.png new file mode 100644 index 0000000000000000000000000000000000000000..90cc18986a29e781d2ee3a1a62ac46520a4497a8 GIT binary patch literal 3620 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 literal 0 HcmV?d00001 diff --git a/public/-/pwa-icons/maskable-logo.png b/public/-/pwa-icons/maskable-logo.png new file mode 100644 index 0000000000000000000000000000000000000000..1b6379795b920b65ee131a391b9427ec01fdbc8a GIT binary patch literal 9126 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 review_apps.values.yml < { 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( - is_link: true, - label: a_string_including('0 Bytes'), - link: presenter.project_tree_path(project) - ) - end - end - 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( is_link: true, 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 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