Add latest changes from gitlab-org/gitlab@master
This commit is contained in:
		
							parent
							
								
									a1e798edcd
								
							
						
					
					
						commit
						de560337ef
					
				|  | @ -15,7 +15,7 @@ include: | |||
|   - local: .gitlab/ci/global.gitlab-ci.yml | ||||
| 
 | ||||
| .build-cng-env: | ||||
|   image: ${GITLAB_DEPENDENCY_PROXY_ADDRESS}ruby:${RUBY_VERSION}-alpine3.20 | ||||
|   image: ${GITLAB_DEPENDENCY_PROXY_ADDRESS}ruby:${RUBY_VERSION}-slim-bookworm | ||||
|   stage: prepare | ||||
|   needs: | ||||
|     # We need this job because we need its `cached-assets-hash.txt` artifact, so that we can pass the assets image tag to the downstream CNG pipeline. | ||||
|  | @ -23,19 +23,27 @@ include: | |||
|       job: build-assets-image | ||||
|   variables: | ||||
|     BUILD_ENV: build.env | ||||
|     CNG_PROJECT_PATH: "${CI_PROJECT_NAMESPACE}/$[[ inputs.cng_path ]]" | ||||
|     CNG_SKIP_REDUNDANT_JOBS: "false" | ||||
|     CNG_VAR_SETUP_LOG_FILE: "tmp/build-cng-env.log" | ||||
|   before_script: | ||||
|     - source ./scripts/utils.sh | ||||
|     - install_gitlab_gem | ||||
|   script: | ||||
|     - 'ruby -r./scripts/trigger-build.rb -e "puts Trigger.variables_for_env_file(Trigger::CNG.new.variables)" > $BUILD_ENV' | ||||
|     - echo "GITLAB_ASSETS_TAG=$(assets_image_tag)" >> $BUILD_ENV | ||||
|     - ruby -e 'puts "FULL_RUBY_VERSION=#{RUBY_VERSION}"' >> $BUILD_ENV | ||||
|     - echo -e "section_start:`date +%s`:cng_var_setup_log[collapsed=true]\r\e[0KCNG Variables Script Log Output" | ||||
|     - cat $CNG_VAR_SETUP_LOG_FILE | ||||
|     - echo -e "section_end:`date +%s`:cng_var_setup_log\r\e[0K" | ||||
|     - echo -e "section_start:`date +%s`:build_env[collapsed=true]\r\e[0KBuild Environment Variables" | ||||
|     - cat $BUILD_ENV | ||||
|     - echo -e "section_end:`date +%s`:build_env\r\e[0K" | ||||
|   artifacts: | ||||
|     reports: | ||||
|       dotenv: $BUILD_ENV | ||||
|     paths: | ||||
|       - $BUILD_ENV | ||||
|       - $CNG_VAR_SETUP_LOG_FILE | ||||
|     expire_in: 7 days | ||||
|     when: always | ||||
| 
 | ||||
|  | @ -66,11 +74,9 @@ include: | |||
|     TOP_UPSTREAM_SOURCE_SHA: "${TOP_UPSTREAM_SOURCE_SHA}" | ||||
|     TOP_UPSTREAM_SOURCE_REF_SLUG: "${TOP_UPSTREAM_SOURCE_REF_SLUG}" | ||||
|     # prevent cache invalidation between pipeline runs | ||||
|     CACHE_BUSTER: "false" | ||||
|     CACHE_BUSTER: "${CACHE_BUSTER}" | ||||
|     # link component version shas to current project instead of default CI_PIPELINE_CREATED_AT which forces rebuilds on each pipeline run | ||||
|     CONTAINER_VERSION_SUFFIX: "${CI_PROJECT_NAME}" | ||||
|     # skip no-op final-images-listing job | ||||
|     SKIP_JOB_REGEX: /final-images-listing/ | ||||
|     CONTAINER_VERSION_SUFFIX: "${CONTAINER_VERSION_SUFFIX}" | ||||
|     # disable buildx cluster while it's not supported on mirrors | ||||
|     DISABLE_BUILDX_CLUSTER: "true" | ||||
|     # disable external gem caching and rely on docker layer caching | ||||
|  | @ -79,9 +85,18 @@ include: | |||
|     SKIP_IMAGE_SIGNING: "true" | ||||
|     SKIP_IMAGE_VERIFICATION: "true" | ||||
|     # set specific arch list | ||||
|     ARCH_LIST: amd64 | ||||
|     ARCH_LIST: "${ARCH_LIST}" | ||||
|     # use larger runner for complex rails build jobs | ||||
|     HIGH_CAPACITY_RUNNER_TAG: high-cpu | ||||
|     # skip no-op final-images-listing job or additionally jobs that don't produce new artifacts if skip jobs is enabled | ||||
|     SKIP_JOB_REGEX: "${SKIP_JOB_REGEX}" | ||||
|     # base images and additional args, these are required because jobs that set them might be skipped | ||||
|     DEBIAN_IMAGE: "${DEBIAN_IMAGE}" | ||||
|     DEBIAN_DIGEST: "${DEBIAN_DIGEST}" | ||||
|     DEBIAN_BUILD_ARGS: "${DEBIAN_BUILD_ARGS}" | ||||
|     ALPINE_IMAGE: "${ALPINE_IMAGE}" | ||||
|     ALPINE_DIGEST: "${ALPINE_DIGEST}" | ||||
|     ALPINE_BUILD_ARGS: "${ALPINE_BUILD_ARGS}" | ||||
|   trigger: | ||||
|     project: '${CI_PROJECT_NAMESPACE}/$[[ inputs.cng_path ]]' | ||||
|     branch: $TRIGGER_BRANCH | ||||
|  |  | |||
|  | @ -2378,7 +2378,7 @@ | |||
|     - !reference [".rails:rules:ee-gitlab-duo-chat-base", rules] | ||||
|     - <<: *if-merge-request | ||||
|       changes: *backend-patterns | ||||
|       when: manual | ||||
|       when: never | ||||
|       allow_failure: true | ||||
| 
 | ||||
| .rails:rules:ee-gitlab-duo-chat-always: | ||||
|  | @ -2392,7 +2392,7 @@ | |||
|     - !reference [".rails:rules:ee-gitlab-duo-chat-optional", rules] | ||||
|     - <<: *if-default-branch-refs | ||||
|       changes: *setup-test-env-patterns | ||||
|       when: manual | ||||
|       when: never | ||||
|       allow_failure: true | ||||
| 
 | ||||
| .rails:rules:db:check-schema: | ||||
|  |  | |||
|  | @ -35,6 +35,7 @@ workflow: | |||
|     - .e2e-test-base | ||||
|     - .cng-qa-cache # cng-cache includes additional cached helm chart | ||||
|   needs: | ||||
|     - build-cng-env | ||||
|     - build-cng | ||||
|   tags: | ||||
|     - e2e | ||||
|  |  | |||
|  | @ -7,7 +7,6 @@ import LinksLayer from '../../../common/private/job_links_layer.vue'; | |||
| import { DOWNSTREAM, MAIN, UPSTREAM, ONE_COL_WIDTH, STAGE_VIEW } from '../constants'; | ||||
| import { validateConfigPaths } from '../utils'; | ||||
| import LinkedGraphWrapper from './linked_graph_wrapper.vue'; | ||||
| import LinkedPipelinesColumn from './linked_pipelines_column.vue'; | ||||
| import StageColumnComponent from './stage_column_component.vue'; | ||||
| 
 | ||||
