diff --git a/.gitlab/ci/cng/main.gitlab-ci.yml b/.gitlab/ci/cng/main.gitlab-ci.yml index 253a1d7bf17..b249b85cf95 100644 --- a/.gitlab/ci/cng/main.gitlab-ci.yml +++ b/.gitlab/ci/cng/main.gitlab-ci.yml @@ -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 diff --git a/.gitlab/ci/rules.gitlab-ci.yml b/.gitlab/ci/rules.gitlab-ci.yml index 615f314472e..3e43846dae9 100644 --- a/.gitlab/ci/rules.gitlab-ci.yml +++ b/.gitlab/ci/rules.gitlab-ci.yml @@ -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: diff --git a/.gitlab/ci/test-on-cng/main.gitlab-ci.yml b/.gitlab/ci/test-on-cng/main.gitlab-ci.yml index dfd3952782b..e8d759cb38e 100644 --- a/.gitlab/ci/test-on-cng/main.gitlab-ci.yml +++ b/.gitlab/ci/test-on-cng/main.gitlab-ci.yml @@ -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 diff --git a/app/assets/javascripts/ci/pipeline_details/graph/components/graph_component.vue b/app/assets/javascripts/ci/pipeline_details/graph/components/graph_component.vue index f41cd787a72..b48536c5154 100644 --- a/app/assets/javascripts/ci/pipeline_details/graph/components/graph_component.vue +++ b/app/assets/javascripts/ci/pipeline_details/graph/components/graph_component.vue @@ -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: { diff --git a/app/assets/javascripts/ci/pipeline_details/graph/components/linked_pipelines_column.vue b/app/assets/javascripts/ci/pipeline_details/graph/components/linked_pipelines_column.vue index 9678b0213be..0e87ae19e8f 100644 --- a/app/assets/javascripts/ci/pipeline_details/graph/components/linked_pipelines_column.vue +++ b/app/assets/javascripts/ci/pipeline_details/graph/components/linked_pipelines_column.vue @@ -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: { diff --git a/app/controllers/admin/sessions_controller.rb b/app/controllers/admin/sessions_controller.rb index 719809e4fd9..0ec4bf29afb 100644 --- a/app/controllers/admin/sessions_controller.rb +++ b/app/controllers/admin/sessions_controller.rb @@ -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? diff --git a/app/helpers/sidebars_helper.rb b/app/helpers/sidebars_helper.rb index 78f89d1aea7..ba985a98ba0 100644 --- a/app/helpers/sidebars_helper.rb +++ b/app/helpers/sidebars_helper.rb @@ -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), diff --git a/app/models/concerns/editable.rb b/app/models/concerns/editable.rb index 91479633d0d..51198615238 100644 --- a/app/models/concerns/editable.rb +++ b/app/models/concerns/editable.rb @@ -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 diff --git a/app/models/concerns/notes/active_record.rb b/app/models/concerns/notes/active_record.rb index ae89beb3271..42ebce4d553 100644 --- a/app/models/concerns/notes/active_record.rb +++ b/app/models/concerns/notes/active_record.rb @@ -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 diff --git a/app/models/note.rb b/app/models/note.rb index 689cb6a1651..2d029801b59 100644 --- a/app/models/note.rb +++ b/app/models/note.rb @@ -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 diff --git a/app/models/user.rb b/app/models/user.rb index 9d1b444aab0..da2526feb84 100644 --- a/app/models/user.rb +++ b/app/models/user.rb @@ -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 diff --git a/app/policies/base_policy.rb b/app/policies/base_policy.rb index 21ada3bb99f..30dfbf3edf2 100644 --- a/app/policies/base_policy.rb +++ b/app/policies/base_policy.rb @@ -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 diff --git a/app/policies/concerns/policy_actor.rb b/app/policies/concerns/policy_actor.rb index 8fa09683b06..59db709954c 100644 --- a/app/policies/concerns/policy_actor.rb +++ b/app/policies/concerns/policy_actor.rb @@ -21,6 +21,10 @@ module PolicyActor false end + def can_access_admin_area? + false + end + def external? false end diff --git a/config/dependency_cruiser.js b/config/dependency_cruiser.js index e511fc150cb..31efacd88c9 100644 --- a/config/dependency_cruiser.js +++ b/config/dependency_cruiser.js @@ -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 diff --git a/config/feature_flags/ops/global_search_issues_tab.yml b/config/feature_flags/ops/global_search_issues_tab.yml deleted file mode 100644 index 101b2588386..00000000000 --- a/config/feature_flags/ops/global_search_issues_tab.yml +++ /dev/null @@ -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 diff --git a/config/feature_flags/ops/global_search_merge_requests_tab.yml b/config/feature_flags/ops/global_search_merge_requests_tab.yml deleted file mode 100644 index 7f4570e5134..00000000000 --- a/config/feature_flags/ops/global_search_merge_requests_tab.yml +++ /dev/null @@ -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 diff --git a/config/feature_flags/ops/global_search_snippet_titles_tab.yml b/config/feature_flags/ops/global_search_snippet_titles_tab.yml deleted file mode 100644 index e0b7422e0f3..00000000000 --- a/config/feature_flags/ops/global_search_snippet_titles_tab.yml +++ /dev/null @@ -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 diff --git a/config/feature_flags/ops/global_search_users_tab.yml b/config/feature_flags/ops/global_search_users_tab.yml deleted file mode 100644 index 57a07aa9e39..00000000000 --- a/config/feature_flags/ops/global_search_users_tab.yml +++ /dev/null @@ -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 diff --git a/db/migrate/20250109055316_migrate_global_search_settings_in_application_settings.rb b/db/migrate/20250109055316_migrate_global_search_settings_in_application_settings.rb index aff522460c2..0b079c18034 100644 --- a/db/migrate/20250109055316_migrate_global_search_settings_in_application_settings.rb +++ b/db/migrate/20250109055316_migrate_global_search_settings_in_application_settings.rb @@ -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 diff --git a/doc/administration/broadcast_messages.md b/doc/administration/broadcast_messages.md index 210da58e5e7..d8b5d0bb83b 100644 --- a/doc/administration/broadcast_messages.md +++ b/doc/administration/broadcast_messages.md @@ -8,7 +8,7 @@ title: Broadcast messages {{< details >}} - Tier: Free, Premium, Ultimate -- Offering: GitLab Self-Managed +- Offering: GitLab Self-Managed, GitLab Dedicated {{< /details >}} diff --git a/doc/user/gitlab_com/_index.md b/doc/user/gitlab_com/_index.md index 83c87ea30a3..4d452d5366a 100644 --- a/doc/user/gitlab_com/_index.md +++ b/doc/user/gitlab_com/_index.md @@ -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
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`
Premium tier: `100`
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
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`
Premium tier: `100`
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`
`100-399` seats: `2,800`
`400` seats or more: `4,000` | -| Ultimate and open source |`999` seats or fewer: `6,000`
`1,000-4,999` seats: `9,000`
`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`
`100-399` seats: `2,800`
`400` seats or more: `4,000` | +| Ultimate and open source |`999` seats or fewer: `6,000`
`1,000-4,999` seats: `9,000`
`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).. diff --git a/doc/user/project/file_lock.md b/doc/user/project/file_lock.md index 8a822fe0e02..50e4abdc755 100644 --- a/doc/user/project/file_lock.md +++ b/doc/user/project/file_lock.md @@ -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 diff --git a/lib/gitlab/auth/current_user_mode.rb b/lib/gitlab/auth/current_user_mode.rb index e102a444a43..695a2561f16 100644 --- a/lib/gitlab/auth/current_user_mode.rb +++ b/lib/gitlab/auth/current_user_mode.rb @@ -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 diff --git a/package.json b/package.json index 30bab874b60..3c94c364f2f 100644 --- a/package.json +++ b/package.json @@ -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", diff --git a/qa/gems/gitlab-orchestrator/lib/gitlab/orchestrator/lib/deployment/default_values.rb b/qa/gems/gitlab-orchestrator/lib/gitlab/orchestrator/lib/deployment/default_values.rb index 7b9bdd5bc77..22557d764f3 100644 --- a/qa/gems/gitlab-orchestrator/lib/gitlab/orchestrator/lib/deployment/default_values.rb +++ b/qa/gems/gitlab-orchestrator/lib/gitlab/orchestrator/lib/deployment/default_values.rb @@ -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 diff --git a/qa/gems/gitlab-orchestrator/lib/gitlab/orchestrator/lib/helpers/ci.rb b/qa/gems/gitlab-orchestrator/lib/gitlab/orchestrator/lib/helpers/ci.rb index 3ab9dd776c4..a5a271af287 100644 --- a/qa/gems/gitlab-orchestrator/lib/gitlab/orchestrator/lib/helpers/ci.rb +++ b/qa/gems/gitlab-orchestrator/lib/gitlab/orchestrator/lib/helpers/ci.rb @@ -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 diff --git a/qa/gems/gitlab-orchestrator/spec/unit/gitlab/orchestrator/deployment/default_values_spec.rb b/qa/gems/gitlab-orchestrator/spec/unit/gitlab/orchestrator/deployment/default_values_spec.rb index 19739e29982..0845e3f240b 100644 --- a/qa/gems/gitlab-orchestrator/spec/unit/gitlab/orchestrator/deployment/default_values_spec.rb +++ b/qa/gems/gitlab-orchestrator/spec/unit/gitlab/orchestrator/deployment/default_values_spec.rb @@ -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 diff --git a/scripts/trigger-build.rb b/scripts/trigger-build.rb index 7d7745e3b12..5a63d3d9c94 100755 --- a/scripts/trigger-build.rb +++ b/scripts/trigger-build.rb @@ -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 = " \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 + + 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 diff --git a/spec/frontend/ci/pipeline_details/graph/components/graph_component_spec.js b/spec/frontend/ci/pipeline_details/graph/components/graph_component_spec.js index ab4a948b1e7..c703a29703a 100644 --- a/spec/frontend/ci/pipeline_details/graph/components/graph_component_spec.js +++ b/spec/frontend/ci/pipeline_details/graph/components/graph_component_spec.js @@ -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); }); }); diff --git a/spec/frontend/ci/pipeline_details/graph/mock_data.js b/spec/frontend/ci/pipeline_details/graph/mock_data.js index b75146fdd41..c60b2289752 100644 --- a/spec/frontend/ci/pipeline_details/graph/mock_data.js +++ b/spec/frontend/ci/pipeline_details/graph/mock_data.js @@ -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 { diff --git a/spec/lib/gitlab/auth/current_user_mode_spec.rb b/spec/lib/gitlab/auth/current_user_mode_spec.rb index 141220cac8f..fbf2b11329c 100644 --- a/spec/lib/gitlab/auth/current_user_mode_spec.rb +++ b/spec/lib/gitlab/auth/current_user_mode_spec.rb @@ -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 diff --git a/spec/migrations/20250109055316_migrate_global_search_settings_in_application_settings_spec.rb b/spec/migrations/20250109055316_migrate_global_search_settings_in_application_settings_spec.rb deleted file mode 100644 index 1c8e51f9dfa..00000000000 --- a/spec/migrations/20250109055316_migrate_global_search_settings_in_application_settings_spec.rb +++ /dev/null @@ -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 diff --git a/spec/models/deploy_key_spec.rb b/spec/models/deploy_key_spec.rb index e0b797b3c89..085c52decc3 100644 --- a/spec/models/deploy_key_spec.rb +++ b/spec/models/deploy_key_spec.rb @@ -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 diff --git a/spec/models/note_spec.rb b/spec/models/note_spec.rb index b3541801098..edb853f5b35 100644 --- a/spec/models/note_spec.rb +++ b/spec/models/note_spec.rb @@ -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 diff --git a/spec/models/user_spec.rb b/spec/models/user_spec.rb index 22119896769..166e149fa18 100644 --- a/spec/models/user_spec.rb +++ b/spec/models/user_spec.rb @@ -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) } diff --git a/spec/policies/base_policy_spec.rb b/spec/policies/base_policy_spec.rb index d5e95625c9f..018ec0a80e3 100644 --- a/spec/policies/base_policy_spec.rb +++ b/spec/policies/base_policy_spec.rb @@ -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 diff --git a/spec/requests/api/graphql/mutations/issues/update_spec.rb b/spec/requests/api/graphql/mutations/issues/update_spec.rb index 3cab64d191d..ac30a4f853b 100644 --- a/spec/requests/api/graphql/mutations/issues/update_spec.rb +++ b/spec/requests/api/graphql/mutations/issues/update_spec.rb @@ -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 diff --git a/spec/requests/api/graphql/mutations/merge_requests/set_assignees_spec.rb b/spec/requests/api/graphql/mutations/merge_requests/set_assignees_spec.rb index cfaeb423e1b..cafc002bf44 100644 --- a/spec/requests/api/graphql/mutations/merge_requests/set_assignees_spec.rb +++ b/spec/requests/api/graphql/mutations/merge_requests/set_assignees_spec.rb @@ -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) diff --git a/spec/requests/api/project_import_spec.rb b/spec/requests/api/project_import_spec.rb index dfd86f95dac..06af787bb0d 100644 --- a/spec/requests/api/project_import_spec.rb +++ b/spec/requests/api/project_import_spec.rb @@ -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 diff --git a/spec/requests/user_avatar_spec.rb b/spec/requests/user_avatar_spec.rb index fd91ce274b3..8e462dd5cea 100644 --- a/spec/requests/user_avatar_spec.rb +++ b/spec/requests/user_avatar_spec.rb @@ -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 diff --git a/spec/scripts/trigger-build_spec.rb b/spec/scripts/trigger-build_spec.rb index 76276fd41dd..db433897be8 100644 --- a/spec/scripts/trigger-build_spec.rb +++ b/spec/scripts/trigger-build_spec.rb @@ -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 diff --git a/spec/services/issuable/callbacks/description_spec.rb b/spec/services/issuable/callbacks/description_spec.rb index 98a7b5dadca..eaedcd76ed5 100644 --- a/spec/services/issuable/callbacks/description_spec.rb +++ b/spec/services/issuable/callbacks/description_spec.rb @@ -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 diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb index f39be2f1a23..9929f4f4b40 100644 --- a/spec/spec_helper.rb +++ b/spec/spec_helper.rb @@ -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 diff --git a/spec/support/helpers/admin_mode_helpers.rb b/spec/support/helpers/admin_mode_helpers.rb index 630c126adf4..1bc69ab4050 100644 --- a/spec/support/helpers/admin_mode_helpers.rb +++ b/spec/support/helpers/admin_mode_helpers.rb @@ -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 diff --git a/spec/support/shared_examples/lib/gitlab/auth/current_user_mode_shared_examples.rb b/spec/support/shared_examples/lib/gitlab/auth/current_user_mode_shared_examples.rb new file mode 100644 index 00000000000..4244d30fdfe --- /dev/null +++ b/spec/support/shared_examples/lib/gitlab/auth/current_user_mode_shared_examples.rb @@ -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 diff --git a/yarn.lock b/yarn.lock index 8f5425d25d3..0339baa23cb 100644 --- a/yarn.lock +++ b/yarn.lock @@ -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"