Add latest changes from gitlab-org/gitlab@master

This commit is contained in:
GitLab Bot 2024-12-19 18:38:07 +00:00
parent a21c232a6c
commit 4b788dd673
64 changed files with 847 additions and 477 deletions

View File

@ -635,7 +635,7 @@ gem 'spamcheck', '~> 1.3.0' # rubocop:todo Gemfile/MissingFeatureCategory
gem 'gitaly', '~> 17.5.0.pre.rc1', feature_category: :gitaly
# KAS GRPC protocol definitions
gem 'gitlab-kas-grpc', '~> 17.6.1', feature_category: :deployment_management
gem 'gitlab-kas-grpc', '~> 17.7.0', feature_category: :deployment_management
# Lock the version before issues below are resolved:
# https://gitlab.com/gitlab-org/gitlab/-/issues/473169#note_2028352939

View File

@ -231,7 +231,7 @@
{"name":"gitlab-glfm-markdown","version":"0.0.23","platform":"ruby","checksum":"89a12909c39aea326adb0b7194f7b89d61b4f9122308435fba0bcb84e4f4ff24"},
{"name":"gitlab-glfm-markdown","version":"0.0.23","platform":"x86_64-darwin","checksum":"4b77a37358d98c3b2269f7dd19f6549555c5de00bf12a4eca25c34076f72f78d"},
{"name":"gitlab-glfm-markdown","version":"0.0.23","platform":"x86_64-linux","checksum":"2b71ec5ae06a524114e2cf423ce6635fd1f5c6776c0c956188aa0b2f0fbfbead"},
{"name":"gitlab-kas-grpc","version":"17.6.2","platform":"ruby","checksum":"cf057d6c9ac2cfdbd59de9af95adeeb74be3996d4babfb5fb67397dd8a976887"},
{"name":"gitlab-kas-grpc","version":"17.7.0","platform":"ruby","checksum":"3960e514672c22e7efad533140255acd59b0a32e22cc270af124361173400600"},
{"name":"gitlab-labkit","version":"0.37.0","platform":"ruby","checksum":"d2dd0a60db2149a9a8eebf2975dc23f54ac3ceb01bdba732eb1b26b86dfffa70"},
{"name":"gitlab-license","version":"2.6.0","platform":"ruby","checksum":"2c1f8ae73835640ec77bf758c1d0c9730635043c01cf77902f7976e826d7d016"},
{"name":"gitlab-mail_room","version":"0.0.25","platform":"ruby","checksum":"223ce7c3c0797b6015eaa37147884e6ddc7be9a7ee90a424358c96bc18613b1a"},

View File

@ -753,7 +753,7 @@ GEM
nokogiri (~> 1, >= 1.10.8)
gitlab-glfm-markdown (0.0.23)
rb_sys (= 0.9.94)
gitlab-kas-grpc (17.6.2)
gitlab-kas-grpc (17.7.0)
grpc (~> 1.0)
gitlab-labkit (0.37.0)
actionpack (>= 5.0.0, < 8.1.0)
@ -2083,7 +2083,7 @@ DEPENDENCIES
gitlab-glfm-markdown (~> 0.0.21)
gitlab-housekeeper!
gitlab-http!
gitlab-kas-grpc (~> 17.6.1)
gitlab-kas-grpc (~> 17.7.0)
gitlab-labkit (~> 0.37.0)
gitlab-license (~> 2.6)
gitlab-mail_room (~> 0.0.24)

View File

@ -232,7 +232,7 @@
{"name":"gitlab-glfm-markdown","version":"0.0.23","platform":"ruby","checksum":"89a12909c39aea326adb0b7194f7b89d61b4f9122308435fba0bcb84e4f4ff24"},
{"name":"gitlab-glfm-markdown","version":"0.0.23","platform":"x86_64-darwin","checksum":"4b77a37358d98c3b2269f7dd19f6549555c5de00bf12a4eca25c34076f72f78d"},
{"name":"gitlab-glfm-markdown","version":"0.0.23","platform":"x86_64-linux","checksum":"2b71ec5ae06a524114e2cf423ce6635fd1f5c6776c0c956188aa0b2f0fbfbead"},
{"name":"gitlab-kas-grpc","version":"17.6.2","platform":"ruby","checksum":"cf057d6c9ac2cfdbd59de9af95adeeb74be3996d4babfb5fb67397dd8a976887"},
{"name":"gitlab-kas-grpc","version":"17.7.0","platform":"ruby","checksum":"3960e514672c22e7efad533140255acd59b0a32e22cc270af124361173400600"},
{"name":"gitlab-labkit","version":"0.37.0","platform":"ruby","checksum":"d2dd0a60db2149a9a8eebf2975dc23f54ac3ceb01bdba732eb1b26b86dfffa70"},
{"name":"gitlab-license","version":"2.6.0","platform":"ruby","checksum":"2c1f8ae73835640ec77bf758c1d0c9730635043c01cf77902f7976e826d7d016"},
{"name":"gitlab-mail_room","version":"0.0.25","platform":"ruby","checksum":"223ce7c3c0797b6015eaa37147884e6ddc7be9a7ee90a424358c96bc18613b1a"},

View File

@ -763,7 +763,7 @@ GEM
nokogiri (~> 1, >= 1.10.8)
gitlab-glfm-markdown (0.0.23)
rb_sys (= 0.9.94)
gitlab-kas-grpc (17.6.2)
gitlab-kas-grpc (17.7.0)
grpc (~> 1.0)
gitlab-labkit (0.37.0)
actionpack (>= 5.0.0, < 8.1.0)
@ -2111,7 +2111,7 @@ DEPENDENCIES
gitlab-glfm-markdown (~> 0.0.21)
gitlab-housekeeper!
gitlab-http!
gitlab-kas-grpc (~> 17.6.1)
gitlab-kas-grpc (~> 17.7.0)
gitlab-labkit (~> 0.37.0)
gitlab-license (~> 2.6)
gitlab-mail_room (~> 0.0.24)

View File