| export default { | ||||
|  | @ -15,7 +14,8 @@ export default { | |||
|   components: { | ||||
|     LinksLayer, | ||||
|     LinkedGraphWrapper, | ||||
|     LinkedPipelinesColumn, | ||||
|     LinkedPipelinesColumn: () => | ||||
|       import(/* webpackChunkName: 'linked_pipelines_column' */ './linked_pipelines_column.vue'), | ||||
|     StageColumnComponent, | ||||
|   }, | ||||
|   props: { | ||||
|  |  | |||
|  | @ -16,7 +16,7 @@ import LinkedPipeline from './linked_pipeline.vue'; | |||
| export default { | ||||
|   components: { | ||||
|     LinkedPipeline, | ||||
|     PipelineGraph: () => import('./graph_component.vue'), | ||||
|     PipelineGraph: () => import(/* webpackChunkName: 'pipeline_graph' */ './graph_component.vue'), | ||||
|   }, | ||||
|   props: { | ||||
|     columnTitle: { | ||||
|  |  | |||
|  | @ -41,7 +41,7 @@ class Admin::SessionsController < ApplicationController | |||
|   private | ||||
| 
 | ||||
|   def user_is_admin! | ||||
|     render_404 unless current_user&.admin? | ||||
|     render_404 unless current_user&.can_access_admin_area? | ||||
|   end | ||||
| 
 | ||||
|   def two_factor_enabled_for_user? | ||||
|  |  | |||
|  | @ -71,10 +71,7 @@ module SidebarsHelper | |||
|         admin_mode_active: current_user_mode.admin_mode?, | ||||
|         enter_admin_mode_url: new_admin_session_path, | ||||
|         leave_admin_mode_url: destroy_admin_session_path, | ||||
|         # Usually, using current_user.admin? is discouraged because it does not | ||||
|         # check for admin mode, but since here we want to check admin? and admin mode | ||||
|         # separately, we'll have to ignore the cop rule. | ||||
|         user_is_admin: user.admin? # rubocop: disable Cop/UserAdmin | ||||
|         user_is_admin: user.can_access_admin_area? | ||||
|       }, | ||||
|       avatar_url: user.avatar_url, | ||||
|       has_link_to_profile: current_user_menu?(:profile), | ||||
|  |  | |||
|  | @ -8,7 +8,7 @@ module Editable | |||
|   end | ||||
| 
 | ||||
|   def last_edited_by | ||||
|     return if last_edited_at.blank? | ||||
|     return unless edited? | ||||
| 
 | ||||
|     super || Users::Internal.ghost | ||||
|   end | ||||
|  |  | |||
|  | @ -5,13 +5,8 @@ module Notes | |||
|     extend ActiveSupport::Concern | ||||
| 
 | ||||
|     included do | ||||
|       # Aliases to make application_helper#edited_time_ago_with_tooltip helper work properly with notes. | ||||
|       # See https://gitlab.com/gitlab-org/gitlab-foss/merge_requests/10392/diffs#note_28719102 | ||||
|       alias_attribute :last_edited_by, :updated_by | ||||
| 
 | ||||
|       belongs_to :author, class_name: "User" | ||||
|       belongs_to :updated_by, class_name: "User" | ||||
|       belongs_to :last_edited_by, class_name: 'User' | ||||
| 
 | ||||
|       has_many :todos | ||||
| 
 | ||||
|  | @ -22,13 +17,21 @@ module Notes | |||
|       validates :author, presence: true | ||||
|       validates :discussion_id, presence: true, format: { with: /\A\h{40}\z/ } | ||||
|       validate :validate_created_after | ||||
|     end | ||||
| 
 | ||||
|       def validate_created_after | ||||
|         return unless created_at | ||||
|         return if created_at >= '1970-01-01' | ||||
|     # Alias to make application_helper#edited_time_ago_with_tooltip helper work properly with notes. | ||||
|     # See https://gitlab.com/gitlab-org/gitlab-foss/merge_requests/10392/diffs#note_28719102 | ||||
|     def last_edited_by | ||||
|       updated_by | ||||
|     end | ||||
| 
 | ||||
|         errors.add(:created_at, s_('Note|The created date provided is too far in the past.')) | ||||
|       end | ||||
|     private | ||||
| 
 | ||||
|     def validate_created_after | ||||
|       return unless created_at | ||||
|       return if created_at >= '1970-01-01' | ||||
| 
 | ||||
|       errors.add(:created_at, s_('Note|The created date provided is too far in the past.')) | ||||
|     end | ||||
|   end | ||||
| end | ||||
|  |  | |||
|  | @ -456,7 +456,7 @@ class Note < ApplicationRecord | |||
|   # Since we used `updated_at` as `last_edited_at`, it could be touched by transforming / resolving a note. | ||||
|   # This makes sure it is only marked as edited when the note body is updated. | ||||
|   def edited? | ||||
|     return false if updated_by.blank? | ||||
|     return false if read_attribute(:last_edited_at).blank? && updated_by.blank? | ||||
| 
 | ||||
|     super | ||||
|   end | ||||
|  |  | |||
|  | @ -2605,6 +2605,10 @@ class User < ApplicationRecord | |||
|     support_pin_data&.fetch(:expires_at, nil) | ||||
|   end | ||||
| 
 | ||||
|   def can_access_admin_area? | ||||
|     admin? | ||||
|   end | ||||
| 
 | ||||
|   protected | ||||
| 
 | ||||
|   # override, from Devise::Validatable | ||||
|  |  | |||
|  | @ -8,7 +8,7 @@ class BasePolicy < DeclarativePolicy::Base | |||
|     next true if user_is_user? && @user.admin_bot? | ||||
| 
 | ||||
|     if Gitlab::CurrentSettings.admin_mode | ||||
|       Gitlab::Auth::CurrentUserMode.new(@user).admin_mode? | ||||
|       @user&.admin? && Gitlab::Auth::CurrentUserMode.new(@user).admin_mode? | ||||
|     else | ||||
|       @user&.admin? | ||||
|     end | ||||
|  |  | |||
|  | @ -21,6 +21,10 @@ module PolicyActor | |||
|     false | ||||
|   end | ||||
| 
 | ||||
|   def can_access_admin_area? | ||||
|     false | ||||
|   end | ||||
| 
 | ||||
|   def external? | ||||
|     false | ||||
|   end | ||||
|  |  | |||
|  | @ -28,8 +28,6 @@ if (!process.env.DISABLE_EXCLUSIONS) { | |||
|     // Merge request widget & tabs
 | ||||
|     'app/assets/javascripts/vue_merge_request_widget/components/checks/constants.js', | ||||
|     'app/assets/javascripts/merge_request_tabs.js', | ||||
|     // CI pipeline
 | ||||
|     'app/assets/javascripts/ci/pipeline_details/graph/components/graph_component.vue', | ||||
|     // Nested Group projects list
 | ||||
|     'app/assets/javascripts/vue_shared/components/nested_groups_projects_list/nested_groups_projects_list_item.vue', | ||||
|   ]); | ||||
|  | @ -65,6 +63,7 @@ module.exports = { | |||
|      See https://github.com/sverweij/dependency-cruiser/blob/main/doc/options-reference.md#exclude-exclude-dependencies-from-being-cruised
 | ||||
|     */ | ||||
|     exclude: { | ||||
|       dynamic: true, | ||||
|       path: [], | ||||
|     }, | ||||
|     // NOTE: This option is required to resolve aliases from the webpack config
 | ||||
|  |  | |||
|  | @ -1,8 +0,0 @@ | |||
| --- | ||||
| name: global_search_issues_tab | ||||
| introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/68640 | ||||
| rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/339207 | ||||
| milestone: '14.3' | ||||
| type: ops | ||||
| group: group::global search | ||||
| default_enabled: true | ||||
|  | @ -1,8 +0,0 @@ | |||
| --- | ||||
| name: global_search_merge_requests_tab | ||||
| introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/68640 | ||||
| rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/339207 | ||||
| milestone: '14.3' | ||||
| type: ops | ||||
| group: group::global search | ||||
| default_enabled: true | ||||
|  | @ -1,8 +0,0 @@ | |||
| --- | ||||
| name: global_search_snippet_titles_tab | ||||
| introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/123668 | ||||
| rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/415353 | ||||
| milestone: '16.1' | ||||
| type: ops | ||||
| group: group::global search | ||||
| default_enabled: true | ||||
|  | @ -1,8 +0,0 @@ | |||
| --- | ||||
| name: global_search_users_tab | ||||
| introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/84186 | ||||
| rollout_issue_url: | ||||
| milestone: '14.10' | ||||
| type: ops | ||||
| group: group::global search | ||||
| default_enabled: true | ||||
|  | @ -9,36 +9,11 @@ class MigrateGlobalSearchSettingsInApplicationSettings < Gitlab::Database::Migra | |||
|   end | ||||
| 
 | ||||
|   def up | ||||
|     ApplicationSetting.reset_column_information | ||||
| 
 | ||||
|     application_setting = ApplicationSetting.last | ||||
|     return unless application_setting | ||||
| 
 | ||||
|     # rubocop:disable Gitlab/FeatureFlagWithoutActor -- Does not execute in user context | ||||
|     search = { | ||||
|       global_search_issues_enabled: Feature.enabled?(:global_search_issues_tab, type: :ops), | ||||
|       global_search_merge_requests_enabled: Feature.enabled?(:global_search_merge_requests_tab, type: :ops), | ||||
|       global_search_snippet_titles_enabled: Feature.enabled?(:global_search_snippet_titles_tab, type: :ops), | ||||
|       global_search_users_enabled: Feature.enabled?(:global_search_users_tab, type: :ops) | ||||
|     } | ||||
| 
 | ||||
|     if Gitlab.ee? | ||||
|       search.merge!( | ||||
|         global_search_code_enabled: Feature.enabled?(:global_search_code_tab, type: :ops), | ||||
|         global_search_commits_enabled: Feature.enabled?(:global_search_commits_tab, type: :ops), | ||||
|         global_search_epics_enabled: Feature.enabled?(:global_search_epics_tab, type: :ops), | ||||
|         global_search_wiki_enabled: Feature.enabled?(:global_search_wiki_tab, type: :ops) | ||||
|       ) | ||||
|     end | ||||
|     # rubocop:enable Gitlab/FeatureFlagWithoutActor | ||||
| 
 | ||||
|     application_setting.update_columns(search: search, updated_at: Time.current) | ||||
|     # no-op this was just a data migration which is already done in 17.9. The plan is to remove the feature-flags used | ||||
|     # in this migration. So better to disable this migration in 17.11 to avoid any migration issues. | ||||
|   end | ||||
| 
 | ||||
|   def down | ||||
|     application_setting = ApplicationSetting.last | ||||
|     return unless application_setting | ||||
| 
 | ||||
|     application_setting.update_column(:search, {}) | ||||
|     # No op | ||||
|   end | ||||
| end | ||||
|  |  | |||
|  | @ -8,7 +8,7 @@ title: Broadcast messages | |||
| {{< details >}} | ||||
| 
 | ||||
| - Tier: Free, Premium, Ultimate | ||||
| - Offering: GitLab Self-Managed | ||||
| - Offering: GitLab Self-Managed, GitLab Dedicated | ||||
| 
 | ||||
| {{< /details >}} | ||||
| 
 | ||||
|  |  | |||
|  | @ -17,240 +17,6 @@ This page contains information about the settings that are used on GitLab.com, a | |||
| 
 | ||||
| See some of these settings on the [instance configuration page](https://gitlab.com/help/instance_configuration) of GitLab.com. | ||||
| 
 | ||||
| ## Email confirmation | ||||
| 
 | ||||
| GitLab.com has the: | ||||
| 
 | ||||
| - [`email_confirmation_setting`](../../administration/settings/sign_up_restrictions.md#confirm-user-email) | ||||
|   setting set to **Hard**. | ||||
| - [`unconfirmed_users_delete_after_days`](../../administration/moderate_users.md#automatically-delete-unconfirmed-users) | ||||
|   setting set to three days. | ||||
| 
 | ||||
| ## Password requirements | ||||
| 
 | ||||
| GitLab.com has the following requirements for passwords on new accounts and password changes: | ||||
| 
 | ||||
| - Minimum character length 8 characters. | ||||
| - Maximum character length 128 characters. | ||||
| - All characters are accepted. For example, `~`, `!`, `@`, `#`, `$`, `%`, `^`, `&`, `*`, `()`, | ||||
|   `[]`, `_`, `+`,  `=`, and `-`. | ||||
| 
 | ||||
| ## SSH key restrictions | ||||
| 
 | ||||
| GitLab.com uses the default [SSH key restrictions](../../security/ssh_keys_restrictions.md). | ||||
| 
 | ||||
| ## SSH host keys fingerprints | ||||
| 
 | ||||
| Go to the current instance configuration to see the SSH host key fingerprints on | ||||
| GitLab.com. | ||||
| 
 | ||||
| 1. Sign in to GitLab. | ||||
| 1. On the left sidebar, select **Help** ({{< icon name="question-o" >}}) > **Help**. | ||||
| 1. On the Help page, select **Check the current instance configuration**. | ||||
| 
 | ||||
| In the instance configuration, you see the **SSH host key fingerprints**: | ||||
| 
 | ||||
| | Algorithm        | MD5 (deprecated) | SHA256  | | ||||
| |------------------|------------------|---------| | ||||
| | ECDSA            | `f1:d0:fb:46:73:7a:70:92:5a:ab:5d:ef:43:e2:1c:35` | `SHA256:HbW3g8zUjNSksFbqTiUWPWg2Bq1x8xdGUrliXFzSnUw` | | ||||
| | ED25519          | `2e:65:6a:c8:cf:bf:b2:8b:9a:bd:6d:9f:11:5c:12:16` | `SHA256:eUXGGm1YGsMAS7vkcx6JOJdOGHPem5gQp4taiCfCLB8` | | ||||
| | RSA              | `b6:03:0e:39:97:9e:d0:e7:24:ce:a3:77:3e:01:42:09` | `SHA256:ROQFvPThGrW4RuWLoL9tq9I9zJ42fK4XywyRtbOz/EQ` | | ||||
| 
 | ||||
| The first time you connect to a GitLab.com repository, one of these keys is | ||||
| displayed in the output. | ||||
| 
 | ||||
| ## SSH `known_hosts` entries | ||||
| 
 | ||||
| Add the following to `.ssh/known_hosts` to skip manual fingerprint | ||||
| confirmation in SSH: | ||||
| 
 | ||||
| ```plaintext | ||||
| gitlab.com ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIAfuCHKVTjquxvt6CM6tdG4SLp1Btn/nOeHHE5UOzRdf | ||||
| gitlab.com ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQCsj2bNKTBSpIYDEGk9KxsGh3mySTRgMtXL583qmBpzeQ+jqCMRgBqB98u3z++J1sKlXHWfM9dyhSevkMwSbhoR8XIq/U0tCNyokEi/ueaBMCvbcTHhO7FcwzY92WK4Yt0aGROY5qX2UKSeOvuP4D6TPqKF1onrSzH9bx9XUf2lEdWT/ia1NEKjunUqu1xOB/StKDHMoX4/OKyIzuS0q/T1zOATthvasJFoPrAjkohTyaDUz2LN5JoH839hViyEG82yB+MjcFV5MU3N1l1QL3cVUCh93xSaua1N85qivl+siMkPGbO5xR/En4iEY6K2XPASUEMaieWVNTRCtJ4S8H+9 | ||||
| gitlab.com ecdsa-sha2-nistp256 AAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYAAABBBFSMqzJeV9rUzU4kWitGjeR4PWSa29SPqJ1fVkhtj3Hw9xjLVXVYrU9QlYWrOLXBpQ6KWjbjTDTdDkoohFzgbEY= | ||||
| ``` | ||||
| 
 | ||||
| ## Mail configuration | ||||
| 
 | ||||
| GitLab.com sends emails from the `mg.gitlab.com` domain by using [Mailgun](https://www.mailgun.com/), | ||||
| and has its own dedicated IP addresses: | ||||
| 
 | ||||
| - `23.253.183.236` | ||||
| - `69.72.35.190` | ||||
| - `69.72.44.107` | ||||
| - `159.135.226.146` | ||||
| - `161.38.202.219` | ||||
| - `192.237.158.143` | ||||
| - `192.237.159.239` | ||||
| - `198.61.254.136` | ||||
| - `198.61.254.160` | ||||
| - `209.61.151.122` | ||||
| 
 | ||||
| The IP addresses for `mg.gitlab.com` are subject to change at any time. | ||||
| 
 | ||||
| ### Service Desk alias email address | ||||
| 
 | ||||
| On GitLab.com, there's a mailbox configured for Service Desk with the email address: | ||||
| `contact-project+%{key}@incoming.gitlab.com`. To use this mailbox, configure the | ||||
| [custom suffix](../project/service_desk/configure.md#configure-a-suffix-for-service-desk-alias-email) in project | ||||
| settings. | ||||
| 
 | ||||
| ## Backups | ||||
| 
 | ||||
| [See our backup strategy](https://handbook.gitlab.com/handbook/engineering/infrastructure/production/#backups). | ||||
| 
 | ||||
| To back up an entire project on GitLab.com, you can export it either: | ||||
| 
 | ||||
| - [Through the UI](../project/settings/import_export.md). | ||||
| - [Through the API](../../api/project_import_export.md#schedule-an-export). You | ||||
|   can also use the API to programmatically upload exports to a storage platform, | ||||
|   such as Amazon S3. | ||||
| 
 | ||||
| With exports, be aware of [what is and is not](../project/settings/import_export.md#project-items-that-are-exported) | ||||
| included in a project export. | ||||
| 
 | ||||
| GitLab is built on Git, so you can back up just the repository of a project by cloning it to another computer. | ||||
| Similarly, you can clone a project's wiki to back it up. All files | ||||
| [uploaded after August 22, 2020](../project/wiki/_index.md#create-a-new-wiki-page) | ||||
| are included when cloning. | ||||
| 
 | ||||
| ## Delayed group deletion | ||||
| 
 | ||||
| {{< details >}} | ||||
| 
 | ||||
| - Tier: Premium, Ultimate | ||||
| - Offering: GitLab.com | ||||
| 
 | ||||
| {{< /details >}} | ||||
| 
 | ||||
| After May 08, 2023, all groups have delayed deletion enabled by default. | ||||
| 
 | ||||
| Groups are permanently deleted after a seven-day delay. | ||||
| 
 | ||||
| If you are on the Free tier, your groups are immediately deleted, and you will not be able to restore them. | ||||
| 
 | ||||
| You can [view and restore groups marked for deletion](../group/_index.md#restore-a-group). | ||||
| 
 | ||||
| ## Delayed project deletion | ||||
| 
 | ||||
| {{< details >}} | ||||
| 
 | ||||
| - Tier: Premium, Ultimate | ||||
| - Offering: GitLab.com | ||||
| 
 | ||||
| {{< /details >}} | ||||
| 
 | ||||
| After May 08, 2023, all groups have delayed project deletion enabled by default. | ||||
| 
 | ||||
| Projects are permanently deleted after a seven-day delay. | ||||
| 
 | ||||
| If you are on the Free tier, your projects are immediately deleted, and you will not be able to restore them. | ||||
| 
 | ||||
| You can [view and restore projects marked for deletion](../project/working_with_projects.md#restore-a-project). | ||||
| 
 | ||||
| ## Inactive project deletion | ||||
| 
 | ||||
| [Inactive project deletion](../../administration/inactive_project_deletion.md) is disabled on GitLab.com. | ||||
| 
 | ||||
| ## Alternative SSH port | ||||
| 
 | ||||
| GitLab.com can be reached by using a [different SSH port](https://about.gitlab.com/blog/2016/02/18/gitlab-dot-com-now-supports-an-alternate-git-plus-ssh-port/) for `git+ssh`. | ||||
| 
 | ||||
| | Setting    | Value               | | ||||
| |------------|---------------------| | ||||
| | `Hostname` | `altssh.gitlab.com` | | ||||
| | `Port`     | `443`               | | ||||
| 
 | ||||
| An example `~/.ssh/config` is the following: | ||||
| 
 | ||||
| ```plaintext | ||||
| Host gitlab.com | ||||
|   Hostname altssh.gitlab.com | ||||
|   User git | ||||
|   Port 443 | ||||
|   PreferredAuthentications publickey | ||||
|   IdentityFile ~/.ssh/gitlab | ||||
| ``` | ||||
| 
 | ||||
| ## GitLab Pages | ||||
| 
 | ||||
| Some settings for [GitLab Pages](../project/pages/_index.md) differ from the | ||||
| [defaults for self-managed instances](../../administration/pages/_index.md): | ||||
| 
 | ||||
| | Setting                                           | GitLab.com             | | ||||
| |:--------------------------------------------------|:-----------------------| | ||||
| | Domain name                                       | `gitlab.io`            | | ||||
| | IP address                                        | `35.185.44.232`        | | ||||
| | Support for custom domains                        | {{< icon name="check-circle" >}} Yes | | ||||
| | Support for TLS certificates                      | {{< icon name="check-circle" >}} Yes | | ||||
| | Maximum site size                                 | 1 GB                   | | ||||
| | Number of custom domains for each GitLab Pages website | 150                    | | ||||
| 
 | ||||
| The maximum size of your Pages site depends on the maximum artifact size, | ||||
| which is part of [GitLab CI/CD](#gitlab-cicd). | ||||
| 
 | ||||
| [Rate limits](#gitlabcom-specific-rate-limits) also exist for GitLab Pages. | ||||
| 
 | ||||
| ## GitLab container registry | ||||
| 
 | ||||
| | Setting                                | GitLab.com                       | Default (self-managed) | | ||||
| |:---------------------------------------|:---------------------------------|------------------------| | ||||
| | Domain name                            | `registry.gitlab.com`            |                        | | ||||
| | IP address                             | `35.227.35.254`                  |                        | | ||||
| | CDN domain name                        | `cdn.registry.gitlab-static.net` |                        | | ||||
| | CDN IP address                         | `34.149.22.116`                  |                        | | ||||
| | Authorization token duration (minutes) | `15`                             | See [increase container registry token duration](../../administration/packages/container_registry.md#increase-token-duration). | | ||||
| 
 | ||||
| To use the GitLab container registry, Docker clients must have access to: | ||||
| 
 | ||||
| - The registry endpoint and GitLab.com for authorization. | ||||
| - Google Cloud Storage or Google Cloud Content Delivery Network to download images. | ||||
| 
 | ||||
| GitLab.com is fronted by Cloudflare. | ||||
| For incoming connections to GitLab.com, you must allow CIDR blocks of Cloudflare | ||||
| ([IPv4](https://www.cloudflare.com/ips-v4/) and [IPv6](https://www.cloudflare.com/ips-v6/)). | ||||
| 
 | ||||
| ## GitLab CI/CD | ||||
| 
 | ||||
| Below are the current settings regarding [GitLab CI/CD](../../ci/_index.md). | ||||
| Any settings or feature limits not listed here are using the defaults listed in | ||||
| the related documentation. | ||||
| 
 | ||||
| | Setting                                                                          | GitLab.com                                                                                                 | Default (GitLab Self-Managed) | | ||||
| |----------------------------------------------------------------------------------|------------------------------------------------------------------------------------------------------------|------------------------| | ||||
| | Artifacts maximum size (compressed)                                              | 1 GB                                                                                                       | See [Maximum artifacts size](../../administration/settings/continuous_integration.md#maximum-artifacts-size). | | ||||
| | Artifacts [expiry time](../../ci/yaml/_index.md#artifactsexpire_in)               | 30 days unless otherwise specified                                                                         | See [Default artifacts expiration](../../administration/settings/continuous_integration.md#default-artifacts-expiration). Artifacts created before June 22, 2020 have no expiry. | | ||||
| | Scheduled Pipeline Cron                                                          | `*/5 * * * *`                                                                                              | See [Pipeline schedules advanced configuration](../../administration/cicd/_index.md#change-maximum-scheduled-pipeline-frequency). | | ||||
| | Maximum jobs in active pipelines                                                 | `500` for Free tier, `1000` for all trial tiers, `20000` for Premium, and `100000` for Ultimate.           | See [Number of jobs in active pipelines](../../administration/instance_limits.md#number-of-jobs-in-active-pipelines). | | ||||
| | Maximum CI/CD subscriptions to a project                                         | `2`                                                                                                        | See [Number of CI/CD subscriptions to a project](../../administration/instance_limits.md#number-of-cicd-subscriptions-to-a-project). | | ||||
| | Maximum number of pipeline triggers in a project                                 | `25000`                                                                                                    | See [Limit the number of pipeline triggers](../../administration/instance_limits.md#limit-the-number-of-pipeline-triggers). | | ||||
| | Maximum pipeline schedules in projects                                           | `10` for Free tier, `50` for all paid tiers                                                                | See [Number of pipeline schedules](../../administration/instance_limits.md#number-of-pipeline-schedules). | | ||||
| | Maximum pipelines for each schedule                                                   | `24` for Free tier, `288` for all paid tiers                                                               | See [Limit the number of pipelines created by a pipeline schedule each day](../../administration/instance_limits.md#limit-the-number-of-pipelines-created-by-a-pipeline-schedule-each-day). | | ||||
| | Maximum number of schedule rules defined for each security policy project        | Unlimited for all paid tiers                                                                               | See [Number of schedule rules defined for each security policy project](../../administration/instance_limits.md#limit-the-number-of-schedule-rules-defined-for-security-policy-project). | | ||||
| | Scheduled job archiving                                                          | 3 months                                                                                                   | Never. Jobs created before June 22, 2020 were archived after September 22, 2020. | | ||||
| | Maximum test cases for each [unit test report](../../ci/testing/unit_test_reports.md) | `500000`                                                                                                   | Unlimited.             | | ||||
| | Maximum registered runners                                                       | Free tier: `50` for each group and `50`for each project<br/>All paid tiers: `1000` for each group and `1000` for each project | See [Number of registered runners for each scope](../../administration/instance_limits.md#number-of-registered-runners-for-each-scope). | | ||||
| | Limit of dotenv variables                                                        | Free tier: `50`<br>Premium tier: `100`<br>Ultimate tier: `150`                                             | See [Limit dotenv variables](../../administration/instance_limits.md#limit-dotenv-variables). | | ||||
| | Maximum downstream pipeline trigger rate (for a given project, user, and commit) | `350` each minute                                                                                           | See [Maximum downstream pipeline trigger rate](../../administration/settings/continuous_integration.md#maximum-downstream-pipeline-trigger-rate). | | ||||
| 
 | ||||
| ## Package registry limits | ||||
| 
 | ||||
| The [maximum file size](../../administration/instance_limits.md#file-size-limits) | ||||
| for a package uploaded to the [GitLab package registry](../packages/package_registry/_index.md) | ||||
| varies by format: | ||||
| 
 | ||||
| | Package type           | GitLab.com                         | | ||||
| |------------------------|------------------------------------| | ||||
| | Conan                  | 5 GB                               | | ||||
| | Generic                | 5 GB                               | | ||||
| | Helm                   | 5 MB                               | | ||||
| | Maven                  | 5 GB                               | | ||||
| | npm                    | 5 GB                               | | ||||
| | NuGet                  | 5 GB                               | | ||||
| | PyPI                   | 5 GB                               | | ||||
| | Terraform              | 1 GB                               | | ||||
| | Machine learning model | 10 GB (uploads are capped at 5 GB) | | ||||
| 
 | ||||
| ## Account and limit settings | ||||
| 
 | ||||
| GitLab.com has the following account limits enabled. If a setting is not listed, | ||||
|  | @ -280,104 +46,172 @@ this limit. Repository limits apply to both public and private projects. | |||
| 
 | ||||
| {{< /alert >}} | ||||
| 
 | ||||
| ## Default import sources | ||||
| ## Backups | ||||
| 
 | ||||
| The [import sources](../project/import/_index.md#supported-import-sources) that are available to you by default depend on | ||||
| which GitLab you use: | ||||
| [See our backup strategy](https://handbook.gitlab.com/handbook/engineering/infrastructure/production/#backups). | ||||
| 
 | ||||
| - GitLab.com: All available import sources are enabled by default. | ||||
| - GitLab Self-Managed: No import sources are enabled by default and must be | ||||
|   [enabled](../../administration/settings/import_and_export_settings.md#configure-allowed-import-sources). | ||||
| To back up an entire project on GitLab.com, you can export it either: | ||||
| 
 | ||||
| ## Import placeholder user limits | ||||
| - [Through the UI](../project/settings/import_export.md). | ||||
| - [Through the API](../../api/project_import_export.md#schedule-an-export). You | ||||
|   can also use the API to programmatically upload exports to a storage platform, | ||||
|   such as Amazon S3. | ||||
| 
 | ||||
| The number of [placeholder users](../project/import/_index.md#placeholder-users) created during an import on GitLab.com is limited for each top-level namespace. The limits | ||||
| differ depending on your plan and seat count. | ||||
| For more information, see the [table of placeholder user limits for GitLab.com](../project/import/_index.md#placeholder-user-limits). | ||||
| With exports, be aware of [what is and is not](../project/settings/import_export.md#project-items-that-are-exported) | ||||
| included in a project export. | ||||
| 
 | ||||
| ## IP range | ||||
| GitLab is built on Git, so you can back up just the repository of a project by cloning it to another computer. | ||||
| Similarly, you can clone a project's wiki to back it up. All files | ||||
| [uploaded after August 22, 2020](../project/wiki/_index.md#create-a-new-wiki-page) | ||||
| are included when cloning. | ||||
| 
 | ||||
| GitLab.com uses the IP ranges `34.74.90.64/28` and `34.74.226.0/24` for traffic from its Web/API | ||||
| fleet. This whole range is solely allocated to GitLab. You can expect connections from webhooks or repository mirroring to come | ||||
| from those IPs and allow them. | ||||
| ## Email confirmation | ||||
| 
 | ||||
| GitLab.com is fronted by Cloudflare. For incoming connections to GitLab.com, you might need to allow CIDR blocks of Cloudflare ([IPv4](https://www.cloudflare.com/ips-v4/) and [IPv6](https://www.cloudflare.com/ips-v6/)). | ||||
| GitLab.com has the: | ||||
| 
 | ||||
| For outgoing connections from CI/CD runners, we are not providing static IP addresses. | ||||
| Most GitLab.com instance runners are deployed into Google Cloud in `us-east1`, except _Linux GPU-enabled_ and _Linux Arm64_, hosted in `us-central1`. | ||||
| You can configure any IP-based firewall by looking up | ||||
| [IP address ranges or CIDR blocks for GCP](https://cloud.google.com/compute/docs/faq#find_ip_range). | ||||
| MacOS runners are hosted on AWS with runner managers hosted on Google Cloud. To configure IP-based firewall, you must allow both [AWS IP address ranges](https://docs.aws.amazon.com/vpc/latest/userguide/aws-ip-ranges.html) and [Google Cloud](https://cloud.google.com/compute/docs/faq#find_ip_range). | ||||
| - [`email_confirmation_setting`](../../administration/settings/sign_up_restrictions.md#confirm-user-email) | ||||
|   setting set to **Hard**. | ||||
| - [`unconfirmed_users_delete_after_days`](../../administration/moderate_users.md#automatically-delete-unconfirmed-users) | ||||
|   setting set to three days. | ||||
| 
 | ||||
| ## Hostname list | ||||
| ## GitLab CI/CD | ||||
| 
 | ||||
| Add these hostnames when you configure allow-lists in local HTTP(S) proxies, | ||||
| or other web-blocking software that governs end-user computers. Pages on | ||||
| GitLab.com load content from these hostnames: | ||||
| Below are the current settings regarding [GitLab CI/CD](../../ci/_index.md). | ||||
| Any settings or feature limits not listed here are using the defaults listed in | ||||
| the related documentation. | ||||
| 
 | ||||
| - `gitlab.com` | ||||
| - `*.gitlab.com` | ||||
| - `*.gitlab-static.net` | ||||
| - `*.gitlab.io` | ||||
| - `*.gitlab.net` | ||||
| | Setting                                                                          | GitLab.com                                                                                                 | Default (GitLab Self-Managed) | | ||||
| |----------------------------------------------------------------------------------|------------------------------------------------------------------------------------------------------------|------------------------| | ||||
| | Artifacts maximum size (compressed)                                              | 1 GB                                                                                                       | See [Maximum artifacts size](../../administration/settings/continuous_integration.md#maximum-artifacts-size). | | ||||
| | Artifacts [expiry time](../../ci/yaml/_index.md#artifactsexpire_in)               | 30 days unless otherwise specified                                                                         | See [Default artifacts expiration](../../administration/settings/continuous_integration.md#default-artifacts-expiration). Artifacts created before June 22, 2020 have no expiry. | | ||||
| | Scheduled Pipeline Cron                                                          | `*/5 * * * *`                                                                                              | See [Pipeline schedules advanced configuration](../../administration/cicd/_index.md#change-maximum-scheduled-pipeline-frequency). | | ||||
| | Maximum jobs in active pipelines                                                 | `500` for Free tier, `1000` for all trial tiers, `20000` for Premium, and `100000` for Ultimate.           | See [Number of jobs in active pipelines](../../administration/instance_limits.md#number-of-jobs-in-active-pipelines). | | ||||
| | Maximum CI/CD subscriptions to a project                                         | `2`                                                                                                        | See [Number of CI/CD subscriptions to a project](../../administration/instance_limits.md#number-of-cicd-subscriptions-to-a-project). | | ||||
| | Maximum number of pipeline triggers in a project                                 | `25000`                                                                                                    | See [Limit the number of pipeline triggers](../../administration/instance_limits.md#limit-the-number-of-pipeline-triggers). | | ||||
| | Maximum pipeline schedules in projects                                           | `10` for Free tier, `50` for all paid tiers                                                                | See [Number of pipeline schedules](../../administration/instance_limits.md#number-of-pipeline-schedules). | | ||||
| | Maximum pipelines for each schedule                                                   | `24` for Free tier, `288` for all paid tiers                                                               | See [Limit the number of pipelines created by a pipeline schedule each day](../../administration/instance_limits.md#limit-the-number-of-pipelines-created-by-a-pipeline-schedule-each-day). | | ||||
| | Maximum number of schedule rules defined for each security policy project        | Unlimited for all paid tiers                                                                               | See [Number of schedule rules defined for each security policy project](../../administration/instance_limits.md#limit-the-number-of-schedule-rules-defined-for-security-policy-project). | | ||||
| | Scheduled job archiving                                                          | 3 months                                                                                                   | Never. Jobs created before June 22, 2020 were archived after September 22, 2020. | | ||||
| | Maximum test cases for each [unit test report](../../ci/testing/unit_test_reports.md) | `500000`                                                                                                   | Unlimited.             | | ||||
| | Maximum registered runners                                                       | Free tier: `50` for each group and `50`for each project<br/>All paid tiers: `1000` for each group and `1000` for each project | See [Number of registered runners for each scope](../../administration/instance_limits.md#number-of-registered-runners-for-each-scope). | | ||||
| | Limit of dotenv variables                                                        | Free tier: `50`<br>Premium tier: `100`<br>Ultimate tier: `150`                                             | See [Limit dotenv variables](../../administration/instance_limits.md#limit-dotenv-variables). | | ||||
| | Maximum downstream pipeline trigger rate (for a given project, user, and commit) | `350` each minute                                                                                           | See [Maximum downstream pipeline trigger rate](../../administration/settings/continuous_integration.md#maximum-downstream-pipeline-trigger-rate). | | ||||
| 
 | ||||
| Documentation and Company pages served over `docs.gitlab.com` and `about.gitlab.com` | ||||
| also load certain page content directly from common public CDN hostnames. | ||||
| ## GitLab container registry | ||||
| 
 | ||||
| ## Webhooks | ||||
| | Setting                                | GitLab.com                       | Default (self-managed) | | ||||
| |:---------------------------------------|:---------------------------------|------------------------| | ||||
| | Domain name                            | `registry.gitlab.com`            |                        | | ||||
| | IP address                             | `35.227.35.254`                  |                        | | ||||
| | CDN domain name                        | `cdn.registry.gitlab-static.net` |                        | | ||||
| | CDN IP address                         | `34.149.22.116`                  |                        | | ||||
| | Authorization token duration (minutes) | `15`                             | See [increase container registry token duration](../../administration/packages/container_registry.md#increase-token-duration). | | ||||
| 
 | ||||
| The following limits apply for [webhooks](../project/integrations/webhooks.md). | ||||
| To use the GitLab container registry, Docker clients must have access to: | ||||
| 
 | ||||
| ### Rate limits | ||||
| - The registry endpoint and GitLab.com for authorization. | ||||
| - Google Cloud Storage or Google Cloud Content Delivery Network to download images. | ||||
| 
 | ||||
| For each top-level namespace, the number of times each minute that a webhook can be called. | ||||
| The limit varies depending on your plan and the number of seats in your subscription. | ||||
| GitLab.com is fronted by Cloudflare. | ||||
| For incoming connections to GitLab.com, you must allow CIDR blocks of Cloudflare | ||||
| ([IPv4](https://www.cloudflare.com/ips-v4/) and [IPv6](https://www.cloudflare.com/ips-v6/)). | ||||
| 
 | ||||
| | Plan              | Default for GitLab.com  | | ||||
| |----------------------|-------------------------| | ||||
| | Free    | `500` | | ||||
| | Premium | `99` seats or fewer: `1,600`<br>`100-399` seats: `2,800`<br>`400` seats or more: `4,000` | | ||||
| | Ultimate and open source |`999` seats or fewer: `6,000`<br>`1,000-4,999` seats: `9,000`<br>`5,000` seats or more: `13,000` | | ||||
| ## GitLab Pages | ||||
| 
 | ||||
| ### Other limits | ||||
| Some settings for [GitLab Pages](../project/pages/_index.md) differ from the | ||||
| [defaults for self-managed instances](../../administration/pages/_index.md): | ||||
| 
 | ||||
| | Setting                                                             | Default for GitLab.com | | ||||
| |:--------------------------------------------------------------------|:-----------------------| | ||||
| | Number of webhooks                                                  | 100 for each project, 50 for each group (subgroup webhooks are not counted towards parent group limits ) | | ||||
| | Maximum payload size                                                | 25 MB                  | | ||||
| | Timeout                                                             | 10 seconds             | | ||||
| | [Parallel Pages deployments](../project/pages/parallel_deployments.md#limits) | 100 extra deployments (Premium tier), 500 extra deployments (Ultimate tier) | | ||||
| | Setting                                           | GitLab.com             | | ||||
| |:--------------------------------------------------|:-----------------------| | ||||
| | Domain name                                       | `gitlab.io`            | | ||||
| | IP address                                        | `35.185.44.232`        | | ||||
| | Support for custom domains                        | {{< icon name="check-circle" >}} Yes | | ||||
| | Support for TLS certificates                      | {{< icon name="check-circle" >}} Yes | | ||||
| | Maximum site size                                 | 1 GB                   | | ||||
| | Number of custom domains for each GitLab Pages website | 150                    | | ||||
| 
 | ||||
| For self-managed instance limits, see: | ||||
| The maximum size of your Pages site depends on the maximum artifact size, | ||||
| which is part of [GitLab CI/CD](#gitlab-cicd). | ||||
| 
 | ||||
| - [Webhook rate limit](../../administration/instance_limits.md#webhook-rate-limit). | ||||
| - [Number of webhooks](../../administration/instance_limits.md#number-of-webhooks). | ||||
| - [Webhook timeout](../../administration/instance_limits.md#webhook-timeout). | ||||
| - [Parallel Pages deployments](../../administration/instance_limits.md#number-of-parallel-pages-deployments). | ||||
| [Rate limits](#gitlabcom-specific-rate-limits) also exist for GitLab Pages. | ||||
| 
 | ||||
| ## GitLab-hosted runners | ||||
| ## GitLab.com at scale | ||||
| 
 | ||||
| You can use GitLab-hosted runners to run your CI/CD jobs on GitLab.com and GitLab Dedicated to seamlessly build, test, and deploy your application on different environments. | ||||
| In addition to the GitLab Enterprise Edition Linux package install, GitLab.com uses | ||||
| the following applications and settings to achieve scale. All settings are | ||||
| publicly available, as [Kubernetes configuration](https://gitlab.com/gitlab-com/gl-infra/k8s-workloads/gitlab-com) | ||||
| or [Chef cookbooks](https://gitlab.com/gitlab-cookbooks). | ||||
| 
 | ||||
| For more information, see [GitLab-hosted runners](../../ci/runners/_index.md). | ||||
| ### Elastic cluster | ||||
| 
 | ||||
| ## Puma | ||||
| We use Elasticsearch and Kibana for part of our monitoring solution: | ||||
| 
 | ||||
| GitLab.com uses the default of 60 seconds for [Puma request timeouts](../../administration/operations/puma.md#change-the-worker-timeout). | ||||
| - [`gitlab-cookbooks` / `gitlab-elk` · GitLab](https://gitlab.com/gitlab-cookbooks/gitlab-elk) | ||||
| - [`gitlab-cookbooks` / `gitlab_elasticsearch` · GitLab](https://gitlab.com/gitlab-cookbooks/gitlab_elasticsearch) | ||||
| 
 | ||||
| ## Maximum number of reviewers and assignees | ||||
| ### Fluentd | ||||
| 
 | ||||
| {{< history >}} | ||||
| We use Fluentd to unify our GitLab logs: | ||||
| 
 | ||||
| - Maximum assignees [introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/368936) in GitLab 15.6. | ||||
| - Maximum reviewers [introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/366485) in GitLab 15.9. | ||||
| - [`gitlab-cookbooks` / `gitlab_fluentd` · GitLab](https://gitlab.com/gitlab-cookbooks/gitlab_fluentd) | ||||
| 
 | ||||
| {{< /history >}} | ||||
| ### Prometheus | ||||
| 
 | ||||
| Merge requests enforce these maximums: | ||||
| Prometheus complete our monitoring stack: | ||||
| 
 | ||||
| - Maximum assignees: 200 | ||||
| - Maximum reviewers: 200 | ||||
| - [`gitlab-cookbooks` / `gitlab-prometheus` · GitLab](https://gitlab.com/gitlab-cookbooks/gitlab-prometheus) | ||||
| 
 | ||||
| ### Grafana | ||||
| 
 | ||||
| For the visualization of monitoring data: | ||||
| 
 | ||||
| - [`gitlab-cookbooks` / `gitlab-grafana` · GitLab](https://gitlab.com/gitlab-cookbooks/gitlab-grafana) | ||||
| 
 | ||||
| ### Sentry | ||||
| 
 | ||||
| Open source error tracking: | ||||
| 
 | ||||
| - [`gitlab-cookbooks` / `gitlab-sentry` · GitLab](https://gitlab.com/gitlab-cookbooks/gitlab-sentry) | ||||
| 
 | ||||
| ### Consul | ||||
| 
 | ||||
| Service discovery: | ||||
| 
 | ||||
| - [`gitlab-cookbooks` / `gitlab_consul` · GitLab](https://gitlab.com/gitlab-cookbooks/gitlab_consul) | ||||
| 
 | ||||
| ### HAProxy | ||||
| 
 | ||||
| High Performance TCP/HTTP Load Balancer: | ||||
| 
 | ||||
| - [`gitlab-cookbooks` / `gitlab-haproxy` · GitLab](https://gitlab.com/gitlab-cookbooks/gitlab-haproxy) | ||||
| 
 | ||||
| ## GitLab.com logging | ||||
| 
 | ||||
| We use [Fluentd](https://gitlab.com/gitlab-com/runbooks/tree/master/logging/doc#fluentd) | ||||
| to parse our logs. Fluentd sends our logs to | ||||
| [Stackdriver Logging](https://gitlab.com/gitlab-com/runbooks/tree/master/logging/doc#stackdriver) | ||||
| and [Cloud Pub/Sub](https://gitlab.com/gitlab-com/runbooks/tree/master/logging/doc#cloud-pubsub). | ||||
| Stackdriver is used for storing logs long-term in Google Cold Storage (GCS). | ||||
| Cloud Pub/Sub is used to forward logs to an [Elastic cluster](https://gitlab.com/gitlab-com/runbooks/tree/master/logging/doc#elastic) using [`pubsubbeat`](https://gitlab.com/gitlab-com/runbooks/tree/master/logging/doc#pubsubbeat-vms). | ||||
| 
 | ||||
| You can view more information in our runbooks such as: | ||||
| 
 | ||||
| - A [detailed list of what we're logging](https://gitlab.com/gitlab-com/runbooks/-/tree/master/docs/logging#what-are-we-logging) | ||||
| - Our [current log retention policies](https://gitlab.com/gitlab-com/runbooks/-/tree/master/docs/logging#retention) | ||||
| - A [diagram of our logging infrastructure](https://gitlab.com/gitlab-com/runbooks/-/tree/master/docs/logging#logging-infrastructure-overview) | ||||
| 
 | ||||
| ### Job logs | ||||
| 
 | ||||
| By default, GitLab does not expire job logs. Job logs are retained indefinitely, | ||||
| and can't be configured on GitLab.com to expire. You can erase job logs | ||||
| [manually with the Jobs API](../../api/jobs.md#erase-a-job) or by | ||||
| [deleting a pipeline](../../ci/pipelines/_index.md#delete-a-pipeline) | ||||
| 
 | ||||
| ## GitLab.com-specific Gitaly RPC concurrency limits | ||||
| 
 | ||||
| Per-repository Gitaly RPC concurrency and queuing limits are configured for different types of Git operations such as `git clone`. When these limits are exceeded, a `fatal: remote error: GitLab is currently unable to handle this request due to load` message is returned to the client. | ||||
| 
 | ||||
| For administrator documentation, see [limit RPC concurrency](../../administration/gitaly/concurrency_limiting.md#limit-rpc-concurrency). | ||||
| 
 | ||||
| ## GitLab.com-specific rate limits | ||||
| 
 | ||||
|  | @ -534,90 +368,98 @@ See [non-configurable limits](../../security/rate_limits.md#non-configurable-lim | |||
| for information on rate limits that are not configurable, and therefore also | ||||
| used on GitLab.com. | ||||
| 
 | ||||
| ## GitLab.com-specific Gitaly RPC concurrency limits | ||||
| ## GitLab-hosted runners | ||||
| 
 | ||||
| Per-repository Gitaly RPC concurrency and queuing limits are configured for different types of Git operations such as `git clone`. When these limits are exceeded, a `fatal: remote error: GitLab is currently unable to handle this request due to load` message is returned to the client. | ||||
| You can use GitLab-hosted runners to run your CI/CD jobs on GitLab.com and GitLab Dedicated to seamlessly build, test, and deploy your application on different environments. | ||||
| 
 | ||||
| For administrator documentation, see [limit RPC concurrency](../../administration/gitaly/concurrency_limiting.md#limit-rpc-concurrency). | ||||
| For more information, see [GitLab-hosted runners](../../ci/runners/_index.md). | ||||
| 
 | ||||
| ## GitLab.com logging | ||||
| ## Hostname list | ||||
| 
 | ||||
| We use [Fluentd](https://gitlab.com/gitlab-com/runbooks/tree/master/logging/doc#fluentd) | ||||
| to parse our logs. Fluentd sends our logs to | ||||
| [Stackdriver Logging](https://gitlab.com/gitlab-com/runbooks/tree/master/logging/doc#stackdriver) | ||||
| and [Cloud Pub/Sub](https://gitlab.com/gitlab-com/runbooks/tree/master/logging/doc#cloud-pubsub). | ||||
| Stackdriver is used for storing logs long-term in Google Cold Storage (GCS). | ||||
| Cloud Pub/Sub is used to forward logs to an [Elastic cluster](https://gitlab.com/gitlab-com/runbooks/tree/master/logging/doc#elastic) using [`pubsubbeat`](https://gitlab.com/gitlab-com/runbooks/tree/master/logging/doc#pubsubbeat-vms). | ||||
| Add these hostnames when you configure allow-lists in local HTTP(S) proxies, | ||||
| or other web-blocking software that governs end-user computers. Pages on | ||||
| GitLab.com load content from these hostnames: | ||||
| 
 | ||||
| You can view more information in our runbooks such as: | ||||
| - `gitlab.com` | ||||
| - `*.gitlab.com` | ||||
| - `*.gitlab-static.net` | ||||
| - `*.gitlab.io` | ||||
| - `*.gitlab.net` | ||||
| 
 | ||||
| - A [detailed list of what we're logging](https://gitlab.com/gitlab-com/runbooks/-/tree/master/docs/logging#what-are-we-logging) | ||||
| - Our [current log retention policies](https://gitlab.com/gitlab-com/runbooks/-/tree/master/docs/logging#retention) | ||||
| - A [diagram of our logging infrastructure](https://gitlab.com/gitlab-com/runbooks/-/tree/master/docs/logging#logging-infrastructure-overview) | ||||
| Documentation and Company pages served over `docs.gitlab.com` and `about.gitlab.com` | ||||
| also load certain page content directly from common public CDN hostnames. | ||||
| 
 | ||||
| ### Job logs | ||||
| ## Imports | ||||
| 
 | ||||
| By default, GitLab does not expire job logs. Job logs are retained indefinitely, | ||||
| and can't be configured on GitLab.com to expire. You can erase job logs | ||||
| [manually with the Jobs API](../../api/jobs.md#erase-a-job) or by | ||||
| [deleting a pipeline](../../ci/pipelines/_index.md#delete-a-pipeline). | ||||
| Settings related to importing data into GitLab. | ||||
| 
 | ||||
| ## GitLab.com at scale | ||||
| ### Default import sources | ||||
| 
 | ||||
| In addition to the GitLab Enterprise Edition Linux package install, GitLab.com uses | ||||
| the following applications and settings to achieve scale. All settings are | ||||
| publicly available, as [Kubernetes configuration](https://gitlab.com/gitlab-com/gl-infra/k8s-workloads/gitlab-com) | ||||
| or [Chef cookbooks](https://gitlab.com/gitlab-cookbooks). | ||||
| The [import sources](../project/import/_index.md#supported-import-sources) that are available to you by default depend on | ||||
| which GitLab you use: | ||||
| 
 | ||||
| ### Elastic cluster | ||||
| - GitLab.com: All available import sources are enabled by default. | ||||
| - GitLab Self-Managed: No import sources are enabled by default and must be | ||||
|   [enabled](../../administration/settings/import_and_export_settings.md#configure-allowed-import-sources). | ||||
| 
 | ||||
| We use Elasticsearch and Kibana for part of our monitoring solution: | ||||
| ### Import placeholder user limits | ||||
| 
 | ||||
| - [`gitlab-cookbooks` / `gitlab-elk` · GitLab](https://gitlab.com/gitlab-cookbooks/gitlab-elk) | ||||
| - [`gitlab-cookbooks` / `gitlab_elasticsearch` · GitLab](https://gitlab.com/gitlab-cookbooks/gitlab_elasticsearch) | ||||
| The number of [placeholder users](../project/import/_index.md#placeholder-users) created during an import on GitLab.com is limited for each top-level namespace. The limits | ||||
| differ depending on your plan and seat count. | ||||
| For more information, see the [table of placeholder user limits for GitLab.com](../project/import/_index.md#placeholder-user-limits). | ||||
| 
 | ||||
| ### Fluentd | ||||
| ## IP range | ||||
| 
 | ||||
| We use Fluentd to unify our GitLab logs: | ||||
| GitLab.com uses the IP ranges `34.74.90.64/28` and `34.74.226.0/24` for traffic from its Web/API | ||||
| fleet. This whole range is solely allocated to GitLab. You can expect connections from webhooks or repository mirroring to come | ||||
| from those IPs and allow them. | ||||
| 
 | ||||
| - [`gitlab-cookbooks` / `gitlab_fluentd` · GitLab](https://gitlab.com/gitlab-cookbooks/gitlab_fluentd) | ||||
| GitLab.com is fronted by Cloudflare. For incoming connections to GitLab.com, you might need to allow CIDR blocks of Cloudflare ([IPv4](https://www.cloudflare.com/ips-v4/) and [IPv6](https://www.cloudflare.com/ips-v6/)). | ||||
| 
 | ||||
| ### Prometheus | ||||
| For outgoing connections from CI/CD runners, we are not providing static IP addresses. | ||||
| Most GitLab.com instance runners are deployed into Google Cloud in `us-east1`, except _Linux GPU-enabled_ and _Linux Arm64_, hosted in `us-central1`. | ||||
| You can configure any IP-based firewall by looking up | ||||
| [IP address ranges or CIDR blocks for GCP](https://cloud.google.com/compute/docs/faq#find_ip_range). | ||||
| MacOS runners are hosted on AWS with runner managers hosted on Google Cloud. To configure IP-based firewall, you must allow both [AWS IP address ranges](https://docs.aws.amazon.com/vpc/latest/userguide/aws-ip-ranges.html) and [Google Cloud](https://cloud.google.com/compute/docs/faq#find_ip_range). | ||||
| 
 | ||||
| Prometheus complete our monitoring stack: | ||||
| ## Mail configuration | ||||
| 
 | ||||
| - [`gitlab-cookbooks` / `gitlab-prometheus` · GitLab](https://gitlab.com/gitlab-cookbooks/gitlab-prometheus) | ||||
| GitLab.com sends emails from the `mg.gitlab.com` domain by using [Mailgun](https://www.mailgun.com/), | ||||
| and has its own dedicated IP addresses: | ||||
| 
 | ||||
| ### Grafana | ||||
| - `23.253.183.236` | ||||
| - `69.72.35.190` | ||||
| - `69.72.44.107` | ||||
| - `159.135.226.146` | ||||
| - `161.38.202.219` | ||||
| - `192.237.158.143` | ||||
| - `192.237.159.239` | ||||
| - `198.61.254.136` | ||||
| - `198.61.254.160` | ||||
| - `209.61.151.122` | ||||
| 
 | ||||
| For the visualization of monitoring data: | ||||
| The IP addresses for `mg.gitlab.com` are subject to change at any time. | ||||
| 
 | ||||
| - [`gitlab-cookbooks` / `gitlab-grafana` · GitLab](https://gitlab.com/gitlab-cookbooks/gitlab-grafana) | ||||
| ### Service Desk alias email address | ||||
| 
 | ||||
| ### Sentry | ||||
| On GitLab.com, there's a mailbox configured for Service Desk with the email address: | ||||
| `contact-project+%{key}@incoming.gitlab.com`. To use this mailbox, configure the | ||||
| [custom suffix](../project/service_desk/configure.md#configure-a-suffix-for-service-desk-alias-email) in project | ||||
| settings. | ||||
| 
 | ||||
| Open source error tracking: | ||||
| ## Maximum number of reviewers and assignees | ||||
| 
 | ||||
| - [`gitlab-cookbooks` / `gitlab-sentry` · GitLab](https://gitlab.com/gitlab-cookbooks/gitlab-sentry) | ||||
| {{< history >}} | ||||
| 
 | ||||
| ### Consul | ||||
| - Maximum assignees [introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/368936) in GitLab 15.6. | ||||
| - Maximum reviewers [introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/366485) in GitLab 15.9. | ||||
| 
 | ||||
| Service discovery: | ||||
| {{< /history >}} | ||||
| 
 | ||||
| - [`gitlab-cookbooks` / `gitlab_consul` · GitLab](https://gitlab.com/gitlab-cookbooks/gitlab_consul) | ||||
| Merge requests enforce these maximums: | ||||
| 
 | ||||
| ### HAProxy | ||||
| 
 | ||||
| High Performance TCP/HTTP Load Balancer: | ||||
| 
 | ||||
| - [`gitlab-cookbooks` / `gitlab-haproxy` · GitLab](https://gitlab.com/gitlab-cookbooks/gitlab-haproxy) | ||||
| 
 | ||||
| ## Sidekiq | ||||
| 
 | ||||
| GitLab.com runs [Sidekiq](https://sidekiq.org) as an [external process](../../administration/sidekiq/_index.md) | ||||
| for Ruby job scheduling. | ||||
| 
 | ||||
| The current settings are in the [GitLab.com Kubernetes pod configuration](https://gitlab.com/gitlab-com/gl-infra/k8s-workloads/gitlab-com/-/blob/master/releases/gitlab/values/gprd.yaml.gotmpl). | ||||
| - Maximum assignees: 200 | ||||
| - Maximum reviewers: 200 | ||||
| 
 | ||||
| ## Merge request limits | ||||
| 
 | ||||
|  | @ -639,3 +481,174 @@ This feature is available for testing, but not ready for production use. | |||
| GitLab limits each merge request to 1000 [diff versions](../project/merge_requests/versions.md). | ||||
| Merge requests that reach this limit cannot be updated further. Instead, | ||||
| close the affected merge request and create a new merge request. | ||||
| 
 | ||||
| ## Password requirements | ||||
| 
 | ||||
| GitLab.com has the following requirements for passwords on new accounts and password changes: | ||||
| 
 | ||||
| - Minimum character length 8 characters. | ||||
| - Maximum character length 128 characters. | ||||
| - All characters are accepted. For example, `~`, `!`, `@`, `#`, `$`, `%`, `^`, `&`, `*`, `()`, | ||||
|   `[]`, `_`, `+`,  `=`, and `-`. | ||||
| 
 | ||||
| ## Project and group deletion | ||||
| 
 | ||||
| Settings related to the deletion of projects and groups. | ||||
| 
 | ||||
| ### Delayed group deletion | ||||
| 
 | ||||
| {{< details >}} | ||||
| 
 | ||||
| - Tier: Premium, Ultimate | ||||
| - Offering: GitLab.com | ||||
| 
 | ||||
| {{< /details >}} | ||||
| 
 | ||||
| After May 08, 2023, all groups have delayed deletion enabled by default. | ||||
| 
 | ||||
| Groups are permanently deleted after a seven-day delay. | ||||
| 
 | ||||
| If you are on the Free tier, your groups are immediately deleted, and you will not be able to restore them. | ||||
| 
 | ||||
| You can [view and restore groups marked for deletion](../group/_index.md#restore-a-group). | ||||
| 
 | ||||
| ### Delayed project deletion | ||||
| 
 | ||||
| {{< details >}} | ||||
| 
 | ||||
| - Tier: Premium, Ultimate | ||||
| - Offering: GitLab.com | ||||
| 
 | ||||
| {{< /details >}} | ||||
| 
 | ||||
| After May 08, 2023, all groups have delayed project deletion enabled by default. | ||||
| 
 | ||||
| Projects are permanently deleted after a seven-day delay. | ||||
| 
 | ||||
| If you are on the Free tier, your projects are immediately deleted, and you will not be able to restore them. | ||||
| 
 | ||||
| You can [view and restore projects marked for deletion](../project/working_with_projects.md#restore-a-project). | ||||
| 
 | ||||
| ### Inactive project deletion | ||||
| 
 | ||||
| [Inactive project deletion](../../administration/inactive_project_deletion.md) is disabled on GitLab.com. | ||||
| 
 | ||||
| ## Package registry limits | ||||
| 
 | ||||
| The [maximum file size](../../administration/instance_limits.md#file-size-limits) | ||||
| for a package uploaded to the [GitLab package registry](../packages/package_registry/_index.md) | ||||
| varies by format: | ||||
| 
 | ||||
| | Package type           | GitLab.com                         | | ||||
| |------------------------|------------------------------------| | ||||
| | Conan                  | 5 GB                               | | ||||
| | Generic                | 5 GB                               | | ||||
| | Helm                   | 5 MB                               | | ||||
| | Maven                  | 5 GB                               | | ||||
| | npm                    | 5 GB                               | | ||||
| | NuGet                  | 5 GB                               | | ||||
| | PyPI                   | 5 GB                               | | ||||
| | Terraform              | 1 GB                               | | ||||
| | Machine learning model | 10 GB (uploads are capped at 5 GB) | | ||||
| 
 | ||||
| ## Puma | ||||
| 
 | ||||
| GitLab.com uses the default of 60 seconds for [Puma request timeouts](../../administration/operations/puma.md#change-the-worker-timeout). | ||||
| 
 | ||||
| ## Sidekiq | ||||
| 
 | ||||
| GitLab.com runs [Sidekiq](https://sidekiq.org) as an [external process](../../administration/sidekiq/_index.md) | ||||
| for Ruby job scheduling. | ||||
| 
 | ||||
| The current settings are in the [GitLab.com Kubernetes pod configuration](https://gitlab.com/gitlab-com/gl-infra/k8s-workloads/gitlab-com/-/blob/master/releases/gitlab/values/gprd.yaml.gotmpl). | ||||
| 
 | ||||
| ## SSH keys and authentication | ||||
| 
 | ||||
| Settings related to authentication with SSH. For information about maximum connections, | ||||
| see [SSH maximum number of connections](#ssh-maximum-number-of-connections). | ||||
| 
 | ||||
| ### Alternative SSH port | ||||
| 
 | ||||
| GitLab.com can be reached by using a [different SSH port](https://about.gitlab.com/blog/2016/02/18/gitlab-dot-com-now-supports-an-alternate-git-plus-ssh-port/) for `git+ssh`. | ||||
| 
 | ||||
| | Setting    | Value               | | ||||
| |------------|---------------------| | ||||
| | `Hostname` | `altssh.gitlab.com` | | ||||
| | `Port`     | `443`               | | ||||
| 
 | ||||
| An example `~/.ssh/config` is the following: | ||||
| 
 | ||||
| ```plaintext | ||||
| Host gitlab.com | ||||
|   Hostname altssh.gitlab.com | ||||
|   User git | ||||
|   Port 443 | ||||
|   PreferredAuthentications publickey | ||||
|   IdentityFile ~/.ssh/gitlab | ||||
| ``` | ||||
| 
 | ||||
| ### SSH host keys fingerprints | ||||
| 
 | ||||
| Go to the current instance configuration to see the SSH host key fingerprints on | ||||
| GitLab.com. | ||||
| 
 | ||||
| 1. Sign in to GitLab. | ||||
| 1. On the left sidebar, select **Help** ({{< icon name="question-o" >}}) > **Help**. | ||||
| 1. On the Help page, select **Check the current instance configuration**. | ||||
| 
 | ||||
| In the instance configuration, you see the **SSH host key fingerprints**: | ||||
| 
 | ||||
| | Algorithm        | MD5 (deprecated) | SHA256  | | ||||
| |------------------|------------------|---------| | ||||
| | ECDSA            | `f1:d0:fb:46:73:7a:70:92:5a:ab:5d:ef:43:e2:1c:35` | `SHA256:HbW3g8zUjNSksFbqTiUWPWg2Bq1x8xdGUrliXFzSnUw` | | ||||
| | ED25519          | `2e:65:6a:c8:cf:bf:b2:8b:9a:bd:6d:9f:11:5c:12:16` | `SHA256:eUXGGm1YGsMAS7vkcx6JOJdOGHPem5gQp4taiCfCLB8` | | ||||
| | RSA              | `b6:03:0e:39:97:9e:d0:e7:24:ce:a3:77:3e:01:42:09` | `SHA256:ROQFvPThGrW4RuWLoL9tq9I9zJ42fK4XywyRtbOz/EQ` | | ||||
| 
 | ||||
| The first time you connect to a GitLab.com repository, one of these keys is | ||||
| displayed in the output. | ||||
| 
 | ||||
| ### SSH key restrictions | ||||
| 
 | ||||
| GitLab.com uses the default [SSH key restrictions](../../security/ssh_keys_restrictions.md). | ||||
| 
 | ||||
| ### SSH `known_hosts` entries | ||||
| 
 | ||||
| Add the following to `.ssh/known_hosts` to skip manual fingerprint | ||||
| confirmation in SSH: | ||||
| 
 | ||||
| ```plaintext | ||||
| gitlab.com ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIAfuCHKVTjquxvt6CM6tdG4SLp1Btn/nOeHHE5UOzRdf | ||||
| gitlab.com ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQCsj2bNKTBSpIYDEGk9KxsGh3mySTRgMtXL583qmBpzeQ+jqCMRgBqB98u3z++J1sKlXHWfM9dyhSevkMwSbhoR8XIq/U0tCNyokEi/ueaBMCvbcTHhO7FcwzY92WK4Yt0aGROY5qX2UKSeOvuP4D6TPqKF1onrSzH9bx9XUf2lEdWT/ia1NEKjunUqu1xOB/StKDHMoX4/OKyIzuS0q/T1zOATthvasJFoPrAjkohTyaDUz2LN5JoH839hViyEG82yB+MjcFV5MU3N1l1QL3cVUCh93xSaua1N85qivl+siMkPGbO5xR/En4iEY6K2XPASUEMaieWVNTRCtJ4S8H+9 | ||||
| gitlab.com ecdsa-sha2-nistp256 AAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYAAABBBFSMqzJeV9rUzU4kWitGjeR4PWSa29SPqJ1fVkhtj3Hw9xjLVXVYrU9QlYWrOLXBpQ6KWjbjTDTdDkoohFzgbEY= | ||||
| ``` | ||||
| 
 | ||||
| ## Webhooks | ||||
| 
 | ||||
| The following limits apply for [webhooks](../project/integrations/webhooks.md). | ||||
| 
 | ||||
| ### Rate limits | ||||
| 
 | ||||
| For each top-level namespace, the number of times each minute that a webhook can be called. | ||||
| The limit varies depending on your plan and the number of seats in your subscription. | ||||
| 
 | ||||
| | Plan              | Default for GitLab.com  | | ||||
| |----------------------|-------------------------| | ||||
| | Free    | `500` | | ||||
| | Premium | `99` seats or fewer: `1,600`<br>`100-399` seats: `2,800`<br>`400` seats or more: `4,000` | | ||||
| | Ultimate and open source |`999` seats or fewer: `6,000`<br>`1,000-4,999` seats: `9,000`<br>`5,000` seats or more: `13,000` | | ||||
| 
 | ||||
| ### Other limits | ||||
| 
 | ||||
| | Setting                                                             | Default for GitLab.com | | ||||
| |:--------------------------------------------------------------------|:-----------------------| | ||||
| | Number of webhooks                                                  | 100 for each project, 50 for each group (subgroup webhooks are not counted towards parent group limits ) | | ||||
| | Maximum payload size                                                | 25 MB                  | | ||||
| | Timeout                                                             | 10 seconds             | | ||||
| | [Parallel Pages deployments](../project/pages/parallel_deployments.md#limits) | 100 extra deployments (Premium tier), 500 extra deployments (Ultimate tier) | | ||||
| 
 | ||||
| For self-managed instance limits, see: | ||||
| 
 | ||||
| - [Webhook rate limit](../../administration/instance_limits.md#webhook-rate-limit). | ||||
| - [Number of webhooks](../../administration/instance_limits.md#number-of-webhooks). | ||||
| - [Webhook timeout](../../administration/instance_limits.md#webhook-timeout). | ||||
| - [Parallel Pages deployments](../../administration/instance_limits.md#number-of-parallel-pages-deployments).. | ||||
|  |  | |||
|  | @ -19,7 +19,7 @@ design files, videos, and other non-text content. | |||
| GitLab supports two different types of file locking: | ||||
| 
 | ||||
| - [Exclusive file locks](../../topics/git/file_management.md#file-locks): Applied through the | ||||
|   command line with Git LFS and `.gitattributes`. | ||||
|   command line with Git LFS and [`.gitattributes`](../../user/project/repository/files/git_attributes.md). | ||||
|   These locks prevent modifications to locked files on any branch. | ||||
| - [Default branch file and directory locks](#default-branch-file-and-directory-locks): Applied | ||||
|   through the GitLab UI. These locks prevent modifications to files and directories on the | ||||
|  | @ -27,7 +27,7 @@ GitLab supports two different types of file locking: | |||
| 
 | ||||
| ## Permissions | ||||
| 
 | ||||
| You can create file locks if you have at least the Developer role for the project. | ||||
| You must have at least the Developer role for the project to create, view, or manage file locks. | ||||
| For more information, see [Roles and permissions](../../user/permissions.md). | ||||
| 
 | ||||
| ## Default branch file and directory locks | ||||
|  | @ -60,6 +60,10 @@ to be aware of in-flight work without restricting their workflow on other branch | |||
| 
 | ||||
| ## Lock a file or directory | ||||
| 
 | ||||
| Prerequisites: | ||||
| 
 | ||||
| - You must have at least the Developer role for the project. | ||||
| 
 | ||||
| To lock a file or directory: | ||||
| 
 | ||||
| 1. On the left sidebar, select **Search or go to** and find your project. | ||||
|  | @ -74,17 +78,41 @@ for locked files, see [issue 4623](https://gitlab.com/gitlab-org/gitlab/-/issues | |||
| 
 | ||||
| ## View and remove locks | ||||
| 
 | ||||
| Locks can be removed by: | ||||
| 
 | ||||
| - The user who created the lock. | ||||
| - Any user with at least the Maintainer role for the project. | ||||
| 
 | ||||
| To view and manage file locks: | ||||
| To view locked files: | ||||
| 
 | ||||
| 1. On the left sidebar, select **Search or go to** and find your project. | ||||
| 1. Select **Code > Locked files**. | ||||
| 
 | ||||
| This list displays all files locked either through Git LFS exclusive locks or the GitLab UI. | ||||
| The **Locked files** page displays all files locked with either Git LFS exclusive locks or the GitLab UI. | ||||
| 
 | ||||
| Prerequisites: | ||||
| 
 | ||||
| - You must be the user who created the lock. | ||||
| - You must have at least the Maintainer role for the project. | ||||
| 
 | ||||
| To remove a lock: | ||||
| 
 | ||||
| {{< tabs >}} | ||||
| 
 | ||||
| {{< tab title="From a file" >}} | ||||
| 
 | ||||
| 1. On the left sidebar, select **Search or go to** and find your project. | ||||
| 1. Go to the file you want to unlock. | ||||
| 1. Select **Unlock**. | ||||
| 1. On the confirmation dialog, select **Unlock**. | ||||
| 
 | ||||
| {{< /tab >}} | ||||
| 
 | ||||
| {{< tab title="From the Locked file page" >}} | ||||
| 
 | ||||
| 1. On the left sidebar, select **Search or go to** and find your project. | ||||
| 1. Select **Code > Locked files**. | ||||
| 1. To the right of the file you want to unlock, select **Unlock**. | ||||
| 1. On the confirmation dialog, select **OK**. | ||||
| 
 | ||||
| {{< /tab >}} | ||||
| 
 | ||||
| {{< /tabs >}} | ||||
| 
 | ||||
| ## Related topics | ||||
| 
 | ||||
|  |  | |||
|  | @ -91,7 +91,7 @@ module Gitlab | |||
|         def optionally_run_in_admin_mode(user) | ||||
|           raise NonSidekiqEnvironmentError unless Gitlab::Runtime.sidekiq? | ||||
| 
 | ||||
|           return yield unless Gitlab::CurrentSettings.admin_mode && user.admin? | ||||
|           return yield unless Gitlab::CurrentSettings.admin_mode && user.can_access_admin_area? | ||||
| 
 | ||||
|           bypass_session!(user.id) do | ||||
|             with_current_admin(user) do | ||||
|  | @ -110,7 +110,7 @@ module Gitlab | |||
|         return false unless user | ||||
| 
 | ||||
|         Gitlab::SafeRequestStore.fetch(admin_mode_rs_key) do | ||||
|           user.admin? && (privileged_runtime? || session_with_admin_mode?) | ||||
|           user.can_access_admin_area? && (privileged_runtime? || session_with_admin_mode?) | ||||
|         end | ||||
|       end | ||||
| 
 | ||||
|  | @ -118,12 +118,12 @@ module Gitlab | |||
|         return false unless user | ||||
| 
 | ||||
|         Gitlab::SafeRequestStore.fetch(admin_mode_requested_rs_key) do | ||||
|           user.admin? && admin_mode_requested_in_grace_period? | ||||
|           user.can_access_admin_area? && admin_mode_requested_in_grace_period? | ||||
|         end | ||||
|       end | ||||
| 
 | ||||
|       def enable_admin_mode!(password: nil, skip_password_validation: false) | ||||
|         return false unless user&.admin? | ||||
|         return false unless user&.can_access_admin_area? | ||||
|         return false unless skip_password_validation || user&.valid_password?(password) | ||||
| 
 | ||||
|         raise NotRequestedError unless admin_mode_requested? | ||||
|  | @ -139,7 +139,7 @@ module Gitlab | |||
|       end | ||||
| 
 | ||||
|       def disable_admin_mode! | ||||
|         return unless user&.admin? | ||||
|         return unless user&.can_access_admin_area? | ||||
| 
 | ||||
|         reset_request_store_cache_entries | ||||
| 
 | ||||
|  | @ -148,7 +148,7 @@ module Gitlab | |||
|       end | ||||
| 
 | ||||
|       def request_admin_mode! | ||||
|         return unless user&.admin? | ||||
|         return unless user&.can_access_admin_area? | ||||
| 
 | ||||
|         reset_request_store_cache_entries | ||||
| 
 | ||||
|  |  | |||
|  | @ -305,7 +305,7 @@ | |||
|     "swagger-cli": "^4.0.4", | ||||
|     "tailwindcss": "^3.4.1", | ||||
|     "timezone-mock": "^1.0.8", | ||||
|     "vite": "^6.2.1", | ||||
|     "vite": "^6.2.2", | ||||
|     "vite-plugin-ruby": "^5.1.1", | ||||
|     "vue-loader-vue3": "npm:vue-loader@17.4.2", | ||||
|     "vue-test-utils-compat": "0.0.14", | ||||
|  |  | |||
|  | @ -45,29 +45,29 @@ module Gitlab | |||
| 
 | ||||
|           # Key value pairs for ci specific component version values | ||||
|           # | ||||
|           # This is defined as key value pairs to allow constructing example cli args for easier reproducability | ||||
|           # This is defined as key value pairs to allow constructing example cli args for easier reproducibility | ||||
|           # | ||||
|           # @return [Hash] | ||||
|           def component_ci_versions | ||||
|             { | ||||
|               "gitlab.gitaly.image.repository" => "#{IMAGE_REPOSITORY}/gitaly", | ||||
|               "gitlab.gitaly.image.tag" => semver?(gitaly_version) ? "v#{gitaly_version}" : gitaly_version, | ||||
|               "gitlab.gitaly.image.tag" => with_semver_prefix(gitaly_version), | ||||
|               "gitlab.gitlab-shell.image.repository" => "#{IMAGE_REPOSITORY}/gitlab-shell", | ||||
|               "gitlab.gitlab-shell.image.tag" => "v#{gitlab_shell_version}", | ||||
|               "gitlab.gitlab-shell.image.tag" => with_semver_prefix(gitlab_shell_version), | ||||
|               "gitlab.migrations.image.repository" => "#{IMAGE_REPOSITORY}/gitlab-toolbox-ee", | ||||
|               "gitlab.migrations.image.tag" => commit_sha, | ||||
|               "gitlab.migrations.image.tag" => toolbox_version, | ||||
|               "gitlab.toolbox.image.repository" => "#{IMAGE_REPOSITORY}/gitlab-toolbox-ee", | ||||
|               "gitlab.toolbox.image.tag" => commit_sha, | ||||
|               "gitlab.toolbox.image.tag" => toolbox_version, | ||||
|               "gitlab.sidekiq.annotations.commit" => commit_short_sha, | ||||
|               "gitlab.sidekiq.image.repository" => "#{IMAGE_REPOSITORY}/gitlab-sidekiq-ee", | ||||
|               "gitlab.sidekiq.image.tag" => commit_sha, | ||||
|               "gitlab.sidekiq.image.tag" => sidekiq_version, | ||||
|               "gitlab.webservice.annotations.commit" => commit_short_sha, | ||||
|               "gitlab.webservice.image.repository" => "#{IMAGE_REPOSITORY}/gitlab-webservice-ee", | ||||
|               "gitlab.webservice.image.tag" => commit_sha, | ||||
|               "gitlab.webservice.image.tag" => webservice_version, | ||||
|               "gitlab.webservice.workhorse.image" => "#{IMAGE_REPOSITORY}/gitlab-workhorse-ee", | ||||
|               "gitlab.webservice.workhorse.tag" => commit_sha, | ||||
|               "gitlab.webservice.workhorse.tag" => workhorse_version, | ||||
|               "gitlab.kas.image.repository" => "#{IMAGE_REPOSITORY}/gitlab-kas", | ||||
|               "gitlab.kas.image.tag" => semver?(kas_version) ? "v#{kas_version}" : kas_version | ||||
|               "gitlab.kas.image.tag" => with_semver_prefix(kas_version) | ||||
|             } | ||||
|           end | ||||
| 
 | ||||
|  | @ -77,8 +77,10 @@ module Gitlab | |||
|           # | ||||
|           # @param [String] version | ||||
|           # @return [Boolean] | ||||
|           def semver?(version) | ||||
|             version.match?(/^[0-9]+\.[0-9]+\.[0-9]+(-rc[0-9]+)?(-ee)?$/) | ||||
|           def with_semver_prefix(version) | ||||
|             return version unless version.match?(/^[0-9]+\.[0-9]+\.[0-9]+(-rc[0-9]+)?(-ee)?$/) | ||||
| 
 | ||||
|             "v#{version}" | ||||
|           end | ||||
|         end | ||||
|       end | ||||
|  |  | |||
|  | @ -8,6 +8,10 @@ module Gitlab | |||
|       module CI | ||||
|         extend self | ||||
| 
 | ||||
|         def ci_project_dir | ||||
|           @ci_project_dir ||= ENV["CI_PROJECT_DIR"] || raise("CI_PROJECT_DIR is not set") | ||||
|         end | ||||
| 
 | ||||
|         def commit_sha | ||||
|           @commit_sha ||= ENV["CI_COMMIT_SHA"] || raise("CI_COMMIT_SHA is not set") | ||||
|         end | ||||
|  | @ -17,19 +21,37 @@ module Gitlab | |||
|         end | ||||
| 
 | ||||
|         def gitaly_version | ||||
|           @gitaly_version ||= File.read(File.join(ci_project_dir, "GITALY_SERVER_VERSION")).strip | ||||
|           @gitaly_version ||= ENV["GITALY_TAG"].presence || File.read( | ||||
|             File.join(ci_project_dir, "GITALY_SERVER_VERSION") | ||||
|           ).strip | ||||
|         end | ||||
| 
 | ||||
|         def gitlab_shell_version | ||||
|           @gitlab_shell_version ||= File.read(File.join(ci_project_dir, "GITLAB_SHELL_VERSION")).strip | ||||
|           @gitlab_shell_version ||= ENV["GITLAB_SHELL_TAG"].presence || File.read( | ||||
|             File.join(ci_project_dir, "GITLAB_SHELL_VERSION") | ||||
|           ).strip | ||||
|         end | ||||
| 
 | ||||
|         def sidekiq_version | ||||
|           @sidekiq_version ||= ENV["GITLAB_SIDEKIQ_TAG"].presence || commit_sha | ||||
|         end | ||||
| 
 | ||||
|         def toolbox_version | ||||
|           @toolbox_version ||= ENV["GITLAB_TOOLBOX_TAG"].presence || commit_sha | ||||
|         end | ||||
| 
 | ||||
|         def webservice_version | ||||
|           @webservice_version ||= ENV["GITLAB_WEBSERVICE_TAG"].presence || commit_sha | ||||
|         end | ||||
| 
 | ||||
|         def workhorse_version | ||||
|           @workhorse_version ||= ENV["GITLAB_WORKHORSE_TAG"].presence || commit_sha | ||||
|         end | ||||
| 
 | ||||
|         def kas_version | ||||
|           @kas_version ||= File.read(File.join(ci_project_dir, "GITLAB_KAS_VERSION")).strip | ||||
|         end | ||||
| 
 | ||||
|         def ci_project_dir | ||||
|           @ci_project_dir ||= ENV["CI_PROJECT_DIR"] || raise("CI_PROJECT_DIR is not set") | ||||
|           @kas_version ||= ENV["GITLAB_KAS_TAG"].presence || File.read( | ||||
|             File.join(ci_project_dir, "GITLAB_KAS_VERSION") | ||||
|           ).strip | ||||
|         end | ||||
|       end | ||||
|     end | ||||
|  |  | |||
|  | @ -8,6 +8,7 @@ RSpec.describe Gitlab::Orchestrator::Deployment::DefaultValues do | |||
|   let(:gitaly_version) { "7aa06a578d76bdc294ee8e9acb4f063e7d9f1d5f" } | ||||
|   let(:kas_version) { "7aa06a578d76bdc294ee8e9acb4f063e7d9f1d5f" } | ||||
|   let(:shell_version) { "14.0.5" } | ||||
|   let(:image_tags) { {} } | ||||
| 
 | ||||
|   let(:env) do | ||||
|     { | ||||
|  | @ -17,10 +18,21 @@ RSpec.describe Gitlab::Orchestrator::Deployment::DefaultValues do | |||
|     } | ||||
|   end | ||||
| 
 | ||||
|   let(:memoized_variables) do | ||||
|     [ | ||||
|       :@ci_project_dir, | ||||
|       :@gitaly_version, | ||||
|       :@kas_version, | ||||
|       :@toolbox_version, | ||||
|       :@webservice_version, | ||||
|       :@workhorse_version, | ||||
|       :@gitlab_shell_version, | ||||
|       :@sidekiq_version | ||||
|     ] | ||||
|   end | ||||
| 
 | ||||
|   before do | ||||
|     described_class.instance_variable_set(:@ci_project_dir, nil) | ||||
|     described_class.instance_variable_set(:@gitaly_version, nil) | ||||
|     described_class.instance_variable_set(:@kas_version, nil) | ||||
|     memoized_variables.each { |variable| described_class.instance_variable_set(variable, nil) } | ||||
| 
 | ||||
|     allow(File).to receive(:read).with(File.join(ci_project_dir, "GITALY_SERVER_VERSION")).and_return(gitaly_version) | ||||
|     allow(File).to receive(:read).with(File.join(ci_project_dir, "GITLAB_SHELL_VERSION")).and_return(shell_version) | ||||
|  | @ -28,7 +40,7 @@ RSpec.describe Gitlab::Orchestrator::Deployment::DefaultValues do | |||
|   end | ||||
| 
 | ||||
|   around do |example| | ||||
|     ClimateControl.modify(env) { example.run } | ||||
|     ClimateControl.modify({ **env, **image_tags }) { example.run } | ||||
|   end | ||||
| 
 | ||||
|   it "returns correct common values" do | ||||
|  | @ -91,4 +103,30 @@ RSpec.describe Gitlab::Orchestrator::Deployment::DefaultValues do | |||
|       expect(described_class.component_ci_versions["gitlab.kas.image.tag"]).to eq("v#{kas_version}") | ||||
|     end | ||||
|   end | ||||
| 
 | ||||
|   context "with explicitly provided image tags" do | ||||
|     let(:image_tags) do | ||||
|       { | ||||
|         "GITALY_TAG" => "13b6c124a0fe566c7e3db4477600e0f004ab69bc", | ||||
|         "GITLAB_SHELL_TAG" => "e6daa09dbb6ded5529224acdd1fd24000866aaaf", | ||||
|         "GITLAB_TOOLBOX_TAG" => "e6ce8d7f67c0787c706d5968a1f84c5e2d4f2368", | ||||
|         "GITLAB_SIDEKIQ_TAG" => "1088d209ac5dd8d245b00946de0760eb8fc9a181", | ||||
|         "GITLAB_WEBSERVICE_TAG" => "b0ccc088a766801c8db9e7c564ad28472f33916c", | ||||
|         "GITLAB_WORKHORSE_TAG" => "4a3990fb621ba6f6b7ddf36089868b24e22bb598", | ||||
|         "GITLAB_KAS_TAG" => "03faf0a4227405febb714c4eaa78e4f16f5d0a37" | ||||
|       } | ||||
|     end | ||||
| 
 | ||||
|     it "uses explicitly provided image tags" do | ||||
|       expect(described_class.component_ci_versions).to include({ | ||||
|         "gitlab.gitaly.image.tag" => image_tags["GITALY_TAG"], | ||||
|         "gitlab.gitlab-shell.image.tag" => image_tags["GITLAB_SHELL_TAG"], | ||||
|         "gitlab.toolbox.image.tag" => image_tags["GITLAB_TOOLBOX_TAG"], | ||||
|         "gitlab.sidekiq.image.tag" => image_tags["GITLAB_SIDEKIQ_TAG"], | ||||
|         "gitlab.webservice.image.tag" => image_tags["GITLAB_WEBSERVICE_TAG"], | ||||
|         "gitlab.webservice.workhorse.tag" => image_tags["GITLAB_WORKHORSE_TAG"], | ||||
|         "gitlab.kas.image.tag" => image_tags["GITLAB_KAS_TAG"] | ||||
|       }) | ||||
|     end | ||||
|   end | ||||
| end | ||||
|  |  | |||
|  | @ -5,6 +5,27 @@ | |||
| # | ||||
| # See https://docs.gitlab.com/ee/development/pipelines/internals.html#using-the-gitlab-ruby-gem-in-the-canonical-project. | ||||
| require 'gitlab' | ||||
| require 'yaml' | ||||
| require 'json' | ||||
| require 'open3' | ||||
| require 'tempfile' | ||||
| require 'httparty' | ||||
| require 'logger' | ||||
| 
 | ||||
| # Monkeypatch gitlab gem in order to increase per_page size when fetching registry repositories | ||||
| # rubocop:disable Style/ClassAndModuleChildren, Gitlab/NoCodeCoverageComment -- monkeypatch | ||||
| # :nocov: | ||||
| # | ||||
| # TODO: Remove this monkeypatch once https://github.com/NARKOZ/gitlab/pull/710 is part of a new gem release (currently v5.1.0 doens't contain it) and the release is used in this project. | ||||
| class Gitlab::Client | ||||
|   module ContainerRegistry | ||||
|     def registry_repositories(project, options = {}) | ||||
|       get("/projects/#{url_encode project}/registry/repositories", query: options) | ||||
|     end | ||||
|   end | ||||
| end | ||||
| # :nocov: | ||||
| # rubocop:enable Style/ClassAndModuleChildren, Gitlab/NoCodeCoverageComment | ||||
| 
 | ||||
| module Trigger | ||||
|   def self.ee? | ||||
|  | @ -158,7 +179,7 @@ module Trigger | |||
| 
 | ||||
|     # Read version files from all components | ||||
|     def version_file_variables | ||||
|       Dir.glob("*_VERSION").each_with_object({}) do |version_file, params| # rubocop:disable Rails/IndexWith | ||||
|       Dir.glob("*_VERSION").each_with_object({}) do |version_file, params| # rubocop:disable Rails/IndexWith -- Non-rails CI script | ||||
|         params[version_file] = version_param_value(version_file) | ||||
|       end | ||||
|     end | ||||
|  | @ -170,19 +191,77 @@ module Trigger | |||
|     end | ||||
|   end | ||||
| 
 | ||||
|   # Variable creation for downstream CNG build triggers | ||||
|   # | ||||
|   # This class additionally contains logic to check if component versions are already present in the container registry | ||||
|   # If they are, it adds those jobs to the SKIP_JOB_REGEX variable to skip them in the CNG build pipeline | ||||
|   # | ||||
|   # In order to correctly compute container versions and skip jobs, following actions are performed: | ||||
|   #   * container version shell script is fetched from upstream | ||||
|   #   * versions.yml file is fetched which contains most of variables required for container version calculation | ||||
|   #   * image digest of stable Debian and Alpine images are fetched (alpine-stable and alpine-debian jobs functionality) | ||||
|   #   * all container versions are computed using same logic as CNG build pipeline | ||||
|   #   * registry is checked for image existence and appropriate jobs are added to skip regex pattern | ||||
|   # | ||||
|   class CNG < Base | ||||
|     ASSETS_HASH = "cached-assets-hash.txt" | ||||
|     DEFAULT_DEBIAN_IMAGE = "debian:bookworm-slim" | ||||
|     DEFAULT_ALPINE_IMAGE = "alpine:3.20" | ||||
|     DEFAULT_SKIPPED_JOBS = %w[final-images-listing].freeze | ||||
|     STABLE_BASE_JOBS = %w[alpine-stable debian-stable].freeze | ||||
| 
 | ||||
|     def variables | ||||
|       hash = super.dup | ||||
|       # Delete variables that aren't useful when using native triggers. | ||||
|       super.tap do |hash| | ||||
|         hash.delete('TRIGGER_SOURCE') | ||||
|         hash.delete('TRIGGERED_USER') | ||||
|       hash.delete('TRIGGER_SOURCE') | ||||
|       hash.delete('TRIGGERED_USER') | ||||
| 
 | ||||
|       unless skip_redundant_jobs? | ||||
|         logger.info("Skipping redundant jobs is disabled, skipping existing container image check") | ||||
|         return hash | ||||
|       end | ||||
| 
 | ||||
|       begin | ||||
|         hash.merge({ | ||||
|           **deploy_component_tag_variables, | ||||
|           'SKIP_JOB_REGEX' => skip_job_regex, | ||||
|           'DEBIAN_IMAGE' => debian_image, | ||||
|           'DEBIAN_DIGEST' => debian_image.split('@').last, | ||||
|           'DEBIAN_BUILD_ARGS' => "--build-arg DEBIAN_IMAGE=#{ENV['GITLAB_DEPENDENCY_PROXY']}#{debian_image}", | ||||
|           'ALPINE_IMAGE' => alpine_image, | ||||
|           'ALPINE_DIGEST' => alpine_image.split('@').last, | ||||
|           'ALPINE_BUILD_ARGS' => "--build-arg ALPINE_IMAGE=#{ENV['GITLAB_DEPENDENCY_PROXY']}#{alpine_image}" | ||||
|         }) | ||||
|       rescue StandardError => e | ||||
|         logger.error("Error while calculating variables, err: #{e.message}") | ||||
|         logger.error(e.backtrace.join("\n")) | ||||
|         logger.error("Falling back to default variables") | ||||
|         hash | ||||
|       end | ||||
|     end | ||||
| 
 | ||||
|     private | ||||
| 
 | ||||
|     def logger | ||||
|       @logger ||= Logger.new(ENV["CNG_VAR_SETUP_LOG_FILE"] || "tmp/cng-var-setup.log") | ||||
|     end | ||||
| 
 | ||||
|     def downstream_project_path | ||||
|       ENV.fetch('CNG_PROJECT_PATH', 'gitlab-org/build/CNG-mirror') | ||||
|     end | ||||
| 
 | ||||
|     def skip_redundant_jobs? | ||||
|       ENV["CNG_SKIP_REDUNDANT_JOBS"] == "true" | ||||
|     end | ||||
| 
 | ||||
|     def default_skip_job_regex | ||||
|       "/#{DEFAULT_SKIPPED_JOBS.join('|')}/" | ||||
|     end | ||||
| 
 | ||||
|     def skip_job_regex | ||||
|       "/#{[*DEFAULT_SKIPPED_JOBS, *STABLE_BASE_JOBS, *skippable_jobs].join('|')}/" | ||||
|     end | ||||
| 
 | ||||
|     def ref_param_name | ||||
|       'CNG_BRANCH' | ||||
|     end | ||||
|  | @ -205,20 +284,35 @@ module Trigger | |||
|       end | ||||
|     end | ||||
| 
 | ||||
|     def gitlab_version | ||||
|       ENV['CI_COMMIT_SHA'] | ||||
|     end | ||||
| 
 | ||||
|     def base_variables | ||||
|       super.merge( | ||||
|         'GITLAB_REF_SLUG' => gitlab_ref_slug | ||||
|       ) | ||||
|       super.merge('GITLAB_REF_SLUG' => gitlab_ref_slug) | ||||
|     end | ||||
| 
 | ||||
|     def default_build_vars | ||||
|       @default_build_vars ||= { | ||||
|         "CONTAINER_VERSION_SUFFIX" => ENV["CI_PROJECT_PATH_SLUG"] || "upstream-trigger", | ||||
|         "CACHE_BUSTER" => "false", | ||||
|         "ARCH_LIST" => ENV["ARCH_LIST"] || "amd64" | ||||
|       } | ||||
|     end | ||||
| 
 | ||||
|     def extra_variables | ||||
|       { | ||||
|         "TRIGGER_BRANCH" => ref, | ||||
|         "GITLAB_VERSION" => ENV['CI_COMMIT_SHA'], | ||||
|         "GITLAB_VERSION" => gitlab_version, | ||||
|         "GITLAB_TAG" => ENV['CI_COMMIT_TAG'], # Always set a value, even an empty string, so that the downstream pipeline can correctly check it. | ||||
|         "FORCE_RAILS_IMAGE_BUILDS" => 'true', | ||||
|         "CE_PIPELINE" => Trigger.ee? ? nil : "true", # Always set a value, even an empty string, so that the downstream pipeline can correctly check it. | ||||
|         "EE_PIPELINE" => Trigger.ee? ? "true" : nil # Always set a value, even an empty string, so that the downstream pipeline can correctly check it. | ||||
|         "EE_PIPELINE" => Trigger.ee? ? "true" : nil, # Always set a value, even an empty string, so that the downstream pipeline can correctly check it. | ||||
|         "FULL_RUBY_VERSION" => RUBY_VERSION, | ||||
|         "SKIP_JOB_REGEX" => default_skip_job_regex, | ||||
|         "DEBIAN_IMAGE" => DEFAULT_DEBIAN_IMAGE, # Make sure default values are always set to not end up as empty string | ||||
|         "ALPINE_IMAGE" => DEFAULT_ALPINE_IMAGE, # Make sure default values are always set to not end up as empty string | ||||
|         **default_build_vars | ||||
|       } | ||||
|     end | ||||
| 
 | ||||
|  | @ -238,6 +332,174 @@ module Trigger | |||
|         raw_version | ||||
|       end | ||||
|     end | ||||
| 
 | ||||
|     # Repository file tree in form of the output of `git ls-tree` command | ||||
|     # | ||||
|     # @return [String] | ||||
|     def repo_tree | ||||
|       logger.info("Fetching repo tree for ref '#{ref}'") | ||||
|       downstream_client | ||||
|         .repo_tree(downstream_project_path, ref: ref, per_page: 100).auto_paginate | ||||
|         .select { |node| node["type"] == "tree" } | ||||
|         .map { |node| "#{node['mode']} #{node['type']} #{node['id']}  #{node['path']}" } | ||||
|         .join("\n") | ||||
|     end | ||||
| 
 | ||||
|     # Script used for container version calculations in CNG build jobs | ||||
|     # | ||||
|     # @return [String] | ||||
|     def container_versions_script | ||||
|       logger.info("Fetching container versions script for ref '#{ref}'") | ||||
|       downstream_client.file_contents( | ||||
|         downstream_project_path, | ||||
|         "build-scripts/container_versions.sh", | ||||
|         ref | ||||
|       ) | ||||
|     end | ||||
| 
 | ||||
|     # Debian image with digest | ||||
|     # | ||||
|     # @return [String] | ||||
|     def debian_image | ||||
|       @debian_image ||= docker_image_with_digest(cng_versions["DEBIAN_IMAGE"]) | ||||
|     end | ||||
| 
 | ||||
|     # Alpine image with digest | ||||
|     # | ||||
|     # @return [String] | ||||
|     def alpine_image | ||||
|       @alpine_image ||= docker_image_with_digest(cng_versions["ALPINE_IMAGE"]) | ||||
|     end | ||||
| 
 | ||||
|     # Edition postfix | ||||
|     # | ||||
|     # @return [String] | ||||
|     def edition | ||||
|       @edition ||= Trigger.ee? ? "ee" : "ce" | ||||
|     end | ||||
| 
 | ||||
|     # Component versions used in CNG builds | ||||
|     # | ||||
|     # @return [Hash] | ||||
|     def cng_versions | ||||
|       @cng_versions ||= YAML | ||||
|         .safe_load(downstream_client.file_contents(downstream_project_path, "ci_files/variables.yml", ref)) | ||||
|         .fetch("variables") | ||||
|     end | ||||
| 
 | ||||
|     # Environment variables required for container version fetching | ||||
|     # All these variables influence final container version values | ||||
|     # | ||||
|     # @return [Hash] | ||||
|     def version_fetch_env_variables | ||||
|       { | ||||
|         **cng_versions, | ||||
|         **version_file_variables, | ||||
|         **default_build_vars, | ||||
|         "GITLAB_VERSION" => gitlab_version, | ||||
|         "RUBY_VERSION" => RUBY_VERSION, | ||||
|         "DEBIAN_DIGEST" => debian_image.split("@").last, | ||||
|         "ALPINE_DIGEST" => alpine_image.split("@").last, | ||||
|         "REPOSITORY_TREE" => repo_tree | ||||
|       } | ||||
|     end | ||||
| 
 | ||||
|     # Image tags used by CNG deployments | ||||
|     # | ||||
|     # @return [Hash] | ||||
|     def deploy_component_tag_variables | ||||
|       { | ||||
|         "GITALY_TAG" => container_versions["gitaly"], | ||||
|         "GITLAB_SHELL_TAG" => container_versions["gitlab-shell"], | ||||
|         "GITLAB_TOOLBOX_TAG" => container_versions["gitlab-toolbox-#{edition}"], | ||||
|         "GITLAB_SIDEKIQ_TAG" => container_versions["gitlab-sidekiq-#{edition}"], | ||||
|         "GITLAB_WEBSERVICE_TAG" => container_versions["gitlab-webservice-#{edition}"], | ||||
|         "GITLAB_WORKHORSE_TAG" => container_versions["gitlab-workhorse-#{edition}"], | ||||
|         "GITLAB_KAS_TAG" => container_versions["gitlab-kas"] | ||||
|       } | ||||
|     end | ||||
| 
 | ||||
|     # Container versions for all components in CNG build pipeline | ||||
|     # | ||||
|     # @return [Hash] | ||||
|     def container_versions | ||||
|       @container_versions ||= Tempfile.create('container-versions') do |file| | ||||
|         file.write(container_versions_script) | ||||
|         file.close | ||||
| 
 | ||||
|         build_vars = version_fetch_env_variables | ||||
|         logger.info("Computing container versions using following env variables:\n#{JSON.pretty_generate(build_vars)}") | ||||
|         out, status = Open3.capture2e(build_vars, "bash -c 'source #{file.path} && get_all_versions'") | ||||
|         raise "Failed to fetch container versions! #{out}" unless status.success? | ||||
| 
 | ||||
|         component_versions = out.split("\n") | ||||
|         unless component_versions.all? { |line| line.match?(/^[A-Za-z0-9_\-]+=[^=]+$/) } | ||||
|           raise "Invalid container versions output format! Expected key=value pairs got:\n#{out}" | ||||
|         end | ||||
| 
 | ||||
|         component_versions | ||||
|           .to_h { |entry| entry.split("=") } | ||||
|           .reject { |name, _version| Trigger.ee? ? name.end_with?("-ce") : name.end_with?("-ee") } | ||||
|           .tap { |versions| logger.info("Computed container versions:\n#{JSON.pretty_generate(versions)}") } | ||||
|       end | ||||
|     end | ||||
| 
 | ||||
|     # List of jobs that can be skipped because tag is already present in the registry | ||||
|     # | ||||
|     # @return [Array] | ||||
|     def skippable_jobs | ||||
|       jobs = container_versions.keys | ||||
|       logger.info("Fetching container registry repositories for project '#{downstream_project_path}'") | ||||
|       repositories = downstream_client.registry_repositories(downstream_project_path, per_page: 100).auto_paginate | ||||
|       build_repositories = repositories.each_with_object({}) do |repo, hash| | ||||
|         job = jobs.find { |job| repo.name.end_with?(job) } | ||||
|         next unless job | ||||
| 
 | ||||
|         hash[job] = repo.id | ||||
|       end | ||||
|       logger.info("Checking repositories (#{build_repositories.keys.join(', ')}) for existing tags") | ||||
|       existing_tags = container_versions.select do |job, tag| | ||||
|         downstream_client.registry_repository_tag(downstream_project_path, build_repositories[job], tag) | ||||
|         logger.info("Tag '#{tag}' exists in the registry, job '#{job}' will be skipped") | ||||
|       rescue Gitlab::Error::ResponseError => e | ||||
|         if e.is_a?(Gitlab::Error::NotFound) | ||||
|           logger.info("Tag '#{tag}' does not exist in the registry, job '#{job}' will not skipped") | ||||
|         else | ||||
|           logger.error("Failed to do a tag '#{tag}' lookup, err: #{e.message}, job '#{job}' will not be skipped") | ||||
|         end | ||||
| 
 | ||||
|         false | ||||
|       end | ||||
| 
 | ||||
|       existing_tags.keys | ||||
|     end | ||||
| 
 | ||||
|     # rubocop:disable Gitlab/HTTParty -- CI script | ||||
| 
 | ||||
|     # Fetch Docker image with digest from DockerHub | ||||
|     # | ||||
|     # @param docker_image [String] | ||||
|     # @return [String] | ||||
|     def docker_image_with_digest(docker_image) | ||||
|       image, tag = docker_image.split(":") | ||||
| 
 | ||||
|       logger.info("Fetching digest for image '#{docker_image}'") | ||||
|       auth_url = "https://auth.docker.io/token?service=registry.docker.io&scope=repository:library/#{image}:pull" | ||||
|       auth_response = HTTParty.get(auth_url) | ||||
|       raise "Failed to get auth token" unless auth_response.success? | ||||
| 
 | ||||
|       token = JSON.parse(auth_response.body)['token'] | ||||
|       manifest_url = "https://registry.hub.docker.com/v2/library/#{image}/manifests/#{tag}" | ||||
|       response = HTTParty.head(manifest_url, headers: { | ||||
|         'Authorization' => "Bearer #{token}", | ||||
|         'Accept' => 'application/vnd.docker.distribution.manifest.v2+json' | ||||
|       }) | ||||
|       raise "Failed to fetch image '#{docker_image}' digest" unless response.success? | ||||
| 
 | ||||
|       digest = response.headers['docker-content-digest'] || raise("Failed to get image digest") | ||||
|       "#{image}:#{tag}@#{digest}" | ||||
|     end | ||||
|     # rubocop:enable Gitlab/HTTParty -- CI script | ||||
|   end | ||||
| 
 | ||||
|   # For GitLab documentation review apps | ||||
|  | @ -342,21 +604,25 @@ module Trigger | |||
|       pipeline = super | ||||
|       project_path = variables['TOP_UPSTREAM_SOURCE_PROJECT'] | ||||
|       merge_request_id = variables['TOP_UPSTREAM_MERGE_REQUEST_IID'] | ||||
|       comment = "<!-- #{IDENTIFIABLE_NOTE_TAG} --> \nStarted database testing [pipeline](https://ops.gitlab.net/#{downstream_project_path}/-/pipelines/#{pipeline.id}) " \ | ||||
|                 "(limited access). This comment will be updated once the pipeline has finished running." | ||||
|       comment = <<~COMMENT.strip | ||||
|         <!-- #{IDENTIFIABLE_NOTE_TAG} --> | ||||
|         Started database testing [pipeline](https://ops.gitlab.net/#{downstream_project_path}/-/pipelines/#{pipeline.id}) (limited access). This comment will be updated once the pipeline has finished running. | ||||
|       COMMENT | ||||
| 
 | ||||
|       # Look for an existing note | ||||
|       db_testing_notes = com_gitlab_client.merge_request_notes(project_path, merge_request_id).auto_paginate.select do |note| | ||||
|         note.body.include?(IDENTIFIABLE_NOTE_TAG) | ||||
|       end | ||||
|       db_testing_notes = com_gitlab_client | ||||
|         .merge_request_notes(project_path, merge_request_id) | ||||
|         .auto_paginate.select do |note| | ||||
|           note.body.include?(IDENTIFIABLE_NOTE_TAG) | ||||
|         end | ||||
| 
 | ||||
|       if db_testing_notes.empty? | ||||
|         # This is the first note | ||||
|         note = com_gitlab_client.create_merge_request_note(project_path, merge_request_id, comment) | ||||
|       return unless db_testing_notes.empty? | ||||
| 
 | ||||
|         puts "Posted comment to:\n" | ||||
|         puts "https://gitlab.com/#{project_path}/-/merge_requests/#{merge_request_id}#note_#{note.id}" | ||||
|       end | ||||
|       # This is the first note | ||||
|       note = com_gitlab_client.create_merge_request_note(project_path, merge_request_id, comment) | ||||
| 
 | ||||
|       puts "Posted comment to:\n" | ||||
|       puts "https://gitlab.com/#{project_path}/-/merge_requests/#{merge_request_id}#note_#{note.id}" | ||||
|     end | ||||
| 
 | ||||
|     private | ||||
|  |  | |||
|  | @ -1,6 +1,6 @@ | |||
| import { shallowMount } from '@vue/test-utils'; | ||||
| import { nextTick } from 'vue'; | ||||
| import mockPipelineResponse from 'test_fixtures/pipelines/pipeline_details.json'; | ||||
| import { mountExtended } from 'helpers/vue_test_utils_helper'; | ||||
| import { shallowMountExtended, mountExtended } from 'helpers/vue_test_utils_helper'; | ||||
| import { LAYER_VIEW, STAGE_VIEW } from '~/ci/pipeline_details/graph/constants'; | ||||
| import PipelineGraph from '~/ci/pipeline_details/graph/components/graph_component.vue'; | ||||
| import JobItem from '~/ci/pipeline_details/graph/components/job_item.vue'; | ||||
|  | @ -14,7 +14,6 @@ import { generateResponse, pipelineWithUpstreamDownstream } from '../mock_data'; | |||
| describe('graph component', () => { | ||||
|   let wrapper; | ||||
| 
 | ||||
|   const findDownstreamColumn = () => wrapper.findByTestId('downstream-pipelines'); | ||||
|   const findLinkedColumns = () => wrapper.findAllComponents(LinkedPipelinesColumn); | ||||
|   const findLinksLayer = () => wrapper.findComponent(LinksLayer); | ||||
|   const findStageColumns = () => wrapper.findAllComponents(StageColumnComponent); | ||||
|  | @ -43,7 +42,7 @@ describe('graph component', () => { | |||
| 
 | ||||
|   const createComponent = ({ | ||||
|     data = {}, | ||||
|     mountFn = shallowMount, | ||||
|     mountFn = shallowMountExtended, | ||||
|     props = {}, | ||||
|     stubOverride = {}, | ||||
|   } = {}) => { | ||||
|  | @ -123,8 +122,10 @@ describe('graph component', () => { | |||
|   }); | ||||
| 
 | ||||
|   describe('when linked pipelines are not present', () => { | ||||
|     beforeEach(() => { | ||||
|     beforeEach(async () => { | ||||
|       createComponent({ mountFn: mountExtended }); | ||||
| 
 | ||||
|       await nextTick(); | ||||
|     }); | ||||
| 
 | ||||
|     it('should not render a linked pipelines column', () => { | ||||
|  | @ -133,11 +134,13 @@ describe('graph component', () => { | |||
|   }); | ||||
| 
 | ||||
|   describe('when linked pipelines are present', () => { | ||||
|     beforeEach(() => { | ||||
|     beforeEach(async () => { | ||||
|       createComponent({ | ||||
|         mountFn: mountExtended, | ||||
|         props: { pipeline: pipelineWithUpstreamDownstream(mockPipelineResponse) }, | ||||
|       }); | ||||
| 
 | ||||
|       await nextTick(); | ||||
|     }); | ||||
| 
 | ||||
|     it('should render linked pipelines columns', () => { | ||||
|  | @ -175,11 +178,13 @@ describe('graph component', () => { | |||
|       }); | ||||
|     }); | ||||
| 
 | ||||
|     it('filters pipelines spawned from the same trigger job', () => { | ||||
|       // The mock data has one downstream with `retried: true and one
 | ||||
|       // with retried false. We filter the `retried: true` out so we
 | ||||
|       // should only pass one downstream
 | ||||
|       expect(findDownstreamColumn().props().linkedPipelines).toHaveLength(1); | ||||
|     it('filters pipelines spawned from the same trigger job', async () => { | ||||
|       const DownstreamColumn = ( | ||||
|         await import('~/ci/pipeline_details/graph/components/linked_pipelines_column.vue') | ||||
|       ).default; | ||||
| 
 | ||||
|       expect(wrapper.findComponent(DownstreamColumn).exists()).toBe(true); | ||||
|       expect(wrapper.findComponent(DownstreamColumn).props('linkedPipelines')).toHaveLength(1); | ||||
|     }); | ||||
|   }); | ||||
| 
 | ||||
|  |  | |||
|  | @ -1,3 +1,4 @@ | |||
| // Fixture located at spec/frontend/fixtures/pipeline_details.rb
 | ||||
| import mockPipelineResponse from 'test_fixtures/pipelines/pipeline_details.json'; | ||||
| import { unwrapPipelineData } from '~/ci/pipeline_details/graph/utils'; | ||||
| import { | ||||
|  |  | |||
|  | @ -124,141 +124,15 @@ RSpec.describe Gitlab::Auth::CurrentUserMode, :request_store, feature_category: | |||
|       context 'when the user is an admin' do | ||||
|         let(:user) { build_stubbed(:user, :admin) } | ||||
| 
 | ||||
|         context 'when admin mode not requested' do | ||||
|           it 'is false by default' do | ||||
|             expect(subject.admin_mode?).to be(false) | ||||
|           end | ||||
| 
 | ||||
|           it 'raises exception if we try to enable it' do | ||||
|             expect do | ||||
|               subject.enable_admin_mode!(password: user.password) | ||||
|             end.to raise_error(::Gitlab::Auth::CurrentUserMode::NotRequestedError) | ||||
| 
 | ||||
|             expect(subject.admin_mode?).to be(false) | ||||
|           end | ||||
|         end | ||||
| 
 | ||||
|         context 'when admin mode requested first' do | ||||
|           before do | ||||
|             subject.request_admin_mode! | ||||
|           end | ||||
| 
 | ||||
|           it 'is false by default' do | ||||
|             expect(subject.admin_mode?).to be(false) | ||||
|           end | ||||
| 
 | ||||
|           it 'cannot be enabled with an invalid password' do | ||||
|             subject.enable_admin_mode!(password: nil) | ||||
| 
 | ||||
|             expect(subject.admin_mode?).to be(false) | ||||
|           end | ||||
| 
 | ||||
|           it 'can be enabled with a valid password' do | ||||
|             subject.enable_admin_mode!(password: user.password) | ||||
| 
 | ||||
|             expect(subject.admin_mode?).to be(true) | ||||
|           end | ||||
| 
 | ||||
|           it 'can be disabled' do | ||||
|             subject.enable_admin_mode!(password: user.password) | ||||
|             subject.disable_admin_mode! | ||||
| 
 | ||||
|             expect(subject.admin_mode?).to be(false) | ||||
|           end | ||||
| 
 | ||||
|           it 'will expire in the future' do | ||||
|             subject.enable_admin_mode!(password: user.password) | ||||
|             expect(subject.admin_mode?).to be(true), 'admin mode is not active in the present' | ||||
| 
 | ||||
|             travel_to(Gitlab::Auth::CurrentUserMode::MAX_ADMIN_MODE_TIME.from_now) do | ||||
|               # in the future this will be a new request, simulate by clearing the RequestStore | ||||
|               Gitlab::SafeRequestStore.clear! | ||||
| 
 | ||||
|               expect(subject.admin_mode?).to be(false), 'admin mode did not expire in the future' | ||||
|             end | ||||
|           end | ||||
| 
 | ||||
|           context 'skipping password validation' do | ||||
|             it 'can be enabled with a valid password' do | ||||
|               subject.enable_admin_mode!(password: user.password, skip_password_validation: true) | ||||
| 
 | ||||
|               expect(subject.admin_mode?).to be(true) | ||||
|             end | ||||
| 
 | ||||
|             it 'can be enabled with an invalid password' do | ||||
|               subject.enable_admin_mode!(skip_password_validation: true) | ||||
| 
 | ||||
|               expect(subject.admin_mode?).to be(true) | ||||
|             end | ||||
|           end | ||||
| 
 | ||||
|           context 'with two independent sessions' do | ||||
|             let(:another_session) { {} } | ||||
|             let(:another_subject) { described_class.new(user) } | ||||
| 
 | ||||
|             before do | ||||
|               allow(ActiveSession).to receive(:list_sessions).with(user).and_return([session, another_session]) | ||||
|             end | ||||
| 
 | ||||
|             it 'cannot be enabled in one and seen in the other' do | ||||
|               Gitlab::Session.with_session(another_session) do | ||||
|                 another_subject.request_admin_mode! | ||||
|                 another_subject.enable_admin_mode!(password: user.password) | ||||
|               end | ||||
| 
 | ||||
|               expect(subject.admin_mode?).to be(false) | ||||
|             end | ||||
|           end | ||||
|         end | ||||
| 
 | ||||
|         context 'bypassing session' do | ||||
|           it 'is active by default' do | ||||
|             described_class.bypass_session!(user.id) do | ||||
|               expect(subject.admin_mode?).to be(true) | ||||
|             end | ||||
|           end | ||||
| 
 | ||||
|           it 'enable has no effect' do | ||||
|             described_class.bypass_session!(user.id) do | ||||
|               subject.request_admin_mode! | ||||
|               subject.enable_admin_mode!(password: user.password) | ||||
| 
 | ||||
|               expect(subject.admin_mode?).to be(true) | ||||
|             end | ||||
|           end | ||||
| 
 | ||||
|           it 'disable has no effect' do | ||||
|             described_class.bypass_session!(user.id) do | ||||
|               subject.disable_admin_mode! | ||||
| 
 | ||||
|               expect(subject.admin_mode?).to be(true) | ||||
|             end | ||||
|           end | ||||
|         end | ||||
|         it_behaves_like 'admin_mode? check if admin_mode can be enabled' | ||||
|       end | ||||
|     end | ||||
| 
 | ||||
|     describe '#enable_admin_mode!' do | ||||
|       let(:user) { build_stubbed(:user, :admin) } | ||||
|       context 'when the user is an admin' do | ||||
|         let(:user) { build_stubbed(:user, :admin) } | ||||
| 
 | ||||
|       it 'creates a timestamp in the session' do | ||||
|         subject.request_admin_mode! | ||||
| 
 | ||||
|         subject.enable_admin_mode!(password: user.password) | ||||
| 
 | ||||
|         expect(session).to include(expected_session_entry(be_within(1.second).of(Time.now))) | ||||
|       end | ||||
| 
 | ||||
|       it 'returns true after successful enable' do | ||||
|         subject.request_admin_mode! | ||||
| 
 | ||||
|         expect(subject.enable_admin_mode!(password: user.password)).to eq(true) | ||||
|       end | ||||
| 
 | ||||
|       it 'returns false after unsuccessful enable' do | ||||
|         subject.request_admin_mode! | ||||
| 
 | ||||
|         expect(subject.enable_admin_mode!(password: 'wrong password')).to eq(false) | ||||
|         it_behaves_like 'enabling admin_mode when it can be enabled' | ||||
|       end | ||||
| 
 | ||||
|       context 'when user is not an admin' do | ||||
|  | @ -270,25 +144,12 @@ RSpec.describe Gitlab::Auth::CurrentUserMode, :request_store, feature_category: | |||
|           expect(subject.enable_admin_mode!(password: user.password)).to eq(false) | ||||
|         end | ||||
|       end | ||||
| 
 | ||||
|       context 'when admin mode is not requested' do | ||||
|         it 'raises error' do | ||||
|           expect do | ||||
|             subject.enable_admin_mode!(password: user.password) | ||||
|           end.to raise_error(Gitlab::Auth::CurrentUserMode::NotRequestedError) | ||||
|         end | ||||
|       end | ||||
|     end | ||||
| 
 | ||||
|     describe '#disable_admin_mode!' do | ||||
|       let(:user) { build_stubbed(:user, :admin) } | ||||
| 
 | ||||
|       it 'sets the session timestamp to nil' do | ||||
|         subject.request_admin_mode! | ||||
|         subject.disable_admin_mode! | ||||
| 
 | ||||
|         expect(session).to include(expected_session_entry(be_nil)) | ||||
|       end | ||||
|       it_behaves_like 'disabling admin_mode' | ||||
|     end | ||||
| 
 | ||||
|     describe '.with_current_request_admin_mode' do | ||||
|  | @ -331,13 +192,6 @@ RSpec.describe Gitlab::Auth::CurrentUserMode, :request_store, feature_category: | |||
|         end | ||||
|       end | ||||
|     end | ||||
| 
 | ||||
|     def expected_session_entry(value_matcher) | ||||
|       { | ||||
|         Gitlab::Auth::CurrentUserMode::SESSION_STORE_KEY => a_hash_including( | ||||
|           Gitlab::Auth::CurrentUserMode::ADMIN_MODE_START_TIME_KEY => value_matcher) | ||||
|       } | ||||
|     end | ||||
|   end | ||||
| 
 | ||||
|   context 'when no session available' do | ||||
|  |  | |||
|  | @ -1,55 +0,0 @@ | |||
| # frozen_string_literal: true | ||||
| 
 | ||||
| require 'spec_helper' | ||||
| require_migration! | ||||
| 
 | ||||
| RSpec.describe MigrateGlobalSearchSettingsInApplicationSettings, feature_category: :global_search do | ||||
|   let!(:application_setting) { table(:application_settings).create! } | ||||
| 
 | ||||
|   describe '#down' do | ||||
|     let(:migration) { described_class.new } | ||||
| 
 | ||||
|     context 'when search settings is already set' do | ||||
|       it 'does not update the search settings' do | ||||
|         migration.up | ||||
|         expect { migration.down }.to change { application_setting.reload.search }.to({}) | ||||
|       end | ||||
|     end | ||||
|   end | ||||
| 
 | ||||
|   describe '#up' do | ||||
|     context 'when search is not already set' do | ||||
|       before do | ||||
|         stub_feature_flags(global_search_code_tab: false) | ||||
|         stub_feature_flags(global_search_commits_tab: false) | ||||
|         stub_feature_flags(global_search_issues_tab: false) | ||||
|       end | ||||
| 
 | ||||
|       it 'migrates search from the feature flags in the application_settings successfully' do | ||||
|         expected_search = if ::Gitlab.ee? | ||||
|                             { | ||||
|                               'global_search_code_enabled' => false, | ||||
|                               'global_search_commits_enabled' => false, | ||||
|                               'global_search_epics_enabled' => true, | ||||
|                               'global_search_issues_enabled' => false, | ||||
|                               'global_search_merge_requests_enabled' => true, | ||||
|                               'global_search_snippet_titles_enabled' => true, | ||||
|                               'global_search_users_enabled' => true, | ||||
|                               'global_search_wiki_enabled' => true | ||||
|                             } | ||||
|                           else | ||||
|                             { | ||||
|                               'global_search_issues_enabled' => false, | ||||
|                               'global_search_merge_requests_enabled' => true, | ||||
|                               'global_search_snippet_titles_enabled' => true, | ||||
|                               'global_search_users_enabled' => true | ||||
|                             } | ||||
|                           end | ||||
| 
 | ||||
|         expect { migrate! }.to change { | ||||
|           application_setting.reload.search | ||||
|         }.from({}).to(expected_search) | ||||
|       end | ||||
|     end | ||||
|   end | ||||
| end | ||||
|  | @ -164,6 +164,12 @@ RSpec.describe DeployKey, :mailer, feature_category: :continuous_delivery do | |||
|         it { expect(subject.can?(:push_code, project)).to be false } | ||||
|       end | ||||
|     end | ||||
| 
 | ||||
|     describe '#can_access_admin_area?' do | ||||
|       it 'returns false' do | ||||
|         expect(subject.can_access_admin_area?).to be_falsey | ||||
|       end | ||||
|     end | ||||
|   end | ||||
| 
 | ||||
|   describe '#audit_details' do | ||||
|  |  | |||
|  | @ -726,6 +726,56 @@ RSpec.describe Note, feature_category: :team_planning do | |||
|     end | ||||
|   end | ||||
| 
 | ||||
|   describe '#last_edited_by' do | ||||
|     let_it_be(:note, reload: true) do | ||||
|       create_timestamp = 1.day.ago | ||||
|       create(:note, created_at: create_timestamp, updated_at: create_timestamp) | ||||
|     end | ||||
| 
 | ||||
|     let(:editor) { note.author } | ||||
| 
 | ||||
|     context 'when the note is not edited' do | ||||
|       it 'returns nil' do | ||||
|         expect(note.last_edited_by).to be_nil | ||||
|       end | ||||
|     end | ||||
| 
 | ||||
|     def update_note(note, **attributes) | ||||
|       # Update updated_at manually because of ThrottledTouch concern | ||||
|       note.update!(attributes.merge(updated_at: Time.current)) | ||||
|     end | ||||
| 
 | ||||
|     context 'with an edited note' do | ||||
|       before do | ||||
|         update_note(note, last_edited_at: Time.current, updated_by: editor) | ||||
|       end | ||||
| 
 | ||||
|       it 'returns the updated_by user' do | ||||
|         expect(note.last_edited_by).to eq(editor) | ||||
|       end | ||||
|     end | ||||
| 
 | ||||
|     context 'with an edited note by a deleted user' do | ||||
|       before do | ||||
|         update_note(note, last_edited_at: Time.current, updated_by: nil) | ||||
|       end | ||||
| 
 | ||||
|       it 'returns the ghost user' do | ||||
|         expect(note.last_edited_by).to eq(Users::Internal.ghost) | ||||
|       end | ||||
|     end | ||||
| 
 | ||||
|     context 'with a legacy edited note where last_edited_at is not set' do | ||||
|       before do | ||||
|         update_note(note, updated_by: editor) | ||||
|       end | ||||
| 
 | ||||
|       it 'returns the updated_by user' do | ||||
|         expect(note.last_edited_by).to eq(editor) | ||||
|       end | ||||
|     end | ||||
|   end | ||||
| 
 | ||||
|   describe '#confidential?' do | ||||
|     context 'when note is not confidential' do | ||||
|       context 'when include_noteable is set to true' do | ||||
|  |  | |||
|  | @ -6069,6 +6069,20 @@ RSpec.describe User, feature_category: :user_profile do | |||
|     end | ||||
|   end | ||||
| 
 | ||||
|   describe '#can_access_admin_area?' do | ||||
|     it 'returns false for regular user' do | ||||
|       user = build_stubbed(:user) | ||||
| 
 | ||||
|       expect(user.can_access_admin_area?).to be_falsy | ||||
|     end | ||||
| 
 | ||||
|     it 'returns true for admin user' do | ||||
|       user = build_stubbed(:user, :admin) | ||||
| 
 | ||||
|       expect(user.can_access_admin_area?).to be_truthy | ||||
|     end | ||||
|   end | ||||
| 
 | ||||
|   shared_examples 'organization owner' do | ||||
|     let!(:org_user) { create(:organization_user, organization: organization, user: user, access_level: access_level) } | ||||
| 
 | ||||
|  |  | |||
|  | @ -85,6 +85,16 @@ RSpec.describe BasePolicy do | |||
|           .to change { policy.allowed?(ability) }.from(false).to(true) | ||||
|       end | ||||
|     end | ||||
| 
 | ||||
|     context 'with a limited admin user', :enable_admin_mode do | ||||
|       let(:current_user) { build_stubbed(:user) } | ||||
| 
 | ||||
|       before do | ||||
|         allow(current_user).to receive(:can_access_admin_area?).and_return(true) | ||||
|       end | ||||
| 
 | ||||
|       it { is_expected.not_to be_allowed(ability) } | ||||
|     end | ||||
|   end | ||||
| 
 | ||||
|   describe 'read_dedicated_hosted_runner_usage' do | ||||
|  |  | |||
|  | @ -68,7 +68,7 @@ RSpec.describe 'Update of an existing issue', feature_category: :team_planning d | |||
| 
 | ||||
|     context 'setting labels' do | ||||
|       before do | ||||
|         allow(Gitlab::QueryLimiting::Transaction).to receive(:threshold).and_return(102) | ||||
|         allow(Gitlab::QueryLimiting::Transaction).to receive(:threshold).and_return(103) | ||||
|       end | ||||
| 
 | ||||
|       let(:mutation) do | ||||
|  |  | |||
|  | @ -70,7 +70,7 @@ RSpec.describe 'Setting assignees of a merge request', :assume_throttled, featur | |||
| 
 | ||||
|   context 'when the current user does not have permission to add assignees' do | ||||
|     let(:current_user) { create(:user) } | ||||
|     let(:db_query_limit) { 31 } | ||||
|     let(:db_query_limit) { 32 } | ||||
| 
 | ||||
|     it 'does not change the assignees' do | ||||
|       project.add_guest(current_user) | ||||
|  |  | |||
|  | @ -64,7 +64,7 @@ RSpec.describe API::ProjectImport, :aggregate_failures, feature_category: :impor | |||
|     it 'executes a limited number of queries', :use_clean_rails_redis_caching do | ||||
|       control = ActiveRecord::QueryRecorder.new { perform_archive_upload } | ||||
| 
 | ||||
|       expect(control.count).to be <= 127 | ||||
|       expect(control.count).to be <= 128 | ||||
|     end | ||||
| 
 | ||||
|     it 'schedules an import using a namespace' do | ||||
|  |  | |||
|  | @ -21,7 +21,7 @@ RSpec.describe 'Loading a user avatar', feature_category: :user_profile do | |||
|       get user.avatar_url # Skip queries on first application load | ||||
| 
 | ||||
|       expect(response).to have_gitlab_http_status(:ok) | ||||
|       expect { get user.avatar_url }.not_to exceed_query_limit(4) | ||||
|       expect { get user.avatar_url }.not_to exceed_query_limit(5) | ||||
|     end | ||||
|   end | ||||
| 
 | ||||
|  |  | |||
|  | @ -1,4 +1,5 @@ | |||
| # frozen_string_literal: true | ||||
| 
 | ||||
| # rubocop:disable RSpec/VerifiedDoubles | ||||
| 
 | ||||
| require 'fast_spec_helper' | ||||
|  | @ -21,7 +22,8 @@ RSpec.describe Trigger, feature_category: :tooling do | |||
|       'GITLAB_USER_NAME' => 'gitlab_user_name', | ||||
|       'GITLAB_USER_LOGIN' => 'gitlab_user_login', | ||||
|       'QA_IMAGE' => 'qa_image', | ||||
|       'DOCS_PROJECT_API_TOKEN' => nil | ||||
|       'DOCS_PROJECT_API_TOKEN' => nil, | ||||
|       'CNG_SKIP_REDUNDANT_JOBS' => "false" | ||||
|     } | ||||
|   end | ||||
| 
 | ||||
|  | @ -458,6 +460,25 @@ RSpec.describe Trigger, feature_category: :tooling do | |||
|           end | ||||
|         end | ||||
|       end | ||||
| 
 | ||||
|       describe "#extra_variables" do | ||||
|         before do | ||||
|           stub_env('CI_PROJECT_PATH_SLUG', 'project-path') | ||||
|           stub_env('ARCH_LIST', 'amd64,arm64') | ||||
|         end | ||||
| 
 | ||||
|         it 'includes extra variables' do | ||||
|           expect(subject.variables).to include({ | ||||
|             "FULL_RUBY_VERSION" => RUBY_VERSION, | ||||
|             "SKIP_JOB_REGEX" => "/final-images-listing/", | ||||
|             "DEBIAN_IMAGE" => "debian:bookworm-slim", | ||||
|             "ALPINE_IMAGE" => "alpine:3.20", | ||||
|             "CONTAINER_VERSION_SUFFIX" => "project-path", | ||||
|             "CACHE_BUSTER" => "false", | ||||
|             "ARCH_LIST" => 'amd64,arm64' | ||||
|           }) | ||||
|         end | ||||
|       end | ||||
|     end | ||||
|   end | ||||
| 
 | ||||
|  |  | |||
|  | @ -18,7 +18,9 @@ RSpec.describe Issuable::Callbacks::Description, feature_category: :portfolio_ma | |||
|       project: project, | ||||
|       description: 'old description', | ||||
|       last_edited_at: Date.yesterday, | ||||
|       last_edited_by: random_user | ||||
|       last_edited_by: random_user, | ||||
|       created_at: 3.days.ago, | ||||
|       updated_at: 3.days.ago | ||||
|     ) | ||||
|   end | ||||
| 
 | ||||
|  |  | |||
|  | @ -382,7 +382,7 @@ RSpec.configure do |config| | |||
|     # See also spec/support/helpers/admin_mode_helpers.rb | ||||
|     if example.metadata[:enable_admin_mode] && !example.metadata[:do_not_mock_admin_mode] | ||||
|       allow_any_instance_of(Gitlab::Auth::CurrentUserMode).to receive(:admin_mode?) do |current_user_mode| | ||||
|         current_user_mode.send(:user)&.admin? | ||||
|         current_user_mode.send(:user)&.can_access_admin_area? | ||||
|       end | ||||
|     end | ||||
| 
 | ||||
|  |  | |||
|  | @ -34,7 +34,7 @@ module AdminModeHelper | |||
|       allow(Gitlab::Auth::CurrentUserMode).to receive(:new).and_call_original | ||||
| 
 | ||||
|       allow(Gitlab::Auth::CurrentUserMode).to receive(:new).with(user).and_return(fake_user_mode) | ||||
|       allow(fake_user_mode).to receive(:admin_mode?).and_return(user&.admin?) | ||||
|       allow(fake_user_mode).to receive(:admin_mode?).and_return(user&.can_access_admin_area?) | ||||
|     end | ||||
|   end | ||||
| end | ||||
|  |  | |||
|  | @ -0,0 +1,161 @@ | |||
| # frozen_string_literal: true | ||||
| 
 | ||||
| RSpec.shared_examples 'admin_mode? check if admin_mode can be enabled' do | ||||
|   context 'when admin mode not requested' do | ||||
|     it 'is false by default' do | ||||
|       expect(subject.admin_mode?).to be(false) | ||||
|     end | ||||
| 
 | ||||
|     it 'raises exception if we try to enable it' do | ||||
|       expect do | ||||
|         subject.enable_admin_mode!(password: user.password) | ||||
|       end.to raise_error(::Gitlab::Auth::CurrentUserMode::NotRequestedError) | ||||
| 
 | ||||
|       expect(subject.admin_mode?).to be(false) | ||||
|     end | ||||
|   end | ||||
| 
 | ||||
|   context 'when admin mode requested first' do | ||||
|     before do | ||||
|       subject.request_admin_mode! | ||||
|     end | ||||
| 
 | ||||
|     it 'is false by default' do | ||||
|       expect(subject.admin_mode?).to be(false) | ||||
|     end | ||||
| 
 | ||||
|     it 'cannot be enabled with an invalid password' do | ||||
|       subject.enable_admin_mode!(password: nil) | ||||
| 
 | ||||
|       expect(subject.admin_mode?).to be(false) | ||||
|     end | ||||
| 
 | ||||
|     it 'can be enabled with a valid password' do | ||||
|       subject.enable_admin_mode!(password: user.password) | ||||
| 
 | ||||
|       expect(subject.admin_mode?).to be(true) | ||||
|     end | ||||
| 
 | ||||
|     it 'can be disabled' do | ||||
|       subject.enable_admin_mode!(password: user.password) | ||||
|       subject.disable_admin_mode! | ||||
| 
 | ||||
|       expect(subject.admin_mode?).to be(false) | ||||
|     end | ||||
| 
 | ||||
|     it 'expires in the future' do | ||||
|       subject.enable_admin_mode!(password: user.password) | ||||
|       expect(subject.admin_mode?).to be(true), 'admin mode is not active in the present' | ||||
| 
 | ||||
|       travel_to(Gitlab::Auth::CurrentUserMode::MAX_ADMIN_MODE_TIME.from_now) do | ||||
|         # in the future this will be a new request, simulate by clearing the RequestStore | ||||
|         Gitlab::SafeRequestStore.clear! | ||||
| 
 | ||||
|         expect(subject.admin_mode?).to be(false), 'admin mode did not expire in the future' | ||||
|       end | ||||
|     end | ||||
| 
 | ||||
|     context 'when skipping password validation' do | ||||
|       it 'can be enabled with a valid password' do | ||||
|         subject.enable_admin_mode!(password: user.password, skip_password_validation: true) | ||||
| 
 | ||||
|         expect(subject.admin_mode?).to be(true) | ||||
|       end | ||||
| 
 | ||||
|       it 'can be enabled with an invalid password' do | ||||
|         subject.enable_admin_mode!(skip_password_validation: true) | ||||
| 
 | ||||
|         expect(subject.admin_mode?).to be(true) | ||||
|       end | ||||
|     end | ||||
| 
 | ||||
|     context 'with two independent sessions' do | ||||
|       let(:another_session) { {} } | ||||
|       let(:another_subject) { described_class.new(user) } | ||||
| 
 | ||||
|       before do | ||||
|         allow(ActiveSession).to receive(:list_sessions).with(user).and_return([session, another_session]) | ||||
|       end | ||||
| 
 | ||||
|       it 'cannot be enabled in one and seen in the other' do | ||||
|         Gitlab::Session.with_session(another_session) do | ||||
|           another_subject.request_admin_mode! | ||||
|           another_subject.enable_admin_mode!(password: user.password) | ||||
|         end | ||||
| 
 | ||||
|         expect(subject.admin_mode?).to be(false) | ||||
|       end | ||||
|     end | ||||
|   end | ||||
| 
 | ||||
|   context 'when bypassing session' do | ||||
|     it 'is active by default' do | ||||
|       described_class.bypass_session!(user.id) do | ||||
|         expect(subject.admin_mode?).to be(true) | ||||
|       end | ||||
|     end | ||||
| 
 | ||||
|     it 'enable has no effect' do | ||||
|       described_class.bypass_session!(user.id) do | ||||
|         subject.request_admin_mode! | ||||
|         subject.enable_admin_mode!(password: user.password) | ||||
| 
 | ||||
|         expect(subject.admin_mode?).to be(true) | ||||
|       end | ||||
|     end | ||||
| 
 | ||||
|     it 'disable has no effect' do | ||||
|       described_class.bypass_session!(user.id) do | ||||
|         subject.disable_admin_mode! | ||||
| 
 | ||||
|         expect(subject.admin_mode?).to be(true) | ||||
|       end | ||||
|     end | ||||
|   end | ||||
| end | ||||
| 
 | ||||
| RSpec.shared_examples 'enabling admin_mode when it can be enabled' do | ||||
|   it 'creates a timestamp in the session' do | ||||
|     subject.request_admin_mode! | ||||
| 
 | ||||
|     subject.enable_admin_mode!(password: user.password) | ||||
| 
 | ||||
|     expect(session).to include(expected_session_entry(be_within(1.second).of(Time.now))) | ||||
|   end | ||||
| 
 | ||||
|   it 'returns true after successful enable' do | ||||
|     subject.request_admin_mode! | ||||
| 
 | ||||
|     expect(subject.enable_admin_mode!(password: user.password)).to be(true) | ||||
|   end | ||||
| 
 | ||||
|   it 'returns false after unsuccessful enable' do | ||||
|     subject.request_admin_mode! | ||||
| 
 | ||||
|     expect(subject.enable_admin_mode!(password: 'wrong password')).to be(false) | ||||
|   end | ||||
| 
 | ||||
|   context 'when admin mode is not requested' do | ||||
|     it 'raises error' do | ||||
|       expect do | ||||
|         subject.enable_admin_mode!(password: user.password) | ||||
|       end.to raise_error(Gitlab::Auth::CurrentUserMode::NotRequestedError) | ||||
|     end | ||||
|   end | ||||
| end | ||||
| 
 | ||||
| RSpec.shared_examples 'disabling admin_mode' do | ||||
|   it 'sets the session timestamp to nil' do | ||||
|     subject.request_admin_mode! | ||||
|     subject.disable_admin_mode! | ||||
| 
 | ||||
|     expect(session).to include(expected_session_entry(be_nil)) | ||||
|   end | ||||
| end | ||||
| 
 | ||||
| def expected_session_entry(value_matcher) | ||||
|   { | ||||
|     Gitlab::Auth::CurrentUserMode::SESSION_STORE_KEY => a_hash_including( | ||||
|       Gitlab::Auth::CurrentUserMode::ADMIN_MODE_START_TIME_KEY => value_matcher) | ||||
|   } | ||||
| end | ||||
|  | @ -15372,10 +15372,10 @@ vite-plugin-ruby@^5.1.1: | |||
|     debug "^4.3.4" | ||||
|     fast-glob "^3.3.2" | ||||
| 
 | ||||
| vite@^6.2.1: | ||||
|   version "6.2.1" | ||||
|   resolved "https://registry.yarnpkg.com/vite/-/vite-6.2.1.tgz#ae865d4bb93a11844be1bc647c8b2dd1856ea180" | ||||
|   integrity sha512-n2GnqDb6XPhlt9B8olZPrgMD/es/Nd1RdChF6CBD/fHW6pUyUTt2sQW2fPRX5GiD9XEa6+8A6A4f2vT6pSsE7Q== | ||||
| vite@^6.2.2: | ||||
|   version "6.2.2" | ||||
|   resolved "https://registry.yarnpkg.com/vite/-/vite-6.2.2.tgz#8098b12a6bfd95abe39399aa7d5faa56545d7a1a" | ||||
|   integrity sha512-yW7PeMM+LkDzc7CgJuRLMW2Jz0FxMOsVJ8Lv3gpgW9WLcb9cTW+121UEr1hvmfR7w3SegR5ItvYyzVz1vxNJgQ== | ||||
|   dependencies: | ||||
|     esbuild "^0.25.0" | ||||
|     postcss "^8.5.3" | ||||
|  |  | |||
		Loading…
	
		Reference in New Issue