@ -4,6 +4,9 @@ import { GlBreakpointInstance as bp } from '@gitlab/ui/dist/utils';
import ShowMore from '~/vue_shared/components/show_more.vue';
import AssigneeAvatarLink from '~/sidebar/components/assignees/assignee_avatar_link.vue';
import { __, s__ } from '~/locale';
import { getIdFromGraphQLId } from '~/graphql_shared/utils';
import { InternalEvents } from '~/tracking';
import { CLICK_PIPELINE_LINK_ON_DEPLOYMENT_PAGE } from '~/deployments/utils';
import AsideItem from './aside_item.vue';
export default {
@ -17,6 +20,7 @@ export default {
directives: {
GlTooltip: GlTooltipDirective,
},
mixins: [InternalEvents.mixin()],
props: {
deployment: {
required: true,
@ -72,6 +76,9 @@ export default {
hasUrl() {
return Boolean(this.environment.externalUrl);
},
pipelineId() {
return getIdFromGraphQLId(this.deployment.job.pipeline.id);
},
},
mounted() {
window.addEventListener('resize', this.handleWindowResize);
@ -87,11 +94,15 @@ export default {
toggleSidebar() {
this.isExpanded = !this.isExpanded;
},
trackPipelineLinkClick() {
this.trackEvent(CLICK_PIPELINE_LINK_ON_DEPLOYMENT_PAGE);
},
},
i18n: {
openUrl: s__('Deployment|Open URL'),
triggerer: s__('Deployment|Triggerer'),
relatedTags: s__('Deployment|Related Tags'),
pipeline: s__('Deployment|Pipeline'),
job: s__('Deployment|Job'),
branch: s__('Deployment|Branch'),
tag: s__('Deployment|Tag'),
@ -172,6 +183,17 @@ export default {
</show-more>
</aside-item>
<aside-item v-if="hasJob" class="gl-mb-3" data-testid="deployment-pipeline">
<template #header>{{ $options.i18n.pipeline }}</template>
<gl-link
:href="deployment.job.pipeline.path"
data-testid="deployment-pipeline-link"
@click="trackPipelineLinkClick"
>
#{{ pipelineId }}
</gl-link>
</aside-item>
<aside-item v-if="hasJob" class="gl-mb-3">
<template #header>{{ $options.i18n.job }}</template>
<gl-link :href="deployment.job.webPath">

View File

@ -21,6 +21,10 @@ query fetchDeployment($fullPath: ID!, $iid: ID!) {
...DeploymentJob
canPlayJob
manualJob
pipeline {
id
path
}
}
commit {
id

View File

@ -3,3 +3,5 @@ export const FINISHED_STATUSES = ['SUCCESS', 'FAILED', 'CANCELED'];
export const UPCOMING_STATUSES = ['RUNNING', 'BLOCKED'];
export const isFinished = ({ status }) => FINISHED_STATUSES.includes(status);
export const CLICK_PIPELINE_LINK_ON_DEPLOYMENT_PAGE = 'clicked_deployment_details_pipeline_link';

View File

@ -1,5 +1,5 @@
<script>
import { GlModal, GlFormGroup, GlFormSelect, GlAlert, GlButton } from '@gitlab/ui';
import { GlModal, GlFormGroup, GlFormSelect, GlAlert } from '@gitlab/ui';
import { differenceBy } from 'lodash';
import * as Sentry from '~/sentry/sentry_browser_wrapper';
import glFeatureFlagMixin from '~/vue_shared/mixins/gl_feature_flags_mixin';
@ -26,16 +26,14 @@ import convertWorkItemMutation from '../graphql/work_item_convert.mutation.graph
import getWorkItemDesignListQuery from './design_management/graphql/design_collection.query.graphql';
export default {
i18n: {
type: __('Type'),
subText: s__('WorkItem|Select which type you would like to change this item to.'),
},
components: {
GlModal,
GlFormGroup,
GlFormSelect,
GlAlert,
GlButton,
},
actionCancel: {
text: __('Cancel'),
},
mixins: [glFeatureFlagMixin()],
inject: ['hasOkrsFeature'],
@ -216,6 +214,15 @@ export default {
selectedWorkItemTypeValue() {
return this.selectedWorkItemType?.value || null;
},
actionPrimary() {
return {
text: s__('WorkItem|Change type'),
attributes: {
variant: 'confirm',
disabled: this.changeTypeDisabled,
},
};
},
},
methods: {
async changeType() {
@ -329,6 +336,10 @@ export default {
modal-id="work-item-change-type"
:title="s__('WorkItem|Change type')"
size="sm"
:action-primary="actionPrimary"
:action-cancel="$options.actionCancel"
@primary="changeType"
@canceled="hide"
>
<gl-alert
v-if="errorMessage"
@ -339,46 +350,31 @@ export default {
>
{{ errorMessage }}
</gl-alert>
<div>
<div class="gl-mb-4">{{ $options.i18n.subText }}</div>
<gl-form-group :label="$options.i18n.type" label-for="work-item-type-select">
<gl-form-select
id="work-item-type-select"
:value="selectedWorkItemTypeValue"
width="md"
:options="allowedConversionWorkItemTypes"
@change="validateWorkItemType"
/>
</gl-form-group>
<gl-alert
v-if="warningMessage"
data-testid="change-type-warning-message"
variant="warning"
:dismissible="false"
>
{{ warningMessage }}
<ul v-if="hasWidgetDifference" class="gl-mb-0">
<li v-for="widget in widgetsWithExistingData" :key="widget.type">
{{ widget.name }}
</li>
</ul>
</gl-alert>
<div class="gl-mb-4">
{{ s__('WorkItem|Select which type you would like to change this item to.') }}
</div>
<template #modal-footer>
<div class="gl-m-0 gl-flex gl-flex-row gl-flex-wrap gl-justify-end">
<gl-button @click="hide">
{{ __('Cancel') }}
</gl-button>
<div class="gl-mr-3"></div>
<gl-button
:disabled="changeTypeDisabled"
category="primary"
variant="confirm"
data-testid="change-type-confirmation-button"
@click="changeType"
>{{ s__('WorkItem|Change type') }}</gl-button
>
</div>
</template>
<gl-form-group :label="__('Type')" label-for="work-item-type-select">
<gl-form-select
id="work-item-type-select"
data-testid="work-item-change-type-select"
:value="selectedWorkItemTypeValue"
width="md"
:options="allowedConversionWorkItemTypes"
@change="validateWorkItemType"
/>
</gl-form-group>
<gl-alert
v-if="warningMessage"
data-testid="change-type-warning-message"
variant="warning"
:dismissible="false"
>
{{ warningMessage }}
<ul v-if="hasWidgetDifference" class="gl-mb-0">
<li v-for="widget in widgetsWithExistingData" :key="widget.type">
{{ widget.name }}
</li>
</ul>
</gl-alert>
</gl-modal>
</template>

View File

@ -7,6 +7,8 @@ module Types
include Gitlab::Graphql::Authorize::AuthorizeResource
PROTECTION_RULE_EXISTS_BATCH_SIZE = 20
description 'A container repository'
authorize :read_container_image
@ -71,10 +73,12 @@ module Types
def protection_rule_exists
return false if Feature.disabled?(:container_registry_protected_containers, object.project.root_ancestor)
BatchLoader::GraphQL.for(object.path).batch do |repository_paths, loader|
::ContainerRegistry::Protection::Rule
.for_push_exists_for_multiple_containers(repository_paths: repository_paths, project_id: object.project_id)
.each { |row| loader.call(row['repository_path'], row['protected']) }
BatchLoader::GraphQL.for([object.project_id, object.path]).batch do |tuples, loader|
tuples.each_slice(PROTECTION_RULE_EXISTS_BATCH_SIZE) do |projects_and_repository_paths|
::ContainerRegistry::Protection::Rule
.for_push_exists_for_projects_and_repository_paths(projects_and_repository_paths)
.each { |row| loader.call([row['project_id'], row['repository_path']], row['protected']) }
end
end
end
end

View File

@ -616,7 +616,7 @@ module ApplicationSettingsHelper
def signup_form_data
{
host: new_user_session_url(host: Gitlab.config.gitlab.host),
host: new_user_registration_url(host: Gitlab.config.gitlab.host),
settings_path: general_admin_application_settings_path(anchor: 'js-signup-settings'),
signup_enabled: @application_setting[:signup_enabled].to_s,
require_admin_approval_after_user_signup: @application_setting[:require_admin_approval_after_user_signup].to_s,

View File

@ -42,27 +42,56 @@ module ContainerRegistry
.exists?
end
def self.for_push_exists_for_multiple_containers(repository_paths:, project_id:)
return none if repository_paths.blank? || project_id.blank?
##
# Accepts a list of projects and repository paths and returns a result set
# indicating whether the repository path is protected.
#
# @param [Array<Array>] projects_repository_paths an array of arrays where each sub-array contains a project id
# and a repository path.
# @return [ActiveRecord::Result] a result set indicating whether each project and repository path is protected.
#
# Example:
# ContainerRegistry::Protection::Rule.for_push_exists_for_projects_and_repository_paths([
# [1, '/my_group/my_project_1/image_1'],
# [1, '/my_group/my_project_1/image_2'],
# [2, '/my_group/my_project_2/image_1'],
# ...
# ])
#
# [
# {'project_id' => 1, 'repository_path_pattern' => '/my_group/my_project_1/image_1', 'protected' => true},
# {'project_id' => 1, 'repository_path_pattern' => '/my_group/my_project_1/image_2', 'protected' => false},
# {'project_id' => 2, 'repository_path_pattern' => '/my_group/my_project_2/image_1', 'protected' => true},
# ...
# ]
#
def self.for_push_exists_for_projects_and_repository_paths(projects_repository_paths)
return none if projects_repository_paths.blank?
project_ids, repository_paths = projects_repository_paths.transpose
cte_query =
select('*').from(
sanitize_sql_array([
"unnest(ARRAY[:repository_paths]) AS x(repository_path)", { repository_paths: repository_paths }
'unnest(ARRAY[:project_ids]::bigint[], ARRAY[:repository_paths]::text[]) ' \
'AS projects_repository_paths(project_id, repository_path)',
{ project_ids: project_ids, repository_paths: repository_paths }
])
)
cte_name = :container_names_and_types_cte
cte_name = :projects_repository_paths_cte
cte = Gitlab::SQL::CTE.new(cte_name, cte_query)
rules_cte_project_id = "#{cte_name}.#{connection.quote_column_name('project_id')}"
rules_cte_repository_path = "#{cte_name}.#{connection.quote_column_name('repository_path')}"
protection_rule_exsits_subquery =
select(1)
.where(project_id: project_id)
.where("#{rules_cte_project_id} = project_id")
.where("#{rules_cte_repository_path} ILIKE #{::Gitlab::SQL::Glob.to_like('repository_path_pattern')}")
query = select(
rules_cte_project_id,
rules_cte_repository_path,
sanitize_sql_array(['EXISTS(?) AS protected', protection_rule_exsits_subquery])
).from(Arel.sql(cte_name.to_s))

View File

@ -1,10 +1,10 @@
---
api_type:
attr: elasticsearch_pause_indexing
attr: elasticsearch
clusterwide: false
column: elasticsearch_pause_indexing
db_type: boolean
default: 'false'
column: elasticsearch
db_type: jsonb
default: "'{}'::jsonb"
description:
encrypted: false
gitlab_com_different_than_default: false

View File

@ -1,12 +0,0 @@
---
api_type:
attr: elasticsearch_analyzers_kuromoji_enabled
clusterwide: true
column: elasticsearch_analyzers_kuromoji_enabled
db_type: boolean
default: 'false'
description:
encrypted: false
gitlab_com_different_than_default: false
jihu: false
not_null: true

View File

@ -1,12 +0,0 @@
---
api_type:
attr: elasticsearch_analyzers_kuromoji_search
clusterwide: true
column: elasticsearch_analyzers_kuromoji_search
db_type: boolean
default: 'false'
description:
encrypted: false
gitlab_com_different_than_default: false
jihu: false
not_null: true

View File

@ -1,12 +0,0 @@
---
api_type:
attr: elasticsearch_analyzers_smartcn_enabled
clusterwide: true
column: elasticsearch_analyzers_smartcn_enabled
db_type: boolean
default: 'false'
description:
encrypted: false
gitlab_com_different_than_default: false
jihu: false
not_null: true

View File

@ -1,12 +0,0 @@
---
api_type:
attr: elasticsearch_analyzers_smartcn_search
clusterwide: true
column: elasticsearch_analyzers_smartcn_search
db_type: boolean
default: 'false'
description:
encrypted: false
gitlab_com_different_than_default: false
jihu: false
not_null: true

View File

@ -1,12 +0,0 @@
---
api_type: boolean
attr: elasticsearch_aws
clusterwide: false
column: elasticsearch_aws
db_type: boolean
default: 'false'
description: Enable the use of AWS hosted Elasticsearch. Premium and Ultimate only.
encrypted: false
gitlab_com_different_than_default: false
jihu: false
not_null: true

View File

@ -1,12 +0,0 @@
---
api_type: string
attr: elasticsearch_aws_access_key
clusterwide: false
column: elasticsearch_aws_access_key
db_type: character
default:
description: AWS IAM access key. Premium and Ultimate only.
encrypted: false
gitlab_com_different_than_default: true
jihu: false
not_null: false

View File

@ -1,13 +0,0 @@
---
api_type: string
attr: elasticsearch_aws_region
clusterwide: false
column: elasticsearch_aws_region
db_type: character
default: "'us-east-1'::character"
description: The AWS region the Elasticsearch domain is configured. Premium and Ultimate
only.
encrypted: false
gitlab_com_different_than_default: false
jihu: false
not_null: false

View File

@ -1,12 +0,0 @@
---
api_type:
attr: elasticsearch_client_request_timeout
clusterwide: false
column: elasticsearch_client_request_timeout
db_type: integer
default: '0'
description:
encrypted: false
gitlab_com_different_than_default: true
jihu: false
not_null: true

View File

@ -1,14 +0,0 @@
---
api_type: integer
attr: elasticsearch_indexed_field_length_limit
clusterwide: false
column: elasticsearch_indexed_field_length_limit
db_type: integer
default: '0'
description: Maximum size of text fields to index by Elasticsearch. 0 value means
no limit. This does not apply to repository and wiki indexing. Premium and Ultimate
only.
encrypted: false
gitlab_com_different_than_default: true
jihu: false
not_null: true

View File

@ -1,13 +0,0 @@
---
api_type: integer
attr: elasticsearch_indexed_file_size_limit_kb
clusterwide: false
column: elasticsearch_indexed_file_size_limit_kb
db_type: integer
default: '1024'
description: Maximum size of repository and wiki files that are indexed by Elasticsearch.
Premium and Ultimate only.
encrypted: false
gitlab_com_different_than_default: false
jihu: false
not_null: true

View File

@ -1,12 +0,0 @@
---
api_type: boolean
attr: elasticsearch_indexing
clusterwide: false
column: elasticsearch_indexing
db_type: boolean
default: 'false'
description: Enable Elasticsearch indexing. Premium and Ultimate only.
encrypted: false
gitlab_com_different_than_default: true
jihu: false
not_null: true

View File

@ -1,13 +0,0 @@
---
api_type: boolean
attr: elasticsearch_limit_indexing
clusterwide: false
column: elasticsearch_limit_indexing
db_type: boolean
default: 'false'
description: Limit Elasticsearch to index certain namespaces and projects. Premium
and Ultimate only.
encrypted: false
gitlab_com_different_than_default: true
jihu: false
not_null: true

View File

@ -1,13 +0,0 @@
---
api_type: integer
attr: elasticsearch_max_bulk_concurrency
clusterwide: false
column: elasticsearch_max_bulk_concurrency
db_type: smallint
default: '10'
description: Maximum concurrency of Elasticsearch bulk requests per indexing operation.
This only applies to repository indexing operations. Premium and Ultimate only.
encrypted: false
gitlab_com_different_than_default: false
jihu: false
not_null: true

View File

@ -1,13 +0,0 @@
---
api_type: integer
attr: elasticsearch_max_bulk_size_mb
clusterwide: false
column: elasticsearch_max_bulk_size_mb
db_type: smallint
default: '10'
description: Maximum size of Elasticsearch bulk indexing requests in MB. This only
applies to repository indexing operations. Premium and Ultimate only.
encrypted: false
gitlab_com_different_than_default: false
jihu: false
not_null: true

View File

@ -1,13 +0,0 @@
---
api_type: integer
attr: elasticsearch_max_code_indexing_concurrency
clusterwide: false
column: elasticsearch_max_code_indexing_concurrency
db_type: integer
default: '30'
description: Maximum concurrency of Elasticsearch code indexing background jobs. This
only applies to repository indexing operations. Premium and Ultimate only.
encrypted: false
gitlab_com_different_than_default: true
jihu: false
not_null: true

View File

@ -1,14 +0,0 @@
---
api_type: boolean
attr: elasticsearch_requeue_workers
clusterwide: false
column: elasticsearch_requeue_workers
db_type: boolean
default: 'false'
description: Enable automatic requeuing of indexing workers. This improves non-code
indexing throughput by enqueuing Sidekiq jobs until all documents are processed.
Premium and Ultimate only.
encrypted: false
gitlab_com_different_than_default: true
jihu: false
not_null: true

View File

@ -1,13 +0,0 @@
---
api_type: integer
attr: elasticsearch_retry_on_failure
clusterwide: false
column: elasticsearch_retry_on_failure
db_type: integer
default: '0'
description: Maximum number of possible retries for Elasticsearch search requests.
Premium and Ultimate only.
encrypted: false
gitlab_com_different_than_default: false
jihu: false
not_null: true

View File

@ -1,12 +0,0 @@
---
api_type: boolean
attr: elasticsearch_search
clusterwide: false
column: elasticsearch_search
db_type: boolean
default: 'false'
description: Enable Elasticsearch search. Premium and Ultimate only.
encrypted: false
gitlab_com_different_than_default: true
jihu: false
not_null: true

View File

@ -1,12 +0,0 @@
---
api_type: string
attr: elasticsearch_username
clusterwide: false
column: elasticsearch_username
db_type: text
default:
description: The `username` of your Elasticsearch instance. Premium and Ultimate only.
encrypted: false
gitlab_com_different_than_default: true
jihu: false
not_null: false

View File

@ -1,13 +0,0 @@
---
api_type: integer
attr: elasticsearch_worker_number_of_shards
clusterwide: false
column: elasticsearch_worker_number_of_shards
db_type: integer
default: '2'
description: Number of indexing worker shards. This improves non-code indexing throughput
by enqueuing more parallel Sidekiq jobs. Default is `2`. Premium and Ultimate only.
encrypted: false
gitlab_com_different_than_default: true
jihu: false
not_null: true

View File

@ -0,0 +1,13 @@
---
description: Tracks when the pipeline link is clicked in the sidebar
internal_events: true
action: clicked_deployment_details_pipeline_link
product_group: environments
product_categories:
- deployment_management
milestone: '17.8'
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/176004
tiers:
- free
- premium
- ultimate

View File

@ -0,0 +1,71 @@
- name: New Planner user role
description: |
We've introduced the new Planner role to give you tailored access to Agile planning tools like epics, roadmaps, and Kanban boards without over-provisioning permissions. This change helps you collaborate more effectively while keeping your workflows secure and aligned with the principle of least privilege.
stage: plan
self-managed: true
gitlab-com: true
available_in: ["Free", "Premium", "Ultimate"]
documentation_link: https://docs.gitlab.com/ee/user/permissions.html
image_url: https://about.gitlab.com/images/17_7/new_planner_user_role.png
published_at: 2024-12-19
release: 17.7
- name: Instance administrators can control which integrations can be enabled
description: |
Instance administrators can now configure an allowlist to control which integrations can be enabled on a GitLab instance. If an empty allowlist is configured, no integrations are allowed on the instance. After an allowlist is configured, new GitLab integrations are not on the allowlist by default. Previously enabled integrations that are later blocked by the allowlist settings are disabled. If these integrations are allowed again, they are re-enabled with their existing configuration.
stage: foundations
self-managed: true
gitlab-com: true
available_in: ["Ultimate"]
documentation_link: https://docs.gitlab.com/ee/administration/settings/project_integration_management.html#integration-allowlist
image_url: https://about.gitlab.com/images/17_7/integrations_allowlist.png
published_at: 2024-12-19
release: 17.7
- name: New user contribution and membership mapping available in direct transfer
description: |
Reassign memberships and contributions to existing users on the destination instance after the import has completed. Any memberships and contributions you import are first mapped to placeholder users. All contributions appear associated with placeholders until you reassign them on the destination instance.
stage: foundations
self-managed: true
gitlab-com: true
available_in: ["Free", "Premium", "Ultimate"]
documentation_link: https://docs.gitlab.com/ee/user/project/import/#user-contribution-and-membership-mapping
image_url: https://about.gitlab.com/images/17_7/user_contributions_mapping.png
published_at: 2024-12-19
release: 17.7
- name: Auto-resolve vulnerabilities when not found in subsequent scans
description: |
We are introducing a new policy type _Vulnerability Management policy_ for users who want vulnerabilities automatically set to Resolved when no longer detected by automated scanning. Simply configure a new policy with the new Auto-resolve option and apply it to the appropriate project(s). You can even configure the policy to only Auto-resolve vulnerabilities of a certain severity or from specific security scanners. Once in place, the next time the project's default branch is scanned, any existing vulnerabilities that are no longer found will be marked as Resolved. The action updates the vulnerability record with an activity note, timestamp when the action occurred, and the pipeline the vulnerability was determined to be removed in.
stage: security_risk_management
self-managed: true
gitlab-com: true
available_in: ["Ultimate"]
documentation_link: https://docs.gitlab.com/ee/user/application_security/policies/vulnerability_management_policy.html
image_url: https://about.gitlab.com/images/17_7/auto-resolve-when-not-found-in-subsequent-scan.png
published_at: 2024-12-19
release: 17.7
- name: Rotate personal, project, and group access tokens in the UI
description: |
You can now use the UI to rotate personal, project, and group access tokens. Previously, you had to use the API to do this.
stage: software_supply_chain_security
self-managed: true
gitlab-com: true
available_in: ["Free", "Premium", "Ultimate"]
documentation_link: https://docs.gitlab.com/ee/user/profile/personal_access_tokens.html#revoke-or-rotate-a-personal-access-token
image_url: https://img.youtube.com/vi/YqK2CF655OE/hqdefault.jpg
published_at: 2024-12-19
release: 17.7
- name: Track CI/CD component usage across projects
description: |
We've added a new GraphQL query that enables DevOps teams to view a list of projects where a component is used across their organization's pipelines. This capability empowers DevOps teams to enhance productivity and make better decisions by providing crucial insights.
stage: verify
self-managed: true
gitlab-com: true
available_in: ["Premium", "Ultimate"]
documentation_link: https://docs.gitlab.com/ee/api/graphql/reference/index.html#queryprojectsusingcomponents
image_url: https://about.gitlab.com/images/17_7/catalog.png
published_at: 2024-12-19
release: 17.7

View File

@ -8,6 +8,24 @@ description: For storing mentioned users, groups, projects referenced in a snipp
description.
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/19009
milestone: '12.6'
gitlab_schema: gitlab_main
sharding_key_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/490493
table_size: small
gitlab_schema: gitlab_main_cell
allow_cross_foreign_keys:
- gitlab_main_clusterwide
desired_sharding_key:
snippet_project_id:
references: projects
backfill_via:
parent:
foreign_key: snippet_id
table: snippets
sharding_key: project_id
belongs_to: snippet
snippet_organization_id:
references: organizations
backfill_via:
parent:
foreign_key: snippet_id
table: snippets
sharding_key: organization_id
belongs_to: snippet

View File

@ -7,6 +7,5 @@ feature_categories:
description: Stores data about X.509 certificate
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/17773
milestone: '12.8'
gitlab_schema: gitlab_main
sharding_key_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/490494
gitlab_schema: gitlab_main_clusterwide
table_size: small

View File

@ -0,0 +1,20 @@
# frozen_string_literal: true
class AddElasticsearchApplicationSettings < Gitlab::Database::Migration[2.2]
disable_ddl_transaction!
milestone '17.8'
def up
add_column :application_settings, :elasticsearch, :jsonb, default: {}, null: false, if_not_exists: true
add_check_constraint(
:application_settings,
"(jsonb_typeof(elasticsearch) = 'object')",
'check_application_settings_elasticsearch_is_hash'
)
end
def down
remove_column :application_settings, :elasticsearch, if_exists: true
end
end

View File

@ -0,0 +1,37 @@
# frozen_string_literal: true
class MoveElasticsearchApplicationSettingsToElasticsearchColumn < Gitlab::Database::Migration[2.2]
milestone '17.8'
restrict_gitlab_migration gitlab_schema: :gitlab_main
ELASTIC_SEARCH_REGEX = /^elasticsearch_/
class ApplicationSetting < MigrationRecord
self.table_name = 'application_settings'
end
def up
application_setting = ApplicationSetting.last
return unless application_setting
elasticsearch_settings = {}
ApplicationSetting.column_names.grep(ELASTIC_SEARCH_REGEX).each do |column_name|
# We skip elasticsearch_url because we have multiple errors moving this field.
# https://gitlab.com/gitlab-org/gitlab/-/merge_requests/174529 tried to solve this,
# but we still have CI errors that we cannot reproduce locally.
next if column_name == 'elasticsearch_url'
elasticsearch_settings[column_name] = application_setting.attribute_in_database(column_name)
end
application_setting.update_columns(elasticsearch: elasticsearch_settings, updated_at: Time.current)
end
def down
application_setting = ApplicationSetting.last
return unless application_setting
application_setting.update_column(:elasticsearch, {})
end
end

View File

@ -0,0 +1,60 @@
# frozen_string_literal: true
class ElasticsearchSettingsFromApplicationSettings < Gitlab::Database::Migration[2.2]
disable_ddl_transaction!
milestone '17.8'
def up
with_lock_retries do
remove_column :application_settings, :elasticsearch_aws
remove_column :application_settings, :elasticsearch_search
remove_column :application_settings, :elasticsearch_indexing
remove_column :application_settings, :elasticsearch_username
remove_column :application_settings, :elasticsearch_aws_region
remove_column :application_settings, :elasticsearch_aws_access_key
remove_column :application_settings, :elasticsearch_limit_indexing
remove_column :application_settings, :elasticsearch_pause_indexing
remove_column :application_settings, :elasticsearch_requeue_workers
remove_column :application_settings, :elasticsearch_max_bulk_size_mb
remove_column :application_settings, :elasticsearch_retry_on_failure
remove_column :application_settings, :elasticsearch_max_bulk_concurrency
remove_column :application_settings, :elasticsearch_client_request_timeout
remove_column :application_settings, :elasticsearch_worker_number_of_shards
remove_column :application_settings, :elasticsearch_analyzers_smartcn_search
remove_column :application_settings, :elasticsearch_analyzers_kuromoji_search
remove_column :application_settings, :elasticsearch_analyzers_smartcn_enabled
remove_column :application_settings, :elasticsearch_analyzers_kuromoji_enabled
remove_column :application_settings, :elasticsearch_indexed_field_length_limit
remove_column :application_settings, :elasticsearch_indexed_file_size_limit_kb
remove_column :application_settings, :elasticsearch_max_code_indexing_concurrency
end
end
def down
with_lock_retries do
add_column :application_settings, :elasticsearch_aws, :boolean, default: false, null: false
add_column :application_settings, :elasticsearch_search, :boolean, default: false, null: false
add_column :application_settings, :elasticsearch_indexing, :boolean, default: false, null: false
add_column :application_settings, :elasticsearch_username, :text
add_column :application_settings, :elasticsearch_aws_region, 'character varying', default: 'us-east-1'
add_column :application_settings, :elasticsearch_aws_access_key, 'character varying'
add_column :application_settings, :elasticsearch_limit_indexing, :boolean, default: false, null: false
add_column :application_settings, :elasticsearch_pause_indexing, :boolean, default: false, null: false
add_column :application_settings, :elasticsearch_requeue_workers, :boolean, default: false, null: false
add_column :application_settings, :elasticsearch_max_bulk_size_mb, :smallint, default: 10, null: false
add_column :application_settings, :elasticsearch_retry_on_failure, :integer, default: 0, null: false
add_column :application_settings, :elasticsearch_max_bulk_concurrency, :smallint, default: 10, null: false
add_column :application_settings, :elasticsearch_client_request_timeout, :integer, default: 0, null: false
add_column :application_settings, :elasticsearch_worker_number_of_shards, :integer, default: 2, null: false
add_column :application_settings, :elasticsearch_analyzers_smartcn_search, :boolean, default: false, null: false
add_column :application_settings, :elasticsearch_analyzers_kuromoji_search, :boolean, default: false, null: false
add_column :application_settings, :elasticsearch_analyzers_smartcn_enabled, :boolean, default: false, null: false
add_column :application_settings, :elasticsearch_analyzers_kuromoji_enabled, :boolean, default: false, null: false
add_column :application_settings, :elasticsearch_indexed_field_length_limit, :integer, default: 0, null: false
add_column :application_settings, :elasticsearch_indexed_file_size_limit_kb, :integer, default: 1024, null: false
add_column :application_settings, :elasticsearch_max_code_indexing_concurrency, :integer, default: 30, null: false
end
add_check_constraint(:application_settings, 'char_length(elasticsearch_username) <= 255', 'check_e5024c8801')
end
end

View File

@ -0,0 +1 @@
b34c958333e8352a72d0dbababdd78240c5f02aeec113d74a89131a97027cc2a

View File

@ -0,0 +1 @@
127e8ea6fe4e343c33b39cf507bee3ea879267bbd048b71e4796402aa7cdc5cc

View File

@ -0,0 +1 @@
0fa814e285a0401e9d72ad7bb0a41bed2d5779f6e6613b9a9117d227740fe317

View File

@ -6939,8 +6939,6 @@ CREATE TABLE application_settings (
container_registry_token_expire_delay integer DEFAULT 5,
after_sign_up_text text,
user_default_external boolean DEFAULT false NOT NULL,
elasticsearch_indexing boolean DEFAULT false NOT NULL,
elasticsearch_search boolean DEFAULT false NOT NULL,
repository_storages character varying DEFAULT 'default'::character varying,
enabled_git_access_protocol character varying,
usage_ping_enabled boolean DEFAULT true NOT NULL,
@ -6967,9 +6965,6 @@ CREATE TABLE application_settings (
unique_ips_limit_enabled boolean DEFAULT false NOT NULL,
default_artifacts_expire_in character varying DEFAULT '0'::character varying NOT NULL,
elasticsearch_url character varying DEFAULT 'http://localhost:9200'::character varying,
elasticsearch_aws boolean DEFAULT false NOT NULL,
elasticsearch_aws_region character varying DEFAULT 'us-east-1'::character varying,
elasticsearch_aws_access_key character varying,
geo_status_timeout integer DEFAULT 10,
uuid character varying,
polling_interval_multiplier numeric DEFAULT 1.0 NOT NULL,
@ -7035,7 +7030,6 @@ CREATE TABLE application_settings (
runners_registration_token_encrypted character varying,
local_markdown_version integer DEFAULT 0 NOT NULL,
first_day_of_week integer DEFAULT 0 NOT NULL,
elasticsearch_limit_indexing boolean DEFAULT false NOT NULL,
default_project_creation integer DEFAULT 2 NOT NULL,
lets_encrypt_notification_email character varying,
lets_encrypt_terms_of_service_accepted boolean DEFAULT false NOT NULL,
@ -7098,9 +7092,6 @@ CREATE TABLE application_settings (
encrypted_slack_app_verification_token_iv character varying(255),
force_pages_access_control boolean DEFAULT false NOT NULL,
updating_name_disabled_for_users boolean DEFAULT false NOT NULL,
elasticsearch_indexed_field_length_limit integer DEFAULT 0 NOT NULL,
elasticsearch_max_bulk_size_mb smallint DEFAULT 10 NOT NULL,
elasticsearch_max_bulk_concurrency smallint DEFAULT 10 NOT NULL,
disable_overriding_approvers_per_merge_request boolean DEFAULT false NOT NULL,
prevent_merge_requests_author_approval boolean DEFAULT false NOT NULL,
prevent_merge_requests_committers_approval boolean DEFAULT false NOT NULL,
@ -7116,7 +7107,6 @@ CREATE TABLE application_settings (
container_registry_features text[] DEFAULT '{}'::text[] NOT NULL,
spam_check_endpoint_url text,
spam_check_endpoint_enabled boolean DEFAULT false NOT NULL,
elasticsearch_pause_indexing boolean DEFAULT false NOT NULL,
repository_storages_weighted jsonb DEFAULT '{}'::jsonb NOT NULL,
max_import_size integer DEFAULT 0 NOT NULL,
compliance_frameworks smallint[] DEFAULT '{}'::smallint[] NOT NULL,
@ -7131,12 +7121,10 @@ CREATE TABLE application_settings (
maintenance_mode boolean DEFAULT false NOT NULL,
maintenance_mode_message text,
wiki_page_max_content_bytes bigint DEFAULT 52428800 NOT NULL,
elasticsearch_indexed_file_size_limit_kb integer DEFAULT 1024 NOT NULL,
enforce_namespace_storage_limit boolean DEFAULT false NOT NULL,
container_registry_delete_tags_service_timeout integer DEFAULT 250 NOT NULL,
kroki_url character varying,
kroki_enabled boolean,
elasticsearch_client_request_timeout integer DEFAULT 0 NOT NULL,
gitpod_enabled boolean DEFAULT false NOT NULL,
gitpod_url text DEFAULT 'https://gitpod.io/'::text,
abuse_notification_email character varying,
@ -7146,10 +7134,6 @@ CREATE TABLE application_settings (
encrypted_ci_jwt_signing_key text,
encrypted_ci_jwt_signing_key_iv text,
container_registry_expiration_policies_worker_capacity integer DEFAULT 4 NOT NULL,
elasticsearch_analyzers_smartcn_enabled boolean DEFAULT false NOT NULL,
elasticsearch_analyzers_smartcn_search boolean DEFAULT false NOT NULL,
elasticsearch_analyzers_kuromoji_enabled boolean DEFAULT false NOT NULL,
elasticsearch_analyzers_kuromoji_search boolean DEFAULT false NOT NULL,
secret_detection_token_revocation_enabled boolean DEFAULT false NOT NULL,
secret_detection_token_revocation_url text,
encrypted_secret_detection_token_revocation_token text,
@ -7188,7 +7172,6 @@ CREATE TABLE application_settings (
encrypted_spam_check_api_key bytea,
encrypted_spam_check_api_key_iv bytea,
floc_enabled boolean DEFAULT false NOT NULL,
elasticsearch_username text,
encrypted_elasticsearch_password bytea,
encrypted_elasticsearch_password_iv bytea,
diff_max_lines integer DEFAULT 50000 NOT NULL,
@ -7368,8 +7351,6 @@ CREATE TABLE application_settings (
unconfirmed_users_delete_after_days integer DEFAULT 7 NOT NULL,
default_branch_protection_defaults jsonb DEFAULT '{}'::jsonb NOT NULL,
gitlab_shell_operation_limit integer DEFAULT 600,
elasticsearch_requeue_workers boolean DEFAULT false NOT NULL,
elasticsearch_worker_number_of_shards integer DEFAULT 2 NOT NULL,
protected_paths_for_get_request text[] DEFAULT '{}'::text[] NOT NULL,
namespace_storage_forks_cost_factor double precision DEFAULT 1.0 NOT NULL,
package_registry_allow_anyone_to_pull_option boolean DEFAULT true NOT NULL,
@ -7403,7 +7384,6 @@ CREATE TABLE application_settings (
encrypted_arkose_labs_data_exchange_key bytea,
encrypted_arkose_labs_data_exchange_key_iv bytea,
rate_limits jsonb DEFAULT '{}'::jsonb NOT NULL,
elasticsearch_max_code_indexing_concurrency integer DEFAULT 30 NOT NULL,
enable_member_promotion_management boolean DEFAULT false NOT NULL,
lock_math_rendering_limits_enabled boolean DEFAULT false NOT NULL,
security_approval_policies_limit integer DEFAULT 5 NOT NULL,
@ -7441,7 +7421,6 @@ CREATE TABLE application_settings (
sign_in_restrictions jsonb DEFAULT '{}'::jsonb NOT NULL,
transactional_emails jsonb DEFAULT '{}'::jsonb NOT NULL,
identity_verification_settings jsonb DEFAULT '{}'::jsonb NOT NULL,
elasticsearch_retry_on_failure integer DEFAULT 0 NOT NULL,
integrations jsonb DEFAULT '{}'::jsonb NOT NULL,
user_seat_management jsonb DEFAULT '{}'::jsonb NOT NULL,
secret_detection_service_url text DEFAULT ''::text NOT NULL,
@ -7451,6 +7430,7 @@ CREATE TABLE application_settings (
show_migrate_from_jenkins_banner boolean DEFAULT true NOT NULL,
encrypted_ci_job_token_signing_key bytea,
encrypted_ci_job_token_signing_key_iv bytea,
elasticsearch jsonb DEFAULT '{}'::jsonb NOT NULL,
CONSTRAINT app_settings_container_reg_cleanup_tags_max_list_size_positive CHECK ((container_registry_cleanup_tags_service_max_list_size >= 0)),
CONSTRAINT app_settings_dep_proxy_ttl_policies_worker_capacity_positive CHECK ((dependency_proxy_ttl_group_policy_worker_capacity >= 0)),
CONSTRAINT app_settings_ext_pipeline_validation_service_url_text_limit CHECK ((char_length(external_pipeline_validation_service_url) <= 255)),
@ -7503,6 +7483,7 @@ CREATE TABLE application_settings (
CONSTRAINT check_application_settings_cluster_agents_is_hash CHECK ((jsonb_typeof(cluster_agents) = 'object'::text)),
CONSTRAINT check_application_settings_code_creation_is_hash CHECK ((jsonb_typeof(code_creation) = 'object'::text)),
CONSTRAINT check_application_settings_duo_workflow_is_hash CHECK ((jsonb_typeof(duo_workflow) = 'object'::text)),
CONSTRAINT check_application_settings_elasticsearch_is_hash CHECK ((jsonb_typeof(elasticsearch) = 'object'::text)),
CONSTRAINT check_application_settings_importers_is_hash CHECK ((jsonb_typeof(importers) = 'object'::text)),
CONSTRAINT check_application_settings_integrations_is_hash CHECK ((jsonb_typeof(integrations) = 'object'::text)),
CONSTRAINT check_application_settings_package_registry_is_hash CHECK ((jsonb_typeof(package_registry) = 'object'::text)),
@ -7520,7 +7501,6 @@ CREATE TABLE application_settings (
CONSTRAINT check_d820146492 CHECK ((char_length(spam_check_endpoint_url) <= 255)),
CONSTRAINT check_e2692d7523 CHECK ((char_length(default_preferred_language) <= 32)),
CONSTRAINT check_e2dd6e290a CHECK ((char_length(jira_connect_application_key) <= 255)),
CONSTRAINT check_e5024c8801 CHECK ((char_length(elasticsearch_username) <= 255)),
CONSTRAINT check_e5aba18f02 CHECK ((char_length(container_registry_version) <= 255)),
CONSTRAINT check_ef6176834f CHECK ((char_length(encrypted_cloud_license_auth_token_iv) <= 255)),
CONSTRAINT check_identity_verification_settings_is_hash CHECK ((jsonb_typeof(identity_verification_settings) = 'object'::text))

View File

@ -10,8 +10,8 @@ DETAILS:
**Tier:** Free, Premium, Ultimate
**Offering:** GitLab.com, GitLab Dedicated
You can run your CI/CD jobs on GitLab.com and GitLab Dedicated using GitLab-hosted runners to seamlessly build, test and deploy
your application on different environments.
Use GitLab-hosted runners to run your CI/CD jobs on GitLab.com and GitLab Dedicated to build, test, and deploy
applications on different environments.
## Hosted runners for GitLab.com
@ -49,7 +49,7 @@ Hosted runners for GitLab.com are configured as such:
- Inbound communication from the public internet to the ephemeral VM is not allowed.
- Firewall rules do not permit communication between VMs.
- The only internal communication allowed to the ephemeral VMs is from the runner manager.
- Ephemeral runner VMs will only serve a single job and will be deleted right after the job execution.
- Ephemeral runner VMs serve a single job and are deleted right after the job execution.
#### Architecture diagram of hosted runners for GitLab.com
@ -71,9 +71,11 @@ The build job ran on `runner-ns46nmmj-project-43717858`, test job on `f131a6a2ru
GitLab sends the command to remove the ephemeral runner VM to the Google Compute API immediately after the CI job completes. The [Google Compute Engine hypervisor](https://cloud.google.com/blog/products/gcp/7-ways-we-harden-our-kvm-hypervisor-at-google-cloud-security-in-plaintext)
takes over the task of securely deleting the virtual machine and associated data.
For more information about the security of hosted runners for GitLab.com, see
[Google Cloud Infrastructure Security Design Overview whitepaper](https://cloud.google.com/docs/security/infrastructure/design/resources/google_infrastructure_whitepaper_fa.pdf),
[GitLab Trust Center](https://about.gitlab.com/security/), or [GitLab Security Compliance Controls](https://handbook.gitlab.com/handbook/security/security-assurance/security-compliance/sec-controls/).
For more information about the security of hosted runners for GitLab.com, see:
- [Google Cloud Infrastructure Security Design Overview whitepaper](https://cloud.google.com/docs/security/infrastructure/design/resources/google_infrastructure_whitepaper_fa.pdf)
- [GitLab Trust Center](https://about.gitlab.com/security/)
- [GitLab Security Compliance Controls](https://handbook.gitlab.com/handbook/security/security-assurance/security-compliance/sec-controls/)
### Caching on hosted runners for GitLab.com
@ -103,7 +105,7 @@ You can find all GitLab Runner breaking changes under [Deprecations and removals
DETAILS:
**Offering:** GitLab.com
If you want to [contribute to GitLab](https://about.gitlab.com/community/contribute/), jobs will be picked up by the
If you want to [contribute to GitLab](https://about.gitlab.com/community/contribute/), jobs are picked up by the
`gitlab-shared-runners-manager-X.gitlab.com` fleet of runners, dedicated for GitLab projects and related community forks.
These runners are backed by the same machine type as our `small` Linux x86-64 runners.

View File

@ -29,8 +29,8 @@ Consider that you're developing a content management system that uses database f
You need a database to test all features in the application. Running a database
container as a service image is a good use case in this scenario.
It's easier and faster to use an existing image and run it as an additional container
than to install `mysql`, for example, every time the project is built.
Use an existing image and run it as an additional container
instead of installing `mysql` every time you build a project.
You're not limited to only database services. You can add as many
services you need to `.gitlab-ci.yml` or manually modify the [`config.toml`](https://docs.gitlab.com/runner/configuration/advanced-configuration.html).
@ -357,9 +357,9 @@ The syntax of `command` is similar to [Dockerfile `CMD`](https://docs.docker.com
Containers started with `docker run` can also connect to services provided by GitLab.
When booting the service is expensive or time consuming, you can use
this technique to run tests from different client environments,
while only booting up the tested service once.
If booting a service is expensive or time consuming, you can
run tests from different client environments,
while booting up the tested service only once.
```yaml
access-service:
@ -405,8 +405,8 @@ time.
> - [Introduced](https://gitlab.com/gitlab-org/gitlab-runner/-/merge_requests/3680) in GitLab Runner 15.6.
Logs generated by applications running in service containers can be captured for subsequent examination and debugging.
You might want to look at service container's logs when the service container has started successfully, but is not
behaving as expected, leading to job failures. The logs can indicate missing or incorrect configuration of the service
View service container logs when a service container starts successfully but causes job failures due to unexpected behavior.
The logs can indicate missing or incorrect configuration of the service
in the container.
`CI_DEBUG_SERVICES` should only be enabled when service containers are being actively debugged as there are both storage
@ -427,24 +427,24 @@ Accepted values are:
Any other values result in an error message and effectively disable the feature.
When enabled, logs for _all_ service containers are captured and streamed into the jobs trace log concurrently with
When enabled, logs for all service containers are captured and streamed into the jobs trace log concurrently with
other logs. Logs from each container are prefixed with the container's aliases, and displayed in a different color.
NOTE:
You may want to adjust the logging level in the service container for which you want to capture logs because the default
logging level may not provide sufficient details to diagnose job failures.
To diagnose job failures, you can adjust the logging level in your service container for which you want to capture logs.
The default logging level might not provide sufficient troubleshooting information.
WARNING:
Enabling `CI_DEBUG_SERVICES` _may_ result in masked variables being revealed. When `CI_DEBUG_SERVICES` is enabled,
service container logs and the CI job's logs are streamed to the job's trace log _concurrently_, which makes it possible
for a service container log to be inserted _inside_ a job's masked log. This would thwart the variable masking mechanism
Enabling `CI_DEBUG_SERVICES` might reveal masked variables. When `CI_DEBUG_SERVICES` is enabled,
service container logs and the CI job's logs are streamed to the job's trace log concurrently. This means that the
service container logs might get inserted into a job's masked log. This would thwart the variable masking mechanism
and result in the masked variable being revealed.
See [Mask a CI/CD Variable](../variables/index.md#mask-a-cicd-variable)
## Debug a job locally
The following commands are run without root privileges. You should be able to run Docker with your user account.
The following commands are run without root privileges. Verify that you can run Docker commands with your user account.
First start by creating a file named `build_script`:
@ -478,7 +478,7 @@ The above command creates a container named `build` that is spawned from the `go
linked to it. The `build_script` is piped using `stdin` to the bash interpreter which in turn executes the
`build_script` in the `build` container.
When you finish testing and no longer need the containers, you can remove them with:
Use the following command to remove containers after testing is complete:
```shell
docker rm -f -v build service-redis
@ -500,7 +500,7 @@ need to access project files or store artifacts. If so, wait for the directory
to exist and for `$CI_COMMIT_SHA` to be checked out. Any changes made before
the job finishes its checkout process are removed by the checkout process.
The service must be able to detect when the job directory is populated and
The service must detect when the job directory is populated and
ready for processing. For example, wait for a specific file to become available.
Services that start working immediately when launched are likely to fail, as the

View File

@ -7,12 +7,12 @@ info: Analysis of Application Settings for Cells 1.0.
## Statistics
- Number of attributes: 510
- Number of attributes: 490
- Number of encrypted attributes: 45 (9.0%)
- Number of attributes documented: 311 (61.0%)
- Number of attributes on GitLab.com different from the defaults: 222 (44.0%)
- Number of attributes with `clusterwide` set: 510 (100.0%)
- Number of attributes with `clusterwide: true` set: 124 (24.0%)
- Number of attributes documented: 296 (60.0%)
- Number of attributes on GitLab.com different from the defaults: 212 (43.0%)
- Number of attributes with `clusterwide` set: 490 (100.0%)
- Number of attributes with `clusterwide: true` set: 120 (24.0%)
## Individual columns
@ -148,30 +148,10 @@ info: Analysis of Application Settings for Cells 1.0.
| `eks_account_id` | `false` | `character` | `string` | `false` | `null` | `true` | `true`| `true` |
| `eks_integration_enabled` | `false` | `boolean` | `boolean` | `true` | `false` | `true` | `true`| `true` |
| `eks_secret_access_key` | `true` | `text` | `string` | `false` | `null` | `true` | `true`| `true` |
| `elasticsearch_analyzers_kuromoji_enabled` | `false` | `boolean` | `` | `true` | `false` | `false` | `true`| `false` |
| `elasticsearch_analyzers_kuromoji_search` | `false` | `boolean` | `` | `true` | `false` | `false` | `true`| `false` |
| `elasticsearch_analyzers_smartcn_enabled` | `false` | `boolean` | `` | `true` | `false` | `false` | `true`| `false` |
| `elasticsearch_analyzers_smartcn_search` | `false` | `boolean` | `` | `true` | `false` | `false` | `true`| `false` |
| `elasticsearch_aws` | `false` | `boolean` | `boolean` | `true` | `false` | `false` | `false`| `true` |
| `elasticsearch_aws_access_key` | `false` | `character` | `string` | `false` | `null` | `true` | `false`| `true` |
| `elasticsearch_aws_region` | `false` | `character` | `string` | `false` | `'us-east-1'::character` | `false` | `false`| `true` |
| `elasticsearch` | `false` | `jsonb` | `` | `true` | `'{}'::jsonb` | `false` | `false`| `false` |
| `elasticsearch_aws_secret_access_key` | `true` | `text` | `string` | `false` | `null` | `true` | `false`| `true` |
| `elasticsearch_client_request_timeout` | `false` | `integer` | `` | `true` | `0` | `true` | `false`| `false` |
| `elasticsearch_indexed_field_length_limit` | `false` | `integer` | `integer` | `true` | `0` | `true` | `false`| `true` |
| `elasticsearch_indexed_file_size_limit_kb` | `false` | `integer` | `integer` | `true` | `1024` | `false` | `false`| `true` |
| `elasticsearch_indexing` | `false` | `boolean` | `boolean` | `true` | `false` | `true` | `false`| `true` |
| `elasticsearch_limit_indexing` | `false` | `boolean` | `boolean` | `true` | `false` | `true` | `false`| `true` |
| `elasticsearch_max_bulk_concurrency` | `false` | `smallint` | `integer` | `true` | `10` | `false` | `false`| `true` |
| `elasticsearch_max_bulk_size_mb` | `false` | `smallint` | `integer` | `true` | `10` | `false` | `false`| `true` |
| `elasticsearch_max_code_indexing_concurrency` | `false` | `integer` | `integer` | `true` | `30` | `true` | `false`| `true` |
| `elasticsearch_password` | `true` | `bytea` | `string` | `false` | `null` | `true` | `false`| `true` |
| `elasticsearch_pause_indexing` | `false` | `boolean` | `` | `true` | `false` | `false` | `false`| `false` |
| `elasticsearch_requeue_workers` | `false` | `boolean` | `boolean` | `true` | `false` | `true` | `false`| `true` |
| `elasticsearch_retry_on_failure` | `false` | `integer` | `integer` | `true` | `0` | `false` | `false`| `true` |
| `elasticsearch_search` | `false` | `boolean` | `boolean` | `true` | `false` | `true` | `false`| `true` |
| `elasticsearch_url` | `false` | `character` | `string` | `false` | `'http://localhost:9200'::character` | `true` | `false`| `true` |
| `elasticsearch_username` | `false` | `text` | `string` | `false` | `null` | `true` | `false`| `true` |
| `elasticsearch_worker_number_of_shards` | `false` | `integer` | `integer` | `true` | `2` | `true` | `false`| `true` |
| `email_additional_text` | `false` | `character` | `string` | `false` | `null` | `true` | `true`| `true` |
| `email_author_in_body` | `false` | `boolean` | `boolean` | `false` | `false` | `false` | `true`| `true` |
| `email_confirmation_setting` | `false` | `smallint` | `string` | `false` | `0` | `true` | `true`| `true` |

View File

@ -414,6 +414,8 @@ Use **Chat** with a capital `c` for **Chat** or **GitLab Duo Chat**.
On first use on a page, use **GitLab Duo Chat**.
Thereafter, use **Chat** by itself.
Do not use **Duo Chat**.
## checkbox
Use one word for **checkbox**. Do not use **check box**.

View File

@ -19,6 +19,9 @@ For more information, see the history.
Use a vulnerability management policy to automatically resolve vulnerabilities that are no longer
detected. This can help reduce the workload of triaging vulnerabilities.
The vulnerability management policy is available at the project level. Group level policies are
being tracked in [this epic](https://gitlab.com/groups/gitlab-org/-/epics/15697).
When a scanner detects a vulnerability on the default branch, the scanner creates a vulnerability
record with the status **Needs triage**. After the vulnerability has been remediated and the next
security scan runs, the scan adds **No longer detected** to the record's activity log but the
@ -43,6 +46,7 @@ detected are marked **Resolved**.
- You can assign a maximum of five rules to each policy.
- You can assign a maximum of five vulnerability management policies to each security policy project.
- Group level vulnerability management policies are not yet supported.
## Create a vulnerability management policy

View File

@ -7077,7 +7077,7 @@ msgstr ""
msgid "ApplicationSettings|A Metrics Dashboard menu item appears in the Monitoring section of the Admin area."
msgstr ""
msgid "ApplicationSettings|A user cap that exceeds the current licensed user count (%{licensedUserCount}) might result in %{linkStart}seat overages%{linkEnd}."
msgid "ApplicationSettings|A user cap that exceeds the current licensed user count (%{licensedUserCount}) may result in a %{linkStart}true-up%{linkEnd}."
msgstr ""
msgid "ApplicationSettings|Add a link to Grafana"
@ -19450,6 +19450,9 @@ msgstr ""
msgid "Deployment|Open URL"
msgstr ""
msgid "Deployment|Pipeline"
msgstr ""
msgid "Deployment|Ready to be deployed."
msgstr ""

View File

@ -301,7 +301,7 @@
"swagger-cli": "^4.0.4",
"tailwindcss": "^3.4.1",
"timezone-mock": "^1.0.8",
"vite": "^6.0.3",
"vite": "^6.0.4",
"vite-plugin-ruby": "^5.1.1",
"vue-loader-vue3": "npm:vue-loader@17.4.2",
"vue-test-utils-compat": "0.0.14",

View File

@ -131,9 +131,6 @@ spec/frontend/branches/components/delete_branch_modal_spec.js
spec/frontend/cascading_settings/components/cascading_lock_icon_spec.js
spec/frontend/cascading_settings/components/lock_tooltip_spec.js
spec/frontend/ci/artifacts/components/artifacts_bulk_delete_spec.js
spec/frontend/ci/catalog/components/ci_catalog_home_spec.js
spec/frontend/ci/catalog/components/list/ci_resources_list_item_spec.js
spec/frontend/ci/catalog/components/pages/ci_resource_details_page_spec.js
spec/frontend/ci/catalog/index_spec.js
spec/frontend/ci/job_details/components/log/line_header_spec.js
spec/frontend/ci/job_details/components/log/line_spec.js

View File

@ -0,0 +1,103 @@
# frozen_string_literal: true
require 'spec_helper'
RSpec.describe 'Change type action', :js, feature_category: :portfolio_management do
include ListboxHelpers, DesignManagementTestHelpers
let_it_be_with_reload(:user) { create(:user) }
let_it_be(:group) { create(:group, :nested) }
let_it_be(:project) { create(:project, :public, namespace: group, developers: user) }
let(:issue) { create(:work_item, :issue, project: project) }
let(:task) { create(:work_item, :task, project: project) }
context 'for signed in user' do
before do
enable_design_management
sign_in(user)
end
context 'when work item type is issue' do
before do
visit project_work_item_path(project, issue.iid)
end
it_behaves_like 'work items change type', 'Task', '[data-testid="issue-type-task-icon"]'
context 'when issue has a child' do
it 'does not allow changing the type' do
within_testid 'work-item-tree' do
click_button 'Add'
click_button "Existing task"
fill_in 'Search existing items', with: task.title
click_button task.title
send_keys :escape
click_button "Add task"
wait_for_all_requests
end
page.refresh
wait_for_all_requests
trigger_change_type('Task')
expect(page).to have_button('Change type', disabled: true)
end
end
end
context 'when work item type is task' do
before do
visit project_work_item_path(project, task.iid)
end
it_behaves_like 'work items change type', 'Issue', '[data-testid="issue-type-issue-icon"]'
context 'when task has a parent' do
it 'does not allow changing the type' do
within_testid 'work-item-parent' do
click_button 'Edit'
send_keys(issue.title)
select_listbox_item(issue.title)
end
trigger_change_type('Issue')
expect(page).to have_button('Change type', disabled: true)
end
end
end
context 'when there is chance of data loss' do
let_it_be(:issue_with_data) do
create(:issue, project: project, title: "Issue with data")
end
let_it_be(:design) { create(:design, :with_file, issue: issue_with_data) }
before do
visit project_work_item_path(project, issue_with_data.iid)
wait_for_all_requests
end
it 'renders the warning about the data loss' do
trigger_change_type('Task')
expect(page).to have_button('Change type', disabled: false)
within_testid('change-type-warning-message', wait: 20) do
message = s_('Some fields are not present in %{type}. If you change type now, this information will be lost.')
expect(page).to have_content(format(message, type: 'task'))
expect(page).to have_content(s_('WorkItem|Designs'))
end
end
end
end
def trigger_change_type(type)
click_button _('More actions'), match: :first
click_button s_('WorkItem|Change type')
find_by_testid('work-item-change-type-select').select(type)
end
end

View File

@ -75,6 +75,7 @@ RSpec.describe 'Work item detail', :js, feature_category: :team_planning do
end
it_behaves_like 'work items parent', :issue
it_behaves_like 'work items change type', 'Issue', '[data-testid="issue-type-issue-icon"]'
end
context 'for signed in owner' do
@ -123,5 +124,11 @@ RSpec.describe 'Work item detail', :js, feature_category: :team_planning do
expect(page).to have_content(note.note)
end
it 'change type action is not displayed' do
click_button _('More actions'), match: :first
expect(find_by_testid('work-item-actions-dropdown')).to have_button(s_('WorkItem|Change type'))
end
end
end

View File

@ -3,44 +3,41 @@ import { createRouter } from '~/ci/catalog/router';
import ciResourceDetailsPage from '~/ci/catalog/components/pages/ci_resource_details_page.vue';
import CiCatalogHome from '~/ci/catalog/components/ci_catalog_home.vue';
const baseRoute = '/';
const resourcesPageComponentStub = {
name: 'page-component',
template: '<div>Hello</div>',
};
describe('CiCatalogHome', () => {
const defaultProps = {};
const baseRoute = '/';
const resourcesPageComponentStub = {
name: 'page-component',
template: '<div>Hello</div>',
};
const router = createRouter(baseRoute, resourcesPageComponentStub);
let router;
beforeEach(() => {
router = createRouter(baseRoute, resourcesPageComponentStub);
});
const createComponent = ({ props = {} } = {}) => {
shallowMount(CiCatalogHome, {
propsData: {
...defaultProps,
...props,
},
router,
});
};
describe('when mounted', () => {
beforeEach(() => {
describe('router', () => {
it.each`
path | component
${baseRoute} | ${resourcesPageComponentStub}
${'/1'} | ${ciResourceDetailsPage}
`('when route is $path it renders the right component', async ({ path, component }) => {
await router.push(path);
createComponent();
});
describe('router', () => {
it.each`
path | component
${baseRoute} | ${resourcesPageComponentStub}
${'/1'} | ${ciResourceDetailsPage}
`('when route is $path it renders the right component', async ({ path, component }) => {
if (path !== '/') {
await router.push(path);
}
const [root] = router.currentRoute.matched;
const [root] = router.currentRoute.matched;
expect(root.components.default).toBe(component);
});
expect(root.components.default).toBe(component);
});
});
});

View File

@ -1,5 +1,3 @@
import Vue from 'vue';
import VueRouter from 'vue-router';
import { update, cloneDeep } from 'lodash';
import { GlAvatar, GlBadge, GlSprintf, GlTruncate } from '@gitlab/ui';
import { shallowMountExtended } from 'helpers/vue_test_utils_helper';
@ -11,15 +9,18 @@ import ProjectVisibilityIcon from '~/ci/catalog/components/shared/project_visibi
import Markdown from '~/vue_shared/components/markdown/non_gfm_markdown.vue';
import { catalogSinglePageResponse } from '../../mock';
Vue.use(VueRouter);
const defaultEvent = { preventDefault: jest.fn, ctrlKey: false, metaKey: false };
const baseRoute = '/';
const resourcesPageComponentStub = {
name: 'page-component',
template: '<div>Hello</div>',
};
describe('CiResourcesListItem', () => {
let wrapper;
let routerPush;
let router;
const router = createRouter();
const resource = catalogSinglePageResponse.data.ciCatalogResources.nodes[0];
const release = {
author: { id: 'author-id', name: 'author', username: 'author-username', webUrl: '/user/1' },
@ -58,6 +59,7 @@ describe('CiResourcesListItem', () => {
const findUserLink = () => wrapper.findByTestId('user-link');
beforeEach(() => {
router = createRouter(baseRoute, resourcesPageComponentStub);
routerPush = jest.spyOn(router, 'push').mockImplementation(() => {});
});

View File

@ -22,13 +22,17 @@ import { catalogSharedDataMock } from '../../mock';
Vue.use(VueApollo);
Vue.use(VueRouter);
let router;
const defaultSharedData = { ...catalogSharedDataMock.data.ciCatalogResource };
const baseRoute = '/';
const resourcesPageComponentStub = {
name: 'page-component',
template: '<div>Hello</div>',
};
describe('CiResourceDetailsPage', () => {
let wrapper;
let sharedDataResponse;
let router;
const defaultProps = {};
@ -57,16 +61,14 @@ describe('CiResourceDetailsPage', () => {
...defaultProps,
...props,
},
stubs: {
RouterView: true,
},
});
};
beforeEach(async () => {
sharedDataResponse = jest.fn();
router = createRouter();
router = createRouter(baseRoute, resourcesPageComponentStub);
await router.push({
name: CI_RESOURCE_DETAILS_PAGE_NAME,
params: { id: defaultSharedData.webPath },

View File

@ -5,6 +5,9 @@ import mockEnvironmentFixture from 'test_fixtures/graphql/deployments/graphql/qu
import { mountExtended } from 'helpers/vue_test_utils_helper';
import ShowMore from '~/vue_shared/components/show_more.vue';
import DeploymentAside from '~/deployments/components/deployment_aside.vue';
import { getIdFromGraphQLId } from '~/graphql_shared/utils';
import { useMockInternalEventsTracking } from 'helpers/tracking_internal_events_helper';
import { CLICK_PIPELINE_LINK_ON_DEPLOYMENT_PAGE } from '~/deployments/utils';
const {
data: {
@ -16,8 +19,9 @@ const {
project: { environment },
},
} = mockEnvironmentFixture;
const { bindInternalEventDocument } = useMockInternalEventsTracking();
describe('~/deployments/components/deployment_header.vue', () => {
describe('~/deployments/components/deployment_aside.vue', () => {
let wrapper;
const findSidebarToggleButton = () => wrapper.findByTestId('deployment-sidebar-toggle-button');
@ -25,6 +29,8 @@ describe('~/deployments/components/deployment_header.vue', () => {
const findSidebarItems = () => wrapper.findByTestId('deployment-sidebar-items');
const findUrlButtonWrapper = () => wrapper.findByTestId('deployment-url-button-wrapper');
const findTriggererItem = () => wrapper.findByTestId('deployment-triggerer-item');
const findPipelineSection = () => wrapper.findByTestId('deployment-pipeline');
const findPipelineLink = () => wrapper.findByTestId('deployment-pipeline-link');
const createComponent = ({ propsData = {} } = {}) => {
wrapper = mountExtended(DeploymentAside, {
@ -63,6 +69,28 @@ describe('~/deployments/components/deployment_header.vue', () => {
expect(link.text()).toContain(deployment.triggerer.name);
});
it('shows a section with a link to the Pipeline', () => {
expect(findPipelineSection().exists()).toBe(true);
expect(findPipelineSection().text()).toContain('Pipeline');
expect(findPipelineLink().exists()).toBe(true);
expect(findPipelineLink().attributes('href')).toBe(deployment.job.pipeline.path);
expect(findPipelineLink().text()).toBe(`#${getIdFromGraphQLId(deployment.job.pipeline.id)}`);
});
it('should call trackEvent method when pipeline link is clicked', async () => {
const { trackEventSpy } = bindInternalEventDocument(wrapper.element);
await findPipelineLink().vm.$emit('click');
expect(trackEventSpy).toHaveBeenCalledTimes(1);
expect(trackEventSpy).toHaveBeenCalledWith(
CLICK_PIPELINE_LINK_ON_DEPLOYMENT_PAGE,
{},
undefined,
);
});
it('shows a link to the tags of a deployment', () => {
deployment.tags.forEach((tag) => {
const link = wrapper.findByRole('link', { name: tag.name });

View File

@ -99,7 +99,6 @@ describe('WorkItemChangeTypeModal component', () => {
const findGlFormSelect = () => wrapper.findComponent(GlFormSelect);
const findWarningAlert = () => wrapper.findByTestId('change-type-warning-message');
const findErrorAlert = () => wrapper.findByTestId('change-type-error-message');
const findConfirmationButton = () => wrapper.findByTestId('change-type-confirmation-button');
beforeEach(async () => {
createComponent();
@ -109,7 +108,13 @@ describe('WorkItemChangeTypeModal component', () => {
it('renders change type modal with the select', () => {
expect(findChangeTypeModal().exists()).toBe(true);
expect(findGlFormSelect().exists()).toBe(true);
expect(findConfirmationButton().props('disabled')).toBe(true);
expect(findChangeTypeModal().props('actionPrimary')).toEqual({
attributes: {
disabled: true,
variant: 'confirm',
},
text: 'Change type',
});
});
it('calls the `namespaceWorkItemTypesQuery` to get the work item types', () => {
@ -137,7 +142,7 @@ describe('WorkItemChangeTypeModal component', () => {
'Parent item type issue is not supported on key result. Remove the parent item to change type.',
);
expect(findConfirmationButton().props('disabled')).toBe(true);
expect(findChangeTypeModal().props('actionPrimary').attributes.disabled).toBe(true);
});
it('does not allow to change type and disables `Change type` button when the work item has child items', async () => {
@ -150,7 +155,7 @@ describe('WorkItemChangeTypeModal component', () => {
expect(findWarningAlert().text()).toBe(
'Key result does not support the task child item types. Remove child items to change type.',
);
expect(findConfirmationButton().props('disabled')).toBe(true);
expect(findChangeTypeModal().props('actionPrimary').attributes.disabled).toBe(true);
});
describe('when widget data has difference', () => {
@ -167,7 +172,7 @@ describe('WorkItemChangeTypeModal component', () => {
await nextTick();
expect(findWarningAlert().text()).toContain('Design');
expect(findConfirmationButton().props('disabled')).toBe(false);
expect(findChangeTypeModal().props('actionPrimary').attributes.disabled).toBe(false);
});
// These are all possible use cases of conflicts among project level work items
@ -193,7 +198,7 @@ describe('WorkItemChangeTypeModal component', () => {
await nextTick();
expect(findWarningAlert().text()).toContain(expectedString);
expect(findConfirmationButton().props('disabled')).toBe(false);
expect(findChangeTypeModal().props('actionPrimary').attributes.disabled).toBe(false);
},
);
});
@ -208,7 +213,7 @@ describe('WorkItemChangeTypeModal component', () => {
await nextTick();
findConfirmationButton().vm.$emit('click');
findChangeTypeModal().vm.$emit('primary');
await waitForPromises();
@ -237,7 +242,7 @@ describe('WorkItemChangeTypeModal component', () => {
await nextTick();
findConfirmationButton().vm.$emit('click');
findChangeTypeModal().vm.$emit('primary');
await waitForPromises();

View File

@ -0,0 +1,101 @@
# frozen_string_literal: true
require 'spec_helper'
require_migration!
RSpec.describe MoveElasticsearchApplicationSettingsToElasticsearchColumn, feature_category: :global_search do
let(:application_settings) { table(:application_settings) }
describe '#up' do
it 'updates setting' do
application_settings.create!(
elasticsearch_aws: true,
elasticsearch_url: 'http://localhost:9200',
elasticsearch_search: true,
elasticsearch_indexing: true,
elasticsearch_username: 'elastic',
elasticsearch_aws_region: 'us-east-1',
elasticsearch_aws_access_key: 'access_key',
elasticsearch_limit_indexing: true,
elasticsearch_pause_indexing: true,
elasticsearch_requeue_workers: true,
elasticsearch_max_bulk_size_mb: 10,
elasticsearch_retry_on_failure: true,
elasticsearch_max_bulk_concurrency: 10,
elasticsearch_client_request_timeout: 0,
elasticsearch_worker_number_of_shards: 4,
elasticsearch_analyzers_smartcn_search: true,
elasticsearch_analyzers_kuromoji_search: true,
elasticsearch_analyzers_smartcn_enabled: true,
elasticsearch_analyzers_kuromoji_enabled: true,
elasticsearch_indexed_field_length_limit: 10,
elasticsearch_indexed_file_size_limit_kb: 1024,
elasticsearch_max_code_indexing_concurrency: 30
)
migrate!
expect(application_settings.last.elasticsearch_url).to eq('http://localhost:9200')
expect(application_settings.last.elasticsearch).to include(
{
'elasticsearch_aws' => true,
'elasticsearch_search' => true,
'elasticsearch_indexing' => true,
'elasticsearch_username' => 'elastic',
'elasticsearch_aws_region' => 'us-east-1',
'elasticsearch_aws_access_key' => 'access_key',
'elasticsearch_limit_indexing' => true,
'elasticsearch_pause_indexing' => true,
'elasticsearch_requeue_workers' => true,
'elasticsearch_max_bulk_size_mb' => 10,
'elasticsearch_retry_on_failure' => 1,
'elasticsearch_max_bulk_concurrency' => 10,
'elasticsearch_client_request_timeout' => 0,
'elasticsearch_worker_number_of_shards' => 4,
'elasticsearch_analyzers_smartcn_search' => true,
'elasticsearch_analyzers_kuromoji_search' => true,
'elasticsearch_analyzers_smartcn_enabled' => true,
'elasticsearch_analyzers_kuromoji_enabled' => true,
'elasticsearch_indexed_field_length_limit' => 10,
'elasticsearch_indexed_file_size_limit_kb' => 1024,
'elasticsearch_max_code_indexing_concurrency' => 30
}
)
end
end
describe '#down' do
it 'updates setting' do
application_settings.create!(
elasticsearch: {
elasticsearch_aws: true,
elasticsearch_search: true,
elasticsearch_indexing: true,
elasticsearch_username: 'elastic',
elasticsearch_aws_region: 'us-east-1',
elasticsearch_aws_access_key: 'access_key',
elasticsearch_limit_indexing: true,
elasticsearch_pause_indexing: true,
elasticsearch_requeue_workers: true,
elasticsearch_max_bulk_size_mb: 10,
elasticsearch_retry_on_failure: 1,
elasticsearch_max_bulk_concurrency: 10,
elasticsearch_client_request_timeout: 0,
elasticsearch_worker_number_of_shards: 4,
elasticsearch_analyzers_smartcn_search: true,
elasticsearch_analyzers_kuromoji_search: true,
elasticsearch_analyzers_smartcn_enabled: true,
elasticsearch_analyzers_kuromoji_enabled: true,
elasticsearch_indexed_field_length_limit: 10,
elasticsearch_indexed_file_size_limit_kb: 1024,
elasticsearch_max_code_indexing_concurrency: 30
}
)
migrate!
schema_migrate_down!
expect(application_settings.last.elasticsearch).to eq({})
end
end
end

View File

@ -334,45 +334,79 @@ RSpec.describe ContainerRegistry::Protection::Rule, type: :model, feature_catego
end
end
describe '.for_push_exists_for_multiple_containers' do
let_it_be(:project) { create(:project) }
describe '.for_push_exists_for_projects_and_repository_paths' do
let_it_be(:project1) { create(:project) }
let_it_be(:project1_crpr) { create(:container_registry_protection_rule, project: project1) }
let_it_be(:ppr_for_maintainer) do
create(:container_registry_protection_rule,
repository_path_pattern: "#{project.full_path}/my-container-prod*",
project: project
)
end
let_it_be(:project2) { create(:project) }
let_it_be(:project2_crpr) { create(:container_registry_protection_rule, project: project2) }
let(:repository_paths) {
let_it_be(:unprotected_project) { create(:project) }
let(:single_project_input) do
[
"#{project.full_path}/my-container-prod-1",
"#{project.full_path}/unmatched-container-name"
[project1.id, project1_crpr.repository_path_pattern],
[project1.id, "#{project1_crpr.repository_path_pattern}/unprotected"]
]
}
subject do
described_class
.for_push_exists_for_multiple_containers(project_id: project.id, repository_paths: repository_paths)
.to_a
end
it do
is_expected.to eq([
{ "repository_path" => repository_paths.first, "protected" => true },
{ "repository_path" => repository_paths.second, "protected" => false }
])
let(:single_project_expected_result) do
[
{ "project_id" => project1.id, "repository_path" => project1_crpr.repository_path_pattern,
"protected" => true },
{ "project_id" => project1.id, "repository_path" => "#{project1_crpr.repository_path_pattern}/unprotected",
"protected" => false }
]
end
context 'when edge cases' do
where(:repository_paths, :expected_result) do
nil | []
[] | []
end
let(:multi_projects_input) do
[
*single_project_input,
[project2.id, project2_crpr.repository_path_pattern],
[project2.id, "#{project2_crpr.repository_path_pattern}/unprotected"]
]
end
with_them do
it { is_expected.to eq(expected_result) }
end
let(:multi_projects_expected_result) do
[
*single_project_expected_result,
{ "project_id" => project2.id, "repository_path" => project2_crpr.repository_path_pattern,
"protected" => true },
{ "project_id" => project2.id, "repository_path" => "#{project2_crpr.repository_path_pattern}/unprotected",
"protected" => false }
]
end
let(:unprotected_projects_input) do
[
*multi_projects_input,
[unprotected_project.id, "#{unprotected_project.full_path}/unprotected1"],
[unprotected_project.id, "#{unprotected_project.full_path}/unprotected2"]
]
end
let(:unprotected_projects_expected_result) do
[
*multi_projects_expected_result,
{ "project_id" => unprotected_project.id, "repository_path" => "#{unprotected_project.full_path}/unprotected1",
"protected" => false },
{ "project_id" => unprotected_project.id, "repository_path" => "#{unprotected_project.full_path}/unprotected2",
"protected" => false }
]
end
subject { described_class.for_push_exists_for_projects_and_repository_paths(projects_and_repository_paths).to_a }
where(:projects_and_repository_paths, :expected_result) do
ref(:single_project_input) | ref(:single_project_expected_result)
ref(:multi_projects_input) | ref(:multi_projects_expected_result)
ref(:unprotected_projects_input) | ref(:unprotected_projects_expected_result)
nil | []
[] | []
end
with_them do
it { is_expected.to match_array expected_result }
end
end
end

View File

@ -164,4 +164,84 @@ RSpec.describe 'getting container repositories in a group', feature_category: :s
expect(container_repositories_count_response).to eq(container_repositories.size)
end
describe 'protectionRuleExists' do
let_it_be(:container_registry_protection_rule) do
create(:container_registry_protection_rule, project: project, repository_path_pattern: container_repository.path)
end
let_it_be(:project_2) { create(:project, :private, group: group) }
let_it_be(:container_repository_2) { create(:container_repository, project: project_2) }
let_it_be(:container_registry_protection_rule_2) do
create(:container_registry_protection_rule, project: project_2, repository_path_pattern: container_repository_2.path)
end
let_it_be(:project_3) { create(:project, :private, group: group) }
let_it_be(:container_repository_3) { create(:container_repository, project: project_3) }
let_it_be(:container_registry_protection_rule_3) do
create(:container_registry_protection_rule, project: project_3, repository_path_pattern: container_repository_3.path)
end
before do
stub_container_registry_tags(repository: container_repository_2.path, tags: %w[tag1 tag2 tag3], with_manifest: false)
stub_container_registry_tags(repository: container_repository_3.path, tags: %w[tag1 tag2 tag3], with_manifest: false)
end
it 'returns true for protected container respositories' do
subject
expect(container_repositories_response.count).to eq 7
expect(find_container_repositories_response(container_repository.path).dig('node', 'protectionRuleExists')).to be true
expect(find_container_repositories_response(container_repository_2.path).dig('node', 'protectionRuleExists')).to be true
expect(find_container_repositories_response(container_repository_3.path).dig('node', 'protectionRuleExists')).to be true
[*container_repositories_delete_scheduled, *container_repositories_delete_failed].each do |repository|
expect(find_container_repositories_response(repository.path).dig('node', 'protectionRuleExists')).to be false
end
end
it 'executes only one database queries for all projects' do
expect { subject }.to match_query_count(1).for_model(::ContainerRegistry::Protection::Rule)
end
context 'when feature flag :container_registry_protected_containers disabled' do
before do
stub_feature_flags(container_registry_protected_containers: false)
end
it 'returns false even for protected container respositories' do
subject
expect(container_repositories_response.count).to eq 7
expect(container_repositories_response).to all(include('node' => include('protectionRuleExists' => false)))
end
end
context 'when 25 container repositories belong to group' do
let_it_be(:group) { create(:group) }
let_it_be(:projects) { create_list(:project, 5, :private, group: group) }
before_all do
projects.each do |project|
container_repositories = create_list(:container_repository, 5, project: project)
create(:container_registry_protection_rule, project: project,
repository_path_pattern: container_repositories.first.path)
end
end
before do
group.container_repositories.each do |container_repository|
stub_container_registry_tags(repository: container_repository.path, tags: %w[tag1 tag2 tag3], with_manifest: false)
end
end
it 'executes only two database queries to check the protection rules for container repositories in batches of 20' do
expect { subject }.to match_query_count(2).for_model(::ContainerRegistry::Protection::Rule)
end
end
def find_container_repositories_response(container_repository_path)
container_repositories_response.find { |res| res.dig('node', 'path') == container_repository_path }
end
end
end

View File

@ -980,3 +980,20 @@ RSpec.shared_examples 'work items linked items' do |is_group = false|
end
end
end
RSpec.shared_examples 'work items change type' do |selected_type, expected_selector|
it 'change work item type to selected type', :aggregate_failures do
click_button _('More actions'), match: :first
click_button s_('WorkItem|Change type')
expect(find('#work-item-change-type')).to have_content(s_('WorkItem|Change type'))
find_by_testid('work-item-change-type-select').select(selected_type)
click_button s_('WorkItem|Change type')
wait_for_requests
expect(page).to have_selector(expected_selector)
end
end

View File

@ -14836,10 +14836,10 @@ vite-plugin-ruby@^5.1.1:
debug "^4.3.4"
fast-glob "^3.3.2"
vite@^6.0.3:
version "6.0.3"
resolved "https://registry.yarnpkg.com/vite/-/vite-6.0.3.tgz#cc01f403e326a9fc1e064235df8a6de084c8a491"
integrity sha512-Cmuo5P0ENTN6HxLSo6IHsjCLn/81Vgrp81oaiFFMRa8gGDj5xEjIcEpf2ZymZtZR8oU0P2JX5WuUp/rlXcHkAw==
vite@^6.0.4:
version "6.0.4"
resolved "https://registry.yarnpkg.com/vite/-/vite-6.0.4.tgz#fe7cfaedff7c701d5582be5c4ed6a2150538ea9d"
integrity sha512-zwlH6ar+6o6b4Wp+ydhtIKLrGM/LoqZzcdVmkGAFun0KHTzIzjh+h0kungEx7KJg/PYnC80I4TII9WkjciSR6Q==
dependencies:
esbuild "^0.24.0"
postcss "^8.4.49